为 GKI 开发内核代码

通用内核映像 (GKI) 通过与上游 Linux 内核紧密对齐来减少内核碎片。但是,有些补丁无法被上游接受是有正当理由的,而且必须满足产品计划,因此有些补丁会在 Android 通用内核 (ACK) 源代码中维护,GKI 正是基于这些源代码构建的。

开发者必须首先使用 Linux 内核邮件列表 (LKML) 向上游提交代码变更,并且仅当有充分理由说明上游不可行时,才可将代码变更提交到 ACK android-mainline 分支。以下列出了有效理由的示例以及处理方法。

  • 补丁已提交到 LKML,但未在产品发布前被接受。要处理此补丁,请执行以下操作:

    • 提供证据,证明补丁已提交到 LKML 以及收到的关于该补丁的评论,或提供补丁预计提交到上游的时间。
    • 决定在 ACK 中应用该补丁、使其在上游获得批准,然后在最终上游版本合并到 ACK 后将其从 ACK 中移除的行动方案。
  • 该补丁为供应商模块定义了 EXPORT_SYMBOLS_GPL(),但由于没有使用该符号的树内模块,因此无法向上游提交。要处理此补丁,请详细说明您的模块为何无法向上游提交,以及您在提出此请求之前考虑过的替代方案。

  • 该补丁对于上游而言不够通用,并且没有时间在产品发布之前对其进行重构。要处理此补丁,请提供预计将重构后的补丁提交到上游的时间(如果没有提交重构后的补丁以供上游审核的计划,ACK 将不会接受该补丁)。

  • 该补丁无法被上游接受,因为…<在此处插入原因>。要处理此补丁,请联系 Android 内核团队,并与我们合作寻找重构补丁的方案,以便将其提交审核并被上游接受。

还有许多其他潜在的理由。当您提交错误或补丁时,请提供有效的理由,并预计会有一些迭代和讨论。我们认识到 ACK 携带了一些补丁,尤其是在 GKI 的早期阶段,此时每个人都在学习如何向上游工作,但又不能为了这样做而放宽产品计划。预计上游要求会随着时间的推移而变得更加严格。

补丁要求

补丁必须符合 Linux 源代码树中描述的 Linux 内核编码标准,无论它们是提交到上游还是 ACK。 scripts/checkpatch.pl 脚本作为 Gerrit 预提交测试的一部分运行,因此请提前运行它以确保其通过。要使用与预提交测试相同的配置运行 checkpatch 脚本,请使用 //build/kernel/static_analysis:checkpatch_presubmit。有关详情,请参阅 build/kernel/kleaf/docs/checkpatch.md

ACK 补丁

提交到 ACK 的补丁必须符合 Linux 内核编码标准和 贡献准则。您必须在提交消息中包含 Change-Id 标记;如果您将补丁提交到多个分支(例如,android-mainlineandroid12-5.4),则必须对补丁的所有实例使用相同的 Change-Id

首先将补丁提交到 LKML 以进行上游审核。如果补丁属于以下情况:

  • 被上游接受,则会自动合并到 android-mainline 中。
  • 未被上游接受,则将其提交到 android-mainline,并附上对上游提交的引用,或说明为何未将其提交到 LKML。

在补丁被上游或 android-mainline 接受后,可以将其向后移植到基于 LTS 的相应 ACK(例如 android12-5.4android11-5.4,适用于修复 Android 特定代码的补丁)。提交到 android-mainline 可使用新的上游候选发布版本进行测试,并保证补丁位于下一个基于 LTS 的 ACK 中。例外情况包括上游补丁被向后移植到 android12-5.4 的情况(因为该补丁可能已在 android-mainline 中)。

上游补丁

贡献准则中所述,计划用于 ACK 内核的上游补丁分为以下几组(按被接受的可能性顺序列出)。

  • UPSTREAM: - 如果有合理的用例,则从“android-mainline”中挑选的补丁很可能被 ACK 接受。
  • BACKPORT: - 来自上游的补丁,如果挑选不干净且需要修改,如果有合理的用例,也很可能被接受。
  • FROMGIT: - 从维护者分支中挑选的补丁,目的是为了提交到 Linux 主线,如果临近截止日期,则可能会被接受。这些补丁的内容和计划都必须有合理的理由。
  • FROMLIST: - 已提交到 LKML 但尚未被维护者分支接受的补丁不太可能被接受,除非理由非常充分,足以证明无论该补丁是否最终进入上游 Linux 都会被接受(我们假设它不会)。与 FROMLIST 补丁必须关联一个问题,以便与 Android 内核团队进行讨论。

