Wytyczne dotyczące modułu Dostawcy

Aby zwiększyć odporność i niezawodność modułów dostawców, postępuj zgodnie z tymi wskazówkami. Postępowanie zgodnie z wielu wskazówkami może ułatwić określenie prawidłowej kolejności ładowania modułów i kolejności, w jakiej sterowniki muszą sprawdzać urządzenia.

Moduł może być biblioteką lub sterownikiem.

  • Moduły biblioteki to biblioteki, które udostępniają interfejsy API do użycia przez inne moduły. Takie moduły zwykle nie są związane z konkretnym sprzętem. Przykłady modułów biblioteki to moduł szyfrowania AES, platforma remoteproc skompilowana jako moduł oraz moduł logbuffer. Kod modułu w module_init() jest uruchamiany w celu konfigurowania struktur danych, ale żaden inny kod nie jest uruchamiany, chyba że zostanie wywołany przez moduł zewnętrzny.

  • Moduły sterownika to sterowniki, które sprawdzają lub łączą się z konkretnym typem urządzenia. Takie moduły są związane ze sprzętem. Przykłady modułów sterownika to UART, PCIe i sprzęt kodujący wideo. Moduły sterownika aktywują się tylko wtedy, gdy powiązane z nimi urządzenie jest obecne w systemie.

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

    • Jeśli urządzenie jest dostępne i sterownik może je wykryć lub z nim nawiązać połączenie, może uruchomić inny kod modułu.

Prawidłowe inicjowanie i zamykanie modułu

Moduł sterownika musi zarejestrować sterownika w module_init() i usunąć go z module_exit(). Jednym ze sposobów na wymuszenie tych ograniczeń jest używanie makr opakowania, które pozwala uniknąć bezpośredniego korzystania z makr module_init(), *_initcall() lub module_exit().

  • W przypadku modułów, które można zwolnić, użyj opcji module_subsystem_driver(). Przykłady: module_platform_driver(), module_i2c_driver()module_pci_driver().

  • W przypadku modułów, których nie można odciążyć, użyj builtin_subsystem_driver(). Przykłady: builtin_platform_driver(), builtin_i2c_driver()builtin_pci_driver().

Niektóre moduły sterownika używają module_init()module_exit(), ponieważ rejestrują więcej niż 1 sterownik. Jeśli moduł sterownika używa funkcji module_init()module_exit() do rejestrowania wielu sterowników, spróbuj połączyć sterowniki w jeden. Możesz na przykład rozróżnić je za pomocą ciągu znaków compatible lub danych pomocniczych urządzenia, zamiast rejestrować osobne sterowniki. Możesz też podzielić moduł sterownika na 2 moduły.

Wyjątki funkcji init i exit

Moduł biblioteki nie rejestruje sterowników i nie podlega ograniczeniom dotyczącym funkcji module_init()module_exit(), ponieważ może potrzebować tych funkcji do konfigurowania struktur danych, kolejek roboczych lub wątków jądra.

Korzystanie z makra MODULE_DEVICE_TABLE

Moduły sterownika muszą zawierać makro MODULE_DEVICE_TABLE, które pozwala w przestrzeni użytkownika określić urządzenia obsługiwane przez moduł sterownika przed jego wczytaniem. Android może używać tych danych do optymalizacji ładowania modułów, np. do unikania ładowania modułów na urządzeniach, których nie ma w systemie. Przykłady użycia makra znajdziesz w kodowaniu źródłowym.

Unikanie niezgodności CRC z powodu zadeklarowanych z wyprzedzeniem typów danych

Nie dołączaj plików nagłówków, aby uzyskać widoczność typów danych zadeklarowanych do przodu. Niektóre struktury, uniezależnienia i inne typy danych zdefiniowane w pliku nagłówka (header-A.h) mogą być zadeklarowane z wyprzedzeniem w innym pliku nagłówka (header-B.h), który zwykle używa wskaźników do tych typów danych. Ten wzór kodu oznacza, że jądro celowo stara się zachować prywatność struktury danych dla użytkowników header-B.h.

Użytkownicy header-B.h nie powinni używać funkcji header-A.h do bezpośredniego dostępu do wewnętrznych struktur tych zadeklarowanych z wyprzedzeniem struktur danych. Spowoduje to problemy z niezgodnością CONFIG_MODVERSIONS CRC (które generują problemy ze zgodnością ABI), gdy inne jądro (np. jądro GKI) spróbuje załadować moduł.

