# 自定义内核

SoC 和板卡设备树支持托管在 `arch/arm64/boot/dts/qcom` 的内核源代码中。

## 开发内核

要在内核开发过程中定制内核并生成补丁，可使用以下内核配方：

# setup the workspace and get the sources following build guide
    # setup the environment
    
      MACHINE=<SoC>-<board>-<variant> DISTRO=qcom-wayland source setup-environment
    
    # create your own layer to host changes
      bitbake-layers create-layer ~/meta-mylayer
      bitbake-layers add-layer ~/meta-mylayer
    
    # use devtool to setup kernel source for development
      devtool modify linux-qcom-base
    
    # do the development , commit changes, build and test
      devtool build linux-qcom-base
      devtool build-image qcom-console-image
    
    # built images are produced in standard location
      ls build-qcom-wayland/tmp-glibc/deploy/images/<SoC>-<board>-<variant>/
    
    # generate the patches and update layer
      devtool finish linux-qcom-base ~/meta-mylayer
    Copy to clipboard

有关定制 Qualcomm Linux 内核配方的更多信息，请参见[内核配方](https://docs.qualcomm.com/doc/80-70018-3SC/topic/yocto-kernel-support.html#kernel-recipe)。

Note

对于定制 BSP 变体，请使用 `linux-qcom-custom`。

有关 Yocto 配置、主机补丁及其应用方法的更多信息，请参阅 [Yocto Project Linux Kernel Development](https://docs.yoctoproject.org/1.5/kernel-dev/kernel-dev.html#applying-patches)。

## 内核独立开发

Linux 内核也可以在没有 Yocto 编译系统支持的情况下编译。

### 前提条件

配置以下依赖项以设置内核编译过程：

> 
> 
> - aarch64 工具链
> - systemd-boot EFI 存根，将其作为 UKI 镜像的一部分包含在内
> - systemd ukify 工具，用于将内核镜像、initramfs 和 DTB 打包到 UKI 镜像中
> - 包含 UKI 镜像的 initramfs
> - 携带更新后 UKI 的 ESP 镜像，必须刷写该镜像才能启动设备。

从 [ARM 开发者网站](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads)下载 aarch64 工具链。

安装所有依赖项后，即可进行增量内核编译，而无需重新编译整个 Yocto。

要编译 Qualcomm^®^ Linux Yocto 基础，请参阅 [Qualcomm Linux 内核入门指南](https://docs.qualcomm.com/doc/80-70018-3SC/topic/getting_started_chapter2.html#qualcomm-linux)。为 Qualcomm Linux 编译 Yocto 基础后，可在以下目录找到 ukify、EFI boot stub、initramfs 和 efi.bin：

> 
> 
> - ukify 位于 `<build-path>/tmp-glibc/sysroots-components/x86_64/systemd-boot-native/usr/bin/ukify`
> - EFI boot stub 位于 `<build-path>/tmp-glibc/deploy/images/<SoC>/linuxaa64.efi.stub`
> - initramfs 位于 `<build-path>/tmp-glibc/deploy/images/<SoC>/initramfs-qcom-image-<SoC>.cpio.gz`
> - `efi.bin` 是在编译过程中生成的，位于 `<build-path>/tmp-glibc/deploy/images/<SoC>/qcom-<BUILD_TYPE>/efi.bin`

您可以使用定制 initramfs。访问托管在 [Linaro Snapshots 站](https://snapshots.linaro.org/member-builds/qcomlt/testimages/arm64/latest/)上的 arm64 的 initramfs，可使用以下命令：

wget https://snapshots.linaro.org/member-builds/qcomlt/testimages/arm64/latest/initramfs-test-image-qemuarm64-*.rootfs.cpio.gz -O <download-path>/linaro-initramfs.cpio.gz
    Copy to clipboard

**获取 Linux 内核源代码**

从 [CodeLinaro](https://git.codelinaro.org/clo/la/kernel/qcom) 托管的 git 代码仓库克隆 Linux 内核源代码。

要克隆代码仓库并获取源代码，请运行以下命令：

git clone https://git.codelinaro.org/clo/la/kernel/qcom kernel-src
      cd kernel-src/
      git checkout <released sha>
    # e.g.
      git checkout origin/kernel.qclinux.1.0.r2-rel
    Copy to clipboard

**配置和编译 Linux 内核**

安装工具链并设置以下路径：

export PATH=<PATH_TO_ARM_TOOLCHAIN>/arm-gnu-toolchain-13.3.rel1-x86_64-aarch64-none-linux-gnu/bin:$PATH
    Copy to clipboard

验证以下命令是否能正常运行：

aarch64-linux-gnu-gcc --version
    Copy to clipboard

完成所有更改后，若要编译并配置 Linux 内核，请运行以下命令：

export CROSS_COMPILE=aarch64-linux-gnu-
    export ARCH=arm64
    make qcom_defconfig
    make -j8 dir-pkg INSTALL_MOD_STRIP=1
    Copy to clipboard

有关构建内核和镜像的更多信息，请参见[编译内核镜像](https://docs.qualcomm.com/doc/80-70018-3SC/topic/yocto-kernel-support.html#id6)。

要编译内核并生成镜像，请使用 `make -j8 dir-pkg INSTALL_MOD_STRIP=1` 命令。镜像会被添加到 `kernel-src/tar-install` 目录中。编译后的文件位于以下目录：

> 
> 
> - 内核镜像位于 `kernel-src/arch/arm64/boot/Image` 目录
> - DTB 部署在 `kernel-src/tar-install/boot/dtbs/` 目录
> - 内核模块位于 `kernel-src/tar-install/lib/modules/` 目录

**使用编译的内核模块更新 initramfs**

将 initramfs 文件复制到 `kernel-src` 目录，并将编译后的模块叠加在 initramfs 上。

Note

- 您可以使用自己定制的或公开可用的 initramfs 编译内核并启动到 shell。
- 如果需要全功能的 rootfs，必须依赖 Yocto 编译版本。

要使用 ramdisk，请运行以下命令：

cp <download-path>/linaro-initramfs.cpio.gz ./initramfs-qcom-image.cpio.gz && (cd tar-install ; find lib/modules | cpio -o -H newc -R +0:+0 | gzip -9 >> ../initramfs-qcom-image.cpio.gz)
    Copy to clipboard

**打包 UKI 镜像**

在准备好包含所有内核模块的 Linux 内核镜像、DTB 和 initramfs 之后，即可打包 UKI 镜像。

Note

> 
> 
> ukify 工具支持 Python (3.10) 或更高版本。要运行 ukify 工具，请安装 `pip install pefile` 来为其执行提供支持。

要编译 ukify 工具，请运行以下命令：

cd build-qcom-wayland/
    Copy to clipboard

STUB = $PWD/tmp-glibc/deploy/images/<SoC>/linuxaa64.efi.stub
    Copy to clipboard

KERNEL_IMAGE = <STANDALONE_kernel-src>/arch/arm64/boot/Image
    Copy to clipboard

INITRD = <STANDALONE_kernel-src>/initramfs-qcom-image.cpio.gz
    Copy to clipboard

DTB = <STANDALONE_kernel-src>/arch/arm64/boot/dts/qcom/qcs6490-rb3gen2.dtb
    Copy to clipboard

KERNEL_VENDOR_CMDLINE = "root=/dev/disk/by-partlabel/system rw rootwait console=ttyMSM0,115200n8 earlycon qcom_geni_serial.con_enabled=1 kernel.sched_pelt_multiplier=4 mem_sleep_default=s2idle"
    Copy to clipboard

UKINAME = "uki.efi"
    Copy to clipboard

UKI_OUT=  <OUT_DIR>/$UKINAME
    Copy to clipboard

rm -f "${UKI_OUT}"
    Copy to clipboard

./tmp-glibc/sysroots-components/x86_64/systemd-boot-native/usr/bin/ukify build --efi-arch=aa64 --stub="${STUB}" --linux="${KERNEL_IMAGE}" --initrd="${INITRD}" --devicetree="${DTB}"  --cmdline="${KERNEL_VENDOR_CMDLINE}" --output="${UKI_OUT}"
    Copy to clipboard

ukify 编译命令生成包含编译的内核和其他镜像的 `uki.efi` 镜像。忽略 ukify 发出的以下警告：

Kernel version not specified, starting autodetection
    Real-Mode Kernel Header magic not found
    + readelf --notes $KERNEL_IMAGE
    readelf: Error: Not an ELF file - it has the wrong magic bytes at the start
    Found uname version: 6.6.38-perf-gb09a1d09b89b-dirty
    Wrote unsigned $UKI_OUT
    Copy to clipboard

**打包 ESP 镜像**

当 UKI 镜像准备就绪后，即更新 ESP 镜像。然后，将更新后的镜像刷写到板卡上并启动。按照下面的说明，通过独立内核的 `uki.efi` 更新 Yocto 编译版本 的 `efi.bin`。

Note

使用 `efi.bin` 中的默认镜像名称覆盖 EFI 中的 UKI 镜像。

要覆盖 UKI 镜像，请运行以下命令：

sudo mount -t vfat efi.bin /mnt/
    cp uki.efi /mnt/EFI/Linux/linux-<SoC>.efi
    umount /mnt
    Copy to clipboard

要刷写 EFI 镜像并重新启动，请参阅 [调通设备](https://docs.qualcomm.com/doc/80-70018-3SC/topic/getting_started_chapter2.html#flash-images-and-boot)。

Note

要编译额外的树外内核模块，需依赖完整的 Yocto 编译机制。

## 配置内核

Yocto 编译系统用于修改内核配置，同时调用 `menuconfig`。

若要修改内核配置，可运行以下命令：

MACHINE=<SoC>-<board>-<variant> DISTRO=qcom-wayland source setup-environment
     bitbake linux-qcom-base -c menuconfig
    
    # Above would update .config in kernel build directory build-qcom-wayland/tmp-glibc/work/<SoC>-<board>-<variant>/linux-qcom-base/6.6-r0/build/
    # one can create a config fragment for modifications made by issuing following
    
      bitbake linux-qcom-base -c diffconfig
    
    # Above would create fragment.cfg in build directory build-qcom-wayland/tmp-glibc/work/<SoC>-<board>-<variant>/linux-qcom-base/6.6-r0/
    Copy to clipboard

或者，使用 Devtool 工具修改内核配置：

devtool modify linux-qcom-base
     devtool menuconfig linux-qcom-base
     devtool finish linux-qcom-base ~/meta-mylayer
    
    # this would create a config fragment as a patch and update in your meta layer
    Copy to clipboard

Note

对于定制 BSP 变体，请使用 `linux-qcom-custom`。

有关内核配置的更多 Yocto 相关信息，请参阅 [Configuring the Kernel](https://docs.yoctoproject.org/4.3.1/kernel-dev/common.html#configuring-the-kernel)。

## 创建调试编译版本

要创建调试编译版本，请在 shell 中将 `DEBUG_BUILD=1` 作为参数传递：

# setup the build environment
      export SHELL=/bin/bash
    
      MACHINE=<SoC>-<board>-<variant> DISTRO=qcom-wayland QCOM_SELECTED_BSP=base source setup-environment
    
    # build qcom linux console image
      DEBUG_BUILD=1 bitbake qcom-console-image
    Copy to clipboard

Note

对于定制 BSP 变体，请使用 `QCOM_SELECTED_BSP=custom`。

## 更新内核命令行

要更新内核命令行，请在相应的 SoC 特定机器包含文件中修改 Yocto 配置变量 `KERNEL_CMDLINE_EXTRA`。例如，`meta-qcom-hwe/conf/machine/include/qcom-<SoC>.inc`。

要更新内核命令行，请修改以下变量：

KERNEL_CMDLINE_EXTRA = "root=/dev/disk/by-partlabel/system rw rootwait console=ttyMSM0,115200n8 pcie_pme=nomsi earlycon"
    Copy to clipboard

## 平台设备树和内核配置

按照以下步骤更新内核中支持的 DTB，并在启动时选择 DTB：

**内核中的 DTB 编译支持**

要将新平台的设备树集成到内核编译中，可更新 `Makefile`。

以下示例展示了为 QCS6490 SoC 定制 DTB 的方法。复制以下方法以添加新的 DTB。

diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
    index 183aeba47193..a7815c774f7c 100644
    --- a/arch/arm64/boot/dts/qcom/Makefile
    +++ b/arch/arm64/boot/dts/qcom/Makefile
    dtb-$(CONFIG_ARCH_QCOM)        += qcs6490-addons-rb3gen2.dtb
    +dtb-$(CONFIG_ARCH_QCOM)        += qcs6490-my-board.dtb
    dtb-$(CONFIG_ARCH_QCOM)        += qcs6490-rb3gen2.dtb
    dtb-$(CONFIG_ARCH_QCOM)        += qcs404-evb-1000.dtb
    Copy to clipboard

**在机器配置中包含 DTB**

Yocto 机器配置也进行了更新，包括相应的设备树 blob。例如，要添加支持 QCS6490 机器的设备树，请使用以下文件：

`meta-qcom-hwe/conf/machine/qcs6490-rb3gen2-core-kit.conf`：

OUT_OF_KERNEL_DTSO - qcs6490-rb3gen2-core-kit.conf
    # List of dtbs for corresponding supported qcs6490 platforms
    KERNEL_DEVICETREE = " \
                         qcom/qcs6490-my-board.dtb   \
                         qcom/qcs6490-addons-rb3gen2.dtb \
                         qcom/qcs6490-my-board.dtb   \
                         "

    # Additional list of DTBOs to be overylaid on top of base kernel devicetree
    # See how existing boards are managing it in the following example:
    # Format - KERNEL_TECH_DTBOS[<base-dtb-name>] = "<dtbo1 <dtbo2> ..."
    
    KERNEL_TECH_DTBOS[qcs6490-addons-rb3gen2] = " \
    qcm6490-graphics.dtbo qcm6490-wlan-rb3.dtbo \
    qcm6490-display-rb3.dtbo qcm6490-bt.dtbo \
    qcm6490-video.dtbo qcm6490-wlan-upstream.dtbo \
    Copy to clipboard

Note

请参见 `meta-qcom-hwe/conf/machine/*.conf` 目录中有关不同 SoC 的机器配置文件。定制 BSP 版本的 DTB 文件名包含 `addons`。

**启动时选择 DTB**

定制 DTB 将作为 UKI 镜像的一部分打包，该镜像可在 EFI 中更新，从而通过所选 DTB 启动。

使用 ukify 工具生成 UKI 镜像。ukify 工具是 `tmp-glibc/sysroots-components/x86_64/systemd-boot-native/usr/bin/ukify` 编译目录中 Yocto 编译的一部分。

要生成 UKI 镜像，请运行以下命令：

# Note - ukify tool need python 3.10 version or above
    
       ukify build --efi-arch=aa64  \
                --stub=<build-path>/tmp-glibc/deploy/images/<SoC>/linuxaa64.efi.stub \
                --linux=<build-path>/tmp-glibc/deploy/images/<SoC>/Image \
                --initrd=<build-path>/tmp-glibc/deploy/images/<SoC>/initramfs-qcom-image-<SoC>.cpio.gz \
                --cmdline="console=ttyMSM0,115200n8 earlycon qcom_geni_serial.con_enabled=1 kernel.sched_pelt_multiplier=4 mem_sleep_default=s2idle" \
                --devicetree=<build-path>/tmp-glibc/deploy/images/<SoC>/<SoC>-my-board.dtb \
                --output=./uki.efi
    Copy to clipboard

ukify 编译命令生成带有自定义板卡 DTB `uki.efi` 的镜像。

要更新 ESP 分区中的 `uki.efi` 镜像，请执行以下操作：

# Following may need sudo privilege
    
    # Take the yocto build generated efi.bin and mount it locally
      mount <build-path>/tmp-glibc/deploy/images/<SoC>/efi.bin  /mnt --options rw
    
    # Overwrite the uki.efi with one packaged above
      cp uki.efi /mnt/EFI/Linux/uki.efi
      umount /mnt
    
    # now efi.bin carries packaged uki.efi which can be flashed to the target and booted
    # UEFI shall now pick the <SoC>-my-board.dtb that is part of uki.efi image
    
    # reboot into fastboot and flash efi.bin
      fastboot flash efi <build-path>/tmp-glibc/deploy/images/<SoC>/efi.bin
    Copy to clipboard

有关设备树规范的更多信息，请参见 [The Devicetree Specification](https://www.devicetree.org/specifications/)。

有关设备树的 Linux 内核文档，请参见 [Linux and the Devicetree](https://docs.kernel.org/devicetree/usage-model.html)。

## 更新 ESP 镜像

使用以下步骤编译 systemd-boot 启动管理器和内核镜像，将其打包为 UKI type-2 镜像文件：

针对硬件 SoC 的 Yocto 编译版本会生成所有需要的镜像，并将启动镜像打包为 `efi.bin`，以便刷写到 EFI 分区中。`efi.bin` 文件由 systemd-boot 启动管理器和内核镜像组成，它们被封装为 UKI type-2 镜像格式。

更新内核源、配置或 DTS 后重新编译 EFI 镜像，并刷写生成的 `efi.bin` 到 EFI 分区。

MACHINE=<SoC>-<board>-<variant> DISTRO=qcom-wayland source setup-environment
    
    # build qcom linux console image
      DEBUG_BUILD=1 bitbake qcom-console-image
    
    # build images are produced in following directory
      ls build-qcom-wayland/tmp-glibc/deploy/images/<SoC>-<qcom>-<variant>/efi.bin
    efi.bin
    
    # reboot into fastboot
      fastboot flash efi efi.bin
      fastboot flash dtb_a dtb.bin
    Copy to clipboard

有关 UKI type-2 镜像格式的更多信息，请参见 [Type #2 EFI Unified Kernel Images](https://uapi-group.org/specifications/specs/boot_loader_specification/#type-2-efi-unified-kernel-images)。

有关 ESP 的更多信息，请参阅[启动](https://docs.qualcomm.com/doc/80-70018-3SC/topic/features.html#boot)和[调通设备](https://docs.qualcomm.com/doc/80-70018-3SC/topic/getting_started_chapter2.html#flash-images-and-boot)。

## 自定义“initramfs”包

要更新 initramfs 包，请修改 `meta-qcom-hwe/recipes-kernel/images/initramfs-qcom-image.bbappend` 文件中的 `PACKAGE_INSTALL` 列表：

less meta-qcom-hwe/recipes-kernel/images/initramfs-qcom-image.bbappend
    
    # Add additional packages needed as part of initrd
    PACKAGE_INSTALL += " \
        e2fsprogs \
        e2fsprogs-e2fsck \
        e2fsprogs-mke2fs \
        e2fsprogs-resize2fs \
        e2fsprogs-tune2fs \
        ${VIRTUAL-RUNTIME_dev_manager} \
        os-release-initrd \
        "
    Copy to clipboard

## 添加内核模块

按照以下步骤，通过 Yocto 编译系统编译树外的内核模块：

1. 为树外内核驱动程序创建 `Makefile`。

> 
> 
> 以下是树外内核驱动程序的 `Makefile` 示例：

all: modules
    obj-m := hello.o
    
    SRC := $(shell pwd)
    
    modules:
      $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules $(KBUILD_OPTIONS)
    
    modules_install:
      $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install
    Copy to clipboard

2. 将模块集成到 Yocto 编译系统中。请参阅以下示例，了解如何使用 Yocto 模块类集成内核模块。

DESCRIPTION = "${SUMMARY}"
    LICENSE = "GPL-2.0-only"
    LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/${LICENSE};md5=801f80980d171dd6425610833a22dbe6"
    
    inherit module
    
    SRC_URI += "file://Makefile \
                file://hello.c  \
                file://COPYING  \
                "
    S = "${WORKDIR}"
    
    EXTRA_OEMAKE += "MACHINE='${MACHINE}'"
    MAKE_TARGETS = "modules"
    MODULES_INSTALL_TARGET = "modules_install"
    
    # Kernel module to be autoloaded
        KERNEL_MODULE_AUTOLOAD += "hello"
    
    # The inherit of module.bbclass will automatically name module packages with
    # "kernel-module-" prefix as required by the oe-core build environment.
    
    RPROVIDES_${PN} += "kernel-module-hello"
    Copy to clipboard

有关树外模块的更多信息，请参见 [Working with Out-of-Tree Modules](https://docs.yoctoproject.org/kernel-dev/common.html#working-with-out-of-tree-modules)。

## 禁用控制台日志

通过以下其中一种方法禁用内核控制台日志：

- 使用 `quiet` 更新内核命令行。

> 
> 
> 有关内核参数的更多信息，请参见 [The kernel’s command-line parameters](https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html)。
- 禁用内核配置中的 `CONFIG_SERIAL_EARLYCON` 和 `CONFIG_SERIAL_MSM_CONSOLE`。
- 将 `console=null` 参数添加到内核命令行参数中。

## 配置 pinctrl 驱动程序

Qualcomm Linux 内核中的 Pinctrl 子系统可管理和配置用于通用输入/输出 (GPIO)、内部集成电路 (I2C)、串行外设接口 (SPI) 以及其他硬件接口的引脚。

Pinctrl 配置（例如**引脚复用**和**引脚分组**）在设备特定的 Pinctrl 驱动程序中进行管理，驱动程序列出了所有可用引脚和功能。

例如，对于 QCS6490，相应的驱动程序在 `kernel-src/drivers/pinctrl/qcom/pinctrl-sc7280.c` 文件中。

Note

有关其他 Qualcomm SoC pinctrl 驱动程序的更多信息，请参见 [Pinctrl 驱动程序](https://github.com/torvalds/linux/tree/master/drivers/pinctrl/qcom)。

以下是 pinctrl 数据对象：

表：Pinctrl 数据对象

| 变量 | 说明 |
| --- | --- |
| static const struct pinctrl_pin_desc sc7280_pins<br>    Copy to clipboard | 枚举所有引脚及其名称 |
| static const struct msm_pingroup sc7280_groups<br>    Copy to clipboard | 定义 GPIO 引脚组的可用复用功能 |
| enum sc7280_functions<br>    Copy to clipboard | 以枚举值形式列出所有可用功能 |

有关 QCS6490 各个 SoC pinctrl 绑定文档所支持功能的更多信息，请参见 [pinctrl 绑定文档](https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/qcom%2Csc7280-pinctrl.yaml)。

**功能选择**

对于 `sc7280_functions` 数据对象，一个或多个 GPIO 引脚用作功能，必须注册到设备树并传递给正确的设备节点。

在系统启动期间，内核 pinctrl 基础架构会注册这些功能。

以下示例展示了内核配置基础架构：

tlmm: pinctrl@f100000 {
        compatible = "qcom,sc7280-pinctrl";
        :
        :
        :
        :
        qup_spi0_data_clk: qup-spi0-data-clk-state {
            pins = "gpio0", "gpio1", "gpio2";
            function = "qup00";
        };
    
        qup_spi0_cs: qup-spi0-cs-state {
            pins = "gpio3";
            function = "qup00";
        };
    
        qup_spi1_data_clk: qup-spi1-data-clk-state {
            pins = "gpio4", "gpio5", "gpio6";
            function = "qup01";
        };
    
        qup_spi1_cs: qup-spi1-cs-state {
            pins = "gpio7";
            function = "qup01";
        };
        :
        :
        :
        :
    
    };

        spi0: spi@980000 {
            compatible = "qcom,geni-spi";
            reg = <0 0x00980000 0 0x4000>;
            clocks = <&gcc GCC_QUPV3_WRAP0_S0_CLK>;
            clock-names = "se";
            pinctrl-names = "default";
            pinctrl-0 = <&qup_spi0_data_clk>, <&qup_spi0_cs>;
            interrupts = <GIC_SPI 601 IRQ_TYPE_LEVEL_HIGH>;
            #address-cells = <1>;
            #size-cells = <0>;
            power-domains = <&rpmhpd SC7280_CX>;
            operating-points-v2 = <&qup_opp_table>;
            interconnects = <&clk_virt MASTER_QUP_CORE_0 0 &clk_virt SLAVE_QUP_CORE_0 0>,
                    <&gem_noc MASTER_APPSS_PROC 0 &cnoc2 SLAVE_QUP_0 0>;
            interconnect-names = "qup-core", "qup-config";
            dmas = <&gpi_dma0 0 0 QCOM_GPI_SPI>,
                <&gpi_dma0 1 0 QCOM_GPI_SPI>;
            dma-names = "tx", "rx";
            status = "disabled";
        };
    Copy to clipboard

## 配置 GPIO 的使用

GPIO 引脚配置需要以下两项设置。这些设置会定义 GPIO 引脚状态，并使这些引脚可用于任何输入/输出活动。

- Mux：mux 设置需要选择从 SoC 特定 pinctrl 驱动程序中的可用功能集中映射的功能名称。

    有关 pinctrl 的更多信息，请参见 [Pinctrl 配置](https://docs.qualcomm.com/doc/80-70018-3SC/topic/customize.html#pinctrl-configuration)。
- 配置：配置方面需要设置驱动强度和偏置属性。

以下示例展示了这两项设置如何通过以下步骤定义 GPIO 引脚：

1. 在设备树中定义引脚配置：

bt_en: bt-en-state {
           pins = "gpio85";
           function = "gpio";
           output-low;
           bias-disable;
        };
        Copy to clipboard
2. 在设备树中配置设备节点或知识产权 (IP) 块：

bluetooth: bluetooth {
           compatible = "qcom,wcn6750-bt";
           pinctrl-names = "default";
           pinctrl-0 = <&bt_en>, <&sw_ctrl>;
           enable-gpios = <&tlmm 85 GPIO_ACTIVE_HIGH>;
           swctrl-gpios = <&tlmm 86 GPIO_ACTIVE_HIGH>;
           vddaon-supply = <&vreg_s7b_0p9>;
           vddbtcxmx-supply = <&vreg_s7b_0p9>;
           vddrfacmn-supply = <&vreg_s7b_0p9>;
           vddrfa0p8-supply = <&vreg_s7b_0p9>;
           vddrfa1p7-supply = <&vreg_s1b_1p8>;
           vddrfa1p2-supply = <&vreg_s8b_1p2>;
           vddrfa2p2-supply = <&vreg_s1c_2p2>;
           vddasd-supply = <&vreg_l11c_2p8>;
           max-speed = <3200000>;
        Copy to clipboard
3. 驱动程序代码必须使用通用 API 在 pinctrl 配置中选择和注册其 GPIO 配置。

    以下为可用 API 示例：

> 
> 
> devm_gpiod_get_optional(&serdev->dev, "enable", GPIOD_OUT_LOW);
>         
>         /**
>         * devm_gpiod_get_optional - Resource-managed gpiod_get_optional()
>         * @dev: GPIO consumer
>         * @con_id: function within the GPIO consumer
>         * @flags: optional GPIO initialization flags
>         *
>         * Managed gpiod_get_optional(). GPIO descriptors returned from this function
>         * are automatically disposed on driver detach. See gpiod_get_optional() for
>         * detailed information about behavior and return values. */
>         
>          gpiod_set_value_cansleep(qcadev->bt_en, 0);
>         
>         /**
>         * gpiod_set_value_cansleep() - assign a gpio's value
>         * @desc: gpio whose value will be assigned
>         * @value: value to assign
>         *
>         * Set the logical value of the GPIO, i.e. taking its ACTIVE_LOW status into
>         * account
>         *
>         * This function is to be called from contexts that can sleep.
>         */
>         Copy to clipboard

**GPIO 作为中断请求 (IRQ)**

按照以下步骤操作，将 GPIO 设置为 IRQ：

1. 在 DTS 文件中配置 GPIO 引脚：

    1. 设置 GPIO 引脚的属性和功能。
    2. 使用以下配置将引脚设置为使用 GPIO 55 以实现 `qup_se_l3()` 功能：

qupv3_se3_rx: qupv3-se3-rx-state {
               pins = "gpio55";
               function = "qup03"; // To be taken from available from functions.
               drive-strength = <2>;
               bias-disable;
            };
            Copy to clipboard
2. 为要将 GPIO 配置为 IRQ 的设备节点创建一个与之前配置类似的 DT 条目。

    在以下示例中，将 GPIO 55 配置为 IRQ，其父节点为顶层模式多路复用器 (TLMM)，触发电平设置为高电平。

interrupts-extended = <&tlmm 55 IRQ_TYPE_LEVEL_HIGH>;
        Copy to clipboard
3. 驱动程序必须读取该值，并使用 `request_irq` API 将其作为中断注册到通用中断控制器 (GIC) 中，该 API 还指定了中断服务寄存器 (ISR) 和 IRQ 标志。

irq_no = platform_get_irq(pdev, 1);
        Copy to clipboard

### 配置 GPIOS 以生成时钟或脉冲宽度调制

将任意 GPIO 通过 `GP_CLK` 配置为替代功能，以获取时钟或脉冲宽度调制 (PWM)。

Note

以下步骤适用于 QCS6490 SoC。

有关如何使用 `GP_CLK` 函数查找 GPIO 的更多信息，请参阅 [Pin descriptions](https://docs.qualcomm.com/bundle/publicresource/topics/80-23889-1/pin-definitions.html#sub$pin-descriptions:~:text=and%20available%20configurations.-,Table%20%3A%20Pin%20descriptions%20%E2%80%93%20general%2Dpurpose%20input/output%20ports,-Pad%20number)。

1. 在 `kernel/arch/arm64/boot/dts/qcom/sc7280.dtsi` 文件中添加 GPIO 配置节点。

> 
> 
> +gpio_pwm_default: gpio_pwm_default {
>     +       mux {
>     +               pins = "gpio42";
>     +               function = "gcc_gp1";    // search "gcc_gp" in "kernel/drivers/pinctrl/qcom/pinctrl-sc7280.c", From this we can find out which GPIO's has GP_CLK functionality
>     +       };
>     +
>     +       config {
>     +               pins = "gpio42";
>     +               bias-disable; /* No PULL */
>     +               drive-strength = <8>; /* 2 MA */
>     +       };
>     +};
>     Copy to clipboard

2. 在 `kernel/arch/arm64/boot/dts/qcom/sc7280.dtsi` 文件中定义设备树节点。

+beeper: beeper {
        +       compatible = "gpio-beeper";
        +       pinctrl-names = "default";
        +       pinctrl-0 = <&gpio_pwm_default>;
        +       clocks = <&clock_gcc GCC_GP1_CLK>; //clock_gcc is gcc clk device node, GCC_GP1_CLK index which defined in "kernel/include/dt-bindings/clock/qcom,gcc-sc7280.h"
        +       clock-names = "gpio-pwm-clk";
        +};
        Copy to clipboard
3. 在设备驱动程序中添加以下代码：

+#include <linux/clk.h>
        +#include <linux/io.h>
        ...
        + struct clk *pclk;
        + struct rcg_clk *gp1_rcg_clk;
        + int ret;
        +
        + pclk = devm_clk_get(&pdev->dev, "gpio-pwm-clk");
        + ret = clk_set_rate(pclk, 50000000); // please check the freq table in kernel/drivers/clk/qcom/gcc-sc7280.c, the freq can be found in the freq table of GCC_GP1_CLK.
        + if (ret)
        +     printk("clk set rate fail, ret = %d\n", ret);
        +
        + ret = clk_prepare_enable(pclk);  // By default this will enable clock as PWM with 50% duty cycle.
        + if (ret)
        +     printk("%s: clk_prepare error!!!\n", __func__);
        + else
        +     printk("%s: clk_prepare success!\n", __func__);
        +
        Copy to clipboard
4. 如果不使用时钟或 PWM，请调用 `clk_disable_unprepare()` 禁用时钟以节省功耗。

Note

确保在调用 `clk_disable_unprepare()` 之前先调用 `clk_prepare_enable()`。
5. 要生成所需的占空比，请在调用 `clk_prepare_enable` API 之后调用 `clk_set_duty_cycle()` API。

## 将 ZRAM 配置为交换设备

ZRAM 是一种压缩交换机制，可在 RAM 中创建虚拟块设备。ZRAM 已作为模块在内核 defconfig 中启用。

在 `recipes-extended/zram/zram/zram-swap-init-update` 文件的 Yocto 编译中配置 ZRAM。

如需启用或配置 ZRAM，可执行以下程序：

# check if zram module is loaded
      lsmod | grep zram
    
    # else load it
      modprobe zram
    
    # Configure /dev/zram0 size according to your RAM size
      echo 128M > /sys/block/zram0/disksize
    
    # activate swap
      mkswap /dev/zram0
      swapon /dev/zram0
    Copy to clipboard

要将 ZRAM 配置为交换设备，请参阅 [zram: Compressed RAM-based block devices](https://www.kernel.org/doc/html/v6.6/admin-guide/blockdev/zram.html)。

## 扩展内存映射

若要扩展内存映射，可调整 DTSI 文件中内存区域的地址和大小。

使用以下信息扩展划分区域：

使用以下语法在 **reserved-memory** 节点中的 `arch/arm64/boot/dts/qcom/<SoC>-<board>-<variant>.dts` 文件中添加划分区域

my_carveout_mem: my_carveout_mem@address {
          reg= <0x0 0xbase_address 0x0 0xsize>;
          no-map;
    }
    Copy to clipboard

例如：

my_carveout_mem: my_carveout_mem@d0800000 {
          reg= <0x0 0xd0800000 0x0 0x100000>;
          no-map;
    }
    Copy to clipboard

表：划分区域的语法

| 变量 | 说明 |
| --- | --- |
| my_carveout_mem@d0800000<br>    Copy to clipboard | 指示设备节点的名称。按照惯例，应将内存区域的基址附加到名称中。 |
| my_carveout_mem<br>    Copy to clipboard | 指示分配给此节点的标签，该标签可供设备树中的其他节点使用 phandle 引用此节点时使用。 |
| reg<br>    Copy to clipboard | 指示属性，该属性是一个 64 位值，用于定义内存区域的基址和大小。 |
| no-map<br>    Copy to clipboard | 指示此区域已划分，内核应从其可寻址范围中删除映射。 |

Note

- 任何区域都不应重叠。如果必须增大某个区域的大小，则移动所有其他后续区域，并在设备树中进行配置以避免重叠。
- 内核要求供内核使用的所有内存区域边界都对齐到 1 MB。
- 现有划分区域受受信任固件保护，防止内核访问。减小其大小或将其删除可能会导致外部中止，造成内核崩溃。

**添加 CMA 区域**

要在**保留内存**节点下的 `arch/arm64/boot/dts/qcom/<SoC>-<board>-<variant>.dts` 文件中添加 CMA 区域，请使用以下语法：

my_cma_mem: my_cma {
       compatible = "shared-dma-pool";
       alloc-ranges = <0x0 0x00000000 0x0 0xffffffff>;
       reusable;
       alignment = <0x0 0x400000>;
       size = <0x0 0x1400000>;
    };
    Copy to clipboard

表：添加 CMA 区域的语法

| 参数 | 说明 |
| --- | --- |
| my_cma_mem<br>    Copy to clipboard | CMA 节点的标签，可用作 phandle。标签 `my_cma` 是 CMA 区域的名称。 |
| shared-dma-pool<br>    Copy to clipboard | 指示此区域是 CMA 区域。 |
| alloc-ranges<br>    Copy to clipboard | 指示此区域是否应处于某个特定的内存限制范围内，因为某些设备无法访问超过 32 位地址限制的内存区域。 |
| reusable<br>    Copy to clipboard | 指示内核可以在该内存区域空闲时使用其中的内存。 |
| alignment<br>    Copy to clipboard | 指示此区域中的任何对齐要求。 |
| size<br>    Copy to clipboard | 指示区域的大小： |
| reg<br>    Copy to clipboard | 这是一个可选属性，指示用于内存分配的固定区域，若不设置此属性，则以随机地址动态分配内存。 |

## 添加自定义 CMA 堆

要使用 DMA-BUF 堆创建自定义 CMA 区域，请使用 Qualcomm Linux 内核中现有的 DMA-BUF 框架。

Qualcomm Linux 内核导出自定义 DMA-BUF 堆的 `cma_heap_add()` API。

/**
    * cma_heap_add - adds a CMA heap to dmabuf heaps
    * @cma:       pointer to the CMA pool to register the heap for
    * @data:      unused
    *
    * Returns 0 on success. Else, returns errno.
    */
    
    int cma_heap_add(struct cma *cma, void *data);
    Copy to clipboard

导出到用户空间的 DMA-BUF 堆，其名称与设备树中 CMA 区域的 phandle 相同。

要添加自定义 CMA 堆，可执行以下操作：

1. 要创建定制 CMA 区域，请参见[连续内存分配器](https://docs.qualcomm.com/doc/80-70018-3SC/topic/features.html#memory-contiguous)。
2. 在要添加自定义堆的驱动程序中：

    1. 解析新添加的 CMA 区域的设备树。
    2. 添加与驱动程序相关的 DMA-BUF 堆。

int create_my_cma_heap(struct device *dev)
            {
               int rc = 0, idx = 0;
            
               rc = of_reserved_mem_device_init_by_idx(dev, dev->of_node, 0);  // Parse the devicetree for the cma region
            
               if (rc) {
                        pr_err("No reserved DMA memory, ret=%d\n", rc);
                        rc = -EINVAL;
                        goto err;
               }
            
               rc = cma_heap_add(dev->cma_area, NULL);  // Add a dmabuf heap associated with the cma region
            
               if (rc) {
                        pr_err("cma_heap_add failed, ret=%d\n", rc);
                        rc = -EINVAL;
                        goto err;
               }
            
            err:
               return rc;
            }
            Copy to clipboard

Note

将 CMA 区域对齐到 4 MB 地址基址并设置大小，以支持页面迁移。迁移发生在 page-block 级别的 2 ^pageblock\_order^ 页。在 Qualcomm Linux 内核中，页面块阶次为 10。

Qualcomm Linux 内核支持标准的 Linux 调度器方案。内核使用调度器根据 CPU 能耗选择适合放置任务的目标 CPU。

### PELT 乘数

在 Linux 内核中，PELT 半衰期乘数会影响系统如何适应不断变化的工作负载，并根据历史利用率数据调整 CPU 频率。该配置可加快 PELT 时钟并缩短半衰期，从而加快系统响应速度。

通过内核命令行参数配置 PELT 乘数：

`kernel.sched_pelt_multiplier=[1, 2, 4] Default value: 1 (half life 32 msec), 2 (half life 16msec), 4(half life 8 msec)]`

### 利用率限制

利用率限制是调度器的一个功能，允许用户空间管理任务的性能要求。

要执行限制操作，请配置以下参数：

- `UCLAMP_MIN`：如果设置为任意值 `> 0`，则任务需求将始终大于或等于该值。如果实际任务需求大于此值，则使用实际需求信号，但如果实际任务需求小于此值，则将 `UCLAMP_MIN` 报告为任务需求。
- `UCLAMP_MAX`：如果设置为任意值 `> 0`，则任务需求将始终小于或等于该值。如果实际任务需求小于该值，则使用实际需求信号；但如果实际任务需求大于该值，则将 `UCLAMP_MAX` 作为任务需求报告。

Note

`UCLAMP_MAX > UCLAMP_MIN`

使用 UCLAMP 影响调度器的任务分配决策。

在异构系统中，调度器使用任务需求或利用率信号（PELT 信号）将任务分类为小任务或大任务。根据任务分类的输入，调度器会选择较小（计算能力较低）或较大（计算能力较高）的 CPU 核心进行任务放置，这可能会对功耗产生影响。

例如，将非重要（琐碎/后台/综合管理）任务限制为较低的值 (`lower UCLAMP_MAX`)，从而促使调度器将任务分配到小群集。同样，将重要/前台/活动任务限制为较高的值 (`higher UCLAMP_MIN`)，从而促使调度器将任务分配到大群集。

UCLAMP 还允许通过调度器控制频率指导。

要满足任务的快速频率上升需求，可将任务限制为较高的需求 (`UCLAMP_MIN`) 值。更高的限制有助于提升群集的频率，以满足任务的性能要求。

有关接口和配置的更多信息，请参见 [Utilization Clamping](https://www.kernel.org/doc/html/v6.6/scheduler/sched-util-clamp.html)。

## 更改默认 CPU 频率调节器

CPU 频率调节器可以在 runtime 更改，也可以在编译版本编译过程中静态设置。

1. Kconfig 配置选项：要设置 CPU 频率调节器，请在内核 defconfiguration 文件中启用相应的驱动程序。

    要设置 `PERFROMANCE` 调节器作为默认 CPU 频率调节器，请在 defconfiguration 文件中设置 `CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y`。
2. 内核命令行选项：要覆盖内核配置选项，请在 `meta-qcom-hwe/conf/machine/include/qcom-<SoC>.inc` 文件中的内核命令行中添加 `cpufreq.default_governor=performance` 参数，以设置适当的 CPU 频率调节器。

## 定制缓存和内存 DVFS

**CPU 频率**与 **L3/DDR 频率**之间的映射根据功耗或性能要求调整。

在 DTSI 中，对于每个 CPUx 节点，都有一个 `operating-points-v2 = <&cpux_opp_table>` 条目。`cpux_opp_table` 保存 CPU、L3 和 DDR 频率之间的静态映射。

例如：

cpu0_opp_300mhz: opp-300000000 {
       opp-hz = /bits/ 64 <300000000>;
       opp-peak-kBps = <800000 9600000>;
    };
    Copy to clipboard

当 CPU 0 以 300 MHz 运行时，它对 L3 表决 9600000，这相当于 300,000 Hz（9600000/w）的 L3 频率。如果表决为 DDR 提供 800,000 Hz，则所得 DDR 工作频率为   200,000 Hz (800000 / w)。

在等式中，“w”表示单个周期内可以写入多少个字节：

- 对于 DDR，“w”为 4（每个通道每个周期执行两次事务，每次事务为 2 个字节）。
- 对于 L3，“w”为 32（每个周期一个事务，每个事务 32 个字节）。

Note

这些值是针对 DDR 每条通道设置的，该映射将 CPU 频率与内存控制器 (MC) 通道带宽相关联。调整此映射表会影响功耗和性能特征。

有关工作性能点 (OPP) 框架和语法的更多信息，请参见 [Generic OPP (Operating Performance Points) Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/opp/opp.txt)。

## 配置启动后设置

启动后设置包括完成初始设置、配置内存以及 CPU 频率参数。这些步骤确保在系统启动后配置正确并且各项功能正常运行。

### 启动后框架

后启动脚本文件用于配置 Qualcomm Linux 分发版上的系统参数。

启动后设置通过 *systemd service* 文件进行管理。

有关 systemd 的更多信息，请参见 [systemd(1) - Linux manual page](https://www.man7.org/linux/man-pages/man1/systemd.1.html)。

### 什么是 systemd 服务？

systemd 服务是指根据特定条件启动或停止的后台进程。

写入一个 `systemd service` 文件以允许 systemd 按照指示解析、理解和运行。

有关后台进程的更多信息，请参见 [background process](https://linuxhandbook.com/run-process-background/)。

### systemd 服务文件的基本结构

按以下步骤根据需要修改 systemd 的行为：

以下是 systemd 服务文件的示例：

[Unit]
    SourcePath=/etc/initscripts/log_restrict.sh
    Description=QTI logging service
    
    [Service]
    ExecStart=/etc/initscripts/post_boot.sh
    
    [Install]
    WantedBy=multi-user.target
    Copy to clipboard

### `[Unit]` 部分

在 systemd 中，单元是指系统知道如何操作和管理的任何资源。它包括服务、套接字、设备、挂载点和外部创建的进程组。

每个单元使用名为 `unit` 文件的配置文件定义，其中包含元数据和配置详细信息。Unit 文件中的 `[Unit]` 部分提供有关说明以及与其他单元关系的信息。

`[Unit]` 部分包含以下字段：

表：[Unit] 部分支持的字段

| 字段 | 说明 |
| --- | --- |
| Description<br>    Copy to clipboard | 可读的 systemd 服务标题。 |
| After<br>    Copy to clipboard | 设置对服务的依赖。例如，如果配置的是 WCN3960 Web 服务器，则希望服务器在网络联机后启动。 |
| Before<br>    Copy to clipboard | 在指定服务之前启动当前服务。在此示例中，WCN3960 Web 服务器在启动下一个云的服务之前运行，因为下一个云服务器依赖于 WCN3960 Web 服务器。 |

### `[Service]` 部分

`[Service]` 部分包含服务执行和终止的详细信息。

`[Service]` 部分包含以下字段：

表：[Service] 部分支持的字段

| 字段 | 说明 |
| --- | --- |
| ExecStart<br>    Copy to clipboard | 服务启动时必须运行的命令。例如，启动 Web 服务器服务时必须执行的命令。 |
| ExecReload<br>    Copy to clipboard | 可选字段，用于指定服务重启方式。对于执行磁盘 I/O 的服务，建议终止并重启服务。如果想要使用特定的重启机制，需使用 `ExecReload` 字段。 |
| Type<br>    Copy to clipboard | 指示给定 systemd 服务的进程的启动类型。可用的选项有 `simple`、`exec`、`forking`、`oneshot`、`dbus`、`notify` 和 `idle`。 |

有关详细信息，参见 [Options](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html?ref=linuxhandbook.com#Options)。

### `[Install]` 部分

`[Install]` 部分处理 systemd 服务或单元文件的安装。当运行启用或禁用服务的 `systemctl enable` 或 `systemctl disable` 命令时使用此方法。

`[Install]` 部分包含以下字段：

- `WantedBy`：类似于 [\[Unit\] 部分](https://docs.qualcomm.com/doc/80-70018-3SC/topic/customize.html#kernelexternaldocumentation-the-unit-section) 的 `After` 和 `Before` 字段。不过，该方法用于指定 systemd 等效的**运行级别**。

    `default.target` 用于系统初始化均完成并要求用户登录时。大多数面向用户的服务（如 WCN3960、cron、GNOME-stuff）都使用此目标。

    `shutdown.target` 是在设备关闭之前运行该服务。

    `multi-user.target` 表示在系统启动时以根用户身份运行服务。

    它既可以是目标设备，也可以是服务，例如 `network.target`。
- `RequiredBy`：此字段类似于 `WantedBy`。但是，该字段指定**硬依赖项**。如果依赖项运行失败，服务也会运行失败。

### 启动后脚本

启动后脚本文件位于 `meta-qcom-hwe` 层的核心配方。

将启动后脚本作为前一个 systemd 服务的一部分运行。所有启动后设置均托管在 `meta-qcom-hwe/recipes-core/initscripts/files/post_boot.sh` 脚本中。

#!/bin/sh
    # Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
    # SPDX-License-Identifier: BSD-3-Clause-Clear
    
    # Apply postboot settings
    Copy to clipboard

### 启动后 systemd 服务的配方

使用 `initscripts_1.0.bbappend` 文件了解安装 `post_boot` 脚本的配方配置过程。

在 `/etc/init.d` 中安装启动后 bash 脚本的配方在 `meta-qcom-hwe/recipes-core/initscripts/initscripts_1.0.bbappend` 文件中。

# postboot
    
    inherit systemd externalsrc
    
    FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
    SRC_URI:append = " \
       file://post_boot.sh \
       file://logging-restrictions.sh \
       file://log-restrict.service \
       file://post-boot.service \
       "
    
    do_install:append() {
       # postboot
       install -m 0755 ${WORKDIR}/post_boot.sh ${D}${sysconfdir}/initscripts/post_boot.sh
       install -m 0644 ${WORKDIR}/post-boot.service -D ${D}${systemd_unitdir}/system/post-boot.service
       ln -sf ${systemd_unitdir}/system/post-boot.service ${D}${systemd_unitdir}/system/multi-user.target.wants/post-boot.service
    }
    
    S = "${WORKDIR}"
    
    INITSCRIPT_PACKAGES =+ "${PN}-post-boot"
    INITSCRIPT_NAME:${PN}-post-boot = "post_boot.sh"
    
    PACKAGES =+ "${PN}-post-boot"
    FILES:${PN}-post-boot += "${systemd_unitdir}/system/post-boot.service ${systemd_unitdir}/system/multi-user.target.wants/post-boot.service ${sysconfdir}/initscripts/post_boot.sh"
    Copy to clipboard

### 调度器 DCVS 设置

调度器 DCVS 设置在 `post_boot script` 文件中定义。

所有启动后设置都在 `meta-qcom-hwe/recipes-core/initscripts/files/post_boot.sh` 脚本中。

#!/bin/sh
    # Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
    # SPDX-License-Identifier: BSD-3-Clause-Clear
    Copy to clipboard

Last Published: May 05, 2025

[Previous Topic
Yocto 支持](https://docs.qualcomm.com/bundle/publicresource/80-70018-3SC/topics/yocto-kernel-support.md) [Next Topic
调试内核](https://docs.qualcomm.com/bundle/publicresource/80-70018-3SC/topics/debug.md)