Wytyczne dotyczące modułu dostawcy

Skorzystaj z poniższych wskazówek, aby zwiększyć solidność i niezawodność modułów dostawcy. Stosowanie się do wielu wskazówek może pomóc w ustaleniu prawidłowej kolejności ładowania modułów i kolejności, w jakiej sterowniki muszą sondować urządzenia.

Modułem może być biblioteka lub sterownik .

  • Moduły biblioteczne to biblioteki udostępniające interfejsy API, z których mogą korzystać inne moduły. Takie moduły zazwyczaj nie są specyficzne dla sprzętu. Przykłady modułów bibliotecznych obejmują moduł szyfrowania AES, framework remoteproc skompilowany jako moduł i moduł bufora logu. Kod modułu w module_init() jest uruchamiany w celu skonfigurowania struktur danych, ale żaden inny kod nie jest uruchamiany, chyba że zostanie wyzwolony przez moduł zewnętrzny.

  • Moduły sterowników to sterowniki, które sondują lub wiążą się z określonym typem urządzenia. Takie moduły są specyficzne dla sprzętu. Przykłady modułów sterowników obejmują sprzęt UART, PCIe i koder wideo. Moduły sterowników aktywują się tylko wtedy, gdy w systemie obecne jest powiązane z nimi urządzenie.

    • Jeśli urządzenie nie jest obecne, jedynym uruchamianym kodem modułu jest kod module_init() , który rejestruje sterownik w strukturze rdzenia sterownika.

    • Jeśli urządzenie jest obecne, a sterownik pomyślnie wyszuka to urządzenie lub połączy się z nim, może zostać uruchomiony inny kod modułu.

Użyj modułu init/exit poprawnie

Moduły sterownika muszą zarejestrować sterownik w module_init() i wyrejestrować sterownik w module_exit() . Prostym sposobem na wyegzekwowanie tych ograniczeń jest użycie makr opakowania, co pozwala uniknąć bezpośredniego użycia makr module_init() , *_initcall() lub module_exit() .

  • W przypadku modułów, które można wyładować, użyj module_ subsystem _driver() . Przykłady: module_platform_driver() , module_i2c_driver() i module_pci_driver() .

  • W przypadku modułów, których nie można wyładować, użyj builtin_ subsystem _driver() Przykłady: builtin_platform_driver() , builtin_i2c_driver() i builtin_pci_driver() .

Niektóre moduły sterowników używają module_init() i module_exit() , ponieważ rejestrują więcej niż jeden sterownik. W przypadku modułu sterownika, który używa module_init() i module_exit() do rejestracji wielu sterowników, spróbuj połączyć sterowniki w jeden sterownik. Na przykład można rozróżnić, używając compatible ciągu znaków lub danych pomocniczych urządzenia, zamiast rejestrować oddzielne sterowniki. Alternatywnie można podzielić moduł sterownika na dwa moduły.

Wyjątki funkcji inicjowania i wyjścia

Moduły biblioteczne nie rejestrują sterowników i są zwolnione z ograniczeń dotyczących module_init() i module_exit() , ponieważ mogą potrzebować tych funkcji do konfigurowania struktur danych, kolejek roboczych lub wątków jądra.

Użyj makra MODULE_DEVICE_TABLE

Moduły sterowników muszą zawierać makro MODULE_DEVICE_TABLE , które umożliwia przestrzeni użytkownika określenie urządzeń obsługiwanych przez moduł sterownika przed załadowaniem modułu. Android może wykorzystać te dane do optymalizacji ładowania modułów, np. uniknięcia ładowania modułów dla urządzeń, których nie ma w systemie. Przykłady użycia makra można znaleźć w kodzie źródłowym.

Unikaj niedopasowań CRC ze względu na typy danych zadeklarowane w przód

Nie dołączaj plików nagłówkowych, aby uzyskać wgląd w typy danych zadeklarowane w przód. Niektóre struktury, unie i inne typy danych zdefiniowane w pliku nagłówkowym ( header-Ah ) można zadeklarować w przód w innym pliku nagłówkowym ( header-Bh ), który zazwyczaj używa wskaźników do tych typów danych. Ten wzorzec kodu oznacza, że ​​jądro celowo próbuje zachować prywatność struktury danych dla użytkowników header-Bh .

