Opracowywanie kodu jądra dla GKI

Ogólny obraz jądra (GKI) zmniejsza fragmentaryzację jądra przez dokładne dopasowanie do jądra Linuxa. Istnieją jednak ważne powody, dla których niektóre poprawki nie mogą być akceptowane w górę, a także harmonogramy produktów, które muszą być dotrzymywane. Dlatego niektóre poprawki są utrzymywane w źródłach Android Common Kernel (ACK), z których jest tworzony GKI.

Deweloperzy muszą jako pierwszą opcję przesłać zmiany kodu w kodzie Linuksa, korzystając z LKML (Linux JKML), a zmiany kodu w gałęzi potwierdzenia android-mainline przesyłać tylko wtedy, gdy z uzasadnionych przyczyn nie są one możliwe. Poniżej znajdziesz przykłady akceptowanych przyczyn i sposobów ich rozwiązania.

  • Aktualizacja została przesłana do LKML, ale nie została zaakceptowana na czas, aby można było ją uwzględnić w wersji produktu. Aby zastosować tę poprawkę:

    • Podaj dowody, że poprawka została przesłana do LKML i że otrzymano komentarze dotyczące tej poprawki, lub oszacowany czas, w którym poprawka zostanie przesłana do upstream.
    • Zdecyduj, jak wdrożyć poprawkę w ACK, uzyskać jej zatwierdzenie w upstreamie, a potem usunąć z ACK, gdy ostateczna wersja upstream zostanie scalona z ACK.
  • Aktualizacja definiuje EXPORT_SYMBOLS_GPL() dla modułu dostawcy, ale nie można jej przesłać do upstream, ponieważ nie ma modułów w drzewie, które używają tego symbolu. W celu obsługi tej poprawki podaj szczegółowe informacje o tym, dlaczego nie można przesłać modułu z góry serwera, oraz podaj rozwiązania alternatywne, które rozważałeś przed wysłaniem żądania.

  • Poprawka nie jest wystarczająco ogólna dla wdrożenia i nie ma czasu na jej refaktoryzację przed opublikowaniem produktu. Aby obsłużyć tę łatkę, podaj szacowany czas, w którym zaktualizowana łatka zostanie przesłana do przerefaktoryzowania (łatka nie zostanie zaakceptowana w ACK bez planu przesłania zaktualizowanej łatki do sprawdzenia).

  • Nie można zaakceptować poprawki przez upstream, ponieważ... <insert reason here>. Aby obsłużyć tę łatkę, skontaktuj się z zespołem odpowiedzialnym za jądro Androida i wspólnie z nami zastanów się nad opcjami refaktoryzacji łatki, aby można było ją przesłać do sprawdzenia i zaakceptować w głównym repozytorium.

Istnieje wiele innych potencjalnych uzasadnienia. Przesyłając błąd lub poprawkę, dołącz wiarygodne uzasadnienie i bądź gotowy na kilka iteracji i dyskusji. Zdajemy sobie sprawę, że ACK wprowadza pewne poprawki, zwłaszcza na wczesnych fazach GKI, gdy wszyscy uczą się, jak pracować nad wdrożeniem, ale nie mogą złagodzić harmonogramów usług. Z czasem wymagania dotyczące tych procesów staną się bardziej restrykcyjne.

Wymagania dotyczące poprawek

Aktualizacje muszą być zgodne ze standardami kodowania jądra Linuxa opisanymi w drzewie źródłowym Linuxa, niezależnie od tego, czy są przesyłane do upstream czy do ACK. Skrypt scripts/checkpatch.pl jest uruchamiany w ramach testowania przed przesłaniem w Gerricie, więc uruchom go wcześniej, aby sprawdzić, czy przejdzie. Aby uruchomić skrypt sprawdzania poprawki z tą samą konfiguracją co testowanie przed przesłaniem, użyj polecenia //build/kernel/static_analysis:checkpatch_presubmit. Szczegółowe informacje znajdziesz w build/kernel/kleaf/docs/checkpatch.md.

Poprawki ACK

Aktualizacje przesyłane do ACK muszą być zgodne ze standardami kodowania jądra Linuksa oraz wytycznymi dotyczącymi publikowania źródeł. W wiadomości zatwierdzenia musisz użyć tagu Change-Id. Jeśli przesyłasz poprawkę do wielu gałęzi (np. android-mainlineandroid12-5.4), musisz użyć tego samego tagu Change-Id dla wszystkich wystąpień poprawki.

Najpierw prześlij poprawki do LKML, aby przesłać je do weryfikacji nadrzędnej. Jeśli poprawka to:

  • Po zaakceptowaniu na upstreamie zostanie ona automatycznie scalona z android-mainline.
  • Nie akceptowane na wyższym poziomie, prześlij je na adres android-mainline, podając odniesienie do przesłanych danych lub wyjaśnienie, dlaczego nie zostały one przesłane do LKML.

Po zaakceptowaniu poprawki w elemencie nadrzędnym lub android-mainline można ją przenieść do odpowiedniego potwierdzenia opartego na LTS (np. android12-5.4 i android11-5.4 w przypadku poprawek naprawiających kod właściwy dla Androida). Przesłanie do android-mainline umożliwia testowanie z nowymi wersjami kandydatów do publikacji w upstreamie i gwarantuje, że łatka zostanie uwzględniona w następnej wersji ACK opartej na LTS. Wyjątkami są przypadki, w których poprawka z upstreamu jest przenoszona do wersji android12-5.4 (ponieważ prawdopodobnie jest już dostępna w wersji android-mainline).

Poprawki na wyższym poziomie

Zgodnie z wytycznymi dotyczącymi zgłaszania poprawek poprawki upstream przeznaczone dla jąder ACK należą do następujących grup (wymienione w kolejności prawdopodobieństwa zaakceptowania):

  • UPSTREAM: – poprawki wybrane z gałęzi „android-mainline” prawdopodobnie zostaną zaakceptowane w gałęzi ACK, jeśli istnieje uzasadniony przypadek użycia.
  • BACKPORT: – poprawki z serwera źródłowego, które nie są wystarczająco skuteczne i wymagają modyfikacji, są również prawdopodobnie akceptowane, jeśli istnieje uzasadniony przypadek użycia.
  • FROMGIT: – poprawki wybrane z gałęzi konserwacyjnej w ramach przygotowań do przesłania do głównej gałęzi Linuksa mogą zostać zaakceptowane, jeśli zbliża się termin. Muszą być one uzasadnione zarówno pod względem treści, jak i harmonogramu.
  • FROMLIST: – łaty, które zostały przesłane do LKML, ale nie zostały jeszcze zaakceptowane w gałęzi konserwatora, prawdopodobnie nie zostaną zaakceptowane, chyba że uzasadnienie jest wystarczająco przekonujące, aby łata została zaakceptowana niezależnie od tego, czy trafi do upstream Linux (zakładamy, że tak się nie stanie). Musi wystąpić problem z poprawkami FROMLIST, aby ułatwić rozmowę z zespołem ds. jądra Androida.

Poprawki dotyczące Androida

Jeśli nie możesz wprowadzić wymaganych zmian w gałęzi głównej, możesz przesłać poprawki spoza drzewa bezpośrednio do ACK. Przesyłanie poprawek spoza gałęzi wymaga utworzenia problemu w IT, który zawiera poprawkę i uzasadnienie, dlaczego nie można jej przesłać w górę (przykłady znajdziesz na poprzedniej liście). Są jednak przypadki, w których nie można przesłać kodu do źródła. Przykłady takich przypadków opisano poniżej. Muszą one być zgodne ze wskazówkami dotyczącymi publikowania treści w przypadku poprawek związanych z Androidem. Powinny też być oznaczone prefiksem ANDROID: w temacie.

Zmiany w konfiguracji gki_defconfig

Wszystkie zmiany w CONFIGgki_defconfig muszą być stosowane zarówno w wersji arm64, jak i w wersji x86, chyba że CONFIG jest specyficzne dla danej architektury. Aby poprosić o zmianę ustawienia CONFIG, utwórz problem w dziale IT, aby omówić tę zmianę. Każda zmiana CONFIG, która wpływa na interfejs modułu jądra (KMI) po zamrożeniu, jest odrzucana. W przypadku, gdy partnerzy proszą o sprzeczne ustawienia dla jednej konfiguracji, rozwiązujemy konflikty podczas dyskusji na temat powiązanych błędów.

Kod, który nie istnieje na poziomie nadrzędnym

Nie można wysyłać zmian w kodzie, które są już przeznaczone tylko na Androida. Na przykład mimo że sterownik bindera jest utrzymywany w górę, modyfikacji funkcji dziedziczenia priorytetów sterownika bindera nie można przesłać w górę, ponieważ są one specyficzne dla Androida. W opisie błędu i łatki wyraźnie wyjaśnij, dlaczego kod nie może zostać wysłany do zespołu. Jeśli to możliwe, podziel poprawki na części, które można przesłać w górę łańcucha, i części dotyczące Androida, których nie można przesłać w górę łańcucha, aby zminimalizować ilość kodu spoza drzewa utrzymywanego w ACK.

Inne zmiany w tej kategorii to aktualizacje plików reprezentacji KMI, list symboli KMI, elementów gki_defconfig, skryptów kompilacji lub konfiguracji bądź innych skryptów, które nie istnieją na wyższym poziomie.

Moduły zewnętrzne

Współtwórcy Linuksa aktywnie zniechęcają do obsługi tworzenia modułów spoza drzewa. Jest to rozsądne stanowisko, ponieważ opiekunowie systemu Linux nie dają gwarancji dotyczących zgodności kodu źródłowego lub binarnego w jądrze i nie chcą obsługiwać kodu, który nie znajduje się w drzewie. GKI zapewnia jednak interfejs ABI dla modułów dostawcy, zapewniając, że interfejsy KMI są stabilne przez cały okres użytkowania jądra systemu. Dlatego istnieje klasa zmian, które obsługują moduły dostawców i są akceptowane przez ACK, ale nie są akceptowane przez upstream.

Rozważ na przykład poprawkę, która dodaje makro EXPORT_SYMBOL_GPL(), a moduły korzystające z eksportu nie znajdują się w drzewie źródłowym. Musisz przesłać prośbę do upstream EXPORT_SYMBOL_GPL() i dostarczyć moduł, który używa nowo wyeksportowanego symbolu. Jeśli jednak istnieje uzasadniona przyczyna, dla której moduł nie jest przesyłany do upstream, możesz zamiast tego przesłać poprawkę do ACK. Musisz uzasadnić, dlaczego moduł nie może być przesyłany w górę. (Nie żądaj wersji bez GPL, EXPORT_SYMBOL()).

Ukryte konfiguracje

Niektóre moduły w drzewie automatycznie wybierają ukryte konfiguracje, których nie można określić w sekcji gki_defconfig. Na przykład CONFIG_SND_SOC_TOPOLOGY jest wybierana automatycznie, gdy CONFIG_SND_SOC_SOF=y jest skonfigurowana. Aby umożliwić kompilowanie modułów poza drzewem, GKI zawiera mechanizm umożliwiający stosowanie ukrytych konfiguracji.

Aby włączyć ukrytą konfigurację, dodaj instrukcję select w pliku init/Kconfig.gki, aby była automatycznie wybierana na podstawie konfiguracji jądra CONFIG_GKI_HACKS_TO_FIX, która jest włączona w pliku gki_defconfig. Używaj tego mechanizmu tylko w przypadku ukrytych konfiguracji. Jeśli konfiguracja nie jest ukryta, musi być określona w gki_defconfig wprost lub jako zależność.

Ładowane ograniczenia

W przypadku platform jądra (takich jak cpufreq) obsługujących ładowane kontrolery możesz zastąpić domyślny kontroler (taki jak regulator schedutil w cpufreq). W przypadku ram (np. platformy termicznej), które nie obsługują ładowanych regulatorów lub sterowników, ale nadal wymagają implementacji specyficznej dla dostawcy, zgłoś problem w IT i skonsultuj się z zespołem ds. jądra Androida.

Wspólnie z Tobą i konserwatorami upstream dodamy niezbędne wsparcie.

Punkty zaczepienia dostawcy

