Coverage Report

Created: 2026-04-28 06:49

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
493
G_DEFINE_TYPE_WITH_PRIVATE(FuIntelThunderboltNvm, fu_intel_thunderbolt_nvm, FU_TYPE_FIRMWARE)
45
1.59k
#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
86
{
213
86
  return pointer != 0 && pointer != 0xFFFFFFFF;
214
86
}
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
            gsize *value,
229
            GError **error)
230
514
{
231
514
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
232
514
  guint16 value_tmp = 0;
233
234
514
  if (!fu_input_stream_read_u16(stream,
235
514
              priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
236
514
            offset,
237
514
              &value_tmp,
238
514
              G_LITTLE_ENDIAN,
239
514
              error)) {
240
12
    g_prefix_error_literal(error, "failed to read ucode section len: ");
241
12
    return FALSE;
242
12
  }
243
502
  *value = value_tmp;
244
502
  *value *= sizeof(guint32);
245
502
  *value += sizeof(guint16);
246
502
  return TRUE;
247
514
}
248
249
/* assumes sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL].offset is already set */
250
static gboolean
251
fu_intel_thunderbolt_nvm_read_sections(FuIntelThunderboltNvm *self,
252
               GInputStream *stream,
253
               GError **error)
254
310
{
255
310
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
256
257
310
  if (priv->gen >= 3 || priv->gen == 0) {
258
267
    guint32 offset32 = 0;
259
267
    if (!fu_input_stream_read_u32(
260
267
      stream,
261
267
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
262
267
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM,
263
267
      &offset32,
264
267
      G_LITTLE_ENDIAN,
265
267
      error))
266
14
      return FALSE;
267
253
    priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] =
268
253
        offset32 + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL];
269
270
253
    if (!fu_input_stream_read_u32(
271
253
      stream,
272
253
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
273
253
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS,
274
253
      &offset32,
275
253
      G_LITTLE_ENDIAN,
276
253
      error))
277
0
      return FALSE;
278
253
    priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] =
279
253
        offset32 + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL];
280
253
  }
281
282
296
  if (priv->is_host && priv->gen > 2) {
283
    /*
284
     * To find the DRAM section, we have to jump from section to
285
     * section in a chain of sections.
286
     * available_sections location tells what sections exist at all
287
     * (with a flag per section).
288
     * ee_ucode_start_addr location tells the offset of the first
289
     * section in the list relatively to the digital section start.
290
     * After having the offset of the first section, we have a loop
291
     * over the section list. If the section exists, we read its
292
     * length (2 bytes at section start) and add it to current
293
     * offset to find the start of the next section. Otherwise, we
294
     * already have the next section offset...
295
     */
296
179
    gsize offset;
297
179
    guint16 offset16 = 0;
298
179
    guint8 available_sections = 0;
299
300
179
    if (!fu_input_stream_read_u8(
301
179
      stream,
302
179
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
303
179
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS,
304
179
      &available_sections,
305
179
      error)) {
306
0
      g_prefix_error_literal(error, "failed to read available sections: ");
307
0
      return FALSE;
308
0
    }
309
179
    if (!fu_input_stream_read_u16(
310
179
      stream,
311
179
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
312
179
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE,
313
179
      &offset16,
314
179
      G_LITTLE_ENDIAN,
315
179
      error)) {
316
0
      g_prefix_error_literal(error, "failed to read ucode offset: ");
317
0
      return FALSE;
318
0
    }
319
179
    offset = offset16;
320
179
    if ((available_sections & FU_INTEL_THUNDERBOLT_NVM_SECTION_FLAG_DRAM) == 0) {
321
1
      g_set_error_literal(error,
322
1
              FWUPD_ERROR,
323
1
              FWUPD_ERROR_INVALID_FILE,
324
1
              "cannot find needed FW sections in the FW image file");
325
1
      return FALSE;
326
1
    }
327
328
1.20k
    for (guint8 i = 1; i < FU_INTEL_THUNDERBOLT_NVM_SECTION_FLAG_DRAM; i <<= 1) {
329
1.03k
      if (available_sections & i) {
330
514
        gsize section_len = 0;
331
332
514
        if (!fu_intel_thunderbolt_nvm_read_ucode_section_len(self,
333
514
                         stream,
334
514
                         offset,
335
514
                         &section_len,
336
514
                         error))
337
12
          return FALSE;
338
339
        /* add section offset */
340
502
        if (!fu_size_checked_inc(&offset, section_len, error)) {
341
0
          g_prefix_error_literal(error, "section offset overflow: ");
342
0
          return FALSE;
343
0
        }
344
502
      }
345
1.03k
    }
346
166
    priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DRAM_UCODE] =
