# 定制

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

SoC 和板级设备树由 `arch/arm64/boot/dts/qcom` 路径下的内核源代码提供支持。

## 内核开发

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

# setup the workspace and get the sources following build guide
    # setup the environment
     
    $ MACHINE=qcm6490 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-kernel-qcom
     
    # do the development , commit changes, build and test
    $ devtool build linux-kernel-qcom
    $ devtool build-image qcom-console-image
     
    # built images are produced in standard location
    $ ls build-qcom-wayland/tmp-glibc/deploy/images/qcm6490/
     
    # generate the patches and update layer
    $ devtool finish linux-kernel-qcom ~/meta-mylayerCopy to clipboard

有关定制 Qualcomm Linux 内核 recipe 的详细信息，参见[内核 recipe](https://docs.qualcomm.com/doc/80-70014-3Y/topic/yocto-kernel-support.html#kernel-recipe)章节。

有关 Yocto 功能、主机补丁及其应用方法的信息，参见 [Yocto 项目 Linux 内核开发](https://docs.yoctoproject.org/1.5/kernel-dev/kernel-dev.html#applying-patches)。

## 内核配置

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

执行如下命令，修改内核配置：

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

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

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

有关 Yocto 相关内核配置的详细详细，参见[配置内核](https://docs.yoctoproject.org/4.3.1/kernel-dev/common.html#configuring-the-kernel)。

## 调试编译版本

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

在 shell 命令提示符中，将 `DEBUG_BUILD=1` 作为参数传递，以创建调试编译版本：

# setup the build environment
    $ export SHELL=/bin/bash
     
    $ MACHINE=qcm6490 DISTRO=qcom-wayland source setup-environment
     
    # build qcom linux console image
    $ DEBUG_BUILD=1 bitbake qcom-console-imageCopy to clipboard

## 更新内核命令行

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

若要更新内核命令行，可修改相应机器配置文件中目标设备特定的 (`qcm6490.conf`) Yocto 机器配置变量 `KERNEL_CMDLINE_EXTRA`。

例如，`./layers/meta-qcom-hwe/conf/machine/qcm6490.conf`。

若要更新内核命令行参数，可修改/添加以下变量：

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

## 平台支持

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

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

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

    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)        += qcm6490-addons-rb3gen2.dtb
    +dtb-$(CONFIG_ARCH_QCOM)        += qcm6490-my-board.dtb
     dtb-$(CONFIG_ARCH_QCOM)        += qcm6490-rb3gen2.dtb
     dtb-$(CONFIG_ARCH_QCOM)        += qcs404-evb-1000.dtbCopy to clipboard

**机器配置中的 DTB 包含文件**

Yocto 机器配置也进行了更新，包括相应的设备树 blob。例如，为添加设备树从而支持 QCM6490 机器，可使用以下文件 `conf/machine/qcm6490.conf`：

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

    # Do note additional list of dtbos to be overylaid on top of base kernel devicetree
    # Refer below how existing boards are managing it
    # Format - KERNEL_TECH_DTBOS[<base-dtb-name>] = "<dtbo1 <dtbo2> ..."
     
    KERNEL_TECH_DTBOS[qcm6490-addons-rb3gen2] = "qcm6490-graphics.dtbo qcm6490-wlan-rb3gen2.dtbo qcm6490-display-rb3gen2.dtbo qcm6490-bt.dtbo"Copy to clipboard

**启动时选择的 DTB**

定制 DTB 可打包至 UKI 镜像中，该镜像可在 EFI 中更新以在启动时使用所选 DTB。

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

执行以下操作，生成 UKI 镜像：

# Note - ukify tool need python 3.10 version or above
     
    $ ukify build --efi-arch=aa64  \
                  --stub=<build-path>/tmp-glibc/deploy/images/qcm6490/linuxaa64.efi.stub \
                  --linux=<build-path>/tmp-glibc/deploy/images/qcm6490/Image \
                  --initrd=<build-path>/tmp-glibc/deploy/images/qcm6490/initramfs-qcom-image-qcm6490.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/qcm6490/qcm6490-my-board.dtb \
                  --output=./uki.efiCopy to clipboard

ukify 编译命令生成包含定制板级 DTB 的 `uki.efi` 镜像。

执行以下操作，更新 ESP 分区 `efi.bin` 镜像中的 `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/qcm6490/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 qcm6490-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/qcm6490/efi.binCopy to clipboard

有关设备树规范的详细信息，参见[设备树规范](https://www.devicetree.org/specifications/)。

如需了解 Linux 内核文档中设备树的相关信息，参见 [Linux 和设备树](https://docs.kernel.org/devicetree/usage-model.html)。

## 更新 ESP 中的镜像

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

适用于 QCM6490 的 Yocto 编译版本生成所有必需的镜像，并将启动镜像打包为可刷入 EFI 分区的 `efi.bin` 文件。`efi.bin` 文件包含 systemd-boot 启动管理器和内核镜像，它们已打包为 UKI Type-2 镜像格式。

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

    $ MACHINE=qcm6490 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/qcm6490/efi.bin
    efi.bin
     
    # reboot into fastboot
    $ fastboot flash efi efi.binCopy to clipboard

有关 UKI Type-2 镜像格式的详细信息，参见 Type #2 [EFI 统一内核镜像](https://uapi-group.org/specifications/specs/boot_loader_specification/#type-2-efi-unified-kernel-images)。

有关 ESP 的信息，参见[启动](https://docs.qualcomm.com/doc/80-70014-3Y/topic/features.html#boot)章节。

## 定制 initramfs

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

按照以下步骤更新 initramfs 包：

$ 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

## 添加内核模块

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

按照以下步骤，通过 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_installCopy to clipboard

这些内核模块通过使用标准 Yocto 模块类的 recipe 集成到 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

有关树外模块的详细信息，参见[使用树外模块](https://docs.yoctoproject.org/kernel-dev/common.html#working-with-out-of-tree-modules)。

## 禁用控制台日志

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

禁用内核控制台日志的方法如下：

1. 使用 `quiet` 更新内核命令行 
    有关内核参数的详细信息，参见 [内核命令行参数](https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html)。
2. 禁用内核配置中的 `CONFIG_SERIAL_EARLYCON` 和 `CONFIG_SERIAL_MSM_CONSOLE`。
3. 将 `console=null` 参数添加至内核命令行参数中。

## Pinctrl 配置

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

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

例如，对于 QCM6490，相应的驱动程序在

`kernel-src/drivers/pinctrl/qcom/pinctrl-sc7280.c` 文件中文件。

数据对象如下：

Table : Pinctrl 数据对象

| 变量 | 说明 |
| --- | --- |
| static const struct pinctrl_pin_desc sc7280_pinsCopy to clipboard | 枚举所有引脚及其名称。 |
| static const struct msm_pingroup sc7280_groupsCopy to clipboard | 定义通用输入/输出 (GPIO) 引脚组的可用复用功能。 |
| enum sc7280_functionsCopy to clipboard | 以枚举值形式列出所有可用功能。 |

如需详细了解 QCM6490 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 常规用法

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

GPIO 引脚配置需要完成以下两项设置：

这些设置会定义 GPIO 引脚状态，并使这些引脚可用于任何输入/输出活动。

- 复用对于复用设置，需要从目标设备特定的 pinctrl 驱动程序中提供的可用功能集中选择一个功能名称，并进行映射。
    有关 pinctrl 的详细信息，参见 [Pinctrl 配置](https://docs.qualcomm.com/doc/80-70014-3Y/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
         * detail
         
        /**
        * gpiod_get_value_cansleep() - return a gpio's value
        * @desc: gpio whose value will be returned
        *
        * Return the GPIO's logical value, i.e. taking the ACTIVE_LOW status into
        * account, or negative errno on failure.
        *
        * This function is to be called from contexts that can sleep.
        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

## ZRAM 用作交换设备

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

ZRAM 在 Yocto 编译版本中通过 `recipes-extended/zram/zram-swap-init-update` 文件进行配置。

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

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

如需将 ZRAM 配置为交换设备，参见 [zram：基于压缩 RAM 的块设备](https://www.kernel.org/doc/html/v6.6/admin-guide/blockdev/zram.html)。

## 扩展内存映射

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

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

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

    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

Table : 划分区域的语法

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

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

**添加 CMA 区域**

若要在 `arch/arm64/boot/dts/qcom/<target>.dtsi` 文件的 **reserved-memory** 节点下添加 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

Table : 添加 CMA 区域的语法

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

## 添加自定义 CMA 堆

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

Qualcomm 内核导出 `cma_heap_add ()` API，用于自定义 DMA-BUF 堆。

    /**
     * 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 区域，具体参见[连续内存分配器 (CMA)](https://docs.qualcomm.com/doc/80-70014-3Y/topic/features.html#memory__section_szj_szj_51c)章节。
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^页面块阶次^ 个页面。在 Qualcomm Linux 中，页面块阶次为 10。

## 调度器

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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` 报告为任务需求。

**注：**`UCLAMP_MAX > UCLAMP_MIN`

- UCLAMP 可用于影响调度器的任务放置决策。

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

例如，可以将不重要的（琐碎的/后台/综合管理）任务限制至较低的值 (`lower UCLAMP_MAX`)，从而影响调度器，使其将任务放置在小群集上。类似地，可以将重要/前台/活动任务限制至更高的值 (`higher UCLAMP_MIN`)，从而影响调度器，使其将任务放置在大群集上。

- UCLAMP 还支持通过调度器影响频率指导。

对于需要快速提升频率的任务，可以限制至更高的需求 (`UCLAMP_MIN`)，这有助于提高群集的频率，从而满足任务的性能要求。

有关接口和配置的详细信息，参见[利用率夹紧](https://www.kernel.org/doc/html/v6.6/scheduler/sched-util-clamp.html)。

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

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

1. Kconfig 配置选项：若要设置 CPU 频率调节器，需在内核 defconfiguration 文件中启用相应的驱动程序。 
    若要将 `PERFROMANCE` 调节器设置为默认 CPU 频率调节器，需在 defconfiguration 文件中设置 `CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y`。
2. 内核命令行选项：若要覆盖内核配置选项，需将 `cpufreq.default_governor=performance` 参数添加至 `meta-qcom-hwe/conf/machine/qcm6490.conf` 文件的内核命令行中，以设置相应的 CPU 频率调节器。

## 定制缓存和内存 DVFS

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

**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，则所得的 L3 工作频率为 300,000 Hz (9600000 / w)。如果为 DDR 提供 800,000 Hz，则所得 DDR 工作频率为
 200,000 Hz (800000 / w)。

在等式中，“w”表示单个周期内可以写入的字节数：
- 对于 DDR，“w”为 4（每个通道每周期执行两个事务，每个事务 2 个字节）。
- 对于 L3，“w”为 32（每周期执行一个事务，每个事务 32 字节）。

Note: 这些值按 DDR 通道设置，映射将 CPU 频率与内存控制器 (MC) 通道带宽关联起来。调整此映射表可影响功耗和性能特性。

有关工作性能点 (OPP) 框架和语法的详细信息，参见[通用 OPP（工作性能点）绑定](https://www.kernel.org/doc/Documentation/devicetree/bindings/opp/opp.txt)。

## 启动后设置

Source: [https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html)

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

### 启动后框架

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

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

有关 systemd 的详细信息，参见 [systemd (1) - Linux 手册页](https://www.man7.org/linux/man-pages/man1/systemd.1.html)。

### 什么是 systemd 服务？

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

编写 `systemd service` 文件的目的是，使 systemd 能够解析、理解并按照指示执行。

有关后台进程的详细信息，参见[后台进程](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.targetCopy to clipboard

### `[Unit]` 部分

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

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

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

Table : [Unit] 部分支持的字段

| 字段 | 说明 |
| --- | --- |
| DescriptionCopy to clipboard | 人类可读的 systemd 服务标题。 |
| AfterCopy to clipboard | 设置对服务的依赖。例如，如果配置的是 WCN3960 Web 服务器，则希望服务器在网络上线后启动。 |
| BeforeCopy to clipboard | 在指定服务之前启动当前服务。在此示例中，WCN3960 Web 服务器在启动下一个云的服务之前运行，因为下一个云服务器依赖于 WCN3960 Web 服务器。 |

### `[Service]` 部分

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

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

Table : [Service] 部分支持的字段

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

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

### `[Install]` 部分

`Install` 部分处理 systemd 服务或 unit 文件的安装。当运行 `systemctl enable` 和 `systemctl disable` 命令来启用或禁用服务时，将使用此部分。

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

- `WantedBy`：类似于 [\[Unit\] 部分](https://docs.qualcomm.com/doc/80-70014-3Y/topic/customize.html#post-boot-settings__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` 层的核心 recipe 中。

启动后脚本将在前述 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 post boot settingsCopy to clipboard

### 启动后 systemd 服务的 recipe

通过 `initscripts_1.0.bbappend` 文件来理解用于安装 `post_boot` 脚本的 recipe 配置过程。

将 postboot bash 脚本安装到 `/etc/init.d` 目录的 recipe 位于 `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` 脚本文件中定义。

所有启动后设置均位于 `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: Aug 22, 2024

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