Tworzenie kodu jądra dla GKI

Ogólny obraz jądra (GKI) zmniejsza fragmentację jądra dzięki ścisłemu dopasowaniu do jądra Linux w wersji upstream. Istnieją jednak uzasadnione powody, dla których niektóre poprawki nie mogą zostać zaakceptowane w górę, a także harmonogramy produktów, które muszą być spełnione, więc niektóre poprawki są utrzymywane w źródłach wspólnego jądra Androida (ACK), z których jest budowane GKI.

Deweloperzy muszą przesyłać zmiany w kodzie do projektu nadrzędnego, korzystając w pierwszej kolejności z listy mailingowej jądra Linuksa (LKML). Zmiany w kodzie należy przesyłać do gałęzi ACKandroid-mainline tylko wtedy, gdy istnieje ważny powód, dla którego projekt nadrzędny nie jest odpowiedni. Poniżej znajdziesz przykłady prawidłowych powodów i sposobów ich obsługi.

  • Poprawka została przesłana do LKML, ale nie została zaakceptowana na czas przed wydaniem produktu. Aby zastosować tę poprawkę:

    • Prześlij dowód, że poprawka została przesłana do LKML i że otrzymano do niej komentarze, lub podaj szacowany czas przesłania poprawki do upstream.
    • Zdecyduj, co zrobić, aby wprowadzić poprawkę do ACK, uzyskać jej zatwierdzenie w wersji upstream, a następnie usunąć ją z ACK, gdy ostateczna wersja upstream zostanie scalona z ACK.
  • Poprawka definiuje EXPORT_SYMBOLS_GPL() dla modułu dostawcy, ale nie można jej przesłać do repozytorium upstream, ponieważ nie ma w drzewie modułów, które korzystają z tego symbolu. Aby obsłużyć tę poprawkę, podaj szczegóły dotyczące tego, dlaczego nie można przesłać modułu do upstreamu, oraz alternatywy, które zostały rozważone przed przesłaniem tej prośby.

  • Poprawka nie jest wystarczająco ogólna, aby można było ją przesłać do upstreamu, a przed wydaniem produktu nie ma czasu na jej refaktoryzację. Aby zastosować tę poprawkę, podaj szacowany czas, w którym zmodyfikowana poprawka zostanie przesłana do upstreamu (poprawka nie zostanie zaakceptowana w ACK bez planu przesłania zmodyfikowanej poprawki do upstreamu w celu sprawdzenia).

  • Poprawka nie może zostać zaakceptowana przez upstream, ponieważ… <insert reason here>. Aby zastosować tę poprawkę, skontaktuj się z zespołem jądra Androida i wspólnie z nami opracuj opcje refaktoryzacji poprawki, aby można było ją przesłać do sprawdzenia i zaakceptować w wersji upstream.

Istnieje wiele innych potencjalnych uzasadnień. Przesyłając błąd lub poprawkę, podaj uzasadnienie i przygotuj się na dyskusję. Zdajemy sobie sprawę, że ACK zawiera pewne poprawki, zwłaszcza we wczesnych fazach GKI, gdy wszyscy uczą się, jak pracować w górę strumienia, ale nie mogą z tego powodu opóźniać harmonogramów produktów. Z czasem wymagania dotyczące przesyłania zmian do górnego strumienia będą się stawać bardziej rygorystyczne.

Wymagania dotyczące poprawek

Poprawki muszą być zgodne ze standardami kodowania jądra systemu Linux opisanymi w drzewie źródłowym systemu Linux, niezależnie od tego, czy są przesyłane do projektu upstream, czy do ACK. scripts/checkpatch.plSkrypt jest uruchamiany w ramach testów wstępnych Gerrit, więc uruchom go z wyprzedzeniem, aby upewnić się, że przejdzie testy. Aby uruchomić skrypt checkpatch z tą samą konfiguracją co w przypadku testów przed przesłaniem, użyj //build/kernel/static_analysis:checkpatch_presubmit. Szczegółowe informacje znajdziesz w pliku build/kernel/kleaf/docs/checkpatch.md.

