/src/fwupd/libfwupdplugin/fu-usb-device-ds20.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuUsbDeviceDs20" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-dump.h" |
12 | | #include "fu-input-stream.h" |
13 | | #include "fu-usb-device-ds20-struct.h" |
14 | | #include "fu-usb-device-ds20.h" |
15 | | |
16 | | /** |
17 | | * FuUsbDeviceDs20: |
18 | | * |
19 | | * A USB DS20 BOS descriptor. |
20 | | * |
21 | | * See also: [class@FuUsbDevice] |
22 | | */ |
23 | | |
24 | | typedef struct { |
25 | | guint32 version_lowest; |
26 | | } FuUsbDeviceDs20Private; |
27 | | |
28 | 0 | G_DEFINE_TYPE_WITH_PRIVATE(FuUsbDeviceDs20, fu_usb_device_ds20, FU_TYPE_FIRMWARE) |
29 | 0 | #define GET_PRIVATE(o) (fu_usb_device_ds20_get_instance_private(o)) |
30 | | |
31 | | typedef struct { |
32 | | guint32 platform_ver; |
33 | | guint16 total_length; |
34 | | guint8 vendor_code; |
35 | | guint8 alt_code; |
36 | | } FuUsbDeviceDs20Item; |
37 | | |
38 | | /** |
39 | | * fu_usb_device_ds20_set_version_lowest: |
40 | | * @self: a #FuUsbDeviceDs20 |
41 | | * @version_lowest: version number |
42 | | * |
43 | | * Sets the lowest possible `platform_ver` for a DS20 descriptor. |
44 | | * |
45 | | * Since: 1.8.5 |
46 | | **/ |
47 | | void |
48 | | fu_usb_device_ds20_set_version_lowest(FuUsbDeviceDs20 *self, guint32 version_lowest) |
49 | 0 | { |
50 | 0 | FuUsbDeviceDs20Private *priv = GET_PRIVATE(self); |
51 | 0 | g_return_if_fail(FU_IS_USB_DEVICE_DS20(self)); |
52 | 0 | priv->version_lowest = version_lowest; |
53 | 0 | } |
54 | | |
55 | | /** |
56 | | * fu_usb_device_ds20_apply_to_device: |
57 | | * @self: a #FuUsbDeviceDs20 |
58 | | * @device: a #FuUsbDevice |
59 | | * @error: (nullable): optional return location for an error |
60 | | * |
61 | | * Sets the DS20 descriptor onto @device. |
62 | | * |
63 | | * Returns: %TRUE for success |
64 | | * |
65 | | * Since: 1.8.5 |
66 | | **/ |
67 | | gboolean |
68 | | fu_usb_device_ds20_apply_to_device(FuUsbDeviceDs20 *self, FuUsbDevice *device, GError **error) |
69 | 0 | { |
70 | 0 | FuUsbDeviceDs20Class *klass = FU_USB_DEVICE_DS20_GET_CLASS(self); |
71 | 0 | gsize actual_length = 0; |
72 | 0 | gsize total_length = fu_firmware_get_size(FU_FIRMWARE(self)); |
73 | 0 | guint8 vendor_code = fu_firmware_get_idx(FU_FIRMWARE(self)); |
74 | 0 | g_autofree guint8 *buf = g_malloc0(total_length); |
75 | 0 | g_autoptr(GInputStream) stream = NULL; |
76 | |
|
77 | 0 | g_return_val_if_fail(FU_IS_USB_DEVICE_DS20(self), FALSE); |
78 | 0 | g_return_val_if_fail(FU_IS_USB_DEVICE(device), FALSE); |
79 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
80 | | |
81 | 0 | if (!fu_usb_device_control_transfer(device, |
82 | 0 | FU_USB_DIRECTION_DEVICE_TO_HOST, |
83 | 0 | FU_USB_REQUEST_TYPE_VENDOR, |
84 | 0 | FU_USB_RECIPIENT_DEVICE, |
85 | 0 | vendor_code, /* bRequest */ |
86 | 0 | 0x0, /* wValue */ |
87 | 0 | 0x07, /* wIndex */ |
88 | 0 | buf, |
89 | 0 | total_length, |
90 | 0 | &actual_length, |
91 | 0 | 500, |
92 | 0 | NULL, /* cancellable */ |
93 | 0 | error)) { |
94 | 0 | g_prefix_error(error, "requested vendor code 0x%02x: ", vendor_code); |
95 | 0 | return FALSE; |
96 | 0 | } |
97 | 0 | if (total_length != actual_length) { |
98 | 0 | g_set_error(error, |
99 | 0 | FWUPD_ERROR, |
100 | 0 | FWUPD_ERROR_INVALID_DATA, |
101 | 0 | "expected 0x%x bytes from vendor code 0x%02x, but got 0x%x", |
102 | 0 | (guint)total_length, |
103 | 0 | vendor_code, |
104 | 0 | (guint)actual_length); |
105 | 0 | return FALSE; |
106 | 0 | } |
107 | | |
108 | | /* debug */ |
109 | 0 | fu_dump_raw(G_LOG_DOMAIN, "PlatformCapabilityOs20", buf, actual_length); |
110 | | |
111 | | /* FuUsbDeviceDs20->parse */ |
112 | 0 | stream = g_memory_input_stream_new_from_data(buf, actual_length, NULL); |
113 | 0 | return klass->parse(self, stream, device, error); |
114 | 0 | } |
115 | | |
116 | | static gboolean |
117 | | fu_usb_device_ds20_validate(FuFirmware *firmware, |
118 | | GInputStream *stream, |
119 | | gsize offset, |
120 | | GError **error) |
121 | 0 | { |
122 | 0 | g_autoptr(FuStructDs20) st = NULL; |
123 | 0 | g_autofree gchar *guid_str = NULL; |
124 | | |
125 | | /* matches the correct UUID */ |
126 | 0 | st = fu_struct_ds20_parse_stream(stream, offset, error); |
127 | 0 | if (st == NULL) |
128 | 0 | return FALSE; |
129 | 0 | guid_str = fwupd_guid_to_string(fu_struct_ds20_get_guid(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); |
130 | 0 | if (g_strcmp0(guid_str, fu_firmware_get_id(firmware)) != 0) { |
131 | 0 | g_set_error(error, |
132 | 0 | FWUPD_ERROR, |
133 | 0 | FWUPD_ERROR_INVALID_FILE, |
134 | 0 | "invalid UUID for DS20, expected %s", |
135 | 0 | fu_firmware_get_id(firmware)); |
136 | 0 | return FALSE; |
137 | 0 | } |
138 | | |
139 | | /* success */ |
140 | 0 | return TRUE; |
141 | 0 | } |
142 | | |
143 | | static gint |
144 | | fu_usb_device_ds20_sort_by_platform_ver_cb(gconstpointer a, gconstpointer b) |
145 | 0 | { |
146 | 0 | FuUsbDeviceDs20Item *ds1 = *((FuUsbDeviceDs20Item **)a); |
147 | 0 | FuUsbDeviceDs20Item *ds2 = *((FuUsbDeviceDs20Item **)b); |
148 | 0 | if (ds1->platform_ver < ds2->platform_ver) |
149 | 0 | return -1; |
150 | 0 | if (ds1->platform_ver > ds2->platform_ver) |
151 | 0 | return 1; |
152 | 0 | return 0; |
153 | 0 | } |
154 | | |
155 | | static gboolean |
156 | | fu_usb_device_ds20_parse(FuFirmware *firmware, |
157 | | GInputStream *stream, |
158 | | FuFirmwareParseFlags flags, |
159 | | GError **error) |
160 | 0 | { |
161 | 0 | FuUsbDeviceDs20 *self = FU_USB_DEVICE_DS20(firmware); |
162 | 0 | FuUsbDeviceDs20Private *priv = GET_PRIVATE(self); |
163 | 0 | gsize streamsz = 0; |
164 | 0 | guint version_lowest = fu_firmware_get_version_raw(firmware); |
165 | 0 | g_autoptr(GPtrArray) dsinfos = g_ptr_array_new_with_free_func(g_free); |
166 | |
|
167 | 0 | if (!fu_input_stream_size(stream, &streamsz, error)) |
168 | 0 | return FALSE; |
169 | 0 | for (gsize off = 0; off < streamsz; off += FU_STRUCT_DS20_SIZE) { |
170 | 0 | g_autofree FuUsbDeviceDs20Item *dsinfo = g_new0(FuUsbDeviceDs20Item, 1); |
171 | 0 | g_autoptr(FuStructDs20) st = NULL; |
172 | | |
173 | | /* parse */ |
174 | 0 | st = fu_struct_ds20_parse_stream(stream, off, error); |
175 | 0 | if (st == NULL) |
176 | 0 | return FALSE; |
177 | 0 | dsinfo->platform_ver = fu_struct_ds20_get_platform_ver(st); |
178 | 0 | dsinfo->total_length = fu_struct_ds20_get_total_length(st); |
179 | 0 | dsinfo->vendor_code = fu_struct_ds20_get_vendor_code(st); |
180 | 0 | dsinfo->alt_code = fu_struct_ds20_get_alt_code(st); |
181 | 0 | g_debug("PlatformVersion=0x%08x, TotalLength=0x%04x, VendorCode=0x%02x, " |
182 | 0 | "AltCode=0x%02x", |
183 | 0 | dsinfo->platform_ver, |
184 | 0 | dsinfo->total_length, |
185 | 0 | dsinfo->vendor_code, |
186 | 0 | dsinfo->alt_code); |
187 | 0 | g_ptr_array_add(dsinfos, g_steal_pointer(&dsinfo)); |
188 | 0 | } |
189 | | |
190 | | /* sort by platform_ver, highest first */ |
191 | 0 | g_ptr_array_sort(dsinfos, fu_usb_device_ds20_sort_by_platform_ver_cb); |
192 | | |
193 | | /* find the newest info that's not newer than the lowest version */ |
194 | 0 | for (guint i = 0; i < dsinfos->len; i++) { |
195 | 0 | FuUsbDeviceDs20Item *dsinfo = g_ptr_array_index(dsinfos, i); |
196 | | |
197 | | /* not valid */ |
198 | 0 | if (dsinfo->platform_ver == 0x0) { |
199 | 0 | g_set_error(error, |
200 | 0 | FWUPD_ERROR, |
201 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
202 | 0 | "invalid platform version 0x%08x", |
203 | 0 | dsinfo->platform_ver); |
204 | 0 | return FALSE; |
205 | 0 | } |
206 | 0 | if (dsinfo->platform_ver < priv->version_lowest) { |
207 | 0 | g_set_error(error, |
208 | 0 | FWUPD_ERROR, |
209 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
210 | 0 | "invalid platform version 0x%08x, expected >= 0x%08x", |
211 | 0 | dsinfo->platform_ver, |
212 | 0 | priv->version_lowest); |
213 | 0 | return FALSE; |
214 | 0 | } |
215 | | |
216 | | /* dwVersion is effectively the minimum version */ |
217 | 0 | if (dsinfo->platform_ver <= version_lowest) { |
218 | 0 | fu_firmware_set_size(firmware, dsinfo->total_length); |
219 | 0 | fu_firmware_set_idx(firmware, dsinfo->vendor_code); |
220 | 0 | return TRUE; |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | /* failed */ |
225 | 0 | g_set_error_literal(error, |
226 | 0 | FWUPD_ERROR, |
227 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
228 | 0 | "no supported platform version"); |
229 | 0 | return FALSE; |
230 | 0 | } |
231 | | |
232 | | static GByteArray * |
233 | | fu_usb_device_ds20_write(FuFirmware *firmware, GError **error) |
234 | 0 | { |
235 | 0 | g_autoptr(FuStructDs20) st = fu_struct_ds20_new(); |
236 | 0 | fwupd_guid_t guid = {0x0}; |
237 | | |
238 | | /* pack */ |
239 | 0 | if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), |
240 | 0 | &guid, |
241 | 0 | FWUPD_GUID_FLAG_MIXED_ENDIAN, |
242 | 0 | error)) |
243 | 0 | return NULL; |
244 | 0 | fu_struct_ds20_set_guid(st, &guid); |
245 | 0 | fu_struct_ds20_set_platform_ver(st, fu_firmware_get_version_raw(firmware)); |
246 | 0 | fu_struct_ds20_set_total_length(st, fu_firmware_get_size(firmware)); |
247 | 0 | fu_struct_ds20_set_vendor_code(st, fu_firmware_get_idx(firmware)); |
248 | 0 | return g_steal_pointer(&st->buf); |
249 | 0 | } |
250 | | |
251 | | static void |
252 | | fu_usb_device_ds20_init(FuUsbDeviceDs20 *self) |
253 | 0 | { |
254 | 0 | fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); |
255 | 0 | fu_firmware_set_size_max(FU_FIRMWARE(self), 1 * FU_MB); |
256 | 0 | } |
257 | | |
258 | | static void |
259 | | fu_usb_device_ds20_class_init(FuUsbDeviceDs20Class *klass) |
260 | 0 | { |
261 | 0 | FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); |
262 | 0 | firmware_class->validate = fu_usb_device_ds20_validate; |
263 | 0 | firmware_class->parse = fu_usb_device_ds20_parse; |
264 | 0 | firmware_class->write = fu_usb_device_ds20_write; |
265 | 0 | } |