Na przykład funkcja struct fwnode_handle jest zdefiniowana w funkcji include/linux/fwnode.h, ale jest zadeklarowana do przodu jako struct fwnode_handle; w funkcji include/linux/device.h, ponieważ jądro próbuje zachować szczegóły funkcji struct fwnode_handle jako prywatne dla użytkowników funkcji include/linux/device.h. W tym scenariuszu nie dodawaj #include <linux/fwnode.h> w module, aby uzyskać dostęp do użytkowników grupy struct fwnode_handle. Każdy projekt, w którym musisz uwzględnić takie pliki nagłówków, wskazuje na zły wzór projektowania.

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

Bezpośredni dostęp do podstawowych struktur danych jądra lub ich modyfikacja mogą powodować niepożądane działanie, w tym wycieki pamięci, awarie i utratę zgodności z przyszłościowymi wersjami jądra. Struktura danych jest podstawową strukturą danych jądra, gdy spełnia co najmniej jeden z tych warunków:

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

  • Struktura danych jest przydzielana lub inicjowana przez moduł, ale jest widoczna dla jądra, ponieważ jest przekazywana pośrednio (za pomocą wskaźnika w strukturze) lub bezpośrednio jako dane wejściowe w funkcji wyeksportowanej przez jądro. Na przykład moduł sterownika cpufreq inicjuje obiekt struct cpufreq_driver, a następnie przekazuje go jako dane wejściowe do cpufreq_register_driver(). Po tym punkcie moduł sterownika cpufreq nie powinien modyfikować bezpośrednio struct cpufreq_driver, ponieważ wywołanie funkcji cpufreq_register_driver() powoduje, że struct cpufreq_driver staje się widoczne dla jądra.

  • Struktura danych nie została zainicjowana przez moduł. Na przykład:struct regulator_dev zwracany przez regulator_register().

Dostęp do podstawowych struktur danych jądra tylko za pomocą funkcji wyeksportowanych przez jądro lub parametrów przekazywanych wyraźnie jako dane wejściowe do funkcji dostawcy. Jeśli nie masz elementu zaczepienia interfejsu API ani dostawcy, które modyfikuje części podstawowej struktury danych jądra, jest to prawdopodobnie celowe i nie należy modyfikować struktury danych z modułów. Na przykład nie modyfikuj żadnych pól w sekcji struct device ani 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 sekcji struct device.links, użyj interfejsu API linkowania urządzeń, takiego jak device_link_add() lub device_link_del().

Nie analizuj węzłów drzewa urządzenia za pomocą właściwości zgodności

Jeśli węzeł drzewa urządzenia (DT) ma właściwość compatible, struct device jest dla niego przydzielany automatycznie lub gdy wywoływana jest funkcja of_platform_populate() w nadrzędnym węźle DT (zazwyczaj przez sterownik urządzenia nadrzędnego). Oczekiwanie domyślne (z wyjątkiem niektórych urządzeń zainicjowanych wcześniej przez algorytm szeregowania) polega na tym, że węzeł przenoszenia danych z właściwością compatible ma struct device i pasujący sterownik urządzenia. Wszystkie inne wyjątki są już obsługiwane przez kod źródłowy.

Dodatkowo fw_devlink (wcześniej of_devlink) traktuje węzły DT z właściwością compatible jako urządzenia z przypisanym struct device, które jest sprawdzane przez sterownik. Jeśli węzeł DT ma właściwość compatible, ale przydzielone struct device nie jest sondowane, fw_devlink może zablokować sondowanie urządzeń konsumenta lub zablokować wywołania sync_state() na urządzeniach dostawcy.

Jeśli sterownik używa funkcji of_find_*() (na przykład of_find_node_by_name() lub of_find_compatible_node()), aby bezpośrednio znaleźć węzeł DT z właściwością compatible, a następnie przeanalizować ten węzeł DT, napraw moduł, pisząc sterownik urządzenia, który może zbadać urządzenie, lub usuń właściwość compatible (możliwe tylko wtedy, gdy nie została przesłana w górę). Aby omówić alternatywne rozwiązania, skontaktuj się z zespołem Androida jądra pod adresem kernel-team@android.com i przygotuj się na uzasadnienie swoich przypadków użycia.

Wyszukiwanie dostawców za pomocą phandle’ów DT

W miarę możliwości odwołuj się do dostawcy za pomocą phandle (odwołania lub wskaźnika do węzła DT) w DT. Używanie standardowych powiązań DT i identyfikatorów phandle 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 przez analizowanie DT w czasie wykonywania. Rdzeń może automatycznie sprawdzać urządzenia w prawidłowej kolejności, co eliminuje potrzebę porządkowania ładowania modułów lub MODULE_SOFTDEP().

Starszy scenariusz (brak obsługi DT w rdzeniu ARM)

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

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