Poprawki ACK

Poprawki przesyłane do ACK muszą być zgodne ze standardami kodowania jądra systemu Linux i wytycznymi dotyczącymi współtworzenia. W komunikacie o zatwierdzeniu musisz umieścić tag Change-Id. Jeśli przesyłasz poprawkę do wielu gałęzi (np. android-mainlineandroid12-5.4), musisz użyć tego samego tagu Change-Id we wszystkich przypadkach.

Najpierw prześlij poprawki do LKML w celu sprawdzenia przez zespół upstream. Jeśli poprawka:

  • Jeśli zostanie zaakceptowana, zostanie automatycznie scalona z android-mainline.
  • Nie zaakceptowano w górę strumienia. Prześlij go do android-mainline, podając odniesienie do przesłanego w górę strumienia lub wyjaśnienie, dlaczego nie został przesłany do LKML.

Gdy poprawka zostanie zaakceptowana w górę lub w android-mainline, można ją przenieść do odpowiedniego ACK opartego na LTS (np. android12-5.4android11-5.4 w przypadku poprawek, które naprawiają kod specyficzny dla Androida). Przesłanie do android-mainline umożliwia testowanie nowych wersji kandydujących do publikacji i gwarantuje, że poprawka znajdzie się w następnym ACK opartym na LTS. Wyjątki obejmują przypadki, w których poprawka z wcześniejszej wersji jest przenoszona do android12-5.4 (ponieważ prawdopodobnie jest już w android-mainline).

Poprawki na wyższym poziomie

Zgodnie z wytycznymi dotyczącymi przesyłania zmian poprawki upstream przeznaczone dla jąder ACK dzielą się na te grupy (podane w kolejności prawdopodobieństwa przyjęcia):

  • UPSTREAM: – poprawki wybrane z gałęzi „android-mainline” prawdopodobnie zostaną zaakceptowane w ACK, jeśli istnieje uzasadniony przypadek użycia.
  • BACKPORT: – Poprawki z upstreamu, które nie są łatwe do wyodrębnienia i wymagają modyfikacji, również mogą zostać zaakceptowane, jeśli istnieje uzasadniony przypadek użycia.
  • FROMGIT: – Poprawki wybrane z gałęzi opiekuna w ramach przygotowań do przesłania do głównej gałęzi Linuksa mogą zostać zaakceptowane, jeśli zbliża się termin. Muszą być uzasadnione zarówno w przypadku treści, jak i harmonogramu.
  • FROMLIST: – poprawki, które zostały przesłane do LKML, ale nie zostały jeszcze zaakceptowane w gałęzi opiekuna, prawdopodobnie nie zostaną zaakceptowane, chyba że uzasadnienie jest wystarczająco przekonujące, aby poprawka została zaakceptowana niezależnie od tego, czy trafi do głównej gałęzi systemu Linux (zakładamy, że tak się nie stanie). Musi wystąpić problem związany z łatkami FROMLIST, aby można było omówić go z zespołem jądra Androida.

Poprawki dotyczące Androida

Jeśli nie możesz wprowadzić wymaganych zmian w górę, możesz spróbować przesłać poprawki zewnętrzne bezpośrednio do ACK. Przesyłanie poprawek poza drzewem wymaga utworzenia zgłoszenia w systemie IT, w którym należy podać poprawkę i uzasadnienie, dlaczego nie można jej przesłać do upstreamu (przykłady znajdziesz na poprzedniej liście). W niektórych przypadkach nie można jednak przesłać kodu do repozytorium nadrzędnego. Te przypadki są rozpatrywane w następujący sposób i muszą być zgodne z wytycznymi dotyczącymi wkładu w przypadku poprawek dotyczących Androida. Muszą też mieć w temacie prefiks ANDROID:.

