本页提供了与以下内容相关的说明、最佳做法和示例: 在 DFv1-to-DFv2 中,将 Banjo 协议转换为 FIDL 协议 驱动程序迁移。
将 DFv1 驱动程序从 Banjo 更新为 FIDL
对驱动程序来说,更新驱动程序的 .fidl
文件是一个很好的起点
迁移,因为所有内容都源于 .fidl
文件。幸运的是
Banjo 和 FIDL 通过同一个 IDL(即 FIDL)生成代码,
可能不需要对现有 .fidl
文件进行重大更改。
以下示例 .fidl
文件显示了
从 Banjo 到 FIDL 的迁移:
- 之前(班卓琴):
//sdk/banjo/fuchsia.hardware.wlanphyimpl/wlanphy-impl.fidl
- 之后 (FIDL):
//sdk/fidl/fuchsia.wlan.phyimpl/phyimpl.fidl
如需将 DFv1 驱动程序从 Banjo 更新为 FIDL,请进行以下更改
(主要在驱动程序的 .fidl
文件中):
1. 在协议定义之前更新属性
要使用 Banjo,.fidl
中必须提供 @transport("Banjo")
属性
文件。但是对于 FIDL,该属性不是必需的(因为 FIDL 是
默认值)。因此,您只需从命令行中删除 @transport("Banjo")
属性即可
驱动程序的 .fidl
文件,例如:
使用 Banjo 时:
@transport("Banjo") @banjo_layout("ddk-protocol") protocol MyExampleProtocol { ... }
如需使用 FIDL,请执行以下操作:
@discoverable @transport("Driver") protocol MyExampleProtocol { ... }
在上面的示例中,
@discoverable
属性 是所有 FIDL 协议的必需属性。通过该属性, 客户端使用其生成的名称搜索此协议。不过,
@transport("Driver")
属性(表示 这是一个驱动程序传输协议)可选 使用驱动程序运行时 FIDL 的驱动程序。 只有在驱动程序发出语音指令时才需要进行驱动程序运行时迁移 位于同一驱动程序主机中的其他驱动程序。如需查看驱动程序传输协议的更多示例,请参阅 驱动程序传输示例目录。
2. 更新函数定义以使用 FIDL 错误语法
如果 .fidl
文件中的某些函数定义包含返回状态,
您需要更新它们以使用 FIDL 错误语法,而不是添加状态
例如:
通过使用返回结构:
protocol MyExampleProtocol { MyExampleFunction() -> (struct { Error zx.status; }); };
(来源:
wlanphy-impl.fidl
)如需返回 FIDL 错误语法,请使用以下代码:
protocol MyExampleProtocol { BMyExampleFunction() -> () error zx.status; };
(来源:
phyimpl.fidl
)
使用 FIDL 错误语法具有以下优势:
返回结构现在可以专注于需要发送的数据 。
由于提取 状态不需要读懂返回结构。
3. 更新 BUILD.gn 中的 FIDL 目标
修改(此 .fidl
文件的)BUILD.gn
文件,以添加以下行
在 FIDL 目标中:
contains_drivers = true
以下示例显示了 FIDL 目标中的 contains_drivers = true
行:
import("//build/fidl/fidl.gni")
fidl("fuchsia.wlan.phyimpl") {
sdk_category = "partner"
sources = [ "phyimpl.fidl" ]
public_deps = [
"//sdk/fidl/fuchsia.wlan.common",
"//sdk/fidl/fuchsia.wlan.ieee80211",
"//zircon/vdso/zx",
]
contains_drivers = true
enable_banjo = true
}
(来源:BUILD.gn
)
如果没有 contains_drivers = true
行,则包含
系统不会将 @transport("Driver")
属性生成到 FIDL 库中
正确。
4. 将 FIDL 文件移至 SDK/FIDL 目录
将更新后的 .fidl
文件从 sdk/banjo
目录移至
sdk/fidl
目录中。
sdk/fidl
目录是默认位置,用于存储所有
生成 FIDL 代码。不过,如果仍在使用 Banjo 结构或函数,
其他地方(即驾驶员通信以外的位置),将此 .fidl
移至其他位置,
此文件不允许。在这种情况下,您可以保留这份.fidl
的副本
文件(位于 sdk/banjo
目录中)。
(可选)更新 DFv1 驱动程序以使用驱动程序运行时
本部分要求更新的 .fidl
文件从
上一部分可以
已成功生成 FIDL 代码。此 FIDL 代码用于
驱动程序之间的驱动程序运行时 FIDL 通信。
如需更新 DFv1 驱动程序以使用驱动程序运行时,请按以下步骤操作:
1. 更新驱动程序运行时的依赖项
更新服务器和客户端以包含用于 驱动程序运行时:
在
BUILD.gn
文件中,更新依赖项字段以包含 以下几行代码://sdk/fidl/<YOUR_FIDL_LIB>_cpp_wire //sdk/fidl/<YOUR_FIDL_LIB>_cpp_driver //src/devices/lib/driver:driver_runtime
将
YOUR_FIDL_LIB
替换为您的 FIDL 库的名称,例如:public_deps = [ ... "//sdk/fidl/fuchsia.factory.wlan:fuchsia.factory.wlan_cpp_wire", "//sdk/fidl/fuchsia.wlan.fullmac:fuchsia.wlan.fullmac_cpp_driver", "//sdk/fidl/fuchsia.wlan.phyimpl:fuchsia.wlan.phyimpl_cpp_driver", ... "//src/devices/lib/driver:driver_runtime", ... ]
(来源:
BUILD.gn
)在源代码的头文件中,更新
include
行,例如:#include <fidl/<YOUR_FIDL_LIB>/cpp/driver/wire.h> #include <lib/fdf/cpp/arena.h> #include <lib/fdf/cpp/channel.h> #include <lib/fdf/cpp/channel_read.h> #include <lib/fdf/cpp/dispatcher.h> #include <lib/fidl/cpp/wire/connect_service.h> #include <lib/fidl/cpp/wire/vector_view.h> ...
2. 为驱动程序运行时 FIDL 设置客户端和服务器对象
更新服务器和客户端以使用驱动程序运行时 FIDL。
在客户端,执行以下操作:
声明 FIDL 客户端对象(
fdf::WireSharedClient<ProtocolName>
或fdf::WireSyncClient<ProtocolName>
)。此 FIDL 客户端对象使您能够进行 向服务器端发送请求。
以下示例代码显示了设备类中的 FIDL 客户端对象:
class Device : public fidl::WireServer<fuchsia_wlan_device::Phy>, public ::ddk::Device<Device, ::ddk::MessageableManual, ::ddk::Unbindable> { ... private: // Dispatcher for being a FIDL server listening MLME requests. async_dispatcher_t* server_dispatcher_; // The FIDL client to communicate with iwlwifi fdf::WireSharedClient<fuchsia_wlan_wlanphyimpl::WlanphyImpl> client_; ...
(来源:
device_dfv2.h
)(仅适用于异步调用)声明调度程序对象 (
fdf::Dispatcher
)。绑定第 1 步中的 FIDL 客户端对象需要使用调度程序。
以下示例代码显示了 FIDL 客户端和调度程序对象 在设备类中:
class Device : public fidl::WireServer<fuchsia_wlan_device::Phy>, public ::ddk::Device<Device, ::ddk::MessageableManual, ::ddk::Unbindable> { ... private: // Dispatcher for being a FIDL server listening MLME requests. async_dispatcher_t* server_dispatcher_; // The FIDL client to communicate with iwlwifi fdf::WireSharedClient<fuchsia_wlan_wlanphyimpl::WlanphyImpl> client_; // Dispatcher for being a FIDL client firing requests to WlanphyImpl device. fdf::Dispatcher client_dispatcher_; ...
(来源:
device_dfv2.h
)您可以使用
fdf::Dispatcher::GetCurrent()
方法,或创建一个新的非默认方法 调度程序(请参见 更新 DFv1 驱动程序以使用非默认调度程序)。
在服务器端,执行以下操作:
继承 FIDL 服务器类 (
fdf::WireServer<ProtocolName>
) 来自设备类声明一个调度程序对象 (
fdf::Dispatcher
),以便 FIDL 服务器 绑定到什么位置。与客户端不同,在服务器端始终需要调度程序 用于绑定 FIDL 服务器对象。
以下示例代码显示了 FIDL 服务器和调度程序对象 在设备类中:
class Device : public fidl::WireServer<fuchsia_wlan_device::Phy>, public ::ddk::Device<Device, ::ddk::MessageableManual, ::ddk::Unbindable> { ... private: // Dispatcher for being a FIDL server listening MLME requests. async_dispatcher_t* server_dispatcher_; ...
(来源:
device_dfv2.h
)您可以使用
fdf::Dispatcher::GetCurrent()
方法,或创建一个新的非默认方法 调度程序(请参见 更新 DFv1 驱动程序以使用非默认调度程序)。
在 .fidl
文件中,执行以下操作:
定义适用于以下应用的驱动程序服务协议: 由客户端和服务器端组成
以下示例代码展示了
.fidl
文件:service Service { wlan_phy_impl client_end:WlanPhyImpl; };
(来源:
phyimpl.fidl
)
3. 更新驱动程序以使用驱动程序运行时 FIDL
完成上一步中的更改后, 您可以开始更新驱动程序的实现,以使用相应驱动程序 运行时 FIDL。
在客户端,执行以下操作:
连接到家长设备驱动程序添加的协议 外拨电话目录,呼叫
DdkConnectRuntimeProtocol()
函数,例如:auto client_end = DdkConnectRuntimeProtocol<fuchsia_wlan_softmac::Service::WlanSoftmac>();
(来源:
device.cc
)此函数会创建一对端点:
- 该函数会返回
fdf::ClientEnd<ProtocolName>
对象 。 fdf::ServerEnd<ProtocolName>
对象以静默方式 父级设备驱动程序。
- 该函数会返回
当调用方获取客户端对象时,将该对象传递给
fdf::WireSharedClient<ProtocolName>()
的构造函数 (或fdf::WireSyncClient<ProtocolName>()
),例如:client_ = fdf::WireSharedClient<fuchsia_wlan_phyimpl::WlanPhyImpl>(std::move(client), client_dispatcher_.get());
(来源:
device.cc
)
在服务器端,执行以下操作:
在设备类中声明一个传出目录对象,例如:
#include <lib/driver/outgoing/cpp/outgoing_directory.h> class Device : public DeviceType, public fdf::WireServer<fuchsia_wlan_phyimpl::WlanPhyImpl>, public DataPlaneIfc { ... fdf::OutgoingDirectory outgoing_dir_;
(来源:
device.h
)调用
fdf::OutgoingDirectory::Create()
函数,以便使父元素 驱动程序创建一个传出目录对象,例如:#include <lib/driver/outgoing/cpp/outgoing_directory.h> ... Device::Device(zx_device_t *parent) : DeviceType(parent), outgoing_dir_( fdf::OutgoingDirectory::Create( fdf::Dispatcher::GetCurrent()->get()))
(来源:
wlan_interface.cc
)将服务添加到传出目录并提供服务。
此外发目录中会提供家长司机的服务协议 以便子驱动程序可以连接到该网络。
在父驱动程序的服务回调函数(即一个函数)中, 在子节点连接到服务时调用的监听器),绑定
fdf::ServerEnd<ProtocolName>
对象fdf::BindServer()
函数,例如:zx_status_t Device::ServeWlanPhyImplProtocol( fidl::ServerEnd<fuchsia_io::Directory> server_end) { // This callback will be invoked when this service is being connected. auto protocol = [this]( fdf::ServerEnd<fuchsia_wlan_phyimpl::WlanPhyImpl> server_end) mutable { fdf::BindServer(fidl_dispatcher_.get(), std::move(server_end), this); protocol_connected_.Signal(); }; // Register the callback to handler. fuchsia_wlan_phyimpl::Service::InstanceHandler handler( {.wlan_phy_impl = std::move(protocol)}); // Add this service to the outgoing directory so that the child driver can // connect to by calling DdkConnectRuntimeProtocol(). auto status = outgoing_dir_.AddService<fuchsia_wlan_phyimpl::Service>( std::move(handler)); if (status.is_error()) { NXPF_ERR("%s(): Failed to add service to outgoing directory: %s\n", status.status_string()); return status.error_value(); } // Serve the outgoing directory to the entity that intends to open it, which // is DFv1 in this case. auto result = outgoing_dir_.Serve(std::move(server_end)); if (result.is_error()) { NXPF_ERR("%s(): Failed to serve outgoing directory: %s\n", result.status_string()); return result.error_value(); } return ZX_OK; }
(来源:
device.cc
)请注意,
fdf::BindServer()
函数需要一个 调度程序作为输入。您可以使用默认的 由驱动程序主机提供的驱动程序调度程序 (fdf::Dispatcher::GetCurrent()->get()
) 或创建一个新的非默认文件 调度程序来单独处理 FIDL 请求(请参阅 更新 DFv1 驱动程序以使用非默认调度程序)。此时,客户端和服务器端已准备好相互通信 使用驱动程序运行时 FIDL。
4. 发出 FIDL 请求
如需进行 FIDL 调用,请使用
在客户端构建的 fdf::WireSharedClient<ProtocolName>()
对象
创建 Deployment 清单
请参阅下面的语法,了解如何进行 FIDL 调用(其中 CLIENT_
是
实例):
异步 FIDL 调用:
CLIENT_.buffer(*std::move(arena))->MyExampleFunction().ThenExactlyOnce([](fdf::WireUnownedResult<FidlFunctionName>& result) mutable { // Your result handler. });
(来源:
device.cc
)同步 FIDL 调用:
auto result = CLIENT_.sync().buffer(*std::move(arena))->MyExampleFunction(); // Your result handler.
(来源:
device.cc
)
您可能会发现以下做法有助于进行 FIDL 调用:
使用 FIDL 错误语法, 您可以调用
result.is_error()
,以检查调用是否会返回 出现网域错误。同样,result.error_value()
调用会返回 确切的错误值。进行异步双向客户端 FIDL 调用时, 传递回调,则可以使用
.Then(callback)
或.ThenExactlyOnce(callback)
:用于指定所需的取消 的语义定义。同步 FIDL 调用将等待来自服务器端的回调。 因此,需要在
.fidl
文件。否则,通话只会执行“即发即弃”和 立即返回。如需了解回调定义,请参阅
.fidl
文件中的以下示例:protocol MyExampleProtocol { // You can only make async calls based on this function. FunctionWithoutCallback(); // You can make both sync and async calls based on this function. FunctionWithCallback() -> (); }
回调定义创建完成后,系统将 在此示例的同步回调和异步回调中 需要在服务器端实施(请参阅
MyExampleFunction()
)。当服务器端设备类继承
fdf::WireServer<ProtocolName>
时 系统会根据您的协议定义生成虚拟函数, 类似于fidl::WireServer<ProtocolName>
。以下是 此函数:void MyExampleFunction(MyExampleFunctionRequestView request, fdf::Arena& arena, MyExampleFunctionCompleter::Sync& completer);
此函数采用三个参数:
MyExampleFunctionRequestView
- 由 FIDL 生成,包含 从客户端发送到服务器的请求结构。fdf::Arena
- 是此 FIDL 消息的缓冲区。该缓冲区 传递或移出了客户端您可以将它用作缓冲区, 通过completer
对象返回消息或错误语法。您 也可以重复使用它对下一级别的驱动程序进行 FIDL 调用。MyExampleFunctionCompleter
- 由 FIDL 生成,用于调用 回调,并将此 FIDL 调用的结果返回给客户端。 如果定义了 FIDL 错误语法,您可以使用completer.ReplySuccess()
或completer.ReplyError()
,用于返回消息和错误状态; 如果未定义 FIDL 错误语法,则只能使用completer.Reply()
即可返回该消息。
您可以将 Area 对象移到 FIDL 调用中,也可以直接将它传递给它 名称:
*arena
。但是,移动场景对象可能会暴露潜在错误。 因此,建议改为传递 arena 对象。由于 Area 可以重复用于下一级别 FIDL 调用,但 arena 对象不会 在传递后被销毁。如果您的司机在
.fidl
文件,FIDL 会根据 定义。不过,在这种情况下,我们还是建议您选择线缆类型,因为 线缆类型是更稳定的选择,而自然类型是临时类型 HLCPP 类型。如果 FIDL 发现交换的端点无效,则会关闭其通道 这称为验证错误:
一个很好的验证错误示例是,传递 0 时(针对不灵活应用) 枚举),而它仅定义了值 1 到 5。验证时 错误,该频道将永久关闭,并且 会导致失败。此行为与 Banjo 不同。(有关 相关信息,请参阅严格与灵活。)
无效值还包括具有
zx_handle_t
值的内核对象 设置为 0。例如zx::vmo(0)
。
(可选)更新 DFv1 驱动程序以使用非默认调度程序
fdf::Dispatcher::GetCurrent()
方法为您提供了
运行驱动程序的默认调度程序。
如果可能,建议单独使用此默认调度程序。不过,
如果您需要创建新的非默认调度程序,则驱动程序运行时
需要了解调度程序属于哪个驱动程序。这样,
驱动程序框架设置适当的属性并关闭
调度程序。
以下各部分介绍了分配和管理的高级用例 您自己的非默认调度程序:
1. 分配调度员
为了确保在受管的线程中创建并运行调度程序,
调度程序的分配必须在
函数(例如 DdkInit()
),例如:
void Device::DdkInit(ddk::InitTxn txn) {
bool fw_init_pending = false;
const zx_status_t status = [&]() -> zx_status_t {
auto dispatcher = fdf::SynchronizedDispatcher::Create(
{}, "nxpfmac-sdio-wlanphy",
[&](fdf_dispatcher_t *) { sync_completion_signal(&fidl_dispatcher_completion_); });
if (dispatcher.is_error()) {
NXPF_ERR("Failed to create fdf dispatcher: %s", dispatcher.status_string());
return dispatcher.status_value();
}
fidl_dispatcher_ = std::move(*dispatcher);
...
(来源:device.cc
)
2. 关闭调度程序
同样,调度程序的关闭也需要在函数内发生
由驱动程序框架出于同样的原因调用。DdkUnbind()
或
device_unbind()
方法很适合执行此操作。
请注意,调度程序关停是异步的,这需要处理
。例如,如果一个调度程序在
DdkUnbind()
调用时,我们需要使用 ddk::UnbindTxn
对象(通过
Unbind()
调用),以调用 ddk::UnbindTxn::Reply()
在调度程序的关闭回调中调用 ,以确保完全关闭。
以下代码段示例演示了关停过程 如上文所述:
将
ddk::UnbindTxn
对象保存在DdkUnbind()
中:void DdkUnbind(ddk::UnbindTxn txn) { // Move the txn here because it’s not copyable. unbind_txn_ = std::move(txn); ... }
在绑定或
DdkInit()
钩子中,使用以下代码创建一个调度程序 调用ddk::UnbindTxn::Reply()
的关停回调:auto dispatcher = fdf::Dispatcher::Create(0, [&](fdf_dispatcher_t*) { if (unbind_txn_) unbind_txn_->Reply(); unbind_txn_.reset(); });
从
DdkUnbind()
末尾的调度程序调用ShutdownAsync()
:void DdkUnbind(ddk::UnbindTxn txn) { // Move the txn here because it’s not copyable. unbind_txn_ = std::move(txn); ... dispatcher.ShutDownAsync(); }
不过,如果在驱动程序中分配了多个调度程序,
由于 ddk::UnbindTxn::Reply()
仅被调用一次,因此您需要实现
执行一系列关停操作例如,假设调度员 A 和 B
(可互换),您可以:
- 在设备 A 的关闭回调中,为 B 调用
ShutdownAsync()
。 - 在 B 的关停回调中调用
ddk::UnbindTxn::Reply()
。
(可选)更新 DFv1 驱动程序以使用双向通信
常见的情况是,只有客户端的设备需要主动 向服务器端设备发出请求。但在某些情况下 需要通过通道发送消息,而无需等待 响应。
要在 DFv1 驱动程序中建立双向通信,您有 以下三个选项:
方式 1 - 在 FIDL 协议中定义事件 (请参阅实现 C++ FIDL 服务器)。
方案 2 - 以相反方向实现第二个 FIDL 协议 以便两端的设备同时位于服务器和客户端。
方式 3 - 在 FIDL 中使用“hanging get”模式( 流控制设计模式中建议的 FIDL 评分准则)。
使用事件的原因(选项 1)包括:
简单 - 一个协议比两个协议更简单。
序列化 - 如果您需要两种协议, 会按照写入通道的顺序进行序列化。
不使用事件(选项 1)的原因包括:
当消息从服务器发送到 客户端。
您需要控制消息的流动。
以下协议实施了选项 2,其中的协议以
.fidl
文件,用于指示两个不同的方向:
对于 WLAN 驱动程序迁移,团队选择了选项 2,因为它 引入来自未知域的其他 FIDL 语法。但它应该 请注意,WLAN 驱动程序是驱动程序运行时的第一个实例 且 FIDL 事件在驱动程序传输中不受支持 迁移过程
根据你的需要来电(选项 3)通常 选项,因为它可让客户端指明它已准备就绪 处理事件,从而提供流程控制。(不过,如有必要, 还有一些其他方式可以为事件添加流控制,具体如下所述 请参阅使用确认信息限制事件部分, FIDL 评分准则。)
更新 DFv1 驱动程序的单元测试以使用 FIDL
如果驱动程序存在基于 Banjo API 的单元测试,您需要 迁移测试以提供模拟 FIDL 服务器,而不是 Banjo 服务器 (或模拟 FIDL 客户端)。
在 DFv1 中,要模拟 FIDL 服务器或客户端,您可以使用
MockDevice::FakeRootParent()
方法,例如:
std::shared_ptr<MockDevice> fake_parent_ = MockDevice::FakeRootParent();
(来源:ft_device_test.cc
)
MockDevice::FakeRootParent()
方法已与
DriverRuntime
测试库(这是唯一受支持的测试库)
(针对 DFv1)。MockDevice::FakeRootParent()
方法会创建
用户的 fdf_testing::DriverRuntime
实例。然后,此实例会启动
驱动程序运行时,并为用户创建前台驱动程序调度程序。
不过,您还可以通过此对象创建后台调度程序。您
可以使用 fdf_testing::DriverRuntime::GetInstance()
获取实例
方法。
如需查看示例,请参阅此模拟单元测试 适用于 DFv1 驱动程序的 PWM 和 vreg FIDL 协议。
此外,以下库可能有助于编写驱动程序单元测试:
//src/devices/bus/testing/fake-pdev/fake-pdev.h
– 这 帮助程序库实现了伪造版本的 pdev FIDL 协议。
其他资源
本部分中提到的所有 Gerrit 更改如下:
- [iwlwifi][wlanphy] 驱动程序运行时迁移(wlanphy <--> iwlwifi)
- [iwlwifi][wlansoftmac] 驱动程序运行时迁移(wlansoftmac <--> iwlwifi)
本部分中提到的所有源代码文件:
//sdk/banjo/fuchsia.hardware.wlanphyimpl/wlanphy-impl.fidl
//sdk/fidl/fuchsia.wlan.phyimpl/phyimpl.fidl
//sdk/fidl/fuchsia.wlan.softmac/softmac.fidl
//sdk/lib/driver/component/cpp/tests/driver_base_test.cc
//sdk/lib/driver/testing/cpp
//src/connectivity/wlan/drivers/third_party/nxp/nxpfmac/device.cc
//src/connectivity/wlan/drivers/wlansoftmac/device.cc
//src/connectivity/wlan/drivers/wlansoftmac/meta/wlansoftmac.cml
//examples/drivers/transport/driver/
本部分中提到的所有文档页面:
- Banjo
- FIDL
- RFC-0126:驱动程序运行时
- 新的 C++ 绑定教程
- 驱动程序调度程序和线程
- FIDL 属性
- 实现 C++ FIDL 服务器
- HLCPP 教程
- 定义驱动程序服务协议 (来自公开驱动程序功能)
- 严格与灵活 (来自 FIDL 语言规范)
- 使用确认对事件进行限制 (来自 FIDL API 评分准则)
- 使用挂起获取延迟响应 (来自 FIDL API 评分准则)