外部元件連線 (PCI) 裝置會使用各種介面 (包括中斷、記憶體對應 I/O (MMIO) 暫存器和直接記憶體存取 (DMA) 緩衝區),將資源公開給系統。Fuchsia 驅動程式會透過父項裝置節點的功能存取這些資源。針對 PCI 裝置,父項會提供 fuchsia.hardware.pci/Device
FIDL 通訊協定的例項,讓驅動程式庫能夠設定裝置。
在本節中,您將新增功能,以便存取 edu
裝置上的下列 MMIO 暫存器:
位址偏移 | 註冊 | R/W | 說明 |
---|---|---|---|
0x00 | 身分識別 | RO | 主要 / 次要版本 ID |
0x04 | 卡片有效性檢查 | RW | 驗證操作的挑戰 |
0x08 | 階乘運算 | RW | 計算儲存值的階乘 |
0x20 | 狀態 | RW | 用於表示作業已完成的位元欄位 |
完成本節後,專案應具有下列目錄結構:
//fuchsia-codelab/qemu_edu/drivers
|- BUILD.bazel
|- meta
| |- qemu_edu.cml
|- edu_device.cc
|- edu_device.h
|- qemu_edu.bind
|- qemu_edu.cc
|- qemu_edu.h
連線至父項裝置
如要從父項裝置節點存取 fuchsia.hardware.pci/Device
介面,請將 fuchsia.hardware.pci.Service
能力新增至驅動程式庫元件資訊清單:
qemu_edu/drivers/meta/qemu_edu.cml
:
{
include: [
"syslog/client.shard.cml",
],
program: {
runner: 'driver',
binary: 'driver/libqemu_edu.so',
bind: 'meta/bind/qemu_edu.bindbc',
// Identifies the device categories, for compatibility tests. This
// example driver uses the 'misc' category; real drivers should
// select a more specific category.
device_categories: [
{ category: 'misc', subcategory: '' },
],
},
use: [
{ service: 'fuchsia.hardware.pci.Service' },
],
}
這可讓驅動程式庫開啟與父項裝置的連線,並存取父項裝置提供的硬體專屬通訊協定。
更新驅動程式庫 Start()
方法,以便在驅動程式庫初始化期間存取父項裝置提供的 fuchsia.hardware.pci/Device
:
qemu_edu/drivers/qemu_edu.cc
:
#include "qemu_edu.h"
#include <lib/driver/component/cpp/driver_export.h>
namespace qemu_edu {
// ...
// Initialize this driver instance
zx::result<> QemuEduDriver::Start() {
// Connect to the parent device node.
zx::result connect_result = incoming()->Connect<fuchsia_hardware_pci::Service::Device>("default");
if (connect_result.is_error()) {
FDF_SLOG(ERROR, "Failed to open pci service.", KV("status", connect_result.status_string()));
return connect_result.take_error();
}
FDF_SLOG(INFO, "edu driver loaded successfully");
return zx::ok();
}
} // namespace qemu_edu
設定中斷和 MMIO
開啟與 fuchsia.hardware.pci/Device
的連線後,您就可以開始將必要的裝置資源對應至驅動程式庫。
在專案目錄中建立新的 qemu_edu/drivers/edu_device.h
檔案,並加入下列內容:
qemu_edu/drivers/edu_device.h
:
#ifndef FUCHSIA_CODELAB_QEMU_EDU_DEVICE_H_
#define FUCHSIA_CODELAB_QEMU_EDU_DEVICE_H_
#include <fidl/fuchsia.hardware.pci/cpp/wire.h>
#include <lib/async/cpp/irq.h>
#include <lib/mmio/mmio.h>
#include <lib/zx/interrupt.h>
namespace edu_device {
// Interacts with the device hardware using a fuchsia.hardware.pci client.
class QemuEduDevice {
public:
explicit QemuEduDevice(async_dispatcher_t* dispatcher,
fidl::ClientEnd<fuchsia_hardware_pci::Device> pci)
: dispatcher_(dispatcher), pci_(std::move(pci)) {}
zx::result<> MapInterruptAndMmio();
private:
void HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt);
async_dispatcher_t* const dispatcher_;
fidl::WireSyncClient<fuchsia_hardware_pci::Device> pci_;
std::optional<fdf::MmioBuffer> mmio_;
zx::interrupt irq_;
async::IrqMethod<QemuEduDevice, &QemuEduDevice::HandleIrq> irq_method_{this};
std::optional<fit::callback<void(zx::result<uint32_t>)>> pending_callback_;
};
} // namespace edu_device
#endif // FUCHSIA_CODELAB_QEMU_EDU_DEVICE_H_
建立新的 qemu_edu/drivers/edu_device.cc
檔案,並加入下列程式碼來實作 MapInterruptAndMmio()
方法。此方法會執行下列工作:
- 存取適當 PCI 區域的 Base Address Register (BAR)。
- 擷取 Fuchsia 的 VMO (虛擬記憶體物件) 區域。
- 在區域周圍建立 MMIO 緩衝區,以便存取個別登錄。
- 設定對應至裝置中斷的中斷要求 (IRQ)。
qemu_edu/drivers/edu_device.cc
:
#include "edu_device.h"
#include <lib/driver/logging/cpp/structured_logger.h>
namespace edu_device {
// Initialize PCI device hardware resources
zx::result<> QemuEduDevice::MapInterruptAndMmio() {
// Retrieve the Base Address Register (BAR) for PCI Region 0
auto bar = pci_->GetBar(0);
if (!bar.ok()) {
FDF_SLOG(ERROR, "failed to get bar", KV("status", bar.status()));
return zx::error(bar.status());
}
if (bar->is_error()) {
FDF_SLOG(ERROR, "failed to get bar", KV("status", bar->error_value()));
return zx::error(bar->error_value());
}
// Create a Memory-Mapped I/O (MMIO) region over BAR0
{
auto& bar_result = bar->value()->result;
if (!bar_result.result.is_vmo()) {
FDF_SLOG(ERROR, "unexpected bar type");
return zx::error(ZX_ERR_NO_RESOURCES);
}
zx::result<fdf::MmioBuffer> mmio = fdf::MmioBuffer::Create(
0, bar_result.size, std::move(bar_result.result.vmo()), ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (mmio.is_error()) {
FDF_SLOG(ERROR, "failed to map mmio", KV("status", mmio.status_value()));
return mmio.take_error();
}
mmio_ = *std::move(mmio);
}
// Configure interrupt handling for the device using INTx
auto result = pci_->SetInterruptMode(fuchsia_hardware_pci::wire::InterruptMode::kLegacy, 1);
if (!result.ok()) {
FDF_SLOG(ERROR, "failed configure interrupt mode", KV("status", result.status()));
return zx::error(result.status());
}
if (result->is_error()) {
FDF_SLOG(ERROR, "failed configure interrupt mode", KV("status", result->error_value()));
return zx::error(result->error_value());
}
// Map the device's interrupt to a system IRQ
auto interrupt = pci_->MapInterrupt(0);
if (!interrupt.ok()) {
FDF_SLOG(ERROR, "failed to map interrupt", KV("status", interrupt.status()));
return zx::error(interrupt.status());
}
if (interrupt->is_error()) {
FDF_SLOG(ERROR, "failed to map interrupt", KV("status", interrupt->error_value()));
return zx::error(interrupt->error_value());
}
irq_ = std::move(interrupt->value()->interrupt);
// Start listening for interrupts.
irq_method_.set_object(irq_.get());
irq_method_.Begin(dispatcher_);
return zx::ok();
}
} // namespace edu_device
將新的裝置資源新增至驅動程式庫類別:
qemu_edu/drivers/qemu_edu.h
:
#include <fidl/examples.qemuedu/cpp/wire.h>
#include <lib/driver/component/cpp/driver_base.h>
#include <lib/driver/devfs/cpp/connector.h>
#include "edu_device.h"
namespace qemu_edu {
class QemuEduDriver : public fdf::DriverBase {
public:
QemuEduDriver(fdf::DriverStartArgs start_args,
fdf::UnownedSynchronizedDispatcher driver_dispatcher)
: fdf::DriverBase("qemu-edu", std::move(start_args), std::move(driver_dispatcher)),
devfs_connector_(fit::bind_member<&QemuEduDriver::Serve>(this)) {}
virtual ~QemuEduDriver() = default;
// Start hook called by the driver factory.
zx::result<> Start() override;
private:
zx::result<> ExportToDevfs();
void Serve(fidl::ServerEnd<examples_qemuedu::Device> request);
fidl::WireSyncClient<fuchsia_driver_framework::Node> node_;
fidl::WireSyncClient<fuchsia_driver_framework::NodeController> controller_;
driver_devfs::Connector<examples_qemuedu::Device> devfs_connector_;
std::shared_ptr<edu_device::QemuEduDevice> device_;
};
} // namespace qemu_edu
更新驅動程式庫的 Run()
方法,以便在驅動程式庫初始化期間呼叫新方法:
qemu_edu/drivers/qemu_edu.cc
:
// Initialize this driver instance
zx::result<> QemuEduDriver::Start() {
// Connect to the parent device node.
zx::result connect_result = incoming()->Connect<fuchsia_hardware_pci::Service::Device>("default");
if (connect_result.is_error()) {
FDF_SLOG(ERROR, "Failed to open pci service.", KV("status", connect_result.status_string()));
return connect_result.take_error();
}
// Map hardware resources from the PCI device
device_ =
std::make_shared<edu_device::QemuEduDevice>(dispatcher(), std::move(connect_result.value()));
auto pci_status = device_->MapInterruptAndMmio();
if (pci_status.is_error()) {
return pci_status.take_error();
}
FDF_SLOG(INFO, "edu driver loaded successfully");
return zx::ok();
}
更新驅動程式庫建構設定,以便納入新的來源檔案,並依附 fuchsia.hardware.pci
的 FIDL 繫結程式庫:
qemu_edu/drivers/BUILD.bazel
:
fuchsia_cc_driver(
name = "qemu_edu",
srcs = [
"edu_device.cc",
"edu_device.h",
"qemu_edu.cc",
"qemu_edu.h",
],
deps = [
"@fuchsia_sdk//fidl/fuchsia.hardware.pci:fuchsia.hardware.pci_llcpp_cc",
"@fuchsia_sdk//pkg/driver_component_cpp",
"@fuchsia_sdk//pkg/driver_devfs_cpp",
"@fuchsia_sdk//pkg/hwreg",
"@fuchsia_sdk//pkg/mmio",
],
)
讀取裝置暫存器
將基本資源對應至驅動程式庫後,您就可以存取個別的註冊。請在專案的 qemu_edu/drivers/edu_device.h
檔案中加入下列註冊定義:
qemu_edu/drivers/edu_device.h
:
#include <hwreg/bitfields.h>
#include <fidl/fuchsia.hardware.pci/cpp/wire.h>
#include <lib/async/cpp/irq.h>
#include <lib/mmio/mmio.h>
#include <lib/zx/interrupt.h>
namespace edu_device {
// Register offset addresses for edu device MMIO area
constexpr uint32_t kIdentificationOffset = 0x00;
constexpr uint32_t kLivenessCheckOffset = 0x04;
constexpr uint32_t kFactorialComputationOffset = 0x08;
constexpr uint32_t kStatusRegisterOffset = 0x20;
constexpr uint32_t kInterruptStatusRegisterOffset = 0x24;
constexpr uint32_t kInterruptRaiseRegisterOffset = 0x60;
constexpr uint32_t kInterruptAcknowledgeRegisterOffset = 0x64;
constexpr uint32_t kDmaSourceAddressOffset = 0x80;
constexpr uint32_t kDmaDestinationAddressOffset = 0x80;
constexpr uint32_t kDmaTransferCountOffset = 0x90;
constexpr uint32_t kDmaCommandRegisterOffset = 0x98;
class Identification : public hwreg::RegisterBase<Identification, uint32_t> {
public:
DEF_FIELD(31, 24, major_version);
DEF_FIELD(23, 16, minor_version);
DEF_FIELD(15, 0, edu);
static auto Get() { return hwreg::RegisterAddr<Identification>(kIdentificationOffset); }
};
class Status : public hwreg::RegisterBase<Status, uint32_t> {
public:
DEF_BIT(0, busy);
DEF_BIT(7, irq_enable);
static auto Get() { return hwreg::RegisterAddr<Status>(kStatusRegisterOffset); }
};
// Interacts with the device hardware using a fuchsia.hardware.pci client.
class QemuEduDevice {
public:
explicit QemuEduDevice(async_dispatcher_t* dispatcher,
fidl::ClientEnd<fuchsia_hardware_pci::Device> pci)
: dispatcher_(dispatcher), pci_(std::move(pci)) {}
zx::result<> MapInterruptAndMmio();
void ComputeFactorial(uint32_t input, fit::callback<void(zx::result<uint32_t>)> callback);
zx::result<uint32_t> LivenessCheck(uint32_t challenge);
Identification IdentificationRegister() { return Identification::Get().ReadFrom(&*mmio_); }
Status StatusRegister() { return Status::Get().ReadFrom(&*mmio_); }
private:
void HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt);
async_dispatcher_t* const dispatcher_;
fidl::WireSyncClient<fuchsia_hardware_pci::Device> pci_;
std::optional<fdf::MmioBuffer> mmio_;
zx::interrupt irq_;
async::IrqMethod<QemuEduDevice, &QemuEduDevice::HandleIrq> irq_method_{this};
std::optional<fit::callback<void(zx::result<uint32_t>)>> pending_callback_;
};
} // namespace edu_device
這會將裝置規格中提供的註冊偏移值宣告為常數。Fuchsia 的 hwreg
程式庫會包裝代表位元欄位的暫存器,讓您不必執行個別位元運算,也能輕鬆存取這些位元欄位。
請在 qemu_edu/drivers/edu_device.cc
中實作下列額外方法,與 MMIO 區域互動,讀取及寫入資料至各自的 edu
裝置登錄:
ComputeFactorial()
:將輸入值寫入階乘運算註冊,並等待裝置使用中斷非同步傳送完成信號。HandleIrq()
:讀取階乘暫存器的運算結果,並將結果回報給待處理的回呼。LivenessCheck()
:將挑戰值寫入活動性檢查暫存器,並確認預期結果。
qemu_edu/drivers/edu_device.cc
:
#include "edu_device.h"
#include <lib/driver/logging/cpp/structured_logger.h>
namespace edu_device {
// ...
// Write data into the factorial register wait for an interrupt.
void QemuEduDevice::ComputeFactorial(uint32_t input,
fit::callback<void(zx::result<uint32_t>)> callback) {
if (pending_callback_.has_value()) {
callback(zx::error(ZX_ERR_SHOULD_WAIT));
}
// Tell the device to raise an interrupt after computation.
auto status = StatusRegister();
status.set_irq_enable(true);
status.WriteTo(&*mmio_);
// Write the value into the factorial register to start computation.
mmio_->Write32(input, kFactorialComputationOffset);
// We will receive an interrupt when the computation completes.
pending_callback_ = std::move(callback);
}
// Respond to INTx interrupts triggered by the device, and return the compute result.
void QemuEduDevice::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq,
zx_status_t status, const zx_packet_interrupt_t* interrupt) {
irq_.ack();
if (!pending_callback_.has_value()) {
FDF_LOG(ERROR, "Received unexpected interrupt!");
return;
}
auto callback = std::move(*pending_callback_);
pending_callback_ = std::nullopt;
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to wait for interrupt", KV("status", zx_status_get_string(status)));
callback(zx::error(status));
return;
}
// Acknowledge the interrupt with the edu device.
auto int_status = mmio_->Read32(kInterruptStatusRegisterOffset);
mmio_->Write32(int_status, kInterruptAcknowledgeRegisterOffset);
// Deassert the legacy INTx interrupt on the PCI bus.
auto irq_result = pci_->AckInterrupt();
if (!irq_result.ok() || irq_result->is_error()) {
FDF_SLOG(ERROR, "Failed to ack PCI interrupt",
KV("status", irq_result.ok() ? irq_result->error_value() : irq_result.status()));
callback(zx::error(ZX_ERR_IO));
return;
}
// Reply with the result.
uint32_t factorial = mmio_->Read32(kFactorialComputationOffset);
FDF_SLOG(INFO, "Replying with", KV("factorial", factorial));
callback(zx::ok(factorial));
}
// Write a challenge value to the liveness check register and return the result.
zx::result<uint32_t> QemuEduDevice::LivenessCheck(uint32_t challenge) {
// Write the challenge value to the liveness check register.
mmio_->Write32(challenge, kLivenessCheckOffset);
// Return the result.
auto value = mmio_->Read32(kLivenessCheckOffset);
return zx::ok(value);
}
} // namespace edu_device
請將下列內容新增至驅動程式庫的 Start()
方法,從 MMIO 區域的 ID 註冊讀取主要和次要版本,並將其輸出至記錄:
qemu_edu/drivers/qemu_edu.cc
:
// Initialize this driver instance
zx::result<> QemuEduDriver::Start() {
// ...
// Map hardware resources from the PCI device
device_ =
std::make_shared<edu_device::QemuEduDevice>(dispatcher(), std::move(connect_result.value()));
auto pci_status = device_->MapInterruptAndMmio();
if (pci_status.is_error()) {
return pci_status.take_error();
}
// Report the version information from the edu device.
auto version_reg = device_->IdentificationRegister();
FDF_SLOG(INFO, "edu device version", KV("major", version_reg.major_version()),
KV("minor", version_reg.minor_version()));
return zx::ok();
}
重新啟動模擬器
關閉所有現有的模擬器例項:
ffx emu stop --all
啟動已啟用驅動程式庫架構的 Fuchsia 模擬器新執行個體:
ffx emu start core.x64 --headless
重新載入驅動程式庫
使用 bazel run
指令建構及執行元件目標:
bazel run //fuchsia-codelab/qemu_edu/drivers:pkg.component
bazel run
指令會重新建構套件,並執行 ffx driver register
重新載入驅動程式庫元件。
檢查系統記錄,確認您可以看到更新的 FDF_SLOG()
訊息,其中包含從識別註冊讀取的版本:
ffx log --filter qemu_edu
[driver_manager][driver_manager.cm][I]: [driver_runner.cc:959] Binding fuchsia-pkg://bazel.pkg.component/qemu_edu#meta/qemu_edu.cm to 00_06_0_
[full-drivers:root.sys.platform.pt.PCI0.bus.00_06_0_][qemu-edu,driver][I]: [fuchsia-codelab/qemu_edu/qemu_edu.cc:75] edu device version major=1 minor=0
恭喜!驅動程式現在可以存取已繫結裝置節點提供的 PCI 硬體資源。