/src/fwupd/libfwupdplugin/fu-acpi-table.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2023 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include "fu-acpi-table-struct.h" |
10 | | #include "fu-acpi-table.h" |
11 | | #include "fu-byte-array.h" |
12 | | #include "fu-common.h" |
13 | | #include "fu-input-stream.h" |
14 | | #include "fu-partial-input-stream.h" |
15 | | #include "fu-sum.h" |
16 | | |
17 | | /** |
18 | | * FuAcpiTable: |
19 | | * |
20 | | * An generic ACPI table. |
21 | | * |
22 | | * See also: [class@FuFirmware] |
23 | | */ |
24 | | |
25 | | typedef struct { |
26 | | guint8 revision; |
27 | | gchar *oem_id; |
28 | | gchar *oem_table_id; |
29 | | guint32 oem_revision; |
30 | | GInputStream *payload; |
31 | | } FuAcpiTablePrivate; |
32 | | |
33 | 0 | G_DEFINE_TYPE_WITH_PRIVATE(FuAcpiTable, fu_acpi_table, FU_TYPE_FIRMWARE) |
34 | 0 |
|
35 | 0 | #define GET_PRIVATE(o) (fu_acpi_table_get_instance_private(o)) |
36 | | |
37 | | static void |
38 | | fu_acpi_table_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) |
39 | 0 | { |
40 | 0 | FuAcpiTable *self = FU_ACPI_TABLE(firmware); |
41 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
42 | 0 | fu_xmlb_builder_insert_kx(bn, "revision", priv->revision); |
43 | 0 | fu_xmlb_builder_insert_kv(bn, "oem_id", priv->oem_id); |
44 | 0 | fu_xmlb_builder_insert_kv(bn, "oem_table_id", priv->oem_table_id); |
45 | 0 | fu_xmlb_builder_insert_kx(bn, "oem_revision", priv->oem_revision); |
46 | 0 | } |
47 | | |
48 | | /** |
49 | | * fu_acpi_table_get_revision: |
50 | | * @self: a #FuAcpiTable |
51 | | * |
52 | | * Gets the revision of the table. |
53 | | * |
54 | | * Returns: integer, default 0x0 |
55 | | * |
56 | | * Since: 1.8.11 |
57 | | **/ |
58 | | guint8 |
59 | | fu_acpi_table_get_revision(FuAcpiTable *self) |
60 | 0 | { |
61 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
62 | 0 | g_return_val_if_fail(FU_IS_ACPI_TABLE(self), G_MAXUINT8); |
63 | 0 | return priv->revision; |
64 | 0 | } |
65 | | |
66 | | /** |
67 | | * fu_acpi_table_get_oem_id: |
68 | | * @self: a #FuAcpiTable |
69 | | * |
70 | | * Gets an optional OEM ID. |
71 | | * |
72 | | * Returns: a string, or %NULL |
73 | | * |
74 | | * Since: 1.8.11 |
75 | | **/ |
76 | | const gchar * |
77 | | fu_acpi_table_get_oem_id(FuAcpiTable *self) |
78 | 0 | { |
79 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
80 | 0 | g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL); |
81 | 0 | return priv->oem_id; |
82 | 0 | } |
83 | | |
84 | | /** |
85 | | * fu_acpi_table_get_oem_table_id: |
86 | | * @self: a #FuAcpiTable |
87 | | * |
88 | | * Gets an optional OEM table ID. |
89 | | * |
90 | | * Returns: a string, or %NULL |
91 | | * |
92 | | * Since: 1.8.11 |
93 | | **/ |
94 | | const gchar * |
95 | | fu_acpi_table_get_oem_table_id(FuAcpiTable *self) |
96 | 0 | { |
97 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
98 | 0 | g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL); |
99 | 0 | return priv->oem_table_id; |
100 | 0 | } |
101 | | |
102 | | /** |
103 | | * fu_acpi_table_get_oem_revision: |
104 | | * @self: a #FuAcpiTable |
105 | | * |
106 | | * Gets the OEM revision. |
107 | | * |
108 | | * Returns: integer, default 0x0 |
109 | | * |
110 | | * Since: 1.8.11 |
111 | | **/ |
112 | | guint32 |
113 | | fu_acpi_table_get_oem_revision(FuAcpiTable *self) |
114 | 0 | { |
115 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
116 | 0 | g_return_val_if_fail(FU_IS_ACPI_TABLE(self), G_MAXUINT32); |
117 | 0 | return priv->oem_revision; |
118 | 0 | } |
119 | | |
120 | | /** |
121 | | * fu_acpi_table_get_payload: |
122 | | * @self: a #FuAcpiTable |
123 | | * |
124 | | * Gets the payload after the ACPI header. This function will fail if there is no payload. |
125 | | * |
126 | | * Returns: (transfer full): a #GInputStream, or %NULL on error |
127 | | * |
128 | | * Since: 2.1.3 |
129 | | **/ |
130 | | GInputStream * |
131 | | fu_acpi_table_get_payload(FuAcpiTable *self, GError **error) |
132 | 0 | { |
133 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
134 | 0 | g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL); |
135 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
136 | 0 | if (priv->payload == NULL) { |
137 | 0 | g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_DATA, "no payload"); |
138 | 0 | return NULL; |
139 | 0 | } |
140 | 0 | return g_object_ref(priv->payload); |
141 | 0 | } |
142 | | |
143 | | static gboolean |
144 | | fu_acpi_table_parse(FuFirmware *firmware, |
145 | | GInputStream *stream, |
146 | | FuFirmwareParseFlags flags, |
147 | | GError **error) |
148 | 0 | { |
149 | 0 | FuAcpiTable *self = FU_ACPI_TABLE(firmware); |
150 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
151 | 0 | gsize streamsz = 0; |
152 | 0 | guint32 length; |
153 | 0 | g_autofree gchar *id = NULL; |
154 | 0 | g_autoptr(FuStructAcpiTable) st = NULL; |
155 | | |
156 | | /* parse */ |
157 | 0 | st = fu_struct_acpi_table_parse_stream(stream, 0x0, error); |
158 | 0 | if (st == NULL) |
159 | 0 | return FALSE; |
160 | 0 | id = fu_struct_acpi_table_get_signature(st); |
161 | 0 | fu_firmware_set_id(FU_FIRMWARE(self), id); |
162 | 0 | priv->revision = fu_struct_acpi_table_get_revision(st); |
163 | 0 | priv->oem_id = fu_struct_acpi_table_get_oem_id(st); |
164 | 0 | priv->oem_table_id = fu_struct_acpi_table_get_oem_table_id(st); |
165 | 0 | priv->oem_revision = fu_struct_acpi_table_get_oem_revision(st); |
166 | | |
167 | | /* length */ |
168 | 0 | length = fu_struct_acpi_table_get_length(st); |
169 | 0 | if (!fu_input_stream_size(stream, &streamsz, error)) |
170 | 0 | return FALSE; |
171 | 0 | if (length > streamsz || length < st->buf->len) { |
172 | 0 | g_set_error(error, |
173 | 0 | FWUPD_ERROR, |
174 | 0 | FWUPD_ERROR_INVALID_DATA, |
175 | 0 | "table length not valid: got 0x%x but expected 0x%x", |
176 | 0 | (guint)streamsz, |
177 | 0 | (guint)length); |
178 | 0 | return FALSE; |
179 | 0 | } |
180 | 0 | fu_firmware_set_size(firmware, length); |
181 | | |
182 | | /* checksum */ |
183 | 0 | if ((flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) { |
184 | 0 | guint8 checksum_actual = 0; |
185 | 0 | if (!fu_input_stream_compute_sum8(stream, &checksum_actual, error)) |
186 | 0 | return FALSE; |
187 | 0 | if (checksum_actual != 0x0) { |
188 | 0 | guint8 checksum = fu_struct_acpi_table_get_checksum(st); |
189 | 0 | g_set_error(error, |
190 | 0 | FWUPD_ERROR, |
191 | 0 | FWUPD_ERROR_INTERNAL, |
192 | 0 | "CRC failed, expected 0x%02x, got 0x%02x", |
193 | 0 | (guint)checksum - checksum_actual, |
194 | 0 | checksum); |
195 | 0 | return FALSE; |
196 | 0 | } |
197 | 0 | } |
198 | | |
199 | | /* optional payload */ |
200 | 0 | if ((flags & FU_FIRMWARE_PARSE_FLAG_CACHE_STREAM) > 0 && |
201 | 0 | length != FU_STRUCT_ACPI_TABLE_SIZE) { |
202 | 0 | priv->payload = fu_partial_input_stream_new(stream, |
203 | 0 | FU_STRUCT_ACPI_TABLE_SIZE, |
204 | 0 | length - FU_STRUCT_ACPI_TABLE_SIZE, |
205 | 0 | error); |
206 | 0 | if (priv->payload == NULL) |
207 | 0 | return FALSE; |
208 | 0 | } |
209 | | |
210 | | /* success */ |
211 | 0 | return TRUE; |
212 | 0 | } |
213 | | |
214 | | static GByteArray * |
215 | | fu_acpi_table_write(FuFirmware *firmware, GError **error) |
216 | 0 | { |
217 | 0 | FuAcpiTable *self = FU_ACPI_TABLE(firmware); |
218 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
219 | 0 | g_autoptr(FuStructAcpiTable) st = fu_struct_acpi_table_new(); |
220 | 0 | g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); |
221 | 0 | g_autoptr(GBytes) payload_blob = NULL; |
222 | | |
223 | | /* generate from images if required */ |
224 | 0 | if (images->len > 0) { |
225 | 0 | g_autoptr(GByteArray) payload = g_byte_array_new(); |
226 | 0 | for (guint i = 0; i < images->len; i++) { |
227 | 0 | FuFirmware *img = g_ptr_array_index(images, i); |
228 | 0 | g_autoptr(GBytes) fw = fu_firmware_write(img, error); |
229 | 0 | if (fw == NULL) |
230 | 0 | return NULL; |
231 | 0 | fu_byte_array_append_bytes(payload, fw); |
232 | 0 | } |
233 | 0 | payload_blob = g_byte_array_free_to_bytes(g_steal_pointer(&payload)); |
234 | 0 | } else if (priv->payload != NULL) { |
235 | 0 | payload_blob = |
236 | 0 | fu_input_stream_read_bytes(priv->payload, 0x0, G_MAXSIZE, NULL, error); |
237 | 0 | if (payload_blob == NULL) |
238 | 0 | return NULL; |
239 | 0 | } else { |
240 | 0 | payload_blob = g_bytes_new(NULL, 0); |
241 | 0 | } |
242 | | |
243 | | /* pack */ |
244 | 0 | if (!fu_struct_acpi_table_set_signature(st, fu_firmware_get_id(firmware), error)) |
245 | 0 | return NULL; |
246 | 0 | fu_struct_acpi_table_set_length(st, |
247 | 0 | FU_STRUCT_ACPI_TABLE_SIZE + g_bytes_get_size(payload_blob)); |
248 | 0 | fu_struct_acpi_table_set_revision(st, priv->revision); |
249 | 0 | if (!fu_struct_acpi_table_set_oem_id(st, priv->oem_id, error)) |
250 | 0 | return NULL; |
251 | 0 | if (!fu_struct_acpi_table_set_oem_table_id(st, priv->oem_table_id, error)) |
252 | 0 | return NULL; |
253 | 0 | fu_struct_acpi_table_set_oem_revision(st, priv->oem_revision); |
254 | 0 | if (!fu_struct_acpi_table_set_creator_id(st, "FWPD", error)) |
255 | 0 | return NULL; |
256 | 0 | fu_struct_acpi_table_set_creator_revision(st, 0x1); |
257 | | |
258 | | /* payload */ |
259 | 0 | fu_byte_array_append_bytes(st->buf, payload_blob); |
260 | | |
261 | | /* fixup checksum */ |
262 | 0 | fu_struct_acpi_table_set_checksum(st, 0x100 - fu_sum8(st->buf->data, st->buf->len)); |
263 | | |
264 | | /* success */ |
265 | 0 | return g_steal_pointer(&st->buf); |
266 | 0 | } |
267 | | |
268 | | static void |
269 | | fu_acpi_table_init(FuAcpiTable *self) |
270 | 0 | { |
271 | 0 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); |
272 | 0 | fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB); |
273 | 0 | fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); |
274 | 0 | fu_firmware_add_image_gtype(FU_FIRMWARE(self), FU_TYPE_FIRMWARE); |
275 | 0 | } |
276 | | |
277 | | static void |
278 | | fu_acpi_table_finalize(GObject *object) |
279 | 0 | { |
280 | 0 | FuAcpiTable *self = FU_ACPI_TABLE(object); |
281 | 0 | FuAcpiTablePrivate *priv = GET_PRIVATE(self); |
282 | |
|
283 | 0 | if (priv->payload != NULL) |
284 | 0 | g_object_unref(priv->payload); |
285 | 0 | g_free(priv->oem_table_id); |
286 | 0 | g_free(priv->oem_id); |
287 | 0 | G_OBJECT_CLASS(fu_acpi_table_parent_class)->finalize(object); |
288 | 0 | } |
289 | | |
290 | | static void |
291 | | fu_acpi_table_class_init(FuAcpiTableClass *klass) |
292 | 0 | { |
293 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
294 | 0 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
295 | 0 | object_class->finalize = fu_acpi_table_finalize; |
296 | 0 | firmware_class->parse = fu_acpi_table_parse; |
297 | 0 | firmware_class->export = fu_acpi_table_export; |
298 | 0 | firmware_class->write = fu_acpi_table_write; |
299 | 0 | } |
300 | | |
301 | | /** |
302 | | * fu_acpi_table_new: |
303 | | * |
304 | | * Creates a new #FuFirmware |
305 | | * |
306 | | * Since: 1.8.11 |
307 | | **/ |
308 | | FuFirmware * |
309 | | fu_acpi_table_new(void) |
310 | 0 | { |
311 | 0 | return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_TABLE, NULL)); |
312 | 0 | } |