/src/fwupd/libfwupdplugin/fu-fmap-firmware.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2020 Benson Leung <bleung@chromium.org> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include "fu-byte-array.h" |
10 | | #include "fu-bytes.h" |
11 | | #include "fu-common.h" |
12 | | #include "fu-fmap-firmware.h" |
13 | | #include "fu-fmap-struct.h" |
14 | | #include "fu-input-stream.h" |
15 | | #include "fu-partial-input-stream.h" |
16 | | |
17 | | /** |
18 | | * FuFmapFirmware: |
19 | | * |
20 | | * A FMAP firmware image. |
21 | | * |
22 | | * See also: [class@FuFirmware] |
23 | | */ |
24 | | |
25 | 16.1k | #define FMAP_AREANAME "FMAP" |
26 | | |
27 | | G_DEFINE_TYPE(FuFmapFirmware, fu_fmap_firmware, FU_TYPE_FIRMWARE) |
28 | | |
29 | | static gboolean |
30 | | fu_fmap_firmware_parse(FuFirmware *firmware, |
31 | | GInputStream *stream, |
32 | | FuFirmwareParseFlags flags, |
33 | | GError **error) |
34 | 1.14k | { |
35 | 1.14k | gsize offset = 0; |
36 | 1.14k | gsize streamsz = 0; |
37 | 1.14k | guint32 nareas; |
38 | 1.14k | g_autoptr(GByteArray) st_hdr = NULL; |
39 | | |
40 | | /* find the magic token */ |
41 | 1.14k | if (!fu_input_stream_find(stream, |
42 | 1.14k | (const guint8 *)FU_STRUCT_FMAP_DEFAULT_SIGNATURE, |
43 | 1.14k | FU_STRUCT_FMAP_SIZE_SIGNATURE, |
44 | 1.14k | &offset, |
45 | 1.14k | error)) |
46 | 299 | return FALSE; |
47 | | |
48 | | /* parse */ |
49 | 841 | st_hdr = fu_struct_fmap_parse_stream(stream, offset, error); |
50 | 841 | if (st_hdr == NULL) |
51 | 79 | return FALSE; |
52 | 762 | fu_firmware_set_addr(firmware, fu_struct_fmap_get_base(st_hdr)); |
53 | | |
54 | 762 | if (!fu_input_stream_size(stream, &streamsz, error)) |
55 | 0 | return FALSE; |
56 | 762 | if (fu_struct_fmap_get_size(st_hdr) != streamsz) { |
57 | 185 | g_set_error(error, |
58 | 185 | FWUPD_ERROR, |
59 | 185 | FWUPD_ERROR_INVALID_DATA, |
60 | 185 | "file size incorrect, expected 0x%04x got 0x%04x", |
61 | 185 | fu_struct_fmap_get_size(st_hdr), |
62 | 185 | (guint)streamsz); |
63 | 185 | return FALSE; |
64 | 185 | } |
65 | 577 | nareas = fu_struct_fmap_get_nareas(st_hdr); |
66 | 577 | if (nareas < 1) { |
67 | 2 | g_set_error_literal(error, |
68 | 2 | FWUPD_ERROR, |
69 | 2 | FWUPD_ERROR_INVALID_DATA, |
70 | 2 | "number of areas invalid"); |
71 | 2 | return FALSE; |
72 | 2 | } |
73 | 575 | offset += st_hdr->len; |
74 | 4.03M | for (gsize i = 0; i < nareas; i++) { |
75 | 4.03M | guint32 area_offset; |
76 | 4.03M | guint32 area_size; |
77 | 4.03M | g_autofree gchar *area_name = NULL; |
78 | 4.03M | g_autoptr(FuFirmware) img = fu_firmware_new(); |
79 | 4.03M | g_autoptr(GByteArray) st_area = NULL; |
80 | 4.03M | g_autoptr(GInputStream) img_stream = NULL; |
81 | | |
82 | | /* load area */ |
83 | 4.03M | st_area = fu_struct_fmap_area_parse_stream(stream, offset, error); |
84 | 4.03M | if (st_area == NULL) |
85 | 190 | return FALSE; |
86 | 4.03M | area_size = fu_struct_fmap_area_get_size(st_area); |
87 | 4.03M | if (area_size == 0) |
88 | 4.01M | continue; |
89 | 16.3k | area_offset = fu_struct_fmap_area_get_offset(st_area); |
90 | 16.3k | img_stream = fu_partial_input_stream_new(stream, |
91 | 16.3k | (gsize)area_offset, |
92 | 16.3k | (gsize)area_size, |
93 | 16.3k | error); |
94 | 16.3k | if (img_stream == NULL) { |
95 | 190 | g_prefix_error_literal(error, "failed to cut FMAP area: "); |
96 | 190 | return FALSE; |
97 | 190 | } |
98 | 16.1k | if (!fu_firmware_parse_stream(img, img_stream, 0x0, flags, error)) |
99 | 0 | return FALSE; |
100 | 16.1k | area_name = fu_struct_fmap_area_get_name(st_area); |
101 | 16.1k | fu_firmware_set_id(img, area_name); |
102 | 16.1k | fu_firmware_set_idx(img, i + 1); |
103 | 16.1k | fu_firmware_set_addr(img, area_offset); |
104 | 16.1k | if (!fu_firmware_add_image_full(firmware, img, error)) |
105 | 2 | return FALSE; |
106 | | |
107 | 16.1k | if (g_strcmp0(area_name, FMAP_AREANAME) == 0) { |
108 | 2.16k | g_autofree gchar *version = NULL; |
109 | 2.16k | version = g_strdup_printf("%d.%d", |
110 | 2.16k | fu_struct_fmap_get_ver_major(st_hdr), |
111 | 2.16k | fu_struct_fmap_get_ver_minor(st_hdr)); |
112 | 2.16k | fu_firmware_set_version(img, version); |
113 | 2.16k | } |
114 | 16.1k | offset += st_area->len; |
115 | 16.1k | } |
116 | | |
117 | | /* success */ |
118 | 193 | return TRUE; |
119 | 575 | } |
120 | | |
121 | | static GByteArray * |
122 | | fu_fmap_firmware_write(FuFirmware *firmware, GError **error) |
123 | 193 | { |
124 | 193 | gsize total_sz; |
125 | 193 | gsize offset; |
126 | 193 | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
127 | 193 | g_autoptr(GByteArray) buf = g_byte_array_new(); |
128 | 193 | g_autoptr(GByteArray) st_hdr = fu_struct_fmap_new(); |
129 | | |
130 | | /* pad to offset */ |
131 | 193 | if (fu_firmware_get_offset(firmware) > 0) |
132 | 0 | fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware), 0x00); |
133 | | |
134 | | /* add header */ |
135 | 193 | total_sz = offset = st_hdr->len + (FU_STRUCT_FMAP_AREA_SIZE * images->len); |
136 | 193 | for (guint i = 0; i < images->len; i++) { |
137 | 102 | FuFirmware *img = g_ptr_array_index(images, i); |
138 | 102 | g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, error); |
139 | 102 | if (fw == NULL) |
140 | 102 | return NULL; |
141 | 0 | total_sz += g_bytes_get_size(fw); |
142 | 0 | } |
143 | | |
144 | | /* header */ |
145 | 91 | fu_struct_fmap_set_base(st_hdr, fu_firmware_get_addr(firmware)); |
146 | 91 | fu_struct_fmap_set_nareas(st_hdr, images->len); |
147 | 91 | fu_struct_fmap_set_size(st_hdr, fu_firmware_get_offset(firmware) + total_sz); |
148 | 91 | g_byte_array_append(buf, st_hdr->data, st_hdr->len); |
149 | | |
150 | | /* add each area */ |
151 | 91 | for (guint i = 0; i < images->len; i++) { |
152 | 0 | FuFirmware *img = g_ptr_array_index(images, i); |
153 | 0 | g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL); |
154 | 0 | g_autoptr(GByteArray) st_area = fu_struct_fmap_area_new(); |
155 | 0 | fu_struct_fmap_area_set_offset(st_area, fu_firmware_get_offset(firmware) + offset); |
156 | 0 | fu_struct_fmap_area_set_size(st_area, g_bytes_get_size(fw)); |
157 | 0 | if (fu_firmware_get_id(img) != NULL) { |
158 | 0 | if (!fu_struct_fmap_area_set_name(st_area, fu_firmware_get_id(img), error)) |
159 | 0 | return NULL; |
160 | 0 | } |
161 | 0 | g_byte_array_append(buf, st_area->data, st_area->len); |
162 | 0 | offset += g_bytes_get_size(fw); |
163 | 0 | } |
164 | | |
165 | | /* add the images */ |
166 | 91 | for (guint i = 0; i < images->len; i++) { |
167 | 0 | FuFirmware *img = g_ptr_array_index(images, i); |
168 | 0 | g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, error); |
169 | 0 | if (fw == NULL) |
170 | 0 | return NULL; |
171 | 0 | fu_byte_array_append_bytes(buf, fw); |
172 | 0 | } |
173 | | |
174 | | /* success */ |
175 | 91 | return g_steal_pointer(&buf); |
176 | 91 | } |
177 | | |
178 | | static void |
179 | | fu_fmap_firmware_init(FuFmapFirmware *self) |
180 | 1.14k | { |
181 | 1.14k | fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); |
182 | 1.14k | } |
183 | | |
184 | | static void |
185 | | fu_fmap_firmware_class_init(FuFmapFirmwareClass *klass) |
186 | 2 | { |
187 | 2 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
188 | 2 | firmware_class->parse = fu_fmap_firmware_parse; |
189 | 2 | firmware_class->write = fu_fmap_firmware_write; |
190 | 2 | } |
191 | | |
192 | | /** |
193 | | * fu_fmap_firmware_new |
194 | | * |
195 | | * Creates a new #FuFirmware of sub type fmap |
196 | | * |
197 | | * Since: 1.5.0 |
198 | | **/ |
199 | | FuFirmware * |
200 | | fu_fmap_firmware_new(void) |
201 | 0 | { |
202 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_FMAP_FIRMWARE, NULL)); |
203 | 0 | } |