Po dodaniu obsługi DT do jąder ARM użytkownicy mogą identyfikować dostawców w DT, odwołując się do węzła drzewa urządzenia dostawcy za pomocą phandle. Konsumenci mogą też nadać nazwę zasobom na podstawie tego, do czego służą, a nie tego, kto je udostępnia. Na przykład sterownik ekranu dotykowego z poprzedniego przykładu może użyć parametrów regulator_get(dev, "core") i regulator_get(dev, "sensor"), aby uzyskać źródła zasilające rdzeń i czujnik urządzenia dotykowego. Powiązany DT dla takiego urządzenia wygląda podobnie do tego 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 {
        ...
    };
};

Scenariusz najgorszej sytuacji

Niektóre sterowniki przeniesione ze starszych jąder zawierają w DT obecne w pliku danych starsze zachowanie, które zajmuje najgorszą część starszego schematu i wymusza zastosowanie nowszego schematu, który ma ułatwić sprawę. W takich sterownikach sterownik klienta odczytuje ciąg znaków do użycia w wyszukiwaniu za pomocą właściwości DT specyficznej dla urządzenia, a dostawca użyje innej właściwości specyficznej dla dostawcy, aby zdefiniować nazwę do użycia w rejestrowaniu zasobu dostawcy. Następnie klient i dostawca nadal używają tego samego starego schematu używania ciągów znaków do wyszukiwania dostawcy. W tym najgorszym scenariuszu:

  • Sterownik dotykowy używa kodu podobnego do tego:

    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 tego:

    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 frameworku.

Interfejsy API platformy, takie jak regulator, clocks, irq, gpio, phys i extcon, zwracają -EPROBE_DEFER jako zwracaną wartość błędu, aby wskazać, że urządzenie próbuje przeprowadzić sondowanie, ale w tej chwili nie może tego zrobić. Jądro powinno spróbować przeprowadzić sondę ponownie później. Aby mieć pewność, że funkcja .probe() Twojego urządzenia działa zgodnie z oczekiwaniami w takich przypadkach, nie zastępuj ani nie zmieniaj mapowania wartości błędu. Zastąpienie lub ponowne zmapowanie wartości błędu może spowodować odrzucenie wartości -EPROBE_DEFER i to, że urządzenie nigdy nie zostanie zbadane.

Używaj wersji interfejsu API devm_*()

Gdy urządzenie pobiera zasób za pomocą interfejsu API devm_*(), zasób jest automatycznie zwalniany przez jądro, jeśli urządzenie nie może przeprowadzić sondowania lub przeprowadzi je pomyślnie, a później zostanie odłączone. Ta funkcja sprawia, że kod obsługi błędów w funkcji probe() jest czystszy, ponieważ nie wymaga skoków goto w celu zwolnienia zasobów pozyskanych przez devm_*() i upraszcza operacje usuwania powiązań sterownika.

Obsługa odłączania urządzenia od sterownika

Pamiętaj o odwiązaniu sterowników urządzeń i nie pozostawiaj tego parametru z wartością undefined, ponieważ niedefiniowana wartość nie oznacza, że odwiązanie jest niedozwolone. Musisz w pełni wdrożyć odłączenie urządzenia od sterownika lub wyraźnie wyłączyć odłączenie urządzenia od sterownika.

Stosowanie rozwiązania polegającego na odłączeniu urządzenia od sterownika

Jeśli zdecydujesz się na pełne wdrożenie odłączania sterowników urządzeń, odłącz je prawidłowo, aby uniknąć wycieków pamięci lub zasobów oraz problemów z bezpieczeństwem. Urządzenie możesz powiązać z kierowcą, wywołując funkcję probe() kierowcy i usuwając powiązanie z urządzeniem przez wywołanie funkcji remove() kierowcy. Jeśli nie istnieje żadna funkcja remove(), jądro nadal może usunąć powiązanie urządzenia, a rdzeń sterownika zakłada, że po odłączeniu urządzenia od urządzenia nie wymaga to czyszczenia. Sterownik, który nie jest powiązany z urządzeniem, nie musi wykonywać żadnych czynności związanych z czyszczeniem, jeśli są spełnione oba te warunki:

  • Wszystkie zasoby pozyskiwane przez funkcję probe() sterownika są pozyskiwane za pomocą interfejsów API devm_*().

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