Android 特定补丁

如果您无法将所需的更改纳入上游,您可以尝试直接向 ACK 提交树外补丁。提交树外补丁需要您在 IT 中创建一个问题,其中引用该补丁并说明该补丁为何无法向上游提交(请参阅前面的列表了解示例)。但是,在少数情况下,代码无法向上游提交。以下介绍了这些情况,并且必须遵循 贡献准则中关于 Android 特定补丁的规定,并在主题中标记 ANDROID: 前缀。

对 gki_defconfig 的更改

除非 CONFIG 是特定于架构的,否则对 gki_defconfig 的所有 CONFIG 更改都必须应用于 arm64 和 x86 版本。要请求更改 CONFIG 设置,请在 IT 中创建一个问题来讨论该更改。在内核模块接口 (KMI) 冻结后,任何影响该接口的 CONFIG 更改都将被拒绝。如果合作伙伴请求单个配置的冲突设置,我们将通过讨论相关错误来解决冲突。

上游不存在的代码

对已是 Android 特定的代码进行的修改无法发送到上游。例如,即使 binder 驱动程序是在上游维护的,对 binder 驱动程序的优先级继承功能的修改也无法发送到上游,因为它们是 Android 特定的。请在您的错误和补丁中明确说明代码为何无法发送到上游。如果可能,请将补丁拆分为可以向上游提交的部分和无法向上游提交的 Android 特定部分,以最大限度地减少 ACK 中维护的树外代码量。

此类别中的其他更改包括更新 KMI 表示文件、KMI 符号列表、gki_defconfig、构建脚本或配置,或其他上游不存在的脚本。

树外模块

上游 Linux 积极反对支持构建树外模块。考虑到 Linux 维护者不对内核内源代码或二进制文件的兼容性做保证,并且不想支持树外代码,因此这是一个合理的立场。但是,GKI 确实为供应商模块提供 ABI 保证,确保 KMI 接口在内核的支持生命周期内保持稳定。因此,有一类更改是为了支持供应商模块,这些更改对于 ACK 是可以接受的,但对于上游是不可接受的。

例如,考虑一个添加 EXPORT_SYMBOL_GPL() 宏的补丁,其中使用导出的模块不在源代码树中。虽然您必须尝试请求上游的 EXPORT_SYMBOL_GPL() 并提供一个使用新导出符号的模块,但如果对于模块为何不向上游提交有合理的理由,您可以改为将补丁提交到 ACK。您需要在问题中包含模块为何无法向上游提交的理由。(不要请求非 GPL 变体 EXPORT_SYMBOL()。)

隐藏配置

某些树内模块会自动选择无法在 gki_defconfig 中指定的隐藏配置。例如,当配置 CONFIG_SND_SOC_SOF=y 时,会自动选择 CONFIG_SND_SOC_TOPOLOGY。为了适应树外模块构建,GKI 包含一种机制来启用隐藏配置。

要启用隐藏配置,请在 init/Kconfig.gki 中添加一个 select 语句,以便根据 CONFIG_GKI_HACKS_TO_FIX 内核配置自动选择它,该配置在 gki_defconfig 中启用。此机制仅适用于隐藏配置;如果配置不是隐藏的,则必须在 gki_defconfig 中显式指定它或将其作为依赖项指定。

可加载调速器

对于支持可加载调速器的内核框架(例如 cpufreq),您可以替换默认调速器(例如 cpufreqschedutil 调速器)。对于不支持可加载调速器或驱动程序但仍需要供应商特定实现的框架(例如热框架),请在 IT 中创建一个问题,并咨询 Android 内核团队

我们将与您和上游维护者合作,添加必要的支持。

供应商钩子

在过去的版本中,您可以将供应商特定的修改直接添加到核心内核中。GKI 2.0 不再允许这样做,因为产品特定的代码必须在模块中实现,并且不会被上游或 ACK 中的核心内核接受。为了启用合作伙伴依赖的增值功能,同时最大限度地减少对核心内核代码的影响,GKI 接受供应商钩子,允许从核心内核代码调用模块。此外,关键数据结构可以用供应商数据字段填充,这些字段可用于存储供应商特定的数据以实现这些功能。

