如何编写显示驱动程序

因此,你决定建立一个新的白板。在深入学习编码之前,请回答以下问题,以确保您已经获得所需的一切知识:

  • 如何了解设备及其寄存器的工作原理?

    • 这通常称为“运算理论”。制造商通常会提供包含寄存器定义的数据表,但这些参考资料可能无法解释设备的实际使用方式。
  • 是否有类似开发板的现有驱动程序?

    • 在可行的情况下,请通过重构代码和修改驱动程序的绑定规则,为类似开发板重复使用代码。
  • 设备是否有固定的显示屏?

    • 某些显示控制器和面板(输出屏幕)是紧密耦合的。如果是新开发板,则需要添加对 GPIO、I2C 和其他控件的支持作为显示驱动程序的一部分。

前提条件

本指南假定您熟悉一个或多个操作系统的驱动程序开发。此外,本文档还假定您熟悉 Fuchsia DDK-TL

编程语言

新驱动程序必须使用 C++ 编写。Rust 支持已计划进行,但仍处于高度实验阶段。

如果已有适当许可的驱动程序是以 C 语言编写的,您可以将该驱动程序移植到 Fuchsia,而无需使用 C++ 实现新版本。请先联系 graphics-dev@fuchsia.dev,然后再做出此决定。

使用入门

对于没有 ACPI 或 PCI 总线的平台,第一步是修改板驱动程序。本指南假定板驱动程序已准备就绪,并且显示驱动程序的代号为 fancy。新驱动程序的所有代码都将位于 src/graphics/display/drivers/fancy-display/ 中。

首先,请创建:

向 build 添加驱动程序

  1. 在名为 BUILD.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",
  ]
}
  1. //src/graphics/display/drivers/fancy-display 添加为要用作测试产品的开发板的依赖项。例如,如果您的设备属于某个 Khadas VIM3 开发板,请将驱动程序添加到 _common_bootfs_deps 列表中,以修改 //boards/vim3.gni

选择要驾车前往的设备

现在您已经有了构建方案,您可以继续创建绑定规则,驱动程序管理器利用这些规则来决定是否可以将驱动程序与设备一起使用。

  1. src/graphics/display/drivers/fancy-display 中,创建 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,
}

对于 PC 设备,intel-i915 绑定规则就是一个很好的例子。对于固定硬件 SoC,请参阅 Amlogic 显示规则

最小驱动程序

最后,添加一个准系统驱动程序,使其在每次成功绑定到设备时仅构造一个新对象。之后,您可以使用数据表让设备执行实际操作

src/graphics/display/drivers/fancy-display 中,创建 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");

显示驱动程序需要实现 DisplayControllerImpl 协议,以公开硬件层并实现 Vsync 通知。显示协调器驱动程序会在系统上的所有设备专属驱动程序和显示驱动程序堆栈客户端(系统合成器和 Virtcon)之间多路复用。

实现提示

驱动程序决定传递给 ApplyConfiguration 的配置何时生效以及如何生效。为避免撕裂,驱动程序应在 vsync 后立即应用新设置。

大多数设备都会针对 vsync 事件生成中断。要确保及时实现 vsync 通知,最简单的方法是生成单独的线程来处理该中断。即使未显示任何图像,您的驱动程序也必须针对每个 vsync 调用 OnDisplayVsync

支持引导加载程序的控制器

如果显示屏在启动时处于活动状态(例如,某个面板打开且显示了一个图像),则您可以在驱动程序中快速获得基本功能。读取引导加载程序日志和/或源代码以查找以下内容:

  • 帧缓冲区的物理地址
  • 用于对该地址进行编程的寄存器
  • 图片的像素尺寸,例如 800x600
  • 图片的像素格式,例如 RGB888、NV12 或 BGRA8888

然后,执行以下操作:

  1. 修改驱动程序以报告具有格式限制的显示内容。
  2. image->handle 中记录任何导入的图片的实际地址。
  3. 调用 ApplyConfig 时,对寄存器重新编程。

如果您还不知道如何观察 vsync,可以使用以 60Hz 的频率调用 OnDisplayVsync 的线程模拟它。

启动“深色”模式的控制器

没有什么办法可以调出缺少基本引导加载程序驱动程序的显示控制器。在大多数情况下,您的路线图将是:

  1. 为设备接通电源。
  2. 初始化时钟。
  3. 发现连接的显示屏。
  4. 针对兼容模式对 PHY 进行编程。
  5. 在 vsync 上对布局(帧缓冲区添加器等)进行编程,以避免撕裂
  6. Sysmem 集成。