W takim przypadku rdzeń sterownika odpowiada za zwalnianie wszystkich zasobów uzyskanych za pomocą interfejsów API devm_*(). Jeśli którekolwiek z poprzednich stwierdzeń jest nieprawdziwe, sterownik musi wykonać czyszczenie (zwolnić zasoby i wyłączyć lub wyciszyć sprzęt) po odwiązaniu od urządzenia. Aby prawidłowo usunąć powiązanie modułu sterownika przez urządzenie, skorzystaj z jednej z tych opcji:

  • Jeśli sprzęt nie wymaga sekwencji zamykania ani wyciszania, zmień devm_*() interfejs API urządzenia, aby pobierać zasoby.

  • Zrealizuj operację remove() w sterowniku w tej samej strukturze co funkcja probe(), a potem wykonaj czynności porządkujące za pomocą funkcji remove().

Wyraźnie wyłączyć odłączanie urządzenia od sterownika (niezalecane)

Jeśli chcesz wyraźnie wyłączyć usuwanie powiązania sterownika urządzenia, musisz zabronić tego procesu oraz zabraniać odłączania modułu.

  • Aby uniemożliwić usuwanie powiązania, ustaw flagę suppress_bind_attrs na true w struct device_driver sterownika. To ustawienie zapobiega wyświetlaniu plików bind i unbind w katalogu sysfs sterownika. Plik unbind umożliwia przestrzeni użytkownika wywołanie odwiązania sterownika od urządzenia.

  • Aby zabronić wyładowywania modułu, upewnij się, że ma on wartość [permanent] w pliku lsmod. Jeśli nie użyjesz właściwości module_exit() lub module_XXX_driver(), moduł zostanie oznaczony jako [permanent].

Nie wczytuj oprogramowania układowego w ramach funkcji sondowania

Sterownik nie powinien wczytywać oprogramowania z poziomu funkcji .probe(), ponieważ może nie mieć do niego dostępu, jeśli sterownik sprawdza to przed zamontowaniem systemu plików na podstawie pamięci flash lub trwałej pamięci masowej. W takich przypadkach interfejs API request_firmware*() może się blokować przez długi czas, a potem zakończyć działanie, co może niepotrzebnie spowolnić proces uruchamiania. Zamiast tego odłóż wczytywanie oprogramowania układowego do momentu, gdy klient zacznie korzystać z urządzenia. Na przykład sterownik wyświetlacza może wczytywać oprogramowanie układowe, gdy wyświetlacz jest otwarty.

W niektórych przypadkach użycie oprogramowania .probe() do wczytania oprogramowania może być dozwolone, na przykład w sterowniku zegara, który do działania wymaga oprogramowania, ale urządzenie nie jest dostępne w przestrzeni użytkownika. Możliwe są też inne odpowiednie zastosowania.

Wdrożenie sondowania asynchronicznego

Obsługuj i używaj asynchronicznego sondowania, aby korzystać z przyszłych ulepszeń, takich jak równoległe ładowanie modułów czy sondowanie urządzenia w celu przyspieszenia czasu uruchamiania, które mogą zostać dodane do Androida w przyszłych wersjach. Moduł sterownika, który nie korzysta z przeszukania asynchronicznego, może obniżyć skuteczność takich optymalizacji.

Aby oznaczyć sterownik jako obsługiwany i preferowany sondowanie asynchroniczne, ustaw pole probe_type w elemencie struct device_driver sterownika. Poniższy przykład pokazuje obsługę włączoną dla sterownika platformy:

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

Aby sterownik działał z wykorzystaniem sondowania asynchronicznego, nie trzeba pisać specjalnego kodu. Podczas dodawania obsługi sond asynchronicznego należy jednak pamiętać o poniższych kwestiach.

  • Nie rób założeń dotyczących wcześniej zbadanych zależności. Sprawdź bezpośrednio lub pośrednio (większość wywołań platformy) i zwróć -EPROBE_DEFER, jeśli co najmniej jeden z dostawców nie jest jeszcze gotowy.

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

  • Jeśli sonda ulegnie awarii, wykonaj prawidłową obsługę błędów i wyczyść dane (patrz Używanie wariantów interfejsu API devm_*()).

Nie zamawiaj sond urządzenia za pomocą parametru MODULE_SOFTDEP