供应商钩子有两种变体(正常和受限),它们基于供应商模块可以附加到的跟踪点(而非跟踪事件)。例如,与其添加新的 sched_exit() 函数以在任务退出时进行核算,不如在 do_exit() 中添加一个钩子,供应商模块可以附加到该钩子以进行处理。一个示例实现包括以下供应商钩子。

  • 正常供应商钩子使用 DECLARE_HOOK() 创建一个跟踪点函数,名称为 trace_name,其中 name 是跟踪的唯一标识符。按照惯例,正常供应商钩子名称以 android_vh 开头,因此 sched_exit() 钩子的名称将为 android_vh_sched_exit
  • 受限供应商钩子适用于调度程序钩子等情况,在这些情况下,即使 CPU 处于离线状态或需要非原子上下文,也必须调用附加的函数。受限供应商钩子无法分离,因此附加到受限钩子的模块永远无法卸载。受限供应商钩子名称以 android_rvh 开头。

要添加供应商钩子,请在 IT 中提交问题并提交补丁(与所有 Android 特定补丁一样,必须存在问题,并且您必须提供理由)。供应商钩子仅在 ACK 中受支持,因此请勿将这些补丁发送到上游 Linux。

向结构体添加供应商字段

您可以使用 ANDROID_VENDOR_DATA() 宏添加 android_vendor_data 字段,将供应商数据与关键数据结构关联起来。例如,为了支持增值功能,请将字段附加到结构体,如以下代码示例所示。

为了避免供应商所需的字段与 OEM 所需的字段之间可能发生的冲突,OEM 绝不能使用使用 ANDROID_VENDOR_DATA() 宏声明的字段。相反,OEM 必须使用 ANDROID_OEM_DATA() 来声明 android_oem_data 字段。

#include <linux/android_vendor.h>
...
struct important_kernel_data {
  [all the standard fields];
  /* Create vendor data for use by hook implementations. The
   * size of vendor data is based on vendor input. Vendor data
   * can be defined as single u64 fields like the following that
   * declares a single u64 field named "android_vendor_data1" :
   */
  ANDROID_VENDOR_DATA(1);

  /*
   * ...or an array can be declared. The following is equivalent to
   * u64 android_vendor_data2[20]:
   */
  ANDROID_VENDOR_DATA_ARRAY(2, 20);

  /*
   * SoC vendors must not use fields declared for OEMs and
   * OEMs must not use fields declared for SoC vendors.
   */
  ANDROID_OEM_DATA(1);

  /* no further fields */
}

定义供应商钩子

通过使用 DECLARE_HOOK()DECLARE_RESTRICTED_HOOK() 声明供应商钩子,然后将它们作为跟踪点添加到代码中,从而将供应商钩子添加到内核代码中。例如,要将 trace_android_vh_sched_exit() 添加到现有的 do_exit() 内核函数中

#include <trace/hooks/exit.h>
void do_exit(long code)
{
    struct task_struct *tsk = current;
    ...
    trace_android_vh_sched_exit(tsk);
    ...
}

trace_android_vh_sched_exit() 函数最初仅检查是否附加了任何内容。但是,如果供应商模块使用 register_trace_android_vh_sched_exit() 注册了处理程序,则会调用注册的函数。处理程序必须了解关于所持有的锁、RCS 状态和其他因素的上下文。钩子必须在 include/trace/hooks 目录下的头文件中定义。

例如,以下代码给出了 trace_android_vh_sched_exit() 在文件 include/trace/hooks/exit.h 中的可能声明。

/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched
#define TRACE_INCLUDE_PATH trace/hooks

#if !defined(_TRACE_HOOK_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_HOOK_SCHED_H
#include <trace/hooks/vendor_hooks.h>
/*
 * Following tracepoints are not exported in tracefs and provide a
 * mechanism for vendor modules to hook and extend functionality
 */

struct task_struct;

DECLARE_HOOK(android_vh_sched_exit,
             TP_PROTO(struct task_struct *p),
             TP_ARGS(p));

#endif /* _TRACE_HOOK_SCHED_H */

/* This part must be outside protection */
#include <trace/define_trace.h>

要实例化供应商钩子所需的接口,请将包含钩子声明的头文件添加到 drivers/android/vendor_hooks.c 并导出符号。例如,以下代码完成了 android_vh_sched_exit() 钩子的声明。

#ifndef __GENKSYMS__
/* struct task_struct */
#include <linux/sched.h>
#endif

#define CREATE_TRACE_POINTS
#include <trace/hooks/vendor_hooks.h>
#include <trace/hooks/exit.h>
/*
 * Export tracepoints that act as a bare tracehook (i.e. have no trace
 * event associated with them) to allow external modules to probe
 * them.
 */
EXPORT_TRACEPOINT_SYMBOL_GPL(android_vh_sched_exit);