347
166
        offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL];
348
166
  }
349
350
283
  return TRUE;
351
296
}
352
353
static gboolean
354
fu_intel_thunderbolt_nvm_missing_needed_drom(FuIntelThunderboltNvm *self)
355
283
{
356
283
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
357
283
  if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0)
358
236
    return FALSE;
359
47
  if (priv->is_host && priv->gen < 3)
360
41
    return FALSE;
361
6
  return TRUE;
362
47
}
363
364
static gboolean
365
fu_intel_thunderbolt_nvm_parse(FuFirmware *firmware,
366
             GInputStream *stream,
367
             FuFirmwareParseFlags flags,
368
             GError **error)
369
415
{
370
415
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
371
415
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
372
415
  guint8 tmp = 0;
373
415
  guint16 version_raw = 0;
374
415
  struct {
375
415
    guint16 device_id;
376
415
    guint gen;
377
415
    FuIntelThunderboltNvmFamily family;
378
415
    guint ports;
379
415
  } hw_info_arr[] = {{0x156D, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FALCON_RIDGE, 2},
380
415
         {0x156B, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FALCON_RIDGE, 1},
381
415
         {0x157E, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_WIN_RIDGE, 1},
382
415
         {0x1578, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 2},
383
415
         {0x1576, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 1},
384
415
         {0x15C0, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 1},
385
415
         {0x15D3, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C, 2},
386
415
         {0x15DA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C, 1},
387
415
         {0x15E7, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 1},
388
415
         {0x15EA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 2},
389
415
         {0x15EF, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 2},
390
415
         {0x15EE, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB, 0},
391
415
         {0x0B26, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_GOSHEN_RIDGE, 2},
392
415
         {0x5786, 5, FU_INTEL_THUNDERBOLT_NVM_FAMILY_BARLOW_RIDGE, 2},
393
         /* Maple ridge devices
394
          * NOTE: These are expected to be flashed via UEFI capsules *not*
395
          * Thunderbolt plugin Flashing via fwupd will require matching kernel
396
          * work. They're left here only for parsing the binaries
397
          */
398
415
         {0x1136, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MAPLE_RIDGE, 2},
399
415
         {0x1137, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MAPLE_RIDGE, 2},
400
415
         {0}};
401
415
  g_autoptr(FuFirmware) img_payload = fu_firmware_new();
402
415
  gsize streamsz = 0;
403
404
  /* add this straight away */
405
415
  priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] = 0x0;
406
407
  /* is native */
408
415
  if (!fu_input_stream_read_u8(stream,
409
415
             priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
410
415
           FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE,
411
415
             &tmp,
412
415
             error)) {
413
20
    g_prefix_error_literal(error, "failed to read native: ");
414
20
    return FALSE;
415
20
  }
416
395
  priv->is_native = tmp & 0x20;
417
418
  /* we're only reading the first chunk */
419
395
  if (!fu_input_stream_size(stream, &streamsz, error))
420
0
    return FALSE;
421
395
  if (streamsz == 0x80)
422
49
    return TRUE;
423
424
  /* host or device */
425
346
  if (!fu_input_stream_read_u8(stream,
426
346
             priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
427
346
           FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST,
428
346
             &tmp,
429
346
             error)) {
430
0
    g_prefix_error_literal(error, "failed to read is-host: ");
431
0
    return FALSE;
432
0
  }
433
346
  priv->is_host = tmp & (1 << 1);
434
435
  /* device ID */
436
346
  if (!fu_input_stream_read_u16(stream,
437
346
              priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
438
346
            FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID,
439
346
              &priv->device_id,
440
346
              G_LITTLE_ENDIAN,
441
346
              error)) {
442
0
    g_prefix_error_literal(error, "failed to read device-id: ");
443
0
    return FALSE;
444
0
  }
445
446
  /* this is best-effort */
447
3.67k
  for (guint i = 0; hw_info_arr[i].device_id != 0; i++) {
448
3.64k
    if (hw_info_arr[i].device_id == priv->device_id) {
449
311
      priv->family = hw_info_arr[i].family;
450
311
      priv->gen = hw_info_arr[i].gen;
451
311
      priv->ports = hw_info_arr[i].ports;
452
311
      break;
453
311
    }
454
3.64k
  }
455
346
  if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) {
456
35
    g_set_error_literal(error,
457
35
            FWUPD_ERROR,
458
35
            FWUPD_ERROR_INVALID_DATA,
459
35
            "unknown NVM family");
460
35
    return FALSE;
461
35
  }
462
311
  if (priv->ports == 0 && priv->is_host) {
463
1
    g_set_error(error,
464
1
          FWUPD_ERROR,
465
1
          FWUPD_ERROR_NOT_SUPPORTED,
466
1
          "unknown controller: %x",
467
1
          priv->device_id);
468
1
    return FALSE;
469
1
  }
470
471
  /* read sections from file */
472
310
  if (!fu_intel_thunderbolt_nvm_read_sections(self, stream, error))
473
27
    return FALSE;
474
283
  if (fu_intel_thunderbolt_nvm_missing_needed_drom(self)) {
475
6
    g_set_error_literal(error,
476
6
            FWUPD_ERROR,
477
6
            FWUPD_ERROR_READ,
478
6
            "cannot find required drom section");
479
6
    return FALSE;
480
6
  }
481
482
  /* vendor:model */
483
277
  if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0x0) {
484
236
    if (!fu_input_stream_read_u16(
485
236
      stream,
486
236
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] +
487
236
          FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID,
488
236
      &priv->vendor_id,
489
236
      G_LITTLE_ENDIAN,
490
236
      error)) {
491
54
      g_prefix_error_literal(error, "failed to read vendor-id: ");
492
54
      return FALSE;
493
54
    }
494
182
    if (!fu_input_stream_read_u16(
495
182
      stream,
496
182
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] +
497
182
          FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID,
498
182
      &priv->model_id,
499
182
      G_LITTLE_ENDIAN,
500
182
      error)) {
501
4
      g_prefix_error_literal(error, "failed to read model-id: ");
502
4
      return FALSE;
503
4
    }
504
182
  }
505
506
  /* versions */
507
219
  switch (priv->family) {
508
42
  case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE:
509
147
  case FU_INTEL_THUNDERBOLT_NVM_FAMILY_GOSHEN_RIDGE:
510
161
  case FU_INTEL_THUNDERBOLT_NVM_FAMILY_BARLOW_RIDGE:
511
161
    if (!fu_input_stream_read_u16(
512
161
      stream,
513
161
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
514
161
          FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION,
515
161
      &version_raw,
516
161
      G_LITTLE_ENDIAN,
517
161
      error)) {
518
0
      g_prefix_error_literal(error, "failed to read version: ");
519
0
      return FALSE;
520
0
    }
521
161
    fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw);
522
161
    break;
523
58
  default:
524
58
    break;
525
219
  }
526
527
219
  if (priv->is_host) {
528
170
    switch (priv->family) {
529
13
    case FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE:
530
13
    case FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C:
531
27
    case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE:
532
      /* used for comparison between old and new image, not a raw number */
533
27
      if (!fu_input_stream_read_u8(
534
27
        stream,
535
27
        priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] +
536
27
            FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE,
537
27
        &tmp,
538
27
        error)) {
539
0
        g_prefix_error_literal(error, "failed to read flash size: ");
540
0
        return FALSE;
541
0
      }
542
27
      priv->flash_size = tmp & 0x07;
543
27
      break;
544
143
    default:
545
143
      break;
546
170
    }
