1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
|
---
title: Plugin Tutorial
---
## Introduction
At the heart of fwupd are plugins that gets run at startup, when devices
get hotplugged and when updates are done.
The idea is we have lots of small plugins that each do one thing, and are
ordered by dependencies against each other at runtime.
Using plugins we can add support for new hardware or new policies without making
big changes all over the source tree.
There are broadly 3 types of plugin methods:
- **Mechanism**: Upload binary data into a specific hardware device.
- **Policy**: Control the system when updates are happening, e.g. preventing the
user from powering-off.
- **Helpers**: Providing more metadata about devices, for instance handling device quirks.
A plugin only needs to define the vfuncs that are required, and the plugin name
is taken automatically from the GType.
/* fu-foo-plugin.h
*
* Copyright 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include <fwupdplugin.h>
G_DECLARE_FINAL_TYPE(FuFooPlugin, fu_foo_plugin, FU, FOO_PLUGIN, FuPlugin)
/* fu-foo-plugin.c
*
* Copyright Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "fu-foo-plugin.h"
struct _FuFooPlugin {
FuPlugin parent_instance;
gpointer proxy;
};
G_DEFINE_TYPE(FuFooPlugin, fu_foo_plugin, FU_TYPE_PLUGIN)
static gboolean
fu_foo_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error)
{
FuPluginData *data = fu_plugin_get_data(plugin);
self->proxy = create_proxy();
if(self->proxy == NULL) {
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
"failed to create proxy");
return FALSE;
}
return TRUE;
}
static void
fu_foo_plugin_init(FuFooPlugin *self)
{
}
static void
fu_foo_constructed(GObject *obj)
{
FuPlugin *plugin = FU_PLUGIN(obj);
FuContext *ctx = fu_plugin_get_context(plugin);
fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_BEFORE, "dfu");
}
static void
fu_foo_finalize(GObject *obj)
{
FuFooPlugin *self = FU_FOO_PLUGIN(obj);
destroy_proxy(self->proxy);
G_OBJECT_CLASS(fu_foo_plugin_parent_class)->finalize(obj);
}
static void
fu_foo_plugin_class_init(FuFooPluginClass *klass)
{
FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass);
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->constructed = fu_foo_constructed;
object_class->finalize = fu_foo_finalize;
plugin_class->startup = fu_foo_plugin_startup;
}
We have to define when our plugin is run in reference to other plugins, in this
case, making sure we run before the `dfu` plugin.
For most plugins it does not matter in what order they are run and this
information is not required.
## Creating an abstract device
This section shows how you would create a device which is exported to the daemon
and thus can be queried and updated by the client software.
The example here is all hardcoded, and a true plugin would have to
derive the details about the `FuDevice` from the hardware, for example reading
data from `sysfs` or `/dev`.
static gboolean
fu_foo_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error)
{
g_autoptr(FuDevice) dev = NULL;
fu_device_set_id(dev, "dummy-1:2:3");
fu_device_add_instance_id(dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e");
fu_device_set_version(dev, "1.2.3");
fu_device_get_version_lowest(dev, "1.2.2");
fu_device_get_version_bootloader(dev, "0.1.2");
fu_device_add_icon(dev, "computer");
fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE);
fu_plugin_device_add(plugin, dev);
return TRUE;
}
static void
fu_foo_plugin_class_init(FuFooPluginClass *klass)
{
…
plugin_class->coldplug = fu_foo_plugin_coldplug;
…
}
This shows a lot of the plugin architecture in action.
Some notable points:
- The device ID (`dummy-1:2:3`) has to be unique on the system between all
plugins, so including the plugin name as a prefix is probably a good idea.
- The GUID value can be generated automatically using
`fu_device_add_instance_id(dev,"some-identifier")` but is quoted here explicitly. The
GUID value has to match the `provides` value in the `.metainfo.xml` file for the
firmware update to succeed.
- Setting a display name and an icon is a good idea in case the GUI software
needs to display the device to the user. Icons can be specified using a full
path, although icon theme names should be preferred for most devices.
- The `FWUPD_DEVICE_FLAG_UPDATABLE` flag tells the client code that the device
is in a state where it can be updated. If the device needs to be in a special
mode (e.g. a bootloader) then the `FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER` flag can
also be used. If the update should only be allowed when there is AC power
available to the computer (i.e. not on battery) then
`FWUPD_DEVICE_FLAG_REQUIRE_AC` should be used as well. There are other flags and
the API documentation should be used when choosing what flags to use for each
kind of device.
- Setting the lowest allows client software to refuse downgrading the device to
specific versions.
This is required in case the upgrade migrates some kind of data-store so as to
be incompatible with previous versions.
Similarly, setting the version of the bootloader (if known) allows the firmware
to depend on a specific bootloader version, for instance allowing signed
firmware to only be installable on hardware with a bootloader new enough to
deploy it.
### Setting the device version
Although the version can be set easily as a string using `fu_device_set_version()`
directly, it is more flexible to tell fwupd what the *version format* should be,
and to allow the daemon to convert it to a string internally.
This also means that if we get the version format from a quirk file, or from metadata,
or even if it changes at runtime -- the correct string version is used at all times.
static gchar *
fu_foo_device_convert_version(FuDevice *device, guint64 version_raw)
{
return fu_version_from_uint24(version_raw, FWUPD_VERSION_FORMAT_TRIPLET);
}
static void
fu_foo_device_class_init(FuFooDeviceClass *klass)
{
…
device_class->convert_version = fu_foo_device_convert_version;
…
}
## Mechanism Plugins
Although it would be a wonderful world if we could update all hardware using a
standard shared protocol this is not the universe we live in.
Using a mechanism like DFU or UpdateCapsule means that fwupd will just work
without requiring any special code, but for the real world we need
to support vendor-specific update protocols with layers of backwards compatibility.
When a plugin has created a device that is `FWUPD_DEVICE_FLAG_UPDATABLE` we can
ask the daemon to update the device with a suitable `.cab` file.
When this is done the daemon checks the update for compatibility with the device,
and then calls the vfuncs to update the device.
static gboolean
fu_foo_plugin_write_firmware(FuPlugin *plugin,
FuDevice *dev,
GBytes *blob_fw,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
gsize sz = 0;
guint8 *buf = g_bytes_get_data(blob_fw, &sz);
/* write 'buf' of size 'sz' to the hardware */
return TRUE;
}
static void
fu_foo_plugin_class_init(FuFooPluginClass *klass)
{
…
plugin_class->write_firmware = fu_foo_plugin_write_firmware;
…
}
It's important to note that the `blob_fw` is the binary firmware file
(e.g. `.dfu`) and **not** the `.cab` binary data.
If `FWUPD_INSTALL_FLAG_FORCE` is used then the usual checks done by the flashing
process can be relaxed (e.g. checking for quirks), but please don't brick the
users hardware even if they ask you to.
## Policy Helpers
For some hardware, we might want to do an action before or after the actual
firmware is squirted into the device.
This could be something as simple as checking the system battery level is over a
certain threshold, or it could be as complicated as ensuring a vendor-specific
GPIO is asserted when specific types of hardware are updated.
static gboolean
fu_foo_plugin_prepare(FuPlugin *plugin, FuDevice *device, GError **error)
{
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC && !on_ac_power()) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_AC_POWER_REQUIRED,
"Cannot install update "
"when not on AC power");
return FALSE;
}
return TRUE;
}
static gboolean
fu_foo_plugin_cleanup(FuPlugin *plugin, FuDevice *device, GError **error)
{
return g_file_set_contents("/var/lib/fwupd/something",
fu_device_get_id(device), -1, error);
}
static void
fu_foo_plugin_class_init(FuFooPluginClass *klass)
{
…
plugin_class->prepare = fu_foo_plugin_prepare;
plugin_class->cleanup = fu_foo_plugin_cleanup;
…
}
## Detaching to bootloader mode
Some hardware can only be updated in a special bootloader mode, which for most
devices can be switched to automatically.
In some cases the user to do something manually, for instance re-inserting the
hardware with a secret button pressed.
Before the device update is performed the fwupd daemon runs an optional
`update_detach()` vfunc which switches the device to bootloader mode.
After the update (or if the update fails) an the daemon runs an optional
`update_attach()` vfunc which should switch the hardware back to runtime mode.
Finally an optional `update_reload()` vfunc is run to get the new firmware
version from the hardware.
The optional vfuncs are **only** run on the plugin currently registered to
handle the device ID, although the registered plugin can change during the
attach and detach phases.
static gboolean
fu_foo_plugin_detach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error)
{
if (hardware_in_bootloader)
return TRUE;
return _device_detach(device, progress, error);
}
static gboolean
fu_foo_plugin_attach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error)
{
if (!hardware_in_bootloader)
return TRUE;
return _device_attach(device, progress, error);
}
static gboolean
fu_foo_plugin_reload(FuPlugin *plugin, FuDevice *device, GError **error)
{
g_autofree gchar *version = _get_version(plugin, device, error);
if (version == NULL)
return FALSE;
fu_device_set_version(device, version);
return TRUE;
}
static void
fu_foo_plugin_class_init(FuFooPluginClass *klass)
{
…
plugin_class->detach = fu_foo_plugin_detach;
plugin_class->attach = fu_foo_plugin_attach;
plugin_class->reload = fu_foo_plugin_reload;
…
}
## The Plugin Object Cache
The fwupd daemon provides a per-plugin cache which allows objects to be added,
removed and queried using a specified key.
Objects added to the cache must be `GObject`s to enable the cache objects to be
properly refcounted.
## Debugging a Plugin
If the fwupd daemon is started with `--plugin-verbose=$plugin` then the
environment variable `FWUPD_$PLUGIN_VERBOSE` is set process-wide.
This allows plugins to detect when they should output detailed debugging
information that would normally be too verbose to keep in the journal.
For example, using `--plugin-verbose=logitech_hidpp` would set
`FWUPD_LOGITECH_HID_VERBOSE=1`.
## Using existing code to develop a plugin
It is not usually possible to share a plugin codebase with firmware update
programs designed for other operating systems.
Matching the same rationale as the Linux kernel, trying to use one code base
between projects with a compatibility shim layer in-between is real headache to
maintain.
The general consensus is that trying to use a abstraction layer for hardware is
a very bad idea as you're not able to take advantage of the platform specific
helpers -- for instance quirk files and the custom GType device creation.
The time the vendor saves by creating a shim layer and importing existing source
code into fwupd will be overtaken 100x by upstream maintenance costs longer term,
which isn't fair.
In a similar way, using C++ rather than GObject C means expanding the test matrix
to include clang in C++ mode and GNU g++ too.
It's also doubled the runtime requirements to now include both the C standard library
as well as the C++ standard library and increases the dependency surface.
Most rewritten fwupd plugins at up to x10 smaller than the standalone code as they
can take advantage of helpers provided by fwupd rather than re-implementing error
handling, device quirking and data chunking.
## General guidelines for plugin developers
### General considerations
When adding support for a new device in fwupd some things need to be
evaluated beforehand:
- how the hardware is discovered, identified and polled.
- how to communicate with the device (USB? file open/read/write?)
- does the device need to be switched to bootloader mode to make it
upgradable?
- about the format of the firmware files, do they follow any standard?
are they already supported in fwupd?
- about the update protocol, is it already supported in fwupd?
- Is the device composed of multiple different devices? Are those
devices enumerated and programmed independently or are they accessed
and flashed through a "root" device?
In most cases, even if the features you need aren't implemented yet,
there's already a plugin that does something similar and can be used as
an example, so it's always a good idea to read the code of the existing
plugins to understand how they work and how to write a new one, as no
documentation will be as complete and updated as the code
itself. Besides, the mechanisms implemented in the plugin collection are
very diverse and the best way of knowing what can be done is to check
what is already been done.
### Leveraging existing fwupd code
Depending on how much of the key items for the device update (firmware
format, update protocol, transport layer) are already supported in
fwupd, the work needed to add support for a new device can range from
editing a quirk file to having to fully implement new device and
firmware types, although in most cases fwupd already implements helper
code that can be extended.
#### If the firmware format, update protocol and device communication are already supported
This is the simplest case, where an existing plugin fully implements the
update process for the new device and we only have to let fwupd know
that that plugin should be used for our device. In this case the only
thing to do is to edit the plugin quirk file and add the device
identifier in the format expected by the plugin together with any
required options for it (at least a "Plugin" key to declare that this is
the plugin to use for this device). Example:
<https://github.com/fwupd/fwupd/blob/main/plugins/vli/vli-usbhub.quirk>
#### If the device type is not supported
Then we have to take a look at the existing device types and check if
there's any of them that have similarities and which can be partially
reused or extended for our device. If the device type is derivable and
it can support our new device by implementing the proper vfuncs, then we
can simply subclass it and add the required functionalities. If not,
we'll need to study what is the best way to reuse it for our needs.
If a plugin already implements most of the things we need besides the
device type, we can add our new device type to that plugin. Otherwise we
should create a plugin that will hold the new device type.
The core fwupd code contains some basic device types (such as
[FuUdevDevice](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-udev-device.c), [FuUsbDevice](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-usb-device.c), [FuBluezDevice](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-bluez-device.c)) that can be used as a base
type for most devices in case we have to implement our own device
access, identification and communication from scratch.
If the device is natively visible by the OS, most of the time fwupd can
detect the device connection and disconnection by listening to udev
events, but a supported device may also be not directly accessible from
the OS -- for example, a composite device that contains an updatable chip
that's connected through I2C to a USB hub that acts as an interface. In
that case, the device discovery and enumeration must be programmed by
the developer, but the same device identification and management
mechanisms apply in all cases. See the "Creating a new device type" and
"Device identification" below for more details.
#### If the firmware type is not supported
Same as with the new device type, there could be an existing firmware
type that can be used as a base type for our new type, so first of all
we should look for firmware types that are similar to the one we're
using. Then, choosing where to define the new type depends on whether
there's already a plugin that implements most of the functionalities we
need or not.
### Example: extending a firmware type
Our firmware files are Intel HEX files that have optional
vendor-specific sections at fixed addresses, this is not supported by
any firmware type in fwupd out of the box but the [FuIhexFirmare](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-ihex-firmware.c) class
parses and models a standard Intel HEX file, so we can create a subclass
of it for our firmware type and override the parse method so that it
calls the method from the parent class, which would parse the file, and
then we can get the data with `fu_firmware_get_bytes()` and do the rest of
the custom parsing. Example:
<https://github.com/fwupd/fwupd/blob/main/plugins/analogix/fu-analogix-firmware.c>
### Example: extending a device type
Communication with our new device is carried out by doing
read/write/ioctl operations on a device file, but using a custom
protocol that is not supported in fwupd.
For this type of device we can create a new type derived from
`FuUdevDevice`, which takes care of discovering this type of devices,
possibly using a vendor-specific protocol, as well as of opening,
reading and writing device files, so we would only have to implement the
protocol on top of those primitives. (Example:
`fu_logitech_hidpp_runtime_bolt_poll_peripherals()` in
<https://github.com/fwupd/fwupd/blob/main/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c>)
The process would be similar if our device was handled by a different
backend (USB or BlueZ).
### Creating a new plugin
The bare minimum a plugin should have is a `constructed` function that
defines the plugin characteristics such as the device type and firmware
type handled by it, the build hash and any plugin-specific quirk keys
that can be used for the plugin.
static void
fu_foo_plugin_constructed(GObject *obj)
{
FuPlugin *plugin = FU_PLUGIN(obj);
FuContext *ctx = fu_plugin_get_context(plugin);
fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_MOUSE);
fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD);
}
static void
fu_foo_plugin_class_init(FuFooPluginClass *klass)
{
plugin_class->init = fu_foo_plugin_constructed;
}
### Creating a new device type
Besides defining its attributes as a data type, a device type should
implement at least the usual `init`, `finalize` and `class_init` functions,
and then, depending on its parent type, which methods it overrides and
what it does, it must implement a set of device methods. These are some
of them, the complete list is in [libfwupdplugin/fu-device.h](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.h).
#### to_string
Called whenever fwupd needs a human-readable representation of the
device.
#### probe
The `probe` method is called the first time a device is opened, before
actually opening it. The generic probe methods implemented in the base
device types (such as USB/udev) take care of basic device identification
and setting the non-specific parameters that don't need the device to be
opened or the interface claimed (vendor id, product id, guids, etc.).
The device-specific probe method should start by calling the generic
method upwards in the class tree and then do any other specific setup
such as setting the appropriate device flags.
#### open
Depending on the type of device, opening it means different things. For
instance, opening a udev device means opening its device file.
If there's no interface-specific `open` method, then opening a device
simply calls the `probe()` and `setup()` methods (the `open()` method would be
called in between if it exists).
#### setup
Sets parameters on the device object that require the device to be open
and have the interface claimed. USB/udev generic devices don't implement
this method, this is normally implemented for each different plugin
device type if needed.
#### prepare
If implemented, can be used to put the device into a mode that makes
updating possible or anything else that has to be done to a device
before updating it is possible.
#### prepare_firmware
If implemented, this takes care of decompressing or parsing the firmware
data. For example, to check if the firmware is valid, if it's suitable
for the device, etc.
It takes a stream of bytes (`GBytes`) as a parameter, representing the
raw binary firmware data.
It should create the firmware object and call the appropriate method to
load the firmware. Otherwise, if it's not implemented for the specific
device type, the generic implementation in
[libfwupdplugin/fu-device.c](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c):`fu_device_prepare_firmware()`
creates a firmware object loaded with a provided image.
#### detach
Implemented if the device needs to be put in bootloader mode before
updating, this does all the necessary operations to put the device in
that mode. fwupd can handle the case where a device needs to be
disconnected to do the mode switch if the device has the
`FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG` flag.
#### attach
The inverse of `detach()`, to configure the device back to application mode.
#### reload
If implemented, this is called after the device update if it needs to
perform any kind of post-update operation.
#### write_firmware
Writes a firmware passed as a raw byte stream. The firmware parsing and
processing is done by the firmware object, so that when this method gets
the blob it simply has to write it to the device in the appropriate way
following the device update protocol.
#### read_firmware
Reads the firmware data from the device without any device-specific
configuration or serial numbers. This is meant to retrieve the current
firmware contents for verification purposes. The data read can then be
output to a binary blob using `fu_firmware_write()`.
#### set_progress
Informs the daemon of the expected duration percentages for the different
phases of update. The daemon runs the `->detach()`, `->write_firmware()`,
`->attach()` and `->reload()` phases as part of the engine during the firmware
update (rather than being done by plugin-specific code) and so this vfunc
informs the daemon how to scale the progress output accordingly.
For instance, if your update takes 2 seconds to detach into bootloader mode,
10 seconds to write the firmware, 7 seconds to attach back into runtime mode
(which includes the time required for USB enumeration) and then 1 second to
read the new firmware version you would use:
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 40, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "reload");
If however your device does not require `->detach()` or `->attach()`, and
`->reload()` is instantaneous, you still however need to include 4 steps:
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload");
If the device has multiple phases that occur when actually in the write phase
then it is perfectly okay to split up the `FuProgress` steps in the
`->write_firmware()` vfunc further. For instance:
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "wait-for-idle");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "reset");
It should be noted that actions that are required to be done *before* the update
should be added as a `->prepare()` vfunc, and those to be done after in the `->cleanup()`
as the daemon will then recover the hardware if the update fails. For instance,
putting the device back into a *normal runtime power saving* state should always
be done during cleanup.
### Creating a new firmware type
The same way a device type implements some methods to complete its
functionality and override certain behaviors, there's a set of firmware
methods that a firmware class can (or must) implement:
#### parse
If implemented, it parses the firmware file passed as a byte
sequence. If the firmware to be used contains a custom header, a
specific structured format or multiple images embedded, this method
should take care of processing the format and appropriately populating
the `FuFirmware` object passed as a parameter. If not implemented, the
whole data blob is taken as is.
#### write
Returns a `FuFirmware` object as a byte sequence. This can be used to
output a firmware read with `fu_device_read_firmware()` as a binary
blob.
#### export
Converts a `FuFirmware` object to an xml representation. If not
implemented, the default implementation generates an xml representation
containing only generic attributes and, optionally, the firmware data as
well as the representation of children firmware nodes.
When testing the implementation of a new firmware type, this is useful
to show if the parsing and processing of the firmware are correct and
can be checked with:
fwupdtool firmware-parse --plugins <plugin> <firmware_file> <firmware_type>
#### tokenize
If implemented it tokenizes a firmware, breaking it into records.
#### build
This is the reverse of `export()`, it builds a `FuFirmware` object from
an xml representation.
#### get_checksum
The default implementation returns a checksum of the payload data of a
`FuFirmware` object. Subclass it only if the checksum of your firmware
needs to be computed differently.
### Generating a skeleton
Rather than copy-and-pasting from other plugins, or using the `FuDeviceClass`
as a guide we have also provided a script that can generate a plugin skeleton.
This skeleton contains all the parts typically needed by a plugin, and plugin
developers might find it easier to delete unneeded code rather then trying to
copy and paste the correct code from other plugins.
To use this, navivate to the root directory and run:
./contrib/create-plugin.py \
--vendor VendorName \
--example ProductName \
--parent Usb \
--author "Your Name" \
--email "your@email.com"
### Device identification
A device is identified in fwupd by its physical and logical ids. A
physical id represents the electrical connection of the device to the
system and many devices can have the same physical id. For example,
`PCI_SLOT_NAME=0000:3e:00:0` (see
[libfwupdplugin/fu-udev-device.c](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-udev-device.c):`fu_udev_device_set_physical_id()` for
examples) . The logical id is used to disambiguate devices with the same
physical id. Together they identify a device uniquely. There are many
examples of this in the existing plugins, such as
`fu_pxi_receiver_device_add_peripherals()` in
<https://github.com/fwupd/fwupd/blob/main/plugins/pixart-rf/fu-pxi-receiver-device.c>
Besides that, each device type will have a unique instance id, which is
a string representing the device subsystem, vendor, model and revision
(specific details depend on the device type). This should identify a
device type in the system, that is, a particular device type, model and
revision by a specific vendor will have a defined instance id and two of
the same device will have the same instance id (see
[libfwupdplugin/fu-udev-device.c](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-udev-device.c):`fu_udev_device_probe()`
for examples).
One or more GUIDs are generated for a device from its identifying
attributes, these GUIDs are then used to match a firmware metadata
against a specific device type. See the implementation of the many
`probe()` methods for examples.
### Support for BLE devices
BLE support in fwupd on Linux is provided by BlueZ. If the device
implements the standard HID-over-GATT BLE profile, then communication
with the device can be done through the [hidraw
interface](https://www.kernel.org/doc/html/latest/hid/hidraw.html). If
the device implements a custom BLE profile instead, then it will have to
be managed by the `FuBluezBackend`, which uses the BlueZ DBus interface
to communicate with the devices. The `FuBluezDevice` type implements
device enumeration as well as the basic primitives to read and write BLE
characteristics, and can be used as the base type for a more specific
BLE device.
### Battery checks
If the device can be updated wirelessly or if the update process doesn't
rely on an external power supply, the vendor might define a minimum
operative battery level to guarantee a correct update. fwupd provides a
simple API to define these requirements per-device.
[fu_device_set_battery_threshold()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c)
can be used to define the minimum battery level required to allow a
firmware update on a device (10% by default). If the battery level is
below that threshold, fwupd will inhibit the device to prevent the user
from starting a firmware update. Then, the battery level of a device can
be queried and then set with
[fu_device_set_battery_level()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c).
## Howtos
### How to create a child device
fwupd devices can be hierarchically ordered to model dependent and
composite devices such as docking stations composed of multiple
updatable chips. When writing support for a new composite device the
parent device should, at some point, poll the devices that "hang" from
it and register them in fwupd. The process of polling and identifying a
child device is totally vendor and device-specific, although the main
requirement for it is that the child device is properly identified
(having physical/logical and instance ids). Then,
[fu_device_add_child()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c)
can be used to add a new child device to an existing one. See
`fu_logitech_hidpp_runtime_bolt_poll_peripherals()` in
<https://github.com/fwupd/fwupd/blob/main/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c>
for an example.
Note that when deploying and installing a firmware set for a composite
device, there might be firmware dependencies between parent and child
devices that require a specific update ordering (for instance, child
devices first, then the parent). This can be modeled by setting an
appropriate firmware priority in the firmware metainfo or by setting the
`FU_DEVICE_PRIVATE_FLAG_INSTALL_PARENT_FIRST` device flag.
### How to add a delay
In certain scenarios you may need to introduce small controlled delays
in the plugin code, for instance, to comply with a communications
protocol or to wait for the device to be ready after a particular
operation. In this case you can insert a delay in microseconds with
`g_usleep` or a delay in milliseconds that shows a progress bar with
`fu_device_sleep` or `fu_device_sleep_full`. Note that, in both cases,
this will stop the application main loop during the wait, so use it only
when necessary.
### How to define private flags
Besides the regular flags and internal flags that any device can have, a
device can define private flags for specific uses. These can be enabled
in the code as well as in quirk files, just as the rest of flags. To
define a private flag:
1. Define the flag value. This is normally defined as a macro that
expands to a binary flag, for example: `#define MY_PRIVATE_FLAG (1 <<
2)`. Note that this will be part of the ABI, so it must be versioned
1. Call `fu_device_register_private_flag` in the device init function
and assign a string identifier to the flag:
`fu_device_register_private_flag(FU_DEVICE (self), MY_PRIVATE_FLAG);`
You can then add it to the device programmatically with
`fu_device_add_private_flag`, remove it with `fu_device_remove_private_flag`
and query it with `fu_device_has_private_flag`. In a quirk file, you can
add the flag identifier to the Flags attribute of a device (eg. `Flags =
myflag,is-bootloader`)
### How to make fwupd wait for a device replug
Certain devices require a disconnection and reconnection to start the
update process. A common example are devices that have two booting
modes: application or runtime mode, and bootloader mode, where the
runtime mode is the normal operation mode and the bootloader mode is
exclusively used to update the device firmware. It's common for these
devices to require some operation from fwupd to switch the booting mode
and then to need a reset to enter bootloader mode. Often, the device is
enumerated differently in both modes, so fwupd needs to know that the
same device will be identified differently depending on the boot mode.
The common way to do this is to add the
`FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG` flag in the device before its detach
method returns. This will make fwupd wait for a predetermined amount of
time for the device to be detected again. Then, to inform fwupd about
the two identities of the same device, the `CounterpartGuid` key can be
used in a device entry to match it with another defined device (example:
<https://github.com/fwupd/fwupd/blob/main/plugins/steelseries/steelseries.quirk>).
### Inhibiting a device
If a device becomes unsuitable for an update for whatever reason (see
"Battery checks" above for an example), a plugin can temporarily disable
firmware updates on it by calling [fu_device_inhibit()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c). The device will
still be listed as present by `fwupdmgr get-devices`, but fwupd won't
allow firmware updates on it. Device inhibition can be disabled with
[fu_device_uninhibit()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c).
Note that there might be multiple inhibits on a specific device, the
device will only be updatable when all of them are removed.
## Debugging tips
The most important rule when debugging is using the `--verbose` and
duplicate `--verbose` flag when running fwupd or fwupdtool.
### Adding debug messages
The usual way to print a debug message is using the `g_debug` macro. Each
relevant module will define its own `G_LOG_DOMAIN` to tag the debug traces
accordingly. See
<https://docs.gtk.org/glib/logging.html> and
<https://docs.gtk.org/glib/running.html> for more
information.
### Inspecting raw binary data
The `fu_dump_full` and `fu_dump_raw` functions implement the
printing of a binary buffer to the console as a stream of bytes in
hexadecimal. See `libfwupdplugin/fu-common.c` for their definitions, you
can find many examples of how to use them in the plugins code.
## The rustgen Helper
The rustgen script generates C source files that allow parsing, modifying and
querying a packed structure or enumeration.
This functionality is provided as parsing untrusted structured data from devices
or firmware files is something fwupd does *a lot*, and so it makes sense to
abstract out common code for maintainability reasons.
It also allows us to force best-practices into the plugins without having to
do careful review of buffer reading and writing.
Structures support integers of specific widths, arrays, GUIDs, strings, default
and constant data of variable size.
The generated code is endian safe and if used correctly, is also safe
against malicious data.
In most cases the structure or enumeration will be defined in a `.rs`
file -- which is the usual file extension of Rust programs.
This was done as the format is heavily inspired by Rust, and it makes editor
highlighting support work correctly.
Although these files *look like* Rust files they're *not actually compiled by
rustc*, so small differences may be noticeable.
#[derive(New, Validate, Parse, Default)]
#[repr(C, packed)]
struct FuExampleHdr {
magic: Guid,
hdrver: u8,
hdrsz: u16le = $struct_size,
payloadsz: u32le,
flags: u8,
}
#[derive(ToString, FromString)]
#[repr(u8)] // optional, and only required if using the enum as a struct item type
enum FuExampleFamily {
Unknown,
Sps,
Txe = 0x5,
Me,
Csme,
}
struct ExamplePacket {
family: FuExampleFamily = Csme,
data: [u8; 254],
}
The struct types currently supported are:
- `u8`: a `guint8`
- `u16le`: little endian `guint16`
- `u24`: a 24 bit number represented as a `guint32`
- `u32le`: little endian `guint32`
- `u64be`: big endian `guint64`
- `char`: a `NUL`-terminated string
- `Guid`: a GUID
- Any `enum` created in the `.rs` file with `#[repr(type)]`
- Any `struct` previously created in the `.rs` file
Arrays of types are also allowed, with the format `[type; multiple]`, for example:
- `buf: [u8; 3] = 0x123456` for a C array of `guint8 buf[3] = {0x12, 0x34, 0x56};`
- `val: [u64be; 7]` for a C array of `guint64 val[7] = {0};`
- `str: [char; 4] = "ABCD"` for a C array of `gchar buf[4] = {'A','B','C','D'};`
-- NOTE: `fu_struct_example_get_str()` would return a `NUL`-terminated string of `ABCD\0`.
Additionally, default or constant values can be auto-populated with the `Default` trait:
- `$struct_size`: the total struct size
- `$struct_offset`: the internal offset in the struct
- string values, specified **without** double or single quotes
- integer values, specified with a `0x` prefix for base-16 and with no prefix for base-10
- previously specified `enum` values
Per-field metadata can also be defined, such as:
- ` = `: set as the default value, or for `u8` arrays initialize with a padding byte
- ` == `: set as the default, and is **also** verified during unpacking.
Default values and padding will be used when creating a new structure,
for instance using `fu_struct_example_new()`.
### Building
When building a plugin with meson a generator can be used:
diff --git a/plugins/example/meson.build b/plugins/example/meson.build
@@ -3,7 +3,6 @@
plugin_quirks += files('example.quirk')
plugin_builtins += static_library('fu_plugin_example',
+ rustgen.process('fu-example.rs'),
sources:
...which creates the files `plugins/libfu_plugin_example.a.p/fu-example-struct.c`
and `plugins/libfu_plugin_example.a.p/fu-example-struct.h` in the build tree.
The latter can be included using `#include fu-example-struct.h` in the
existing plugin code.
### Structs
There are traits that control the generation of struct code. These include:
- `New`: for `fu_struct_example_new()`, needed to create new instances
- `Validate`: for `fu_struct_example_validate()`, needed to check memory buffers are valid
- `Parse`: for `fu_struct_example_parse()`, to create a struct from a memory buffer
- `Getters`: for `fu_struct_example_get_XXXX()`, to get access to field values
- `Setters`: for `fu_struct_example_set_XXXX()`, to set specific field values
`Getters` is implied by `Parse`, and `[Getters,Setters]` is implied by `New`.
Regardless of traits used, the header offset addresses are defined, for instance:
#define FU_STRUCT_EXAMPLE_OFFSET_MAGIC 0x0
#define FU_STRUCT_EXAMPLE_OFFSET_HDRVER 0x10
#define FU_STRUCT_EXAMPLE_OFFSET_HDRSZ 0x11
#define FU_STRUCT_EXAMPLE_OFFSET_PAYLOADSZ 0x13
#define FU_STRUCT_EXAMPLE_OFFSET_FLAGS 0x17
Any elements defined as a typed array (e.g. `[u8; 16]`) will also have the element
size defined in bytes:
#define FU_STRUCT_EXAMPLE_SIZE_MAGIC 0x10
If the default has been set (but not a constant value) the default is also defined:
#define FU_STRUCT_EXAMPLE_DEFAULT_HDRSZ 24
Finally, the size in bytes of the whole structure is also included:
#define FU_STRUCT_EXAMPLE_SIZE 0x18
**NOTE:** constants never have getters or setters defined -- they're constant after all.
They are verified during `_validate()` and `_parse()` however.
### Enums
There are traits that control the generation of enum code. These include:
- `ToString`: for `fu_example_family_to_string()`, needed to create output
- `FromString`: for `fu_example_family_from_string()`, needed to parse input
- `Bitfield`: for `ToString` and `FromString`, to hint these are actually bitfields
**NOTE:** Enums are defined as a native unsigned type, and should not be copied by
reference without first casting to an integer of known width.
|