为 GKI 开发内核代码

通用内核映像 (GKI) 通过与上游 Linux 内核紧密结合来减少内核碎片。然而,有些补丁不能被上游接受是有正当理由的,而且有一些必须满足的产品时间表,所以有些补丁是在构建 GKI 的 Android 通用内核 (ACK) 源中维护的。

开发人员必须优先使用 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 脚本,请使用repo checkout 中的build/static_analysis/checkpatch_presubmit.sh

确认补丁

提交给 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 的更改

gki_defconfig的所有CONFIG更改必须同时应用于 arm64 和 x86 版本,除非CONFIG是特定于体系结构的。要请求更改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_TOPOLOGY CONFIG_SND_SOC_SOF=y时会自动选择 CONFIG_SND_SOC_TOPOLOGY。为了适应树外模块的构建,GKI 包含了一种启用隐藏配置的机制。

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

可加载的调速器

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

我们将与您和上游维护者一起添加必要的支持。

供应商挂钩

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

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

  • 普通的供应商挂钩使用DECLARE_HOOK()创建一个名为trace_ name的跟踪点函数,其中name是跟踪的唯一标识符。按照惯例,正常的供应商挂钩名称以android_vh开头,因此sched_exit()挂钩的名称将是android_vh_sched_exit
  • 对于调度程序挂钩等情况,即使 CPU 离线或需要非原子上下文也必须调用附加函数,因此需要受限供应商挂钩。受限制的供应商挂钩无法分离,因此附加到受限制挂钩的模块永远无法卸载。只允许一个附件,因此任何其他附加尝试都会失败,并-EBUSY 。受限制的供应商挂钩名称以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目录的头文件中定义。

例如,以下代码在文件include/trace/hooks/sched.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 */
#include <linux/sched.h>

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>

注意:钩子声明中使用的数据结构需要完全定义以保证 ABI 稳定性。否则,取消引用不透明指针或在大小上下文中使用结构是不安全的。上例中的#include <linux/sched.h>使struct task_struct的定义可用并启用ABI 跟踪。

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

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

附加到供应商挂钩

要使用供应商挂钩,供应商模块需要为挂钩注册一个处理程序(通常在模块初始化期间完成)。例如,以下代码显示了用于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, NULL);
    ...
}

核心内核特性

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

用户应用程序编程接口 (UAPI)

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