Funkcja MODULE_SOFTDEP() nie jest niezawodnym rozwiązaniem gwarantującym prawidłowe działanie sondowania urządzeń i nie należy jej używać z podanych niżej powodów.

  • Odroczona sonda. Podczas wczytywania modułu może zostać odroczona próba uzyskania informacji o urządzeniu, ponieważ jeden z jego dostawców nie jest gotowy. Może to spowodować niezgodność między kolejnością ładowania modułów a kolejnością sprawdzania urządzeń.

  • 1 kierowca, wiele urządzeń Moduł sterownika może zarządzać określonym typem urządzenia. Jeśli system zawiera więcej niż 1 występowanie typu urządzenia, a poszczególne urządzenia mają różne wymagania dotyczące kolejności sondowania, nie możesz uwzględnić tych wymagań, stosując kolejność wczytywania modułów.

  • Asynchroniczne sondowanie. Moduły sterowników, które przeprowadzają sondowanie asynchroniczne, nie przeprowadzają sondowania urządzenia od razu po załadowaniu modułu. Zamiast tego drugi wątek obsługuje sondowanie urządzeń, co może prowadzić do niezgodności między kolejnością wczytywania modułu i kolejności sond urządzenia. Jeśli na przykład moduł głównego sterownika I2C wykonuje wyszukiwanie asynchroniczne, a moduł sterownika dotykowego zależy od PMIC na magistrali I2C, nawet jeśli sterownik dotykowy i sterownik PMIC są ładowane we właściwej kolejności, próba wyszukiwania sterownika dotykowego może zostać podjęta przed próbą wyszukiwania sterownika PMIC.

Jeśli masz moduły sterownika, które korzystają z funkcji MODULE_SOFTDEP(), napraw je tak, aby nie korzystały z tej funkcji. Aby Ci pomóc, zespół Androida wprowadził zmiany, które umożliwiają jądrowi rozwiązywanie problemów z kolejnością bez używania MODULE_SOFTDEP(). W szczególności możesz użyć funkcji fw_devlink, aby zapewnić kolejność próbkowania, a (po zakończeniu próbkowania przez wszystkich konsumentów urządzenia) użyć funkcji wywołania zwrotnego sync_state() do wykonania niezbędnych zadań.

W przypadku konfiguracji używaj instrukcji #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 nadal kompilować, nawet jeśli w przyszłości konfiguracja zmieni się na Tristate. Różnice są następujące:

  • #if IS_ENABLED(CONFIG_XXX) przyjmuje wartość true, gdy CONFIG_XXX ma wartość module (=m) lub wbudowany (=y).

  • Wartość #ifdef CONFIG_XXX jest obliczana jako true, gdy parametr CONFIG_XXX ma wartość wbudowana (=y), ale nie wtedy, gdy ma wartość moduł (=m). Używaj tego tylko wtedy, gdy masz pewność, że chcesz uzyskać ten sam efekt, gdy konfiguracja ma wartość moduł lub jest wyłączona.CONFIG_XXX

Użyj odpowiedniego makra do kompilacji warunkowej.

Jeśli pole CONFIG_XXX jest ustawione na moduł (=m), system kompilacji automatycznie definiuje CONFIG_XXX_MODULE. Jeśli sterownik jest kontrolowany przez CONFIG_XXX i chcesz sprawdzić, czy jest kompilowany jako moduł, postępuj zgodnie z tymi wytycznymi:

  • W pliku C (lub dowolnym pliku źródłowym, który nie jest plikiem nagłówka) sterownika nie używaj #ifdef CONFIG_XXX_MODULE, ponieważ jest to niepotrzebnie restrykcyjne i powoduje błąd, jeśli nazwa pliku konfiguracyjnego zostanie zmieniona na CONFIG_XYZ. W przypadku każdego pliku źródłowego, który nie jest nagłówkiem, a został skompilowany w moduł, system kompilacji automatycznie definiuje MODULE dla zakresu tego pliku. Dlatego, aby sprawdzić, czy plik C (lub dowolny plik źródłowy bez nagłówka) jest kompilowany jako część modułu, użyj polecenia #ifdef MODULE (bez prefiksu CONFIG_).

  • W plikach nagłówków ta sama kontrola jest trudniejsza, ponieważ pliki nagłówków nie są kompilowane bezpośrednio w pliki binarne, ale kompilowane jako część pliku C (lub innych plików źródłowych). W plikach nagłówków stosuj te reguły:

    • W przypadku pliku nagłówka, który używa funkcji #ifdef MODULE, wynik zależy od tego, z którego pliku źródłowego jest on używany. Oznacza to, że ten sam plik nagłówka w ramach tej samej kompilacji może mieć różne części kodu skompilowane dla różnych plików źródłowych (moduł, wbudowany lub wyłączony). Może to być przydatne, gdy chcesz zdefiniować makro, które ma się rozwijać w jeden sposób w przypadku kodu wbudowanego, a w inny w przypadku modułu.

    • W przypadku pliku nagłówka, który musi być skompilowany w fragmentie kodu, gdy określony parametr CONFIG_XXX ma wartość module (niezależnie od tego, czy plik źródłowy, który go zawiera, jest modułem), plik nagłówka musi używać parametru #ifdef CONFIG_XXX_MODULE.