547
170
  }
548
549
  /* we're only reading enough to get the vendor-id and model-id */
550
219
  if (streamsz < priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS])
551
42
    return TRUE;
552
553
  /* has PD */
554
177
  if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] != 0x0) {
555
111
    guint32 pd_pointer = 0x0;
556
111
    if (!fu_input_stream_read_u32(
557
111
      stream,
558
111
      priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] +
559
111
          FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER,
560
111
      &pd_pointer,
561
111
      G_LITTLE_ENDIAN,
562
111
      error)) {
563
25
      g_prefix_error_literal(error, "failed to read pd-pointer: ");
564
25
      return FALSE;
565
25
    }
566
86
    priv->has_pd = fu_intel_thunderbolt_nvm_valid_pd_pointer(pd_pointer);
567
86
  }
568
569
  /* as as easy-to-grab payload blob */
570
152
  if (!fu_firmware_parse_stream(img_payload, stream, 0x0, flags, error))
571
0
    return FALSE;
572
152
  fu_firmware_set_id(img_payload, FU_FIRMWARE_ID_PAYLOAD);
573
152
  if (!fu_firmware_add_image(firmware, img_payload, error))
574
0
    return FALSE;
575
576
  /* success */
577
152
  return TRUE;
578
152
}
579
580
/* can only write version 3 NVM */
581
static GByteArray *
582
fu_intel_thunderbolt_nvm_write(FuFirmware *firmware, GError **error)
583
77
{
584
77
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
585
77
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
586
77
  g_autoptr(FuIntelThunderboltNvmDigital) st = fu_intel_thunderbolt_nvm_digital_new();
587
77
  g_autoptr(FuIntelThunderboltNvmDrom) st_drom = fu_intel_thunderbolt_nvm_drom_new();
588
77
  g_autoptr(FuIntelThunderboltNvmArcParams) st_arc =
589
77
      fu_intel_thunderbolt_nvm_arc_params_new();
590
77
  g_autoptr(FuIntelThunderboltNvmDram) st_dram = fu_intel_thunderbolt_nvm_dram_new();
591
592
  /* digital section */
593
77
  fu_intel_thunderbolt_nvm_digital_set_available_sections(
594
77
      st,
595
77
      FU_INTEL_THUNDERBOLT_NVM_SECTION_FLAG_DRAM);
596
77
  fu_intel_thunderbolt_nvm_digital_set_device_id(st, priv->device_id);
597
77
  fu_intel_thunderbolt_nvm_digital_set_version(st, fu_firmware_get_version_raw(firmware));
598
77
  fu_intel_thunderbolt_nvm_digital_set_flags_host(st, priv->is_host ? 0x2 : 0x0);
599
77
  fu_intel_thunderbolt_nvm_digital_set_flash_size(st, priv->flash_size);
600
77
  fu_intel_thunderbolt_nvm_digital_set_flags_is_native(st, priv->is_native ? 0x20 : 0x0);
601
602
  /* drom section */
603
77
  fu_intel_thunderbolt_nvm_digital_set_drom(st, st->buf->len);
604
77
  fu_intel_thunderbolt_nvm_drom_set_vendor_id(st_drom, priv->vendor_id);
605
77
  fu_intel_thunderbolt_nvm_drom_set_model_id(st_drom, priv->model_id);
606
77
  fu_byte_array_append_array(st->buf, st_drom->buf);
607
608
  /* ARC param section */
609
77
  fu_intel_thunderbolt_nvm_digital_set_arc_params(st, st->buf->len);
610
77
  fu_intel_thunderbolt_nvm_arc_params_set_pd_pointer(st_arc, priv->has_pd ? 0x1 : 0x0);
611
77
  fu_byte_array_append_array(st->buf, st_arc->buf);
612
613
  /* dram section */
614
77
  fu_intel_thunderbolt_nvm_digital_set_ucode(st, st->buf->len);
615
77
  fu_byte_array_append_array(st->buf, st_dram->buf);
616
617
  /* success */
618
77
  return g_steal_pointer(&st->buf);
619
77
}
620
621
static gboolean
622
fu_intel_thunderbolt_nvm_check_compatible(FuFirmware *firmware,
623
            FuFirmware *firmware_other,
624
            FuFirmwareParseFlags flags,
625
            GError **error)
