Caution: This page may contain information that is specific to the legacy version of the driver framework (DFv1). Also the workflows documented on this page may only be specific to the Fuchsia source checkout (fuchsia.git
) environment.
So you've decided to bring up a new board. Before you dive into coding, ensure that you have everything you need by answering these questions:
How will I understand how the device and its registers work?
Is there an existing driver for a similar board?
Does the device have a fixed display?
This guide assumes that you are familiar with driver development for one or more operating systems. It also assumes that you are familiar with the Fuchsia DDK-TL.
New drivers must be written in C++. Rust support is planned, but is still highly experimental.
If an appropriately licensed driver already exists and is written in C, it may be acceptable to port it to Fuchsia rather than implementing a new version in C++. Please contact graphics-dev@fuchsia.dev before making this decision.
For platforms without ACPI or a PCI bus, Modifying board drivers is the first step. This guide assumes that the board driver is ready and that the display driver is codenamed fancy
. All code for the new driver will live in src/graphics/display/drivers/fancy-display/
.
To begin, create:
DisplayControllerImpl
and the bind rulesBUILD.gn
# Copyright 2021 The Fuchsia Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import("//build/bind/bind.gni") import("//build/drivers.gni") driver_bind_rules("fancy-display-bind") { rules = "fancy-display.bind" bind_output = "fancy-display.bindbc" tests = "bind_tests.json" deps = [ "//src/devices/bind/board_maker_company.platform", ] } # Factored out so that it can be used in tests. source_set("common") { public_deps = [ ":fancy-display-bind", ] sources = [ "fancy-display.cc", ] } fuchsia_driver("fancy-display") { sources = [] deps = [ ":common", "//src/devices/lib/driver", ] }
//src/graphics/display/drivers/fancy-display
as a dependency for the board(s) that you are using as test products. For example, if your device is part of a Khadas VIM3 board, modify //boards/vim3.gni
by adding your driver to the _common_bootfs_deps
list.Now that you have a build recipe, you can move on to creating the bind rules, which the driver manager uses to decide whether a driver can be used with a device.
src/graphics/display/drivers/fancy-display
, create fancy-display.bind
:// Copyright 2021 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. using fuchsia.pci; fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE; fuchsia.BIND_PCI_VID == fuchsia.pci.BIND_PCI_VID.PLANK_HW_INC; accept fuchsia.BIND_PCI_DID { // Fancy 0x0100, // Fancy+ series 0x0120, 0x0121, }
For PC devices, the intel-i915 bind rules are a good example. For fixed-hardware SoCs, see the Amlogic display rules.
Finally, add a bare bones driver that simply constructs a new object every time it successfully binds to a device. Later, you can use the datasheet to get the device to actually do something.
In src/graphics/display/drivers/fancy-display
, create fancy-display.cc
:
#include <ddktl/device.h> #include <fuchsia/hardware/display/controller/cpp/banjo.h> namespace fancy_display { class Device; using DeviceType = ddk::Device<Device> // A Device exposes a single display controller for use by the display // coordinator driver in src/graphics/display/drivers/coordinator. // // This object is constructed once for each device that matches this // driver's bind rules. class Device : public DeviceType { public: explicit Device(zx_device_t* parent) : DeviceType(parent) {} // If Bind() returns an error, the driver won't claim the device. zx_status_t Bind() { return ZX_OK }; // Functionality needed by the common display driver core. void DisplayControllerImplSetDisplayControllerInterface( const display_controller_interface_protocol* interface) {} zx_status_t DisplayControllerImplImportBufferCollection( uint64_t collection_id, zx::channel collection_token) { return ZX_ERR_NOT_SUPPORTED; } zx_status_t DisplayControllerImplReleaseBufferCollection( uint64_t collection_id) { return ZX_ERR_NOT_SUPPORTED; } zx_status_t DisplayControllerImplImportImage(const image_metadata_t* image_metadata, uint64_t collection_id, uint32_t index, uint64_t* out_image_handle) { return ZX_ERR_NOT_SUPPORTED; } void DisplayControllerImplReleaseImage(image_t* image) {} config_check_result_t DisplayControllerImplCheckConfiguration( const display_config_t** display_configs, size_t display_count, client_composition_opcode_t* out_client_composition_opcodes_list, size_t client_composition_opcodes_count, size_t* out_client_composition_opcodes_actual); void DisplayControllerImplApplyConfiguration( const display_config_t** display_config, size_t display_count) {} void DisplayControllerImplSetEld( uint64_t display_id, const uint8_t* raw_eld_list, size_t raw_eld_count) {} zx_status_t DisplayControllerImplSetBufferCollectionConstraints( const image_buffer_usage_t* usage, uint64_t collection_id) { return ZX_ERR_NOT_SUPPORTED; } }; } // namespace fancy_display // Main bind function called from dev manager. zx_status_t fancy_display_bind(void* ctx, zx_device_t* parent) { fbl::AllocChecker alloc_checker; auto dev = fbl::make_unique_checked<fancy_display::Device>( &alloc_checker, parent); if (!alloc_checker.check()) { return ZX_ERR_NO_MEMORY; } auto status = dev->Bind(); if (status == ZX_OK) { // The driver/device manager now owns this memory. [[maybe_unused]] auto ptr = dev.release(); } return status; } // zx_driver_ops_t is the ABI between driver modules and the device manager. // This lambda is used so that drivers can be rebuilt without compiler // warnings if/when new fields are added to the struct. static zx_driver_ops_t fancy_display_ops = [](){ zx_driver_ops_t ops; ops.version = DRIVER_OPS_VERSION; ops.bind = fancy_display_bind; return ops; }(); // ZIRCON_DRIVER marks the compiled driver as compatible with the zircon // 0.1 driver ABI. ZIRCON_DRIVER(fancy_display, fancy_display_ops, "zircon", "0.1");
Display drivers are required to implement the DisplayControllerImpl
protocol, which exposes hardware layers and implements vsync notifications. A display-coordinator driver multiplexes between all the device-specific drivers on the system and the display driver stack clients, which are the system compositor and Virtcon.
The driver decides when and how a configuration passed to ApplyConfiguration
takes effect. In order to avoid tearing{: .external}, drivers should apply new settings just after vsync.
Most devices generate interrupts for vsync events. The easiest way to ensure timely vsync notifications is to spawn a separate thread just for servicing that interrupt. Even if no images are displayed, your driver must call OnDisplayVsync
for every vsync.
If the display is active on boot, e.g. a panel is turned on and an image is displayed, then you can get basic functionality in your driver quickly. Read bootloader logs and/or source to find:
Then:
image->handle
.ApplyConfig
is called, re-program the registers.If you do not yet know how to observe vsyncs, you can fake it with a thread that calls OnDisplayVsync
at 60Hz.
There is no one right way to bring up a display controller that lacks even a basic bootloader driver. In most cases, your roadmap will be: