文章目录
前言
在linux系统上,固件升级基本上都离不开fwupd。之前做过的一个项目就是在linux系统上使用fwupd对ble蓝牙设备进行通信,但fwupd上使用ble蓝牙通信的插件很少,只有高通的工程师在开发而且代码还没有合并到主分支,官方文档也没有提供详细的接口说明和开发文档。在这篇文章里我尽量详细的介绍一下该怎么在fwupd中开发属于自己的插件。
fwupd源码地址:https://github.com/fwupd/fwupd
lvfs地址:https://fwupd.org/
fwupd插件开发文档:https://fwupd.github.io/libfwupdplugin/fwupdmgr.html#description
fwupd架构
fwupd主要由 lvfs
和 fwupd
两部分组成,两者缺一不可。lvfs
主要是用来存储和管理固件包,并为 fwupd 提供固件网络下载。而 fwupd
是实际与硬件交互的,将从lvfs获取到的固件通过自己编写的流程烧录到硬件中。
开发新插件
我们先把代码拉下来看一下目录结构,我拉的是 https://github.com/fwupd/fwupd/tree/wip/d4s/bluez-example
这个带ble蓝牙demo分支的。进入到 fwupd/plugins
这个目录,我们开发新插件主要是在这个目录下。
一,编译fwupd
我们首先编译公版的fwupd,进入到虚拟环境后再去开发和调试。因为进入虚拟环境后输入的命令是我们自己编译后的fwupd,而不是系统自带的公版fwupd。
编译和调试的流程在这里给出链接(使用linux系统物理机,因为fwupd是直接与硬件交互的使用虚拟机可能中间会有隔离):https://github.com/fwupd/fwupd/blob/wip/d4s/bluez-example/docs/building.md
二,文件结构
我们打开 fwupd\plugins\audio-s5gen2
文件夹,逐个介绍讲解一下。
.quirk文件
这个配置文件是用来识别我们的设备的,以audio-s5gen2.quirk
文件为例。[BLUETOOTH\ALIAS_C5171] Plugin = audio_s5gen2 ProxyGType = FuQcS5gen2BleDevice AudioS5gen2Gaia2VendorId = 0x000A AudioS5gen2Gaia3VendorId = 0x001D
[BLUETOOTH\ALIAS_C5171]
是识别的名称,C5171
是蓝牙名,更改成自己设备的蓝牙名后编译输入fwupdtool get-devices
命令就可以识别到自己的设备了。也可以通过蓝牙的PID和VID去识别,[BLUETOOTH\VID_0000&PID_0000]
和[BLUETOOTH\VID_0000]
都可以,效果是一样的。
Plugin
是配置需要用到的插件,这里直接配置成文件夹的名称就好了。
ProxyGType
是配置使用哪个类型去代理,因为一个插件的文件夹里面可能存在多个类型的设备,每个设备需要的控制代码不一致。
剩下两个是自定义值,有需要的小伙伴可以自己看看源码这两个值的作用。.c .h .rs文件
这三个文件一般是关联的,.rs
文件主要是用来定义数据结构的,就是我们通信时使用的结构体可以在这里定义,fwupd会自动生成函数去创建,解析这个结构体的函数,就免去我们自己去处理数据结构的代码。具体使用可以参考实例文件。fu_xxx_plugin.c fu_xxx_plugin.h文件
这两个主要是管理和配置不同类型设备的文件。meson.build文件
就是编译的配置文件,这个大同小异,直接复制现有的插件,修改一下就好了。如果是使用蓝牙的插件可以加上下面的代码去做判断。if bluez.allowed() # 配置项 xxx endif
三、代码结构
下面主要介绍一下最主要的代码结构和最主要的函数,其实就是关键的函数和配置关键的回调函数,我们实际开发可以直接参考现有的插件。
fu-xxx-plugin.c, fu-xxx-plugin.h
头文件基本上都一样的,修改一下名字就好了。这里主要说一下 .c 文件的代码。
在/* * Copyright 2023 Denis Pynkin <denis.pynkin@collabora.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "fu-audio-s5gen2-device.h" #include "fu-audio-s5gen2-firmware.h" #include "fu-audio-s5gen2-hid-device.h" #include "fu-audio-s5gen2-plugin.h" struct _FuAudioS5gen2Plugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAudioS5gen2Plugin, fu_audio_s5gen2_plugin, FU_TYPE_PLUGIN) static void fu_audio_s5gen2_plugin_init(FuAudioS5gen2Plugin *self) { } static void fu_audio_s5gen2_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_QC_S5GEN2_HID_DEVICE); fu_plugin_set_device_gtype_default(plugin, FU_TYPE_QC_S5GEN2_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_QC_S5GEN2_FIRMWARE); } static void fu_audio_s5gen2_plugin_class_init(FuAudioS5gen2PluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_audio_s5gen2_plugin_constructed; }
fu_audio_s5gen2_plugin_class_init
函数里面配置回调函数,具体有哪些可以看代码或者文档,这里只讲最主要的。plugin_class->constructed
这个是要加的,并且需要在回调函数里面使用fu_plugin_add_device_gtype
添加设备的gtype
否则无法识别设备,如果有多个gtype
设备可以使用fu_plugin_set_device_gtype_default
函数设置默认设备。在.quirk
中配置的gtype
要跟这里的一致,不然也无法识别。fu-xxx-device.c, fu-xxx-device.h
这个是单独控制某个设备的代码,头文件基本一致,也是只讲源文件部分。
其实最主要的就是static void fu_qc_s5gen2_ble_device_class_init(FuQcS5gen2BleDeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_qc_s5gen2_ble_device_finalize; device_class->to_string = fu_qc_s5gen2_ble_device_to_string; device_class->probe = fu_qc_s5gen2_ble_device_probe; device_class->set_quirk_kv = fu_qc_s5gen2_ble_device_set_quirk_kv; // 还有个关键的函数,这里没有的 // device_class->write_firmware = fu_xxx_write_firmware; }
object_class->finalize
,device_class->probe
和device_class->write_firmware
这三个。
2.1object_class->finalize
这个是释放资源,没什么好说的。
2.2device_class->probe
这个函数会预先调用,可以看一下这个实例里面这个函数做了什么,其实就使用bluez
后端去建立ble蓝牙收发的通信,示例代码里面使用是的notify
的方式获取设备回复的数据,然后将注册是handle保存起来,就可以通过fu_io_channel_read_raw
函数去读数据了,写数据不需要注册,直接使用fu_bluez_device_write
函数往特征服务里面写数据就可以了 。这里要主要一下如果使用的是 fedora 的系统需要使用 sudo setenforce 0 这个命令关闭一下 selinux,否则 notify 函数会注册失败。其他系统我不清楚,最好都关闭一下。
2.3device_class->write_firmware
这个虽然在示例里面没有写出来,但是这个也是一个重要的函数回调,这个函数是控制烧写流程的。这个函数的实现可以参考一下其他的插件,虽然用到的后端不一样(通信方式)但是获取lvfs上的固件和写固件的动作是一样的,只是把读写的API替换一下而已。fu-xxx-firmware.c, fu-xxx-firmware.h
其实这里还有专门做固件解析的头文件和源码,虽然上面的示例代码里面没有提供出来,这个做固件解析的跟xxx-device.c xxx-device.h
和xxx-plugin.c xxx-plugin.h
结构是差不多的,也是那套流程。就是在fu_xxx_firmware_class_init(FuXxxFirmwareClass *klass)
这里配置回调函数,然后在我们插件的meson.build文件
里面添加编译。这里就不详细说了,如果需要知道使用流程可以参考现有的插件。meson.build
注意这里的是plugin\meson.build
而不是我们插件里面的meson.build
需要在plugin\meson.build
里面添加上我们的插件,这样编译 fwupd 时才会将我们的插件编译进去。
基本的结构和流程就是这样了,后面就是漫长的调试流程了。可能文章讲的比较粗糙,有其他问题可以在评论区提出来,我尽量的回答。