626
0
{
627
0
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
628
0
  FuIntelThunderboltNvm *other = FU_INTEL_THUNDERBOLT_NVM(firmware_other);
629
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
630
0
  FuIntelThunderboltNvmPrivate *priv_other = GET_PRIVATE(other);
631
632
0
  if (priv->is_host != priv_other->is_host) {
633
0
    g_set_error(error,
634
0
          FWUPD_ERROR,
635
0
          FWUPD_ERROR_INVALID_FILE,
636
0
          "incorrect firmware mode, got %s, expected %s",
637
0
          priv->is_host ? "host" : "device",
638
0
          priv_other->is_host ? "host" : "device");
639
0
    return FALSE;
640
0
  }
641
0
  if (priv->vendor_id != priv_other->vendor_id) {
642
0
    g_set_error(error,
643
0
          FWUPD_ERROR,
644
0
          FWUPD_ERROR_INVALID_FILE,
645
0
          "incorrect device vendor, got 0x%04x, expected 0x%04x",
646
0
          priv->vendor_id,
647
0
          priv_other->vendor_id);
648
0
    return FALSE;
649
0
  }
650
0
  if (priv->device_id != priv_other->device_id) {
651
0
    g_set_error(error,
652
0
          FWUPD_ERROR,
653
0
          FWUPD_ERROR_INVALID_FILE,
654
0
          "incorrect device type, got 0x%04x, expected 0x%04x",
655
0
          priv->device_id,
656
0
          priv_other->device_id);
657
0
    return FALSE;
658
0
  }
659
0
  if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_VID_PID) == 0) {
660
0
    if (priv->model_id != priv_other->model_id) {
661
0
      g_set_error(error,
662
0
            FWUPD_ERROR,
663
0
            FWUPD_ERROR_INVALID_FILE,
664
0
            "incorrect device model, got 0x%04x, expected 0x%04x",
665
0
            priv->model_id,
666
0
            priv_other->model_id);
667
0
      return FALSE;
668
0
    }
669
    /* old firmware has PD but new doesn't (we don't care about other way around) */
670
0
    if (priv->has_pd && !priv_other->has_pd) {
671
0
      g_set_error_literal(error,
672
0
              FWUPD_ERROR,
673
0
              FWUPD_ERROR_INVALID_FILE,
674
0
              "incorrect PD section");
675
0
      return FALSE;
676
0
    }
677
0
    if (priv->flash_size != priv_other->flash_size) {
678
0
      g_set_error(error,
679
0
            FWUPD_ERROR,
680
0
            FWUPD_ERROR_INVALID_FILE,
681
0
            "incorrect flash size, got 0x%x and expected 0x%x",
682
0
            priv->flash_size,
683
0
            priv_other->flash_size);
684
0
      return FALSE;
685
0
    }
686
0
  }
687
0
  return TRUE;
688
0
}
689
690
static gboolean
691
fu_intel_thunderbolt_nvm_build(FuFirmware *firmware, XbNode *n, GError **error)
692
0
{
693
0
  FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware);
694
0
  FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self);
695
0
  const gchar *tmp;
696
697
  /* simple properties */
698
0
  tmp = xb_node_query_text(n, "vendor_id", NULL);
699
0
  if (tmp != NULL) {
700
0
    guint64 val = 0;
701
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
702
0
      return FALSE;
703
0
    priv->vendor_id = val;
704
0
  }