W poprzednich wersjach można było dodawać modyfikacje specyficzne dla dostawcy bezpośrednio do jądra. W GKI 2.0 nie jest to możliwe, ponieważ kod związany z konkretną usługą musi być wdrożony w modułach i nie będzie akceptowany w rdzeniach podstawowych ani w potwierdzeniu. Aby umożliwić partnerom korzystanie z dodatkowych funkcji przy minimalnym wpływie na podstawowy kod jądra, GKI akceptuje punkty zaczepienia dostawców, które umożliwiają wywoływanie modułów z poziomu podstawowego kodu jądra. Dodatkowo kluczowe struktury danych mogą być wypełniane polami danych dostawcy, które są dostępne do przechowywania danych dotyczących dostawcy, aby można było stosować te funkcje.

Elementy wywołujące dostawców występują w 2 wersjach (normalnej i ograniczonej), które są oparte na punktach śledzenia (a nie zdarzeniach śledzenia), do których mogą się przyłączać moduły dostawców. Na przykład zamiast dodawać nową funkcję sched_exit(), która będzie odpowiedzialna za rozliczanie po zakończeniu zadania, dostawcy mogą dodać wątek w funkcji do_exit(), do którego moduł dostawcy może się przyłączyć w celu przetworzenia. Przykładowa implementacja zawiera te elementy:

  • Zwykłe punkty zaczepienia dostawcy wykorzystują DECLARE_HOOK() do utworzenia funkcji punktu śledzenia o nazwie trace_name, gdzie name to unikalny identyfikator logu czasu. Zgodnie z konwencją normalne nazwy punktów zaczepienia dostawców zaczynają się od android_vh, więc nazwa haka sched_exit() miałaby postać android_vh_sched_exit.
  • Funkcje dostawcy z ograniczonym dostępem są potrzebne w przypadkach takich jak funkcje harmonogramu, w których dołączona funkcja musi być wywoływana nawet wtedy, gdy procesor jest offline lub wymaga kontekstu nieatomowego. Elementy dostawcy z ograniczonym dostępem nie mogą być odłączane, więc moduły, które się do nich przyłączają, nigdy nie mogą zostać usunięte. Nazwy ograniczonych haków dostawcy zaczynają się od android_rvh.

Aby dodać element dostawcy, prześlij problem w IT i prześlij poprawki (jak w przypadku wszystkich poprawek dotyczących Androida, problem musi istnieć i musisz podać uzasadnienie). Obsługa punktów zaczepienia dostawcy jest dostępna tylko w ramach potwierdzenia, więc nie wysyłaj tych poprawek do nadrzędnego systemu Linux.

Dodawanie pól dostawcy do struktur

Dane dostawcy możesz powiązać z kluczowymi strukturami danych, dodając pola android_vendor_data za pomocą makr ANDROID_VENDOR_DATA(). Na przykład, aby obsługiwać funkcje o wartości dodanej, dołącz pola do struktury, jak pokazano w tym przykładowym kodzie.

Aby uniknąć potencjalnych konfliktów między polami potrzebnymi przez dostawców a polami potrzebnymi przez producentów OEM, producenci OEM nie mogą używać pól zadeklarowanych za pomocą makr ANDROID_VENDOR_DATA(). Zamiast tego producenci OEM muszą używać wartości ANDROID_OEM_DATA() do deklarowania pól 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 */
}

Definiowanie haka dostawcy

Dodaj do kodu jądra punkty zaczepienia dostawcy jako punkty śledzenia, deklarując je za pomocą funkcji DECLARE_HOOK() lub DECLARE_RESTRICTED_HOOK(), a następnie dodając je do kodu jako punkt śledzenia. Jeśli na przykład chcesz dodać funkcję trace_android_vh_sched_exit() do istniejącej funkcji jądra do_exit():

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

Funkcja trace_android_vh_sched_exit() sprawdza najpierw, czy coś jest dołączone. Jeśli jednak moduł dostawcy zarejestruje moduł obsługi za pomocą funkcji register_trace_android_vh_sched_exit(), zostanie wywołana zarejestrowana funkcja. Moduł obsługi musi znać kontekst dotyczący wstrzymywanych zamków, stanu RCS i innych czynników. Punkt zaczepienia musi być zdefiniowany w pliku nagłówka w katalogu include/trace/hooks.

