# 定制

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-70017-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 编译系统编译。

### 前提条件

Provision 以下依赖项，以设置 Linux 内核编译流程:

> 
> 
> - aarch64 工具链
> - systemd-boot EFI stub，将其作为 UKI 镜像的一部分
> - 将内核镜像、initramfs 和 DTB 打包成 UKI 镜像的 systemd ukify 工具
> - 使用 UKI 镜像打包 initramfs
> - 包含更新的 UKI 的 ESP 镜像，必须被刷写以启动设备

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

安装所有依赖项使您能够进行增量内核编译，而无需重编译整个 Yocto。

要构建 Qualcomm Linux Yocto base，请参阅 [入门指南](https://docs.qualcomm.com/doc/80-70017-3SC/topic/getting_started_chapter2.html#getting-started-chapter2)。在为 Qualcomm Linux 编译 Yocto base 后，在以下目录中找到 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 site](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 内核**

安装工具链并设置 PATH。

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

检查以下 cmd 是否运行正常：

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-70017-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` 目录
> - DTBs 部署在 `kernel-src/tar-install/boot/dtbs/` 目录
> - 内核模块在 `kernel-src/tar-install/lib/modules/` 目录

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

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

Note

- 您可以使用自己的自定义 initramfs 或公开可用的 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 镜像。使用 ukify 实用程序运行以下命令:

Note

ukify 工具支持 python 3.10 或更高版本。要运行 ukify 工具，请安装 `pip install pefile` 以支持 ukify 工具的执行。

要编译 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 镜像。然后，将更新后的镜像刷写到板上并启动。按照以下说明将 Yocto 构建中的 efi.bin 更新为独立内核中的 uki.efi。

Note

将 EFI 中的 UKI 镜像覆盖为 efi.bin 中的默认镜像名称。

要覆盖 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-70017-3SC/topic/getting_started_chapter2.html#flash-images-and-boot)。

Note

要构建额外的 out-of-tree 内核模块，请依赖完整的 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

参见不同 SoC `meta-qcom-hwe/conf/machine/*.conf` 目录中的机器配置文件。定制 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` 镜像，执行以下操作：`efi.bin` 镜像。

# 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-70017-3SC/topic/features.html#id2)。

## 定制 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 编译系统编译树外的内核模块：

以下是用于树外内核驱动程序的 `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

这些内核模块通过使用标准 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 Drivers](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 binding documentation](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-70017-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. 为 `qup_se_l3()` 功能设置引脚使用 GPIO 55，配置如下：

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

## 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-70017-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

按 4 MB 基址和大小对齐 CMA 区域，以支持页面迁移。迁移发生在 page-block 级别的 2 ^pageblock\_order^ 页。在 Qualcomm Linux 内核中，pageblock\_order 为 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)]`

### 利用率夹紧 (Utilization clamping)

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

要进行利用率夹紧，配置以下参数:

- `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 频率调节器可以在运行时更改，也可以在编译版本编译过程中静态设置。

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-70017-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: Jan 25, 2025

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