Coverage Report

Created: 2025-08-29 06:48

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