Zmiany w gki_defconfig

Wszystkie zmiany CONFIGgki_defconfig muszą być stosowane zarówno w wersji arm64, jak i x86, chyba że CONFIG jest specyficzne dla architektury. Aby poprosić o zmianę ustawienia CONFIG, utwórz zgłoszenie w dziale IT, aby omówić tę zmianę. Wszelkie zmiany, które wpływają na interfejs modułu jądra (KMI) po jego zamrożeniu, są odrzucane.CONFIG Jeśli partnerzy poproszą o sprzeczne ustawienia w przypadku jednej konfiguracji, rozwiążemy konflikty, omawiając powiązane błędy.

Kod, który nie istnieje w wersji nadrzędnej

Nie można przesyłać do repozytorium zmian w kodzie, który jest już specyficzny dla Androida. Na przykład mimo że sterownik bindera jest utrzymywany w górę strumienia, modyfikacje funkcji dziedziczenia priorytetów sterownika bindera nie mogą być wysyłane w górę strumienia, ponieważ są specyficzne dla Androida. Wyraźnie opisz błąd i poprawkę, wyjaśniając, dlaczego nie można przesłać kodu do repozytorium nadrzędnego. W miarę możliwości podziel poprawki na części, które można przesłać do upstreamu, i części specyficzne dla Androida, których nie można przesłać do upstreamu, aby zminimalizować ilość kodu poza drzewem utrzymywanego w ACK.

Inne zmiany w tej kategorii to aktualizacje plików reprezentacji KMI, list symboli KMI, gki_defconfig, skryptów kompilacji lub konfiguracji oraz innych skryptów, które nie istnieją w wersji upstream.

Moduły zewnętrzne

Projekt Linux aktywnie odradza obsługę tworzenia modułów zewnętrznych. Jest to uzasadnione stanowisko, ponieważ opiekunowie systemu Linux nie gwarantują zgodności kodu źródłowego ani binarnego w jądrze i nie chcą obsługiwać kodu, który nie znajduje się w drzewie. Jednak GKI zapewnia zgodność interfejsu ABI w przypadku modułów dostawcy, dzięki czemu interfejsy KMI są stabilne przez cały okres obsługi jądra. Dlatego istnieje klasa zmian, które obsługują moduły dostawców i są akceptowane w ACK, ale nie w przypadku upstream.

Załóżmy na przykład, że masz poprawkę, która dodaje makra EXPORT_SYMBOL_GPL(), a moduły korzystające z eksportu nie znajdują się w drzewie źródłowym. Musisz spróbować przesłać żądanie EXPORT_SYMBOL_GPL() do repozytorium upstream i dostarczyć moduł, który używa nowo wyeksportowanego symbolu. Jeśli jednak istnieje uzasadniony powód, dla którego moduł nie jest przesyłany do repozytorium upstream, możesz przesłać poprawkę do ACK. W zgłoszeniu musisz podać uzasadnienie, dlaczego modułu nie można przesłać do repozytorium upstream. (Nie proś o wersję bez licencji GPL, EXPORT_SYMBOL()).

Ukryte konfiguracje

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

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

Wczytywalne regulatory

W przypadku struktur jądra (np. cpufreq), które obsługują moduły sterujące z możliwością wczytywania, możesz zastąpić domyślny moduł sterujący (np. moduł schedutilcpufreq). W przypadku platform (np. platformy termicznej), które nie obsługują sterowników ani modułów sterujących z możliwością ładowania, ale nadal wymagają implementacji specyficznej dla dostawcy, utwórz zgłoszenie w dziale IT i skonsultuj się z zespołem jądra Androida.

Będziemy współpracować z Tobą i osobami odpowiedzialnymi za utrzymanie projektu, aby dodać niezbędne funkcje.

Punkty zaczepienia dostawcy

