mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-15 02:54:47 +00:00
Split out the fwupd tutorial into a new file
No content changes.
This commit is contained in:
parent
cad96542e2
commit
e031774a8b
@ -67,405 +67,7 @@
|
||||
<xi:include href="xml/fu-usb-device.xml"/>
|
||||
</reference>
|
||||
|
||||
<reference id="tutorial">
|
||||
<title>Plugin Tutorial</title>
|
||||
<partintro>
|
||||
<section>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
At the heart of fwupd is a plugin loader 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.
|
||||
</para>
|
||||
<para>
|
||||
There are broadly 3 types of plugin methods:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Mechanism</emphasis>: Upload binary data
|
||||
into a specific hardware device.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Policy</emphasis>: Control the system when
|
||||
updates are happening, e.g. preventing the user from powering-off.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Helpers</emphasis>: Providing more
|
||||
metadata about devices, for instance handling device quirks.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
In general, building things out-of-tree isn't something that we think is
|
||||
a very good idea; the API and ABI <emphasis>internal</emphasis> to fwupd is still
|
||||
changing and there's a huge benefit to getting plugins upstream where
|
||||
they can undergo review and be ported as the API adapts.
|
||||
For this reason we don't install the plugin headers onto the system,
|
||||
although you can of course just install the <code>.so</code> binary file
|
||||
manually.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A plugin only needs to define the vfuncs that are required, and the
|
||||
plugin name is taken automatically from the suffix of the
|
||||
<filename>.so</filename> file.
|
||||
</para>
|
||||
<example>
|
||||
<title>A sample plugin</title>
|
||||
<programlisting>
|
||||
/*
|
||||
* Copyright (C) 2017 Richard Hughes
|
||||
*/
|
||||
|
||||
#include <fu-plugin.h>
|
||||
#include <fu-plugin-vfuncs.h>
|
||||
|
||||
struct FuPluginData {
|
||||
gpointer proxy;
|
||||
};
|
||||
|
||||
void
|
||||
fu_plugin_initialize (FuPlugin *plugin)
|
||||
{
|
||||
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_BEFORE, "dfu");
|
||||
fu_plugin_alloc_data (plugin, sizeof (FuPluginData));
|
||||
}
|
||||
|
||||
void
|
||||
fu_plugin_destroy (FuPlugin *plugin)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
destroy_proxy (data->proxy);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_plugin_startup (FuPlugin *plugin, GError **error)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
data->proxy = create_proxy ();
|
||||
if (data->proxy == NULL) {
|
||||
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to create proxy");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
|
||||
<para>
|
||||
We have to define when our plugin is run in reference to other plugins,
|
||||
in this case, making sure we run before the <code>dfu</code> plugin.
|
||||
For most plugins it does not matter in what order they are run and
|
||||
this information is not required.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Creating an abstract device</title>
|
||||
<para>
|
||||
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 <code>FuDevice</code> from the hardware,
|
||||
for example reading data from <code>sysfs</code> or <code>/dev</code>.
|
||||
</para>
|
||||
<example>
|
||||
<title>Example adding a custom device</title>
|
||||
<programlisting>
|
||||
#include <fu-plugin.h>
|
||||
|
||||
gboolean
|
||||
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
|
||||
{
|
||||
g_autoptr(FuDevice) dev = NULL;
|
||||
fu_device_set_id (dev, "dummy-1:2:3");
|
||||
fu_device_add_guid (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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<para>
|
||||
This shows a lot of the plugin architecture in action. Some notable points:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
The device ID (<code>dummy-1:2:3</code>) has to be unique on the
|
||||
system between all plugins, so including the plugin name as a
|
||||
prefix is probably a good idea.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The GUID value can be generated automatically using
|
||||
<code>fu_device_add_guid(dev,"some-identifier")</code> but is quoted
|
||||
here explicitly.
|
||||
The GUID value has to match the <code>provides</code> value in the
|
||||
<code>.metainfo.xml</code> file for the firmware update to succeed.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <code>FWUPD_DEVICE_FLAG_UPDATABLE</code> 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 <code>FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER</code> 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
|
||||
<code>FWUPD_DEVICE_FLAG_REQUIRE_AC</code> 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.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
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
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Mechanism Plugins</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
When a plugin has created a device that is <code>FWUPD_DEVICE_FLAG_UPDATABLE</code>
|
||||
we can ask the daemon to update the device with a suitable
|
||||
<code>.cab</code> file.
|
||||
When this is done the daemon checks the update for compatibility with
|
||||
the device, and then calls the vfuncs to update the device.
|
||||
</para>
|
||||
|
||||
<example>
|
||||
<title>Updating a device</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update (FuPlugin *plugin,
|
||||
FuDevice *dev,
|
||||
GBytes *blob_fw,
|
||||
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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<para>
|
||||
It's important to note that the <code>blob_fw</code> is the binary
|
||||
firmware file (e.g. <code>.dfu</code>) and <emphasis role="strong">not</emphasis>
|
||||
the <code>.cab</code> binary data.
|
||||
</para>
|
||||
<para>
|
||||
If <code>FWUPD_INSTALL_FLAG_FORCE</code> 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.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Policy Helpers</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
|
||||
<example>
|
||||
<title>Running before a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<example>
|
||||
<title>Running after a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_cleanup (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
return g_file_set_contents ("/var/lib/fwupd/something",
|
||||
fu_device_get_id (device), -1, error);
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Detaching to bootloader mode</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
Before the device update is performed the fwupd daemon runs an optional
|
||||
<code>update_detach()</code> vfunc which switches the device to
|
||||
bootloader mode.
|
||||
After the update (or if the update fails) an the daemon runs an
|
||||
optional <code>update_attach()</code> vfunc which should switch the
|
||||
hardware back to runtime mode.
|
||||
Finally an optional <code>update_reload()</code> vfunc is run to
|
||||
get the new firmware version from the hardware.
|
||||
</para>
|
||||
<para>
|
||||
The optional vfuncs are <emphasis role="strong">only</emphasis> run
|
||||
on the plugin currently registered to handle the device ID, although
|
||||
the registered plugin can change during the attach and detach phases.
|
||||
</para>
|
||||
|
||||
<example>
|
||||
<title>Running before a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
if (hardware_in_bootloader)
|
||||
return TRUE;
|
||||
return _device_detach(device, error);
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<example>
|
||||
<title>Running after a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
if (!hardware_in_bootloader)
|
||||
return TRUE;
|
||||
return _device_attach(device, error);
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<example>
|
||||
<title>Running after a device update on success</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>The Plugin Object Cache</title>
|
||||
<para>
|
||||
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 <code>GObject</code>s to enable the
|
||||
cache objects to be properly refcounted.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Debugging a Plugin</title>
|
||||
<para>
|
||||
If the fwupd daemon is started with <code>--plugin-verbose=$plugin</code>
|
||||
then the environment variable <code>FWUPD_$PLUGIN_VERBOSE</code> 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 <code>--plugin-verbose=logitech_hidpp</code> would set
|
||||
<code>FWUPD_LOGITECH_HID_VERBOSE=1</code>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Using existing code to develop a plugin</title>
|
||||
<para>
|
||||
It is not usually possible to <em>share</em> 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.
|
||||
</para>
|
||||
<para>
|
||||
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 <em>vendor</em> saves by creating a shim layer and
|
||||
importing existing source code into fwupd will be overtaken 100x by
|
||||
<em>upstream</em> maintenance costs longer term, which isn't fair.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</partintro>
|
||||
</reference>
|
||||
<xi:include href="tutorial.xml"/>
|
||||
|
||||
<index id="api-index-full">
|
||||
<title>API Index</title>
|
||||
|
@ -5,6 +5,10 @@ gnome.gtkdoc(
|
||||
join_paths(meson.source_root(), 'libfwupdplugin'),
|
||||
join_paths(meson.build_root(), 'libfwupd'),
|
||||
join_paths(meson.build_root(), 'libfwupdplugin'),
|
||||
join_paths(meson.current_source_dir()),
|
||||
],
|
||||
content_files : [
|
||||
'tutorial.xml',
|
||||
],
|
||||
main_xml : 'fwupd-docs.xml',
|
||||
install : true
|
||||
|
402
docs/tutorial.xml
Normal file
402
docs/tutorial.xml
Normal file
@ -0,0 +1,402 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd">
|
||||
<reference id="tutorial">
|
||||
<title>Plugin Tutorial</title>
|
||||
<partintro>
|
||||
<section>
|
||||
<title>Introduction</title>
|
||||
<para>
|
||||
At the heart of fwupd is a plugin loader 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.
|
||||
</para>
|
||||
<para>
|
||||
There are broadly 3 types of plugin methods:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Mechanism</emphasis>: Upload binary data
|
||||
into a specific hardware device.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Policy</emphasis>: Control the system when
|
||||
updates are happening, e.g. preventing the user from powering-off.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<emphasis role="strong">Helpers</emphasis>: Providing more
|
||||
metadata about devices, for instance handling device quirks.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<para>
|
||||
In general, building things out-of-tree isn't something that we think is
|
||||
a very good idea; the API and ABI <emphasis>internal</emphasis> to fwupd is still
|
||||
changing and there's a huge benefit to getting plugins upstream where
|
||||
they can undergo review and be ported as the API adapts.
|
||||
For this reason we don't install the plugin headers onto the system,
|
||||
although you can of course just install the <code>.so</code> binary file
|
||||
manually.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
A plugin only needs to define the vfuncs that are required, and the
|
||||
plugin name is taken automatically from the suffix of the
|
||||
<filename>.so</filename> file.
|
||||
</para>
|
||||
<example>
|
||||
<title>A sample plugin</title>
|
||||
<programlisting>
|
||||
/*
|
||||
* Copyright (C) 2017 Richard Hughes
|
||||
*/
|
||||
|
||||
#include <fu-plugin.h>
|
||||
#include <fu-plugin-vfuncs.h>
|
||||
|
||||
struct FuPluginData {
|
||||
gpointer proxy;
|
||||
};
|
||||
|
||||
void
|
||||
fu_plugin_initialize (FuPlugin *plugin)
|
||||
{
|
||||
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_BEFORE, "dfu");
|
||||
fu_plugin_alloc_data (plugin, sizeof (FuPluginData));
|
||||
}
|
||||
|
||||
void
|
||||
fu_plugin_destroy (FuPlugin *plugin)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
destroy_proxy (data->proxy);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fu_plugin_startup (FuPlugin *plugin, GError **error)
|
||||
{
|
||||
FuPluginData *data = fu_plugin_get_data (plugin);
|
||||
data->proxy = create_proxy ();
|
||||
if (data->proxy == NULL) {
|
||||
g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"failed to create proxy");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
|
||||
<para>
|
||||
We have to define when our plugin is run in reference to other plugins,
|
||||
in this case, making sure we run before the <code>dfu</code> plugin.
|
||||
For most plugins it does not matter in what order they are run and
|
||||
this information is not required.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Creating an abstract device</title>
|
||||
<para>
|
||||
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 <code>FuDevice</code> from the hardware,
|
||||
for example reading data from <code>sysfs</code> or <code>/dev</code>.
|
||||
</para>
|
||||
<example>
|
||||
<title>Example adding a custom device</title>
|
||||
<programlisting>
|
||||
#include <fu-plugin.h>
|
||||
|
||||
gboolean
|
||||
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
|
||||
{
|
||||
g_autoptr(FuDevice) dev = NULL;
|
||||
fu_device_set_id (dev, "dummy-1:2:3");
|
||||
fu_device_add_guid (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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<para>
|
||||
This shows a lot of the plugin architecture in action. Some notable points:
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
The device ID (<code>dummy-1:2:3</code>) has to be unique on the
|
||||
system between all plugins, so including the plugin name as a
|
||||
prefix is probably a good idea.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The GUID value can be generated automatically using
|
||||
<code>fu_device_add_guid(dev,"some-identifier")</code> but is quoted
|
||||
here explicitly.
|
||||
The GUID value has to match the <code>provides</code> value in the
|
||||
<code>.metainfo.xml</code> file for the firmware update to succeed.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <code>FWUPD_DEVICE_FLAG_UPDATABLE</code> 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 <code>FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER</code> 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
|
||||
<code>FWUPD_DEVICE_FLAG_REQUIRE_AC</code> 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.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
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
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Mechanism Plugins</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
When a plugin has created a device that is <code>FWUPD_DEVICE_FLAG_UPDATABLE</code>
|
||||
we can ask the daemon to update the device with a suitable
|
||||
<code>.cab</code> file.
|
||||
When this is done the daemon checks the update for compatibility with
|
||||
the device, and then calls the vfuncs to update the device.
|
||||
</para>
|
||||
|
||||
<example>
|
||||
<title>Updating a device</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update (FuPlugin *plugin,
|
||||
FuDevice *dev,
|
||||
GBytes *blob_fw,
|
||||
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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<para>
|
||||
It's important to note that the <code>blob_fw</code> is the binary
|
||||
firmware file (e.g. <code>.dfu</code>) and <emphasis role="strong">not</emphasis>
|
||||
the <code>.cab</code> binary data.
|
||||
</para>
|
||||
<para>
|
||||
If <code>FWUPD_INSTALL_FLAG_FORCE</code> 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.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Policy Helpers</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
|
||||
<example>
|
||||
<title>Running before a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<example>
|
||||
<title>Running after a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_cleanup (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
return g_file_set_contents ("/var/lib/fwupd/something",
|
||||
fu_device_get_id (device), -1, error);
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Detaching to bootloader mode</title>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
Before the device update is performed the fwupd daemon runs an optional
|
||||
<code>update_detach()</code> vfunc which switches the device to
|
||||
bootloader mode.
|
||||
After the update (or if the update fails) an the daemon runs an
|
||||
optional <code>update_attach()</code> vfunc which should switch the
|
||||
hardware back to runtime mode.
|
||||
Finally an optional <code>update_reload()</code> vfunc is run to
|
||||
get the new firmware version from the hardware.
|
||||
</para>
|
||||
<para>
|
||||
The optional vfuncs are <emphasis role="strong">only</emphasis> run
|
||||
on the plugin currently registered to handle the device ID, although
|
||||
the registered plugin can change during the attach and detach phases.
|
||||
</para>
|
||||
|
||||
<example>
|
||||
<title>Running before a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_detach (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
if (hardware_in_bootloader)
|
||||
return TRUE;
|
||||
return _device_detach(device, error);
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<example>
|
||||
<title>Running after a device update</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_attach (FuPlugin *plugin, FuDevice *device, GError **error)
|
||||
{
|
||||
if (!hardware_in_bootloader)
|
||||
return TRUE;
|
||||
return _device_attach(device, error);
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
<example>
|
||||
<title>Running after a device update on success</title>
|
||||
<programlisting>
|
||||
gboolean
|
||||
fu_plugin_update_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;
|
||||
}
|
||||
</programlisting>
|
||||
</example>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>The Plugin Object Cache</title>
|
||||
<para>
|
||||
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 <code>GObject</code>s to enable the
|
||||
cache objects to be properly refcounted.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Debugging a Plugin</title>
|
||||
<para>
|
||||
If the fwupd daemon is started with <code>--plugin-verbose=$plugin</code>
|
||||
then the environment variable <code>FWUPD_$PLUGIN_VERBOSE</code> 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 <code>--plugin-verbose=logitech_hidpp</code> would set
|
||||
<code>FWUPD_LOGITECH_HID_VERBOSE=1</code>.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Using existing code to develop a plugin</title>
|
||||
<para>
|
||||
It is not usually possible to <em>share</em> 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.
|
||||
</para>
|
||||
<para>
|
||||
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 <em>vendor</em> saves by creating a shim layer and
|
||||
importing existing source code into fwupd will be overtaken 100x by
|
||||
<em>upstream</em> maintenance costs longer term, which isn't fair.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
</partintro>
|
||||
</reference>
|
Loading…
Reference in New Issue
Block a user