Użytkownicy header-Bh nie powinni dołączać header-Ah aby uzyskać bezpośredni dostęp do elementów wewnętrznych tych zadeklarowanych w przód struktur danych. Takie postępowanie powoduje problemy z niedopasowaniem CRC CONFIG_MODVERSIONS (co generuje problemy ze zgodnością z ABI), gdy inne jądro (takie jak jądro GKI) próbuje załadować moduł.

Na przykład struct fwnode_handle jest zdefiniowana w include/linux/fwnode.h , ale w przód jest zadeklarowana jako struct fwnode_handle; w include/linux/device.h , ponieważ jądro stara się zachować szczegóły struct fwnode_handle w tajemnicy przed użytkownikami include/linux/device.h . W tym scenariuszu nie dodawaj #include <linux/fwnode.h> do modułu, aby uzyskać dostęp do elementów struct fwnode_handle . Każdy projekt, w którym trzeba uwzględnić takie pliki nagłówkowe, wskazuje na zły wzorzec projektowy.

Nie uzyskuj bezpośredniego dostępu do podstawowych struktur jądra

Bezpośredni dostęp lub modyfikowanie podstawowych struktur danych jądra może prowadzić do niepożądanych zachowań, w tym wycieków pamięci, awarii i zepsutej kompatybilności z przyszłymi wydaniami jądra. Struktura danych jest podstawową strukturą danych jądra, jeśli spełnia którykolwiek z następujących warunków:

  • Struktura danych jest zdefiniowana w KERNEL-DIR /include/ . Na przykład struct device i struct dev_links_info . Struktury danych zdefiniowane w include/linux/soc są wyłączone.

  • Struktura danych jest przydzielana lub inicjowana przez moduł, ale staje się widoczna dla jądra poprzez przekazanie jej, pośrednio (przez wskaźnik w strukturze) lub bezpośrednio, jako dane wejściowe funkcji eksportowanej przez jądro. Na przykład moduł sterownika cpufreq inicjuje struct cpufreq_driver , a następnie przekazuje ją jako dane wejściowe do cpufreq_register_driver() . Od tego momentu moduł sterownika cpufreq nie powinien bezpośrednio modyfikować struct cpufreq_driver , ponieważ wywołanie funkcji cpufreq_register_driver() powoduje, struct cpufreq_driver jest widoczna dla jądra.

  • Struktura danych nie została zainicjowana przez Twój moduł. Na przykład struct regulator_dev zwrócona przez regulator_register() .

Dostęp do podstawowych struktur danych jądra można uzyskać tylko poprzez funkcje eksportowane przez jądro lub poprzez parametry jawnie przekazywane jako dane wejściowe do haków dostawcy. Jeśli nie masz API lub haka dostawcy do modyfikowania części podstawowej struktury danych jądra, prawdopodobnie jest to zamierzone i nie powinieneś modyfikować struktury danych z modułów. Na przykład nie modyfikuj żadnych pól wewnątrz struct device lub struct device.links .

  • Aby zmodyfikować device.devres_head , użyj funkcji devm_*() takiej jak devm_clk_get() , devm_regulator_get() lub devm_kzalloc() .

  • Aby zmodyfikować pola w struct device.links , użyj interfejsu API łącza urządzenia, takiego jak device_link_add() lub device_link_del() .

Nie analizuj węzłów drzewa urządzeń ze zgodną właściwością

Jeśli węzeł drzewa urządzeń (DT) ma compatible właściwość, struct device jest dla niego przydzielane automatycznie lub po wywołaniu funkcji of_platform_populate() w nadrzędnym węźle DT (zwykle przez sterownik urządzenia nadrzędnego). Domyślne oczekiwanie (z wyjątkiem niektórych urządzeń zainicjowanych wcześniej dla programu planującego) jest takie, że węzeł DT z compatible właściwością ma struct device i pasujący sterownik urządzenia. Wszystkie inne wyjątki są już obsługiwane przez kod nadrzędny.

Ponadto fw_devlink (wcześniej nazywany of_devlink ) traktuje węzły DT z właściwością compatible jako urządzenia z przydzielonym struct device , które jest sprawdzane przez sterownik. Jeśli węzeł DT ma compatible właściwość, ale przydzielone struct device nie jest sondowane, fw_devlink może zablokować sondowanie swoim urządzeniom konsumenckim lub może zablokować wywoływanie wywołań sync_state() dla urządzeń dostawców.