W poprzednich wersjach można było dodawać modyfikacje specyficzne dla dostawcy bezpośrednio do jądra systemu. W przypadku GKI 2.0 nie jest to możliwe, ponieważ kod specyficzny dla produktu musi być zaimplementowany w modułach i nie będzie akceptowany w głównych jądrach ani w ACK. Aby włączyć funkcje dodatkowe, z których korzystają partnerzy, przy minimalnym wpływie na podstawowy kod jądra, GKI akceptuje punkty zaczepienia dostawcy, które umożliwiają wywoływanie modułów z podstawowego kodu jądra. Dodatkowo kluczowe struktury danych można uzupełnić polami danych dostawcy, które są dostępne do przechowywania danych specyficznych dla dostawcy w celu wdrożenia tych funkcji.

Haczyki dostawcy występują w 2 wariantach (normalnym i ograniczonym), które są oparte na punktach śledzenia (nie na zdarzeniach śledzenia), do których mogą być dołączane moduły dostawcy. Na przykład zamiast dodawać nową funkcję sched_exit() do księgowania po zakończeniu zadaniasched_exit(), dostawcy mogą dodać w do_exit() punkt zaczepienia, do którego może się przyłączyć moduł dostawcy w celu przetworzenia. Przykładowe wdrożenie obejmuje te punkty zaczepienia dostawcy:

  • Zwykłe punkty zaczepienia dostawcy używają DECLARE_HOOK() do tworzenia funkcji punktu śledzenia o nazwie trace_name, gdzie name to unikalny identyfikator śledzenia. Zgodnie z konwencją nazwy zwykłych funkcji dostawcy zaczynają się od android_vh, więc nazwa funkcji sched_exit() to android_vh_sched_exit.
  • Ograniczone punkty zaczepienia dostawcy są potrzebne w przypadku punktów zaczepienia harmonogramu, w których dołączona funkcja musi być wywoływana nawet wtedy, gdy procesor jest offline lub wymaga kontekstu nieatomowego. Ograniczonych punktów zaczepienia dostawcy nie można odłączyć, więc moduły, które się do nich podłączają, nigdy nie mogą zostać odładowane. Nazwy ograniczonych funkcji dostawcy zaczynają się od znaku android_rvh.

Aby dodać punkt zaczepienia dostawcy, zgłoś problem w dziale IT i prześlij poprawki (tak jak w przypadku wszystkich poprawek dotyczących Androida – problem musi istnieć i musisz podać uzasadnienie). Obsługa haków dostawcy jest dostępna tylko w ACK, więc nie wysyłaj tych poprawek do upstreamu Linuksa.

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(). Aby na przykład obsługiwać funkcje o wartości dodanej, dołącz pola do struktur, jak pokazano w tym przykładowym kodzie.

Aby uniknąć potencjalnych konfliktów między polami potrzebnymi dostawcom a polami potrzebnymi producentom OEM, producenci OEM nie mogą używać pól zadeklarowanych za pomocą makr ANDROID_VENDOR_DATA(). Zamiast tego producenci OEM muszą używać elementu ANDROID_OEM_DATA(), aby deklarować pola 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 punktów zaczepienia dostawcy

Dodaj do kodu jądra zaczepy dostawcy jako punkty śledzenia, deklarując je za pomocą DECLARE_HOOK() lub DECLARE_RESTRICTED_HOOK(), a następnie dodając je do kodu jako punkt śledzenia. Jeśli na przykład chcesz dodać 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() początkowo sprawdza tylko, 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. Procedura obsługi musi uwzględniać kontekst dotyczący utrzymywanych blokad, stanu RCS i innych czynników. Hook musi być zdefiniowany w pliku nagłówkowym w katalogu include/trace/hooks.