注意:为了保证 ABI 稳定性,必须完全定义在钩子声明中使用的数据结构。否则,取消引用不透明指针或在大小上下文中struct是不安全的。提供此类数据结构的完整定义的 include 语句应放在 drivers/android/vendor_hooks.c#ifndef __GENKSYMS__ 部分中。 include/trace/hooks 中的头文件不应包含带有类型定义的内核头文件,以避免导致 KMI 崩溃的 CRC 更改。而是应前向声明类型。

附加到供应商钩子

要使用供应商钩子,供应商模块需要为钩子注册一个处理程序(通常在模块初始化期间完成)。例如,以下代码显示了模块 foo.kotrace_android_vh_sched_exit() 处理程序。

#include <trace/hooks/sched.h>
...
static void foo_sched_exit_handler(void *data, struct task_struct *p)
{
    foo_do_exit_accounting(p);
}
...
static int foo_probe(..)
{
    ...
    rc = register_trace_android_vh_sched_exit(foo_sched_exit_handler, NULL);
    ...
}

从头文件使用供应商钩子

要从头文件使用供应商钩子,您可能需要更新供应商钩子头文件以取消定义 TRACE_INCLUDE_PATH,以避免指示找不到跟踪点头文件的构建错误。例如,

In file included from .../common/init/main.c:111:
In file included from .../common/include/trace/events/initcall.h:74:
.../common/include/trace/define_trace.h:95:10: fatal error: 'trace/hooks/initcall.h' file not found
   95 | #include TRACE_INCLUDE(TRACE_INCLUDE_FILE)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:90:32: note: expanded from macro 'TRACE_INCLUDE'
   90 | # define TRACE_INCLUDE(system) __TRACE_INCLUDE(system)
      |                                ^~~~~~~~~~~~~~~~~~~~~~~
.../common/include/trace/define_trace.h:87:34: note: expanded from macro '__TRACE_INCLUDE'
   87 | # define __TRACE_INCLUDE(system) __stringify(TRACE_INCLUDE_PATH/system.h)
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:10:27: note: expanded from macro '__stringify'
   10 | #define __stringify(x...)       __stringify_1(x)
      |                                 ^~~~~~~~~~~~~~~~
.../common/include/linux/stringify.h:9:29: note: expanded from macro '__stringify_1'
    9 | #define __stringify_1(x...)     #x
      |                                 ^~
<scratch space>:14:1: note: expanded from here
   14 | "trace/hooks/initcall.h"
      | ^~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

要修复此类构建错误,请对您包含的供应商钩子头文件应用等效的修复程序。如需了解更多信息,请参阅 https://r.android.com/3066703

diff --git a/include/trace/hooks/mm.h b/include/trace/hooks/mm.h
index bc6de7e53d66..039926f7701d 100644
--- a/include/trace/hooks/mm.h
+++ b/include/trace/hooks/mm.h
@@ -2,7 +2,10 @@
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mm

+#ifdef CREATE_TRACE_POINTS
 #define TRACE_INCLUDE_PATH trace/hooks
+#define UNDEF_TRACE_INCLUDE_PATH
+#endif

定义 UNDEF_TRACE_INCLUDE_PATH 会告知 include/trace/define_trace.h 在创建跟踪点后取消定义 TRACE_INCLUDE_PATH

核心内核功能

如果以前的任何技术都无法让您从模块实现某个功能,那么您必须将该功能作为 Android 特定修改添加到核心内核中。在问题跟踪器 (IT) 中创建一个问题以开始对话。

用户应用编程接口 (UAPI)

  • UAPI 头文件。除非更改是针对 Android 特定接口的,否则对 UAPI 头文件的更改必须在上游进行。使用供应商特定的头文件来定义供应商模块和供应商用户空间代码之间的接口。
  • sysfs 节点。不要向 GKI 内核添加新的 sysfs 节点(此类添加仅在供应商模块中有效)。由构成 Android 框架的 SoC 和设备无关库和 Java 代码使用的 sysfs 节点只能以兼容的方式更改,如果它们不是 Android 特定的 sysfs 节点,则必须在上游更改。您可以创建供应商特定的 sysfs 节点以供供应商用户空间使用。默认情况下,使用 SELinux 拒绝用户空间访问 sysfs 节点。供应商有责任添加适当的 SELinux 标签,以允许授权的供应商软件进行访问。
  • DebugFS 节点。供应商模块可以在 debugfs 中定义节点,仅用于调试(因为 debugfs 在设备的正常运行期间未挂载)。