为 GKI 开发内核代码

通用内核映像 (GKI) 通过与上游 Linux 内核保持严密一致来减少内核碎片化问题。然而,有些正当理由会导致上游无法接受某些补丁,加之必须满足产品时间表的要求,因此,某些补丁会在用来构建 GKI 的 Android 通用内核 (ACK) 源代码中进行维护。

开发者必须以 Linux 内核邮寄名单 (LKML) 为首选向上游提交代码更改,只有在有充分理由表明向上游提交不可行时,方可将代码更改提交到 ACK android-mainline 分支。下面列举了正当理由的示例及其处理方式。

  • 补丁已提交到 LKML,但未被及时接受,赶不上产品发布时间。如需处理此补丁,请执行以下操作:

    • 提供补丁已提交到 LKML 的证据以及收到的有关该补丁的注释,或者提供向上游提交该补丁的预计最晚时间。
    • 就以下操作做出决定:将补丁提交到 ACK,使其在上游获得批准,然后在最终上游版本合并到 ACK 中之后将其从 ACK 中移出。
  • 补丁为某个供应商模块定义了 EXPORT_SYMBOLS_GPL(),但无法向上游提交,因为没有使用该符号的树内模块。如需处理此补丁,请详细说明无法向上游提交模块的原因,以及您在提出此请求之前考虑过的替代方案。

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

  • 上游无法接受补丁,原因如下:<在此插入原因>。如需处理此补丁,请与 Android 内核团队联系,并与我们合作,设法重构补丁,以便向上游提交补丁供审核并在上游接受补丁。

除此之外,可能还存在许多其他正当理由。在提交 bug 或补丁时,请提供有效的理由,并做好进行一些迭代和讨论的准备。我们知道 ACK 会带有一些补丁,特别是在 GKI 早期开发阶段,当时所有人都在学习如何进行上游开发工作,但又不能放宽产品时间表。预计随着时间的推移,向上游传送的要求会变得越来越严格。

补丁要求

无论是向上游提交还是提交到 ACK,补丁都必须符合 Linux 源代码树中所述的 Linux 内核编码标准。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 中(例如,可将用于修复 Android 专用代码的补丁移植到 android12-5.4android11-5.4 中)。将补丁提交到 android-mainline 就能使用新的上游发布候选版本进行测试,并保证下一个基于 LTS 的 ACK 中包含相应补丁。例外情况包括将上游补丁向后移植到 android12-5.4 的情形(因为补丁可能已包含在 android-mainline 中)。

上游补丁

按照贡献准则中的规定,面向 ACK 内核的上游补丁分为以下几组(按照被接受的可能性依次列出)。

  • UPSTREAM: - 如果存在合理用例,从“android-mainline”中择优挑选的补丁很有可能会在 ACK 中被接受。
  • BACKPORT: - 如果存在合理用例,来自上游的补丁哪怕没有经过彻底择优挑选且需要修改,也有可能被接受。
  • FROMGIT: - 如果最后期限迫在眉睫,从准备提交到 Linux Mainline 的维护人员分支中择优挑选的补丁可能会被接受。必须从内容和时间表两方面证明有正当理由接受这些补丁。
  • FROMLIST: - 如果补丁已提交到 LKML,但尚未在维护人员分支中被接受,则此类补丁不太可能被接受,除非有充分的正当理由让人相信,无论补丁是否进入上游 Linux(我们假定不会),都会被接受。必须存在与 FROMLIST 补丁相关的问题才能推动与 Android 内核团队的讨论。

Android 专用补丁

如果您无法向上游提交所需更改,可以尝试直接向 ACK 提交树外补丁。提交树外补丁需要在 IT 中创建一个问题,在其中引用补丁,并说明无法向上游提交该补丁的原因(如需查看示例,请参阅前文的列表)。不过,在少数情况下是无法向上游提交代码的。这些情况如下所述,它们必须遵循 Android 专用补丁的贡献准则,并在主题中使用 ANDROID: 前缀进行标记。

对 gki_defconfig 的更改

gki_defconfig 的所有 CONFIG 更改都必须同时应用到 arm64 和 x86 版本,除非 CONFIG 因架构而异。如需请求对 CONFIG 设置进行更改,请在 IT 中创建问题以讨论更改。如果 CONFIG 更改会在内核模块接口 (KMI) 冻结后对其造成影响,则相应更改会被拒绝。如果合作伙伴对同一项配置所请求的设置发生冲突,我们会通过讨论相关 bug 来解决冲突。

上游不存在的代码

如果代码已经是 Android 专用代码,就无法向上游发送对其所做的修改。例如,即使 binder 驱动程序是在上游维护的,也无法向上游发送对 binder 驱动程序的优先级继承功能所做的修改,因为它们是 Android 专用的。请在 bug 和补丁信息中清楚说明无法向上游发送代码的原因。如果可能的话,请将补丁拆分为可向上游提交的代码段和无法向上游提交的 Android 专用代码段,以最大限度地减少在 ACK 中维护的树外代码量。

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

树外模块

上游 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 开头。

如需添加供应商钩子,请在问题跟踪器中提交问题,并提交补丁(与所有 Android 专用补丁一样,必须存在一个问题,而且您必须提供正当理由)。只有 ACK 支持供应商钩子,因此请勿将这些补丁发送到上游 Linux。

在结构中添加供应商字段

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

为了避免供应商所需的字段与 OEM 所需的字段发生冲突,OEM 绝不能使用以 ANDROID_VENDOR_DATA() 宏声明的字段,而必须使用 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 目录下的头文件中定义。

例如,以下代码将在 include/trace/hooks/exit.h 文件中为 trace_android_vh_sched_exit() 提供可能的声明。

/* 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 稳定性。否则,对不透明指针解引用或在有大小的上下文中使用结构体是不安全的。提供此类数据结构完整定义的包含文件应位于 drivers/android/vendor_hooks.c#ifndef __GENKSYMS__ 部分中。include/trace/hooks 中的头文件不应包含具有类型定义的内核头文件,以避免会破坏 KMI 的 CRC 更改。请改为向前声明类型。

连接到供应商钩子

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

#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);
    ...
}

核心内核功能

如果使用前述所有方法都不能通过模块实现功能,那么就必须将功能作为 Android 专用修改添加到核心内核中。请在问题跟踪器 (IT) 中创建一个问题以展开讨论。

用户应用编程接口 (UAPI)

  • UAPI 头文件:对 UAPI 头文件所做的更改必须在上游进行,除非是对 Android 专用接口进行更改。请使用供应商专用头文件来定义供应商模块与供应商用户空间代码之间的接口。
  • sysfs 节点:请勿向 GKI 内核添加新的 sysfs 节点(此类添加仅在供应商模块中有效)。与 SoC 和设备无关的库以及包含 Android 框架的 Java 代码所使用的 sysfs 节点只能以兼容的方式进行更改,而且如果它们不是 Android 专用 sysfs 节点的话,还必须在上游进行更改。您可以创建要由供应商用户空间所使用的供应商专用 sysfs 节点。默认情况下,需要使用 SELinux 拒绝用户空间对 sysfs 节点进行访问。供应商应负责添加相应的 SELinux 标签,以便允许获得授权的供应商软件进行访问。
  • DebugFS 节点:供应商模块可在 debugfs 中将节点定义为仅用于调试(因为在设备正常操作期间不会装载 debugfs)。