Na przykład poniższy kod zawiera możliwą deklarację elementu 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ć instancję interfejsów wymaganych przez wywołanie zwrotne dostawcy, dodaj plik nagłówkowy z deklaracją wywołania zwrotnego do drivers/android/vendor_hooks.c i wyeksportuj symbole. Na przykład ten kod uzupełnia deklarację haka 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: struktury danych używane w deklaracji haka muszą być w pełni zdefiniowane, aby zagwarantować stabilność interfejsu ABI. W przeciwnym razie odwoływanie się do nieprzezroczystych wskaźników lub używanie struktury w kontekstach o określonym rozmiarze jest niebezpieczne. Plik nagłówkowy, który zawiera pełną definicję takich struktur danych, powinien znajdować się w sekcji #ifndef __GENKSYMS__ pliku drivers/android/vendor_hooks.c. Pliki nagłówkowe w include/trace/hooks nie powinny zawierać pliku nagłówkowego jądra z definicjami typów, aby uniknąć zmian CRC, które powodują uszkodzenie interfejsu KMI. Zamiast tego zadeklaruj typy z wyprzedzeniem.

Dołączanie do punktów zaczepienia dostawcy

Aby używać funkcji dostawcy, moduł dostawcy musi zarejestrować procedurę obsługi funkcji (zwykle odbywa się to podczas inicjowania modułu). Na przykład poniższy kod pokazuje moduł obsługi foo.ko dla 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 funkcji dostawcy z plików nagłówkowych

Aby używać funkcji dostawcy z plików nagłówkowych, może być konieczne zaktualizowanie pliku nagłówkowego funkcji dostawcy, aby cofnąć definicję TRACE_INCLUDE_PATH i uniknąć błędów kompilacji wskazujących, że nie można znaleźć pliku nagłówkowego 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 typ błędu kompilacji, zastosuj odpowiednią poprawkę w pliku nagłówkowym funkcji dostawcy, który dołączasz. 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

Zdefiniowanie UNDEF_TRACE_INCLUDE_PATH powoduje, że include/trace/define_trace.h po utworzeniu punktów śledzenia cofa definicję TRACE_INCLUDE_PATH.

Podstawowe funkcje jądra

Jeśli żadna z powyższych metod nie umożliwia wdrożenia funkcji z modułu, musisz dodać ją jako modyfikację specyficzną dla Androida do podstawowego jądra. Aby rozpocząć rozmowę, utwórz zgłoszenie w systemie śledzenia zgłoszeń (IT).

Interfejs programowania aplikacji użytkownika (UAPI)

  • Pliki nagłówkowe UAPI Zmiany w plikach nagłówkowych UAPI muszą być wprowadzane w repozytorium nadrzędnym, chyba że dotyczą interfejsów specyficznych dla Androida. Użyj plików nagłówkowych specyficznych dla dostawcy, aby zdefiniować interfejsy między modułami dostawcy a kodem przestrzeni użytkownika dostawcy.
  • węzły sysfs. Nie dodawaj nowych węzłów sysfs do jądra GKI (takie dodatki są prawidłowe tylko w modułach dostawcy). Węzły sysfs używane przez biblioteki niezależne od SoC i urządzenia oraz kod Java, który tworzy platformę Androida, można zmieniać tylko w sposób zgodny z poprzednimi wersjami. Jeśli nie są to węzły sysfs specyficzne dla Androida, należy je zmieniać w górę strumienia. Możesz utworzyć węzły sysfs specyficzne dla dostawcy, które będą używane przez przestrzeń użytkownika dostawcy. Domyślnie dostęp do węzłów sysfs z przestrzeni użytkownika jest blokowany za pomocą SELinux. To dostawca musi dodać odpowiednie etykiety SELinux, aby umożliwić dostęp autoryzowanemu oprogramowaniu dostawcy.
  • węzły DebugFS, Moduły dostawcy mogą definiować węzły w debugfs tylko na potrzeby debugowania (ponieważ debugfs nie jest montowany podczas normalnego działania urządzenia).