Na przykład poniższy kod przedstawia możliwą deklarację funkcji trace_android_vh_sched_exit() w pliku 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>

Aby utworzyć instancje interfejsów wymaganych przez element dostawcy, dodaj plik nagłówka z deklaracją elementu do pliku drivers/android/vendor_hooks.c i wyeksportuj symbole. Na przykład ten kod uzupełnia deklarację 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);

UWAGA: aby zapewnić stabilność ABI, należy w pełni zdefiniować struktury danych używane w deklaracji haka. W przeciwnym razie depozytowanie nieprzezroczystych wskaźników lub używanie struktury w kontekstach o określonej wielkości nie jest bezpieczne. Element include, który zawiera pełną definicję takich struktur danych, powinien znajdować się w sekcji #ifndef __GENKSYMS__ w pliku drivers/android/vendor_hooks.c. Pliki nagłówków w folderze include/trace/hooks nie powinny zawierać pliku nagłówka jądra z definicjami typów, aby uniknąć zmian CRC, które naruszają KMI. Zamiast tego przekaż deklarację typów.

Dołączanie do punktów wywołania dostawcy

Aby używać punktów zaczepienia dostawcy, moduł dostawcy musi zarejestrować moduł obsługi tego haka (zwykle odbywa się to podczas jego inicjowania). Na przykład kod poniżej pokazuje moduł foo.ko obsługujący trace_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);
    ...
}

Używanie haka dostawcy z plików nagłówkowych

Aby używać elementów dostawcy z plików nagłówka, konieczne może być zaktualizowanie pliku nagłówka elementu dostawcy w celu odkomentowania TRACE_INCLUDE_PATH. Pozwoli to uniknąć błędów kompilacji, które wskazują, że nie można znaleźć pliku nagłówka punktu śledzenia. Na przykład

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.

Aby naprawić ten błąd kompilacji, zastosuj odpowiednią poprawkę do dołączonego pliku nagłówka webhooka dostawcy. Więcej informacji znajdziesz na stronie 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

Definiowanie UNDEF_TRACE_INCLUDE_PATH powoduje, że include/trace/define_trace.h odwołuje definicję TRACE_INCLUDE_PATH po utworzeniu punktów śledzenia.

Podstawowe funkcje jądra

Jeśli żadna z poprzednich metod nie umożliwia implementacji funkcji z modułu, musisz dodać tę funkcję jako modyfikację specyficzną dla Androida do jądra. Aby rozpocząć rozmowę, utwórz zgłoszenie w narzędziu do śledzenia zgłoszeń (IT).

Interfejs programowania aplikacji użytkownika (UAPI)

  • Pliki nagłówka UAPI Zmiany w plikach nagłówków UAPI muszą być wprowadzane w górę, chyba że dotyczą interfejsów specyficznych dla Androida. Do definiowania interfejsów między modułami dostawcy i kodem przestrzeni użytkownika dostawcy należy używać plików nagłówka odpowiednich dla konkretnego dostawcy.
  • węzła sysfs. Nie dodawaj nowych węzłów sysfs do jądra GKI (takie dodatki są ważne tylko w modułach dostawców). Węzły sysfs używane przez biblioteki niezależne od SoC i urządzenia oraz kod Java, które stanowią podstawę platformy Android, można zmieniać tylko w zgodny sposób. Jeśli nie są to węzły sysfs specyficzne dla Androida, muszą być zmieniane w górę. Możesz utworzyć węzły sysfs dla konkretnego producenta, które będą używane przez jego przestrzeń użytkownika. Domyślnie dostęp do węzłów sysfs przez przestrzeń użytkownika jest zabroniony przez SELinux. Do dostawcy należy dodanie odpowiednich etykiet SELinux, które pozwolą na dostęp dla autoryzowanego oprogramowania dostawcy.
  • Węzły DebugFS. Moduły dostawców mogą definiować węzły w debugfs tylko do debugowania (ponieważ debugfs nie jest montowany podczas normalnej pracy urządzenia).