Jeśli Twój sterownik używa funkcji of_find_*() (takiej jak of_find_node_by_name() lub of_find_compatible_node() ) do bezpośredniego znalezienia węzła DT, który ma compatible właściwość, a następnie przeanalizowania tego węzła DT, napraw moduł, pisząc sterownik urządzenia, który może sondować urządzenie lub usuń compatible właściwość (możliwe tylko wtedy, gdy nie została przesłana). Aby omówić alternatywy, skontaktuj się z zespołem jądra systemu Android pod adresem kernel-team@android.com i przygotuj się na uzasadnienie swoich przypadków użycia.

Użyj uchwytów DT, aby wyszukać dostawców

Jeśli to możliwe, skontaktuj się z dostawcą używając phandle (odniesienie/wskaźnik do węzła DT) w DT. Używanie standardowych powiązań ID i phandles do odwoływania się do dostawców umożliwia fw_devlink (wcześniej of_devlink ) automatyczne określanie zależności między urządzeniami poprzez analizowanie ID w czasie wykonywania. Jądro może następnie automatycznie sprawdzać urządzenia we właściwej kolejności, eliminując potrzebę porządkowania ładowania modułów lub MODULE_SOFTDEP() .

Starszy scenariusz (brak obsługi DT w jądrze ARM)

Wcześniej, zanim do jąder ARM dodano obsługę DT, konsumenci, tacy jak urządzenia dotykowe, wyszukiwali dostawców, takich jak organy regulacyjne, przy użyciu globalnie unikalnych ciągów znaków. Na przykład sterownik ACME PMIC może rejestrować lub ogłaszać wiele regulatorów (takich jak acme-pmic-ldo1 do acme-pmic-ldo10 ), a sterownik dotykowy może wyszukiwać regulator za pomocą regulator_get(dev, "acme-pmic-ldo10") . Jednakże na innej płycie LDO8 może zasilać urządzenie dotykowe, tworząc uciążliwy system, w którym ten sam sterownik dotykowy musi określić prawidłowy ciąg wyszukiwania dla regulatora dla każdej płyty, w której używane jest urządzenie dotykowe.

Obecny scenariusz (obsługa DT w jądrze ARM)

Po dodaniu obsługi DT do jąder ARM, konsumenci mogą identyfikować dostawców w DT, odwołując się do węzła drzewa urządzeń dostawcy za pomocą phandle . Konsumenci mogą również nazwać zasób na podstawie tego, do czego jest używany, a nie tego, kto go dostarcza. Na przykład sterownik dotykowy z poprzedniego przykładu mógłby użyć regulator_get(dev, "core") i regulator_get(dev, "sensor") , aby uzyskać dostawców zasilających rdzeń i czujnik urządzenia dotykowego. Powiązany identyfikator takiego urządzenia jest podobny do poniższego przykładowego kodu:

touch-device {
    compatible = "fizz,touch";
    ...
    core-supply = <&acme_pmic_ldo4>;
    sensor-supply = <&acme_pmic_ldo10>;
};

acme-pmic {
    compatible = "acme,super-pmic";
    ...
    acme_pmic_ldo4: ldo4 {
        ...
    };
    ...
    acme_pmic_ldo10: ldo10 {
        ...
    };
};

Najgorszy scenariusz z obu światów

Niektóre sterowniki przeniesione ze starszych jąder zawierają starsze zachowanie w DT, które przejmuje najgorszą część starszego schematu i wymusza to na nowszym schemacie, który ma ułatwić wszystko. W takich sterownikach sterownik konsumenta odczytuje ciąg znaków, który ma być użyty do wyszukiwania przy użyciu właściwości DT specyficznej dla urządzenia, dostawca używa innej właściwości specyficznej dla dostawcy w celu zdefiniowania nazwy, która będzie używana do rejestracji zasobu dostawcy, następnie konsument i dostawca w dalszym ciągu korzystają z ten sam stary schemat używania ciągów znaków do wyszukiwania dostawcy. W tym najgorszym z obu światów scenariuszu:

  • Sterownik dotykowy wykorzystuje kod podobny do następującego:

    str = of_property_read(np, "fizz,core-regulator");
    core_reg = regulator_get(dev, str);
    str = of_property_read(np, "fizz,sensor-regulator");
    sensor_reg = regulator_get(dev, str);
    
  • DT używa kodu podobnego do następującego:

    touch-device {
      compatible = "fizz,touch";
      ...
      fizz,core-regulator = "acme-pmic-ldo4";
      fizz,sensor-regulator = "acme-pmic-ldo4";
    };
    acme-pmic {
      compatible = "acme,super-pmic";
      ...
      ldo4 {
        regulator-name = "acme-pmic-ldo4"
        ...
      };
      ...
      acme_pmic_ldo10: ldo10 {
        ...
        regulator-name = "acme-pmic-ldo10"
      };
    };
    

Nie modyfikuj błędów interfejsu API platformy

Frameworkowe interfejsy API, takie jak regulator , clocks , irq , gpio , phys i extcon , zwracają -EPROBE_DEFER jako wartość zwracaną przez błąd, wskazującą, że urządzenie próbuje sondować, ale nie może w tej chwili, a jądro powinno ponowić próbę sondowania później. Aby w takich przypadkach funkcja .probe() urządzenia nie działała zgodnie z oczekiwaniami, nie zastępuj ani nie zmieniaj mapowania wartości błędu. Zastąpienie lub ponowne mapowanie wartości błędu może spowodować usunięcie -EPROBE_DEFER i uniemożliwienie sondowania urządzenia.

Użyj wariantów API devm_*().

Gdy urządzenie pozyskuje zasób przy użyciu interfejsu API devm_*() , zasób jest automatycznie zwalniany przez jądro, jeśli urządzenie nie wykona sondowania lub pomyślnie sonduje, a później zostanie niezwiązane. Ta funkcjonalność sprawia, że ​​kod obsługi błędów w funkcji probe() jest czystszy, ponieważ nie wymaga skoków goto w celu zwolnienia zasobów uzyskanych przez devm_*() i upraszcza operacje usuwania powiązań sterowników.

Obsługuj usuwanie powiązania sterownika urządzenia

Świadomie rozwiązuj sterowniki urządzeń i nie pozostawiaj opcji niezdefiniowanej, ponieważ wartość niezdefiniowana nie oznacza niedozwolona. Musisz albo w pełni zaimplementować usuwanie powiązania sterownika urządzenia , albo jawnie wyłączyć usuwanie powiązania sterownika urządzenia.

Implementowanie usuwania powiązania sterownika urządzenia

Decydując się na pełne wdrożenie usuwania powiązania sterowników urządzeń, należy je rozłączyć w czysty sposób, aby uniknąć wycieków pamięci lub zasobów oraz problemów z bezpieczeństwem. Można powiązać urządzenie ze sterownikiem, wywołując funkcję probe() sterownika i odłączyć urządzenie, wywołując funkcję remove() sterownika. Jeśli nie istnieje żadna funkcja remove() , jądro może nadal odłączyć urządzenie; rdzeń sterownika zakłada, że ​​sterownik nie musi wykonywać żadnych czynności porządkowych po odłączeniu się od urządzenia. Sterownik niezwiązany z urządzeniem nie musi wykonywać żadnych jawnych działań czyszczących, jeśli spełnione są oba poniższe warunki:

  • Wszystkie zasoby uzyskiwane przez funkcję probe() sterownika są przekazywane poprzez interfejsy API devm_*() .

  • Urządzenie sprzętowe nie wymaga sekwencji wyłączania ani wyciszania.

W tej sytuacji rdzeń sterownika obsługuje zwalnianie wszystkich zasobów uzyskanych poprzez interfejsy API devm_*() . Jeśli którekolwiek z powyższych stwierdzeń jest nieprawdziwe, sterownik musi przeprowadzić czyszczenie (zwolnić zasoby i wyłączyć lub wyciszyć sprzęt) po odłączeniu się od urządzenia. Aby mieć pewność, że urządzenie będzie mogło bezproblemowo odłączyć moduł sterownika, użyj jednej z następujących opcji:

  • Jeśli sprzęt nie wymaga sekwencji wyłączania lub wyciszania, zmień moduł urządzenia, aby uzyskać zasoby za pomocą interfejsów API devm_*() .

  • Zaimplementuj operację sterownika remove() w tej samej strukturze, co funkcja probe() , a następnie wykonaj kroki czyszczenia, korzystając z funkcji remove() .

Jawne wyłączenie usuwania powiązania sterownika urządzenia (niezalecane)

