/src/fwupd/libfwupdplugin/fu-backend.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2021 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "FuBackend" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "fu-device-locker.h" |
12 | | #include "fu-device-private.h" |
13 | | |
14 | | /** |
15 | | * FuBackend: |
16 | | * |
17 | | * An device discovery backend, for instance USB, BlueZ or UDev. |
18 | | * |
19 | | * See also: [class@FuDevice] |
20 | | */ |
21 | | |
22 | | typedef struct { |
23 | | FuContext *ctx; |
24 | | gchar *name; |
25 | | gboolean enabled; |
26 | | gboolean done_setup; |
27 | | gboolean can_invalidate; |
28 | | GType device_gtype; |
29 | | GHashTable *devices; /* device_id : * FuDevice */ |
30 | | GThread *thread_init; |
31 | | } FuBackendPrivate; |
32 | | |
33 | | enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; |
34 | | |
35 | | enum { PROP_0, PROP_NAME, PROP_CAN_INVALIDATE, PROP_CONTEXT, PROP_DEVICE_GTYPE, PROP_LAST }; |
36 | | |
37 | | static guint signals[SIGNAL_LAST] = {0}; |
38 | | |
39 | | static void |
40 | | fu_backend_codec_iface_init(FwupdCodecInterface *iface); |
41 | | |
42 | 2 | G_DEFINE_TYPE_EXTENDED(FuBackend, |
43 | 2 | fu_backend, |
44 | 2 | G_TYPE_OBJECT, |
45 | 2 | 0, |
46 | 2 | G_ADD_PRIVATE(FuBackend) |
47 | 2 | G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_backend_codec_iface_init)); |
48 | 2 | |
49 | 2 | #define GET_PRIVATE(o) (fu_backend_get_instance_private(o)) |
50 | | |
51 | | /** |
52 | | * fu_backend_device_added: |
53 | | * @self: a #FuBackend |
54 | | * @device: a device |
55 | | * |
56 | | * Emits a signal that indicates the device has been added. |
57 | | * |
58 | | * Since: 1.6.1 |
59 | | **/ |
60 | | void |
61 | | fu_backend_device_added(FuBackend *self, FuDevice *device) |
62 | 0 | { |
63 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
64 | 0 | g_return_if_fail(FU_IS_BACKEND(self)); |
65 | 0 | g_return_if_fail(FU_IS_DEVICE(device)); |
66 | 0 | g_return_if_fail(priv->thread_init == g_thread_self()); |
67 | | |
68 | | /* assign context if set */ |
69 | 0 | if (priv->ctx != NULL) |
70 | 0 | fu_device_set_context(device, priv->ctx); |
71 | | |
72 | | /* we set this here to be able to get the parent in plugins */ |
73 | 0 | fu_device_set_backend(device, self); |
74 | | |
75 | | /* set backend ID if required */ |
76 | 0 | if (fu_device_get_backend_id(device) == NULL) |
77 | 0 | fu_device_set_backend_id(device, priv->name); |
78 | | |
79 | | /* set created to *now* */ |
80 | 0 | if (fu_device_get_created_usec(device) == 0) |
81 | 0 | fu_device_set_created_usec(device, g_get_real_time()); |
82 | | |
83 | | /* sanity check */ |
84 | 0 | if (g_hash_table_contains(priv->devices, fu_device_get_backend_id(device))) { |
85 | 0 | g_debug("replacing existing device with backend_id %s", |
86 | 0 | fu_device_get_backend_id(device)); |
87 | 0 | } |
88 | | |
89 | | /* add */ |
90 | 0 | g_hash_table_insert(priv->devices, |
91 | 0 | g_strdup(fu_device_get_backend_id(device)), |
92 | 0 | g_object_ref(device)); |
93 | 0 | g_signal_emit(self, signals[SIGNAL_ADDED], 0, device); |
94 | 0 | } |
95 | | |
96 | | /** |
97 | | * fu_backend_device_removed: |
98 | | * @self: a #FuBackend |
99 | | * @device: a device |
100 | | * |
101 | | * Emits a signal that indicates the device has been removed. |
102 | | * |
103 | | * Since: 1.6.1 |
104 | | **/ |
105 | | void |
106 | | fu_backend_device_removed(FuBackend *self, FuDevice *device) |
107 | 0 | { |
108 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
109 | 0 | g_return_if_fail(FU_IS_BACKEND(self)); |
110 | 0 | g_return_if_fail(FU_IS_DEVICE(device)); |
111 | 0 | g_return_if_fail(priv->thread_init == g_thread_self()); |
112 | 0 | g_signal_emit(self, signals[SIGNAL_REMOVED], 0, device); |
113 | 0 | g_hash_table_remove(priv->devices, fu_device_get_backend_id(device)); |
114 | 0 | } |
115 | | |
116 | | /** |
117 | | * fu_backend_device_changed: |
118 | | * @self: a #FuBackend |
119 | | * @device: a device |
120 | | * |
121 | | * Emits a signal that indicates the device has been changed. |
122 | | * |
123 | | * Since: 1.6.1 |
124 | | **/ |
125 | | void |
126 | | fu_backend_device_changed(FuBackend *self, FuDevice *device) |
127 | 0 | { |
128 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
129 | 0 | g_return_if_fail(FU_IS_BACKEND(self)); |
130 | 0 | g_return_if_fail(FU_IS_DEVICE(device)); |
131 | 0 | g_return_if_fail(priv->thread_init == g_thread_self()); |
132 | 0 | g_signal_emit(self, signals[SIGNAL_CHANGED], 0, device); |
133 | 0 | } |
134 | | |
135 | | /** |
136 | | * fu_backend_registered: |
137 | | * @self: a #FuBackend |
138 | | * @device: a device |
139 | | * |
140 | | * Calls the ->registered() vfunc for the backend. This allows the backend to perform shared |
141 | | * backend actions on superclassed devices. |
142 | | * |
143 | | * Since: 1.7.4 |
144 | | **/ |
145 | | void |
146 | | fu_backend_registered(FuBackend *self, FuDevice *device) |
147 | 0 | { |
148 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
149 | 0 | g_return_if_fail(FU_IS_BACKEND(self)); |
150 | 0 | g_return_if_fail(FU_IS_DEVICE(device)); |
151 | 0 | if (klass->registered != NULL) |
152 | 0 | klass->registered(self, device); |
153 | 0 | } |
154 | | |
155 | | /** |
156 | | * fu_backend_get_device_parent: |
157 | | * @self: a #FuBackend |
158 | | * @device: a #FuDevice |
159 | | * @subsystem: (nullable): an optional device subsystem, e.g. "usb:usb_device" |
160 | | * @error: (nullable): optional return location for an error |
161 | | * |
162 | | * Asks the backend to create the parent device (of the correct type) for a given device subsystem. |
163 | | * |
164 | | * Returns: (transfer full): a #FuDevice or %NULL if not found or unimplemented |
165 | | * |
166 | | * Since: 2.0.0 |
167 | | **/ |
168 | | FuDevice * |
169 | | fu_backend_get_device_parent(FuBackend *self, |
170 | | FuDevice *device, |
171 | | const gchar *subsystem, |
172 | | GError **error) |
173 | 0 | { |
174 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
175 | |
|
176 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), NULL); |
177 | 0 | g_return_val_if_fail(FU_IS_DEVICE(device), NULL); |
178 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
179 | | |
180 | 0 | if (klass->get_device_parent == NULL) { |
181 | 0 | g_set_error(error, |
182 | 0 | FWUPD_ERROR, |
183 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
184 | 0 | "->get_device_parent is not implemented in %s", |
185 | 0 | G_OBJECT_TYPE_NAME(self)); |
186 | 0 | return NULL; |
187 | 0 | } |
188 | 0 | return klass->get_device_parent(self, device, subsystem, error); |
189 | 0 | } |
190 | | |
191 | | /** |
192 | | * fu_backend_create_device: |
193 | | * @self: a #FuBackend |
194 | | * @backend_id: a backend ID, typically a sysfs path |
195 | | * @error: (nullable): optional return location for an error |
196 | | * |
197 | | * Asks the backend to create a device (of the correct type) for a given device backend ID. |
198 | | * |
199 | | * Returns: (transfer full): a #FuDevice or %NULL if not found or unimplemented |
200 | | * |
201 | | * Since: 2.0.0 |
202 | | **/ |
203 | | FuDevice * |
204 | | fu_backend_create_device(FuBackend *self, const gchar *backend_id, GError **error) |
205 | 0 | { |
206 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
207 | 0 | g_autoptr(FuDevice) device = NULL; |
208 | |
|
209 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), NULL); |
210 | 0 | g_return_val_if_fail(backend_id != NULL, NULL); |
211 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
212 | | |
213 | 0 | if (klass->create_device == NULL) { |
214 | 0 | g_set_error(error, |
215 | 0 | FWUPD_ERROR, |
216 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
217 | 0 | "->create_device is not implemented in %s", |
218 | 0 | G_OBJECT_TYPE_NAME(self)); |
219 | 0 | return NULL; |
220 | 0 | } |
221 | 0 | device = klass->create_device(self, backend_id, error); |
222 | 0 | if (device == NULL) |
223 | 0 | return NULL; |
224 | 0 | fu_device_set_backend(device, self); |
225 | | |
226 | | /* success */ |
227 | 0 | return g_steal_pointer(&device); |
228 | 0 | } |
229 | | |
230 | | /** |
231 | | * fu_backend_create_device_for_donor: |
232 | | * @self: a #FuBackend |
233 | | * @donor: a donor #FuDevice |
234 | | * @error: (nullable): optional return location for an error |
235 | | * |
236 | | * Asks the backend to create a device (of the correct type) for a given donor device. |
237 | | * |
238 | | * Returns: (transfer full): a #FuDevice or %NULL if not found or unimplemented |
239 | | * |
240 | | * Since: 2.0.0 |
241 | | **/ |
242 | | FuDevice * |
243 | | fu_backend_create_device_for_donor(FuBackend *self, FuDevice *donor, GError **error) |
244 | 0 | { |
245 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
246 | 0 | g_autoptr(FuDevice) device = NULL; |
247 | |
|
248 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), NULL); |
249 | 0 | g_return_val_if_fail(FU_IS_DEVICE(donor), NULL); |
250 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
251 | | |
252 | | /* no impl */ |
253 | 0 | if (klass->create_device_for_donor == NULL) |
254 | 0 | return g_object_ref(donor); |
255 | | |
256 | | /* impl */ |
257 | 0 | device = klass->create_device_for_donor(self, donor, error); |
258 | 0 | if (device == NULL) |
259 | 0 | return NULL; |
260 | 0 | fu_device_set_backend(device, self); |
261 | | |
262 | | /* success */ |
263 | 0 | return g_steal_pointer(&device); |
264 | 0 | } |
265 | | |
266 | | /** |
267 | | * fu_backend_invalidate: |
268 | | * @self: a #FuBackend |
269 | | * |
270 | | * Normally when calling [method@FuBackend.setup] multiple times it is only actually done once. |
271 | | * Calling this method causes the next requests to [method@FuBackend.setup] to actually probe the |
272 | | * hardware. |
273 | | * |
274 | | * Only subclassed backends setting `can-invalidate=TRUE` at construction time can use this |
275 | | * method, as it is not always safe to call for backends shared between multiple plugins. |
276 | | * |
277 | | * This should be done in case the backing information source has changed, for instance if |
278 | | * a platform subsystem has been updated. |
279 | | * |
280 | | * This also optionally calls the ->invalidate() vfunc for the backend. This allows the backend |
281 | | * to perform shared backend actions on superclassed devices. |
282 | | * |
283 | | * Since: 1.8.0 |
284 | | **/ |
285 | | void |
286 | | fu_backend_invalidate(FuBackend *self) |
287 | 0 | { |
288 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
289 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
290 | |
|
291 | 0 | g_return_if_fail(FU_IS_BACKEND(self)); |
292 | 0 | g_return_if_fail(priv->can_invalidate); |
293 | | |
294 | 0 | priv->done_setup = FALSE; |
295 | 0 | if (klass->invalidate != NULL) |
296 | 0 | klass->invalidate(self); |
297 | 0 | } |
298 | | |
299 | | /** |
300 | | * fu_backend_add_string: |
301 | | * @self: a #FuBackend |
302 | | * @idt: indent level |
303 | | * @str: a string to append to |
304 | | * |
305 | | * Add backend-specific device metadata to an existing string. |
306 | | * |
307 | | * Since: 1.8.4 |
308 | | **/ |
309 | | void |
310 | | fu_backend_add_string(FuBackend *self, guint idt, GString *str) |
311 | 0 | { |
312 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
313 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
314 | |
|
315 | 0 | fwupd_codec_string_append(str, idt, G_OBJECT_TYPE_NAME(self), ""); |
316 | 0 | fwupd_codec_string_append(str, idt + 1, "Name", priv->name); |
317 | 0 | fwupd_codec_string_append_bool(str, idt + 1, "Enabled", priv->enabled); |
318 | 0 | fwupd_codec_string_append_bool(str, idt + 1, "DoneSetup", priv->done_setup); |
319 | 0 | fwupd_codec_string_append_bool(str, idt + 1, "CanInvalidate", priv->can_invalidate); |
320 | | |
321 | | /* subclassed */ |
322 | 0 | if (klass->to_string != NULL) |
323 | 0 | klass->to_string(self, idt, str); |
324 | 0 | } |
325 | | |
326 | | /** |
327 | | * fu_backend_setup: |
328 | | * @self: a #FuBackend |
329 | | * @flags: some #FuBackendSetupFlags, e.g. %FU_BACKEND_SETUP_FLAG_USE_HOTPLUG |
330 | | * @progress: a #FuProgress |
331 | | * @error: (nullable): optional return location for an error |
332 | | * |
333 | | * Sets up the backend ready for use, which typically calls the subclassed setup |
334 | | * function. No devices should be added or removed at this point. |
335 | | * |
336 | | * Returns: %TRUE for success |
337 | | * |
338 | | * Since: 1.6.1 |
339 | | **/ |
340 | | gboolean |
341 | | fu_backend_setup(FuBackend *self, FuBackendSetupFlags flags, FuProgress *progress, GError **error) |
342 | 0 | { |
343 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
344 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
345 | |
|
346 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); |
347 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
348 | | |
349 | 0 | if (priv->done_setup) |
350 | 0 | return TRUE; |
351 | 0 | if (klass->setup != NULL) { |
352 | 0 | if (!klass->setup(self, flags, progress, error)) { |
353 | 0 | priv->enabled = FALSE; |
354 | 0 | return FALSE; |
355 | 0 | } |
356 | 0 | } |
357 | 0 | priv->done_setup = TRUE; |
358 | 0 | return TRUE; |
359 | 0 | } |
360 | | |
361 | | static gboolean |
362 | | fu_backend_from_json(FwupdCodec *codec, FwupdJsonObject *json_obj, GError **error) |
363 | 0 | { |
364 | 0 | FuBackend *self = FU_BACKEND(codec); |
365 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
366 | 0 | g_autoptr(FwupdJsonArray) json_arr = NULL; |
367 | 0 | const gchar *fwupd_version; |
368 | 0 | g_autoptr(GPtrArray) devices_added = |
369 | 0 | g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
370 | 0 | g_autoptr(GPtrArray) devices_remove = NULL; |
371 | | |
372 | | /* no registered specialized GType */ |
373 | 0 | if (priv->device_gtype == FU_TYPE_DEVICE) { |
374 | 0 | g_debug("no registered device GType for backend %s", fu_backend_get_name(self)); |
375 | 0 | return TRUE; |
376 | 0 | } |
377 | | |
378 | | /* if recorded */ |
379 | 0 | fwupd_version = fwupd_json_object_get_string(json_obj, "FwupdVersion", NULL); |
380 | | |
381 | | /* four steps: |
382 | | * |
383 | | * 1. store all the existing devices matching the tag in devices_remove |
384 | | * 2. read the devices in the array: |
385 | | * - if the platform-id exists: replace the event data & remove from devices_remove |
386 | | * - otherwise add to devices_added |
387 | | * 3. emit devices in devices_remove |
388 | | * 4. emit devices in devices_added |
389 | | */ |
390 | 0 | devices_remove = fu_backend_get_devices(self); |
391 | 0 | json_arr = fwupd_json_object_get_array(json_obj, "UsbDevices", NULL); |
392 | 0 | if (json_arr == NULL) { |
393 | | /* remain compatible with all the old emulation files */ |
394 | 0 | return TRUE; |
395 | 0 | } |
396 | 0 | for (guint i = 0; i < fwupd_json_array_get_size(json_arr); i++) { |
397 | 0 | FuDevice *device_old; |
398 | 0 | const gchar *device_gtypestr; |
399 | 0 | GType device_gtype; |
400 | 0 | g_autofree gchar *id_display = NULL; |
401 | 0 | g_autoptr(FuDevice) device_tmp = NULL; |
402 | 0 | g_autoptr(FwupdJsonObject) object_tmp = NULL; |
403 | | |
404 | | /* sanity check */ |
405 | 0 | object_tmp = fwupd_json_array_get_object(json_arr, i, error); |
406 | 0 | if (object_tmp == NULL) |
407 | 0 | return FALSE; |
408 | | |
409 | | /* get the GType */ |
410 | 0 | device_gtypestr = fwupd_json_object_get_string(object_tmp, "GType", NULL); |
411 | 0 | if (device_gtypestr == NULL) |
412 | 0 | device_gtypestr = "FuUsbDevice"; |
413 | 0 | device_gtype = g_type_from_name(device_gtypestr); |
414 | 0 | if (device_gtype == G_TYPE_INVALID) { |
415 | 0 | g_set_error(error, |
416 | 0 | FWUPD_ERROR, |
417 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
418 | 0 | "unknown GType name %s", |
419 | 0 | device_gtypestr); |
420 | 0 | return FALSE; |
421 | 0 | } |
422 | 0 | if (!g_type_is_a(device_gtype, priv->device_gtype)) { |
423 | 0 | g_debug("ignoring device backend GType %s", |
424 | 0 | g_type_name(priv->device_gtype)); |
425 | 0 | continue; |
426 | 0 | } |
427 | | |
428 | | /* create device */ |
429 | 0 | device_tmp = g_object_new(device_gtype, "backend", self, NULL); |
430 | 0 | fu_device_add_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED); |
431 | 0 | if (fwupd_version != NULL) |
432 | 0 | fu_device_set_fwupd_version(device_tmp, fwupd_version); |
433 | 0 | if (!fu_device_from_json(device_tmp, object_tmp, error)) |
434 | 0 | return FALSE; |
435 | 0 | if (fu_device_get_backend_id(device_tmp) == NULL) { |
436 | 0 | g_set_error(error, |
437 | 0 | FWUPD_ERROR, |
438 | 0 | FWUPD_ERROR_NOT_SUPPORTED, |
439 | 0 | "no backend specified %s", |
440 | 0 | device_gtypestr); |
441 | 0 | return FALSE; |
442 | 0 | } |
443 | 0 | id_display = fu_device_get_id_display(device_tmp); |
444 | | |
445 | | /* does a device with this platform ID [and the same created date] already exist */ |
446 | 0 | device_old = fu_backend_lookup_by_id(self, fu_device_get_backend_id(device_tmp)); |
447 | | |
448 | | /* yes, and it has the same timestamp */ |
449 | 0 | if (device_old != NULL) { |
450 | 0 | g_debug("created timestamp %" G_GINT64_FORMAT "->%" G_GINT64_FORMAT, |
451 | 0 | fu_device_get_created_usec(device_old), |
452 | 0 | fu_device_get_created_usec(device_tmp)); |
453 | 0 | } |
454 | 0 | if (device_old != NULL && fu_device_get_created_usec(device_old) == |
455 | 0 | fu_device_get_created_usec(device_tmp)) { |
456 | 0 | GPtrArray *events = fu_device_get_events(device_tmp); |
457 | |
|
458 | 0 | fu_device_clear_events(device_old); |
459 | 0 | for (guint j = 0; j < events->len; j++) { |
460 | 0 | FuDeviceEvent *event = g_ptr_array_index(events, j); |
461 | 0 | fu_device_add_event(device_old, event); |
462 | 0 | } |
463 | 0 | g_debug("changed %s", id_display); |
464 | 0 | fu_backend_device_changed(self, device_old); |
465 | 0 | g_ptr_array_remove(devices_remove, device_old); |
466 | 0 | continue; |
467 | 0 | } |
468 | | |
469 | | /* new to us! */ |
470 | 0 | g_debug("not found %s, adding", id_display); |
471 | 0 | g_ptr_array_add(devices_added, g_object_ref(device_tmp)); |
472 | 0 | } |
473 | | |
474 | | /* emit removes then adds */ |
475 | 0 | for (guint i = 0; i < devices_remove->len; i++) { |
476 | 0 | FuDevice *device = g_ptr_array_index(devices_remove, i); |
477 | 0 | if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) |
478 | 0 | continue; |
479 | 0 | fu_backend_device_removed(self, device); |
480 | 0 | } |
481 | 0 | for (guint i = 0; i < devices_added->len; i++) { |
482 | 0 | FuDevice *donor = g_ptr_array_index(devices_added, i); |
483 | 0 | g_autoptr(FuDevice) device = NULL; |
484 | 0 | g_autoptr(GError) error_local = NULL; |
485 | | |
486 | | /* convert from FuUdevDevice to the superclass, e.g. FuHidrawDevice */ |
487 | 0 | if (!fu_device_probe(donor, &error_local)) { |
488 | 0 | if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { |
489 | 0 | g_propagate_error(error, g_steal_pointer(&error_local)); |
490 | 0 | return FALSE; |
491 | 0 | } |
492 | 0 | } |
493 | 0 | device = fu_backend_create_device_for_donor(self, donor, error); |
494 | 0 | if (device == NULL) |
495 | 0 | return FALSE; |
496 | 0 | fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATED); |
497 | | |
498 | | /* some devices only add plugin-matching instance IDs in FuDevice->setup() */ |
499 | 0 | if (fu_device_has_private_flag(device, |
500 | 0 | FU_DEVICE_PRIVATE_FLAG_EMULATED_REQUIRE_SETUP)) { |
501 | 0 | g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); |
502 | 0 | if (locker == NULL) |
503 | 0 | return FALSE; |
504 | 0 | } |
505 | | |
506 | 0 | fu_backend_device_added(self, device); |
507 | 0 | } |
508 | | |
509 | | /* success */ |
510 | 0 | return TRUE; |
511 | 0 | } |
512 | | |
513 | | static void |
514 | | fu_backend_add_json(FwupdCodec *codec, FwupdJsonObject *json_obj, FwupdCodecFlags flags) |
515 | 0 | { |
516 | 0 | FuBackend *self = FU_BACKEND(codec); |
517 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
518 | 0 | g_autoptr(GList) devices = NULL; |
519 | 0 | g_autoptr(FwupdJsonArray) json_arr = fwupd_json_array_new(); |
520 | | |
521 | | /* remain compatible with all the old emulation files */ |
522 | 0 | fwupd_json_object_add_string(json_obj, "FwupdVersion", PACKAGE_VERSION); |
523 | 0 | devices = g_hash_table_get_values(priv->devices); |
524 | 0 | for (GList *l = devices; l != NULL; l = l->next) { |
525 | 0 | FuDevice *device = FU_DEVICE(l->data); |
526 | 0 | g_autoptr(FwupdJsonObject) json_device = fwupd_json_object_new(); |
527 | 0 | if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) |
528 | 0 | continue; |
529 | 0 | fu_device_add_json(device, json_device, FWUPD_CODEC_FLAG_NONE); |
530 | 0 | fwupd_json_array_add_object(json_arr, json_device); |
531 | 0 | } |
532 | 0 | fwupd_json_object_add_array(json_obj, "UsbDevices", json_arr); |
533 | 0 | } |
534 | | |
535 | | /** |
536 | | * fu_backend_coldplug: |
537 | | * @self: a #FuBackend |
538 | | * @progress: a #FuProgress |
539 | | * @error: (nullable): optional return location for an error |
540 | | * |
541 | | * Adds devices using the subclassed backend. If [method@FuBackend.setup] has not |
542 | | * already been called then it is run before this function automatically. |
543 | | * |
544 | | * Returns: %TRUE for success |
545 | | * |
546 | | * Since: 1.6.1 |
547 | | **/ |
548 | | gboolean |
549 | | fu_backend_coldplug(FuBackend *self, FuProgress *progress, GError **error) |
550 | 0 | { |
551 | 0 | FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); |
552 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); |
553 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, FALSE); |
554 | 0 | if (!fu_backend_setup(self, FU_BACKEND_SETUP_FLAG_NONE, progress, error)) |
555 | 0 | return FALSE; |
556 | 0 | if (klass->coldplug == NULL) |
557 | 0 | return TRUE; |
558 | 0 | return klass->coldplug(self, progress, error); |
559 | 0 | } |
560 | | |
561 | | /** |
562 | | * fu_backend_get_name: |
563 | | * @self: a #FuBackend |
564 | | * |
565 | | * Return the name of the backend, which is normally set by the subclass. |
566 | | * |
567 | | * Returns: backend name |
568 | | * |
569 | | * Since: 1.6.1 |
570 | | **/ |
571 | | const gchar * |
572 | | fu_backend_get_name(FuBackend *self) |
573 | 0 | { |
574 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
575 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), NULL); |
576 | 0 | return priv->name; |
577 | 0 | } |
578 | | |
579 | | /** |
580 | | * fu_backend_get_context: |
581 | | * @self: a #FuBackend |
582 | | * |
583 | | * Gets the context for a backend. |
584 | | * |
585 | | * Returns: (transfer none): a #FuContext or %NULL if not set |
586 | | * |
587 | | * Since: 1.6.1 |
588 | | **/ |
589 | | FuContext * |
590 | | fu_backend_get_context(FuBackend *self) |
591 | 0 | { |
592 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
593 | 0 | return priv->ctx; |
594 | 0 | } |
595 | | |
596 | | /** |
597 | | * fu_backend_get_enabled: |
598 | | * @self: a #FuBackend |
599 | | * |
600 | | * Return the boolean value of a key if it's been configured |
601 | | * |
602 | | * Returns: %TRUE if the backend is enabled |
603 | | * |
604 | | * Since: 1.6.1 |
605 | | **/ |
606 | | gboolean |
607 | | fu_backend_get_enabled(FuBackend *self) |
608 | 0 | { |
609 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
610 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); |
611 | 0 | return priv->enabled; |
612 | 0 | } |
613 | | |
614 | | /** |
615 | | * fu_backend_set_enabled: |
616 | | * @self: a #FuBackend |
617 | | * @enabled: enabled state |
618 | | * |
619 | | * Sets the backend enabled state. |
620 | | * |
621 | | * Since: 1.6.1 |
622 | | **/ |
623 | | void |
624 | | fu_backend_set_enabled(FuBackend *self, gboolean enabled) |
625 | 0 | { |
626 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
627 | 0 | g_return_if_fail(FU_IS_BACKEND(self)); |
628 | 0 | priv->enabled = FALSE; |
629 | 0 | } |
630 | | |
631 | | /** |
632 | | * fu_backend_lookup_by_id: |
633 | | * @self: a #FuBackend |
634 | | * @backend_id: a device backend ID |
635 | | * |
636 | | * Gets a device previously added by the backend. |
637 | | * |
638 | | * Returns: (transfer none): device, or %NULL if not found |
639 | | * |
640 | | * Since: 1.6.1 |
641 | | **/ |
642 | | FuDevice * |
643 | | fu_backend_lookup_by_id(FuBackend *self, const gchar *backend_id) |
644 | 0 | { |
645 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
646 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), NULL); |
647 | 0 | g_return_val_if_fail(backend_id != NULL, NULL); |
648 | 0 | return g_hash_table_lookup(priv->devices, backend_id); |
649 | 0 | } |
650 | | |
651 | | static gint |
652 | | fu_backend_get_devices_sort_cb(gconstpointer a, gconstpointer b) |
653 | 0 | { |
654 | 0 | FuDevice *deva = *((FuDevice **)a); |
655 | 0 | FuDevice *devb = *((FuDevice **)b); |
656 | 0 | return g_strcmp0(fu_device_get_backend_id(deva), fu_device_get_backend_id(devb)); |
657 | 0 | } |
658 | | |
659 | | /** |
660 | | * fu_backend_get_devices: |
661 | | * @self: a #FuBackend |
662 | | * |
663 | | * Gets all the devices added by the backend. |
664 | | * |
665 | | * Returns: (transfer container) (element-type FuDevice): devices |
666 | | * |
667 | | * Since: 1.6.1 |
668 | | **/ |
669 | | GPtrArray * |
670 | | fu_backend_get_devices(FuBackend *self) |
671 | 0 | { |
672 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
673 | 0 | g_autoptr(GList) values = NULL; |
674 | 0 | g_autoptr(GPtrArray) devices = NULL; |
675 | |
|
676 | 0 | g_return_val_if_fail(FU_IS_BACKEND(self), NULL); |
677 | | |
678 | 0 | devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
679 | 0 | values = g_hash_table_get_values(priv->devices); |
680 | 0 | for (GList *l = values; l != NULL; l = l->next) |
681 | 0 | g_ptr_array_add(devices, g_object_ref(l->data)); |
682 | 0 | g_ptr_array_sort(devices, fu_backend_get_devices_sort_cb); |
683 | 0 | return g_steal_pointer(&devices); |
684 | 0 | } |
685 | | |
686 | | static void |
687 | | fu_backend_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) |
688 | 0 | { |
689 | 0 | FuBackend *self = FU_BACKEND(object); |
690 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
691 | 0 | switch (prop_id) { |
692 | 0 | case PROP_NAME: |
693 | 0 | g_value_set_string(value, priv->name); |
694 | 0 | break; |
695 | 0 | case PROP_CAN_INVALIDATE: |
696 | 0 | g_value_set_boolean(value, priv->can_invalidate); |
697 | 0 | break; |
698 | 0 | case PROP_CONTEXT: |
699 | 0 | g_value_set_object(value, priv->ctx); |
700 | 0 | break; |
701 | 0 | case PROP_DEVICE_GTYPE: |
702 | 0 | g_value_set_gtype(value, priv->device_gtype); |
703 | 0 | break; |
704 | 0 | default: |
705 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
706 | 0 | break; |
707 | 0 | } |
708 | 0 | } |
709 | | |
710 | | static void |
711 | | fu_backend_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) |
712 | 0 | { |
713 | 0 | FuBackend *self = FU_BACKEND(object); |
714 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
715 | 0 | switch (prop_id) { |
716 | 0 | case PROP_NAME: |
717 | 0 | priv->name = g_value_dup_string(value); |
718 | 0 | break; |
719 | 0 | case PROP_CAN_INVALIDATE: |
720 | 0 | priv->can_invalidate = g_value_get_boolean(value); |
721 | 0 | break; |
722 | 0 | case PROP_CONTEXT: |
723 | 0 | g_set_object(&priv->ctx, g_value_get_object(value)); |
724 | 0 | break; |
725 | 0 | case PROP_DEVICE_GTYPE: |
726 | 0 | priv->device_gtype = g_value_get_gtype(value); |
727 | 0 | break; |
728 | 0 | default: |
729 | 0 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); |
730 | 0 | break; |
731 | 0 | } |
732 | 0 | } |
733 | | |
734 | | static void |
735 | | fu_backend_codec_iface_init(FwupdCodecInterface *iface) |
736 | 0 | { |
737 | 0 | iface->add_json = fu_backend_add_json; |
738 | 0 | iface->from_json = fu_backend_from_json; |
739 | 0 | } |
740 | | |
741 | | static void |
742 | | fu_backend_init(FuBackend *self) |
743 | 0 | { |
744 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
745 | 0 | priv->enabled = TRUE; |
746 | 0 | priv->device_gtype = FU_TYPE_DEVICE; |
747 | 0 | priv->thread_init = g_thread_self(); |
748 | 0 | priv->devices = |
749 | 0 | g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref); |
750 | 0 | } |
751 | | |
752 | | static void |
753 | | fu_backend_dispose(GObject *object) |
754 | 0 | { |
755 | 0 | FuBackend *self = FU_BACKEND(object); |
756 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
757 | 0 | g_hash_table_remove_all(priv->devices); |
758 | 0 | g_clear_object(&priv->ctx); |
759 | 0 | G_OBJECT_CLASS(fu_backend_parent_class)->dispose(object); |
760 | 0 | } |
761 | | |
762 | | static void |
763 | | fu_backend_finalize(GObject *object) |
764 | 0 | { |
765 | 0 | FuBackend *self = FU_BACKEND(object); |
766 | 0 | FuBackendPrivate *priv = GET_PRIVATE(self); |
767 | 0 | g_free(priv->name); |
768 | 0 | g_hash_table_unref(priv->devices); |
769 | 0 | G_OBJECT_CLASS(fu_backend_parent_class)->finalize(object); |
770 | 0 | } |
771 | | |
772 | | static void |
773 | | fu_backend_class_init(FuBackendClass *klass) |
774 | 0 | { |
775 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
776 | 0 | GParamSpec *pspec; |
777 | |
|
778 | 0 | object_class->get_property = fu_backend_get_property; |
779 | 0 | object_class->set_property = fu_backend_set_property; |
780 | 0 | object_class->finalize = fu_backend_finalize; |
781 | 0 | object_class->dispose = fu_backend_dispose; |
782 | | |
783 | | /** |
784 | | * FuBackend:name: |
785 | | * |
786 | | * The backend name. |
787 | | * |
788 | | * Since: 1.6.1 |
789 | | */ |
790 | 0 | pspec = |
791 | 0 | g_param_spec_string("name", |
792 | 0 | NULL, |
793 | 0 | NULL, |
794 | 0 | NULL, |
795 | 0 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); |
796 | 0 | g_object_class_install_property(object_class, PROP_NAME, pspec); |
797 | | |
798 | | /** |
799 | | * FuBackend:can-invalidate: |
800 | | * |
801 | | * If the backend can be invalidated. |
802 | | * |
803 | | * Since: 1.8.0 |
804 | | */ |
805 | 0 | pspec = |
806 | 0 | g_param_spec_boolean("can-invalidate", |
807 | 0 | NULL, |
808 | 0 | NULL, |
809 | 0 | FALSE, |
810 | 0 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); |
811 | 0 | g_object_class_install_property(object_class, PROP_CAN_INVALIDATE, pspec); |
812 | | |
813 | | /** |
814 | | * FuBackend:context: |
815 | | * |
816 | | * The #FuContent to use. |
817 | | * |
818 | | * Since: 1.6.1 |
819 | | */ |
820 | 0 | pspec = |
821 | 0 | g_param_spec_object("context", |
822 | 0 | NULL, |
823 | 0 | NULL, |
824 | 0 | FU_TYPE_CONTEXT, |
825 | 0 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); |
826 | 0 | g_object_class_install_property(object_class, PROP_CONTEXT, pspec); |
827 | | |
828 | | /** |
829 | | * FuBackend:device-gtype: |
830 | | * |
831 | | * The #GType to use when creating emulated devices. |
832 | | * |
833 | | * Since: 2.0.0 |
834 | | */ |
835 | 0 | pspec = g_param_spec_gtype("device-gtype", |
836 | 0 | NULL, |
837 | 0 | NULL, |
838 | 0 | FU_TYPE_DEVICE, |
839 | 0 | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); |
840 | 0 | g_object_class_install_property(object_class, PROP_DEVICE_GTYPE, pspec); |
841 | | |
842 | | /** |
843 | | * FuBackend::device-added: |
844 | | * @self: the #FuBackend instance that emitted the signal |
845 | | * @device: the #FuDevice |
846 | | * |
847 | | * The ::device-added signal is emitted when a device has been added. |
848 | | * |
849 | | * Since: 1.6.1 |
850 | | **/ |
851 | 0 | signals[SIGNAL_ADDED] = g_signal_new("device-added", |
852 | 0 | G_TYPE_FROM_CLASS(object_class), |
853 | 0 | G_SIGNAL_RUN_LAST, |
854 | 0 | 0, |
855 | 0 | NULL, |
856 | 0 | NULL, |
857 | 0 | g_cclosure_marshal_VOID__OBJECT, |
858 | 0 | G_TYPE_NONE, |
859 | 0 | 1, |
860 | 0 | FU_TYPE_DEVICE); |
861 | | /** |
862 | | * FuBackend::device-removed: |
863 | | * @self: the #FuBackend instance that emitted the signal |
864 | | * @device: the #FuDevice |
865 | | * |
866 | | * The ::device-removed signal is emitted when a device has been removed. |
867 | | * |
868 | | * Since: 1.6.1 |
869 | | **/ |
870 | 0 | signals[SIGNAL_REMOVED] = g_signal_new("device-removed", |
871 | 0 | G_TYPE_FROM_CLASS(object_class), |
872 | 0 | G_SIGNAL_RUN_LAST, |
873 | 0 | 0, |
874 | 0 | NULL, |
875 | 0 | NULL, |
876 | 0 | g_cclosure_marshal_VOID__OBJECT, |
877 | 0 | G_TYPE_NONE, |
878 | 0 | 1, |
879 | 0 | FU_TYPE_DEVICE); |
880 | | /** |
881 | | * FuBackend::device-changed: |
882 | | * @self: the #FuBackend instance that emitted the signal |
883 | | * @device: the #FuDevice |
884 | | * |
885 | | * The ::device-changed signal is emitted when a device has been changed. |
886 | | * |
887 | | * Since: 1.6.1 |
888 | | **/ |
889 | 0 | signals[SIGNAL_CHANGED] = g_signal_new("device-changed", |
890 | 0 | G_TYPE_FROM_CLASS(object_class), |
891 | 0 | G_SIGNAL_RUN_LAST, |
892 | 0 | 0, |
893 | 0 | NULL, |
894 | 0 | NULL, |
895 | 0 | g_cclosure_marshal_VOID__OBJECT, |
896 | 0 | G_TYPE_NONE, |
897 | 0 | 1, |
898 | 0 | FU_TYPE_DEVICE); |
899 | 0 | } |