705
0
  tmp = xb_node_query_text(n, "device_id", NULL);
706
0
  if (tmp != NULL) {
707
0
    guint64 val = 0;
708
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
709
0
      return FALSE;
710
0
    priv->device_id = val;
711
0
  }
712
0
  tmp = xb_node_query_text(n, "model_id", NULL);
713
0
  if (tmp != NULL) {
714
0
    guint64 val = 0;
715
0
    if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, FU_INTEGER_BASE_AUTO, error))
716
0
      return FALSE;
717
0
    priv->model_id = val;
718
0
  }
719
0
  tmp = xb_node_query_text(n, "family", NULL);
720
0
  if (tmp != NULL) {
721
0
    priv->family = fu_intel_thunderbolt_nvm_family_from_string(tmp);
722
0
    if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) {
723
0
      g_set_error(error,
724
0
            FWUPD_ERROR,
725
0
            FWUPD_ERROR_INVALID_DATA,
726
0
            "unknown family: %s",
727
0
            tmp);
728
0
      return FALSE;
729
0
    }
730
0
  }
731
0
  tmp = xb_node_query_text(n, "flash_size", NULL);
732
0
  if (tmp != NULL) {
733
0
    guint64 val = 0;
734
0
    if (!fu_strtoull(tmp, &val, 0x0, 0x07, FU_INTEGER_BASE_AUTO, error))
735
0
      return FALSE;
736
0
    priv->flash_size = val;
737
0
  }
738
0
  tmp = xb_node_query_text(n, "is_host", NULL);
739
0
  if (tmp != NULL) {
740
0
    if (!fu_strtobool(tmp, &priv->is_host, error))
741
0
      return FALSE;
742
0
  }
743
0
  tmp = xb_node_query_text(n, "is_native", NULL);
744
0
  if (tmp != NULL) {
745
0
    if (!fu_strtobool(tmp, &priv->is_native, error))
746
0
      return FALSE;
747
0
  }
748
749
  /* success */
750
0
  return TRUE;
751
0
}
752
753
static gchar *
754
fu_intel_thunderbolt_nvm_convert_version(FuFirmware *firmware, guint64 version_raw)
755
161
{
756
161
  return fu_version_from_uint16(version_raw, fu_firmware_get_version_format(firmware));
757
161
}
758
759
static void
760
fu_intel_thunderbolt_nvm_init(FuIntelThunderboltNvm *self)
761
503
{
762
503
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID);
763
503
  fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE);
764
503
  fu_firmware_set_images_max(FU_FIRMWARE(self), 1024);
765
503
  fu_firmware_set_size_max(FU_FIRMWARE(self), 32 * FU_MB);
766
503
  fu_firmware_set_version_format(FU_FIRMWARE(self), FWUPD_VERSION_FORMAT_BCD);
767
503
}
768
769
static void
770
fu_intel_thunderbolt_nvm_class_init(FuIntelThunderboltNvmClass *klass)
771
1
{
772
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
773
1
  firmware_class->convert_version = fu_intel_thunderbolt_nvm_convert_version;
774
1
  firmware_class->export = fu_intel_thunderbolt_nvm_export;
775
1
  firmware_class->parse = fu_intel_thunderbolt_nvm_parse;
776
1
  firmware_class->write = fu_intel_thunderbolt_nvm_write;
777
1
  firmware_class->build = fu_intel_thunderbolt_nvm_build;
778
1
  firmware_class->check_compatible = fu_intel_thunderbolt_nvm_check_compatible;
779
1
}
780
781
/**
782
 * fu_intel_thunderbolt_nvm_new:
783
 *
784
 * Creates a new #FuFirmware of Intel NVM format
785
 *
786
 * Since: 1.8.5
787
 **/
788
FuFirmware *
789
fu_intel_thunderbolt_nvm_new(void)
790
0
{
791
0
  return FU_FIRMWARE(g_object_new(FU_TYPE_INTEL_THUNDERBOLT_NVM, NULL));
792
0
}