Decydując się na jawne wyłączenie usuwania powiązania sterownika urządzenia, musisz uniemożliwić rozłączanie i wyładowanie modułu.

  • Aby uniemożliwić rozłączenie, ustaw flagę suppress_bind_attrs na true w struct device_driver ; to ustawienie zapobiega wyświetlaniu plików bind i unbind w katalogu sysfs sterownika. Plik unbind pozwala przestrzeni użytkownika na wyzwolenie usunięcia powiązania sterownika z jego urządzenia.

  • Aby uniemożliwić wyładowanie modułu, upewnij się, że moduł ma [permanent] w lsmod . Jeśli nie użyjesz module_exit() lub module_XXX_driver() , moduł zostanie oznaczony jako [permanent] .

Nie ładuj oprogramowania sprzętowego z poziomu funkcji sondy

Sterownik nie powinien ładować oprogramowania sprzętowego za pomocą funkcji .probe() , ponieważ może nie mieć do niego dostępu, jeśli sterownik sprawdzi przed zamontowaniem systemu plików opartego na pamięci flash lub trwałej pamięci masowej. W takich przypadkach funkcja API request_firmware*() może blokować się przez długi czas, a następnie zawieść, co może niepotrzebnie spowolnić proces uruchamiania. Zamiast tego odłóż ładowanie oprogramowania sprzętowego do momentu, gdy klient zacznie korzystać z urządzenia. Na przykład sterownik wyświetlacza może załadować oprogramowanie sprzętowe po otwarciu urządzenia wyświetlającego.

Użycie .probe() do załadowania oprogramowania sprzętowego może być w niektórych przypadkach OK, na przykład w przypadku sterownika zegara, który wymaga oprogramowania sprzętowego do działania, ale urządzenie nie jest dostępne w przestrzeni użytkownika. Możliwe są inne odpowiednie przypadki użycia.

Zaimplementuj sondowanie asynchroniczne

Obsługuj i używaj sondowania asynchronicznego, aby korzystać z przyszłych ulepszeń, takich jak ładowanie modułów równoległych lub sondowanie urządzeń w celu przyspieszenia czasu uruchamiania, które mogą zostać dodane do systemu Android w przyszłych wersjach. Moduły sterowników, które nie korzystają z sondowania asynchronicznego, mogą zmniejszyć skuteczność takich optymalizacji.

Aby oznaczyć sterownik jako obsługujący i preferujący sondowanie asynchroniczne, ustaw pole probe_type w elemencie członkowskim struct device_driver sterownika. Poniższy przykład pokazuje, że taka obsługa jest włączona dla sterownika platformy:

static struct platform_driver acme_driver = {
        .probe          = acme_probe,
        ...
        .driver         = {
                .name   = "acme",
                ...
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

Sprawienie, że sterownik będzie współpracował z sondowaniem asynchronicznym, nie wymaga specjalnego kodu. Podczas dodawania obsługi sondowania asynchronicznego należy jednak pamiętać o następujących kwestiach.

  • Nie należy przyjmować założeń dotyczących wcześniej zbadanych zależności. Sprawdź bezpośrednio lub pośrednio (większość wywołań frameworka) i zwróć -EPROBE_DEFER , jeśli jeden lub więcej dostawców nie jest jeszcze gotowych.

  • Jeśli dodasz urządzenia podrzędne w funkcji sondowania urządzenia nadrzędnego, nie zakładaj, że urządzenia podrzędne zostaną sprawdzone natychmiast.

  • Jeśli sonda ulegnie awarii, wykonaj odpowiednią obsługę błędów i wyczyść (zobacz Korzystanie z wariantów API devm_*() ).

Nie używaj MODULE_SOFTDEP do zamawiania sond urządzeń

Funkcja MODULE_SOFTDEP() nie jest niezawodnym rozwiązaniem gwarantującym kolejność sondowania urządzeń i nie wolno jej używać z następujących powodów.

  • Odroczona sonda. Po załadowaniu modułu sonda urządzenia może zostać odroczona, ponieważ jeden z jego dostawców nie jest gotowy. Może to prowadzić do niezgodności pomiędzy kolejnością ładowania modułu a kolejnością sond urządzenia.

  • Jeden sterownik, wiele urządzeń. Moduł sterownika może zarządzać określonym typem urządzenia. Jeśli system zawiera więcej niż jedno wystąpienie danego typu urządzenia i każde z tych urządzeń ma inne wymagania dotyczące kolejności sond, nie można spełnić tych wymagań przy użyciu kolejności ładowania modułów.

  • Sondowanie asynchroniczne. Moduły sterowników wykonujące sondowanie asynchroniczne nie sondują urządzenia natychmiast po załadowaniu modułu. Zamiast tego wątek równoległy obsługuje sondowanie urządzeń, co może prowadzić do niezgodności między kolejnością ładowania modułów a kolejnością sondowania urządzeń. Na przykład, gdy główny moduł sterownika I2C wykonuje sondowanie asynchroniczne, a moduł sterownika dotykowego zależy od modułu PMIC znajdującego się na magistrali I2C, nawet jeśli sterownik dotykowy i sterownik PMIC ładują się we właściwej kolejności, może zostać podjęta próba sondowania sterownika dotykowego przed sonda sterownika PMIC.

Jeśli masz moduły sterowników korzystające z funkcji MODULE_SOFTDEP() , napraw je, aby nie korzystały z tej funkcji. Aby Ci pomóc, zespół Androida wprowadził zmiany, które umożliwiają jądru obsługę problemów z porządkowaniem bez użycia MODULE_SOFTDEP() . W szczególności możesz użyć fw_devlink aby zapewnić kolejność sondowania i (po sondowaniu przez wszystkich konsumentów urządzenia) użyć wywołania zwrotnego sync_state() w celu wykonania wszelkich niezbędnych zadań.

W przypadku konfiguracji użyj #if IS_ENABLED() zamiast #ifdef

Użyj #if IS_ENABLED(CONFIG_XXX) zamiast #ifdef CONFIG_XXX , aby mieć pewność, że kod wewnątrz bloku #if będzie się nadal kompilował, jeśli konfiguracja zmieni się w przyszłości na konfigurację trójstanową. Różnice są następujące:

  • #if IS_ENABLED(CONFIG_XXX) ma wartość true , gdy CONFIG_XXX jest ustawione na moduł ( =m ) lub wbudowany ( =y ).

  • #ifdef CONFIG_XXX ma wartość true , gdy CONFIG_XXX jest ustawione na wbudowane ( =y ), ale nie, gdy CONFIG_XXX jest ustawione na moduł ( =m ). Używaj tej opcji tylko wtedy, gdy jesteś pewien, że chcesz zrobić to samo, gdy konfiguracja jest ustawiona na moduł lub jest wyłączona.

Użyj prawidłowego makra do kompilacji warunkowych

Jeśli CONFIG_XXX jest ustawione na moduł ( =m ), system kompilacji automatycznie definiuje CONFIG_XXX_MODULE . Jeśli Twój sterownik jest kontrolowany przez CONFIG_XXX i chcesz sprawdzić, czy sterownik jest kompilowany jako moduł, skorzystaj z poniższych wskazówek:

  • W pliku C (lub dowolnym pliku źródłowym, który nie jest plikiem nagłówkowym) sterownika, nie używaj #ifdef CONFIG_XXX_MODULE , ponieważ jest to niepotrzebnie ograniczające i psuje się, jeśli nazwa konfiguracji zostanie zmieniona na CONFIG_XYZ . W przypadku dowolnego pliku źródłowego innego niż nagłówek, który jest skompilowany w moduł, system kompilacji automatycznie definiuje MODULE dla zakresu tego pliku. Dlatego też, aby sprawdzić, czy jako część modułu kompilowany jest plik C (lub inny plik źródłowy niebędący nagłówkiem), należy użyć #ifdef MODULE (bez przedrostka CONFIG_ ).

  • W plikach nagłówkowych to samo sprawdzenie jest trudniejsze, ponieważ pliki nagłówkowe nie są kompilowane bezpośrednio do pliku binarnego, ale raczej kompilowane jako część pliku C (lub innych plików źródłowych). Użyj następujących reguł dla plików nagłówkowych:

    • W przypadku pliku nagłówkowego używającego #ifdef MODULE wynik zmienia się w zależności od tego, który plik źródłowy go używa. Oznacza to, że ten sam plik nagłówkowy w tej samej kompilacji może mieć różne części kodu skompilowane dla różnych plików źródłowych (moduł w porównaniu z wbudowanym lub wyłączonym). Może to być przydatne, gdy chcesz zdefiniować makro, które musi rozwijać się w jeden sposób w przypadku kodu wbudowanego i w inny sposób w przypadku modułu.

    • W przypadku pliku nagłówkowego, który wymaga skompilowania fragmentu kodu, gdy określona CONFIG_XXX jest ustawiona na moduł (niezależnie od tego, czy plik źródłowy zawierający ten plik jest modułem), plik nagłówkowy musi używać #ifdef CONFIG_XXX_MODULE .