Richtlinien für Anbietermodule

Verwenden Sie die folgenden Richtlinien, um die Robustheit und Zuverlässigkeit Ihrer Anbietermodule zu erhöhen. Viele Richtlinien können, wenn sie befolgt werden, dazu beitragen, die richtige Modulladereihenfolge und die Reihenfolge, in der Treiber nach Geräten suchen müssen, leichter zu bestimmen.

Ein Modul kann eine Bibliothek oder ein Treiber sein.

  • Bibliotheksmodule sind Bibliotheken, die APIs für die Verwendung durch andere Module bereitstellen. Solche Module sind normalerweise nicht hardwarespezifisch. Beispiele für Bibliotheksmodule sind ein AES-Verschlüsselungsmodul, das als Modul kompilierte remoteproc Framework und ein Logbuffer-Modul. Der Modulcode in module_init() wird ausgeführt, um Datenstrukturen einzurichten, aber kein anderer Code wird ausgeführt, es sei denn, er wird durch ein externes Modul ausgelöst.

  • Treibermodule sind Treiber, die nach einem bestimmten Gerätetyp suchen oder sich an diesen binden. Solche Module sind hardwarespezifisch. Beispiele für Treibermodule sind UART-, PCIe- und Video-Encoder-Hardware. Treibermodule werden nur aktiviert, wenn das zugehörige Gerät im System vorhanden ist.

    • Wenn das Gerät nicht vorhanden ist, wird als einziger Modulcode der module_init() Code ausgeführt, der den Treiber beim Treiberkern-Framework registriert.

    • Wenn das Gerät vorhanden ist und der Treiber erfolgreich nach diesem Gerät sucht oder sich daran bindet, wird möglicherweise anderer Modulcode ausgeführt.

Modul-Init/Exit korrekt verwenden

Treibermodule müssen einen Treiber in module_init() registrieren und die Registrierung eines Treibers in module_exit() aufheben. Eine einfache Möglichkeit, diese Einschränkungen durchzusetzen, ist die Verwendung von Wrapper-Makros, wodurch die direkte Verwendung der Makros module_init() , *_initcall() oder module_exit() vermieden wird.

  • Für Module, die entladen werden können, verwenden Sie module_ subsystem _driver() . Beispiele: module_platform_driver() , module_i2c_driver() und module_pci_driver() .

  • Für Module, die nicht entladen werden können, verwenden Sie builtin_ subsystem _driver() . Beispiele: builtin_platform_driver() , builtin_i2c_driver() und builtin_pci_driver() .

Einige Treibermodule verwenden module_init() und module_exit() , weil sie mehr als einen Treiber registrieren. Versuchen Sie bei einem Treibermodul, das module_init() und module_exit() zum Registrieren mehrerer Treiber verwendet, die Treiber in einem einzigen Treiber zu kombinieren. Sie könnten beispielsweise anhand der compatible Zeichenfolge oder der Aux-Daten des Geräts unterscheiden, anstatt separate Treiber zu registrieren. Alternativ können Sie das Treibermodul in zwei Module aufteilen.

Ausnahmen für Init- und Exit-Funktionen

Bibliotheksmodule registrieren keine Treiber und sind von den Einschränkungen für module_init() und module_exit() ausgenommen, da sie diese Funktionen möglicherweise zum Einrichten von Datenstrukturen, Arbeitswarteschlangen oder Kernel-Threads benötigen.

Verwenden Sie das Makro MODULE_DEVICE_TABLE

Treibermodule müssen das Makro MODULE_DEVICE_TABLE enthalten, das es dem Benutzerbereich ermöglicht, die von einem Treibermodul unterstützten Geräte zu ermitteln, bevor das Modul geladen wird. Android kann diese Daten verwenden, um das Laden von Modulen zu optimieren, um beispielsweise das Laden von Modulen für Geräte zu vermeiden, die nicht im System vorhanden sind. Beispiele zur Verwendung des Makros finden Sie im Upstream-Code.

Vermeiden Sie CRC-Nichtübereinstimmungen aufgrund vorwärts deklarierter Datentypen

Schließen Sie keine Header-Dateien ein, um Einblick in vorwärts deklarierte Datentypen zu erhalten. Einige Strukturen, Unions und andere Datentypen, die in einer Header-Datei ( header-Ah ) definiert sind, können in einer anderen Header-Datei ( header-Bh ) vorwärtsdeklariert werden, die normalerweise Zeiger auf diese Datentypen verwendet. Dieses Codemuster bedeutet, dass der Kernel absichtlich versucht, die Datenstruktur für die Benutzer von header-Bh privat zu halten.

Benutzer von header-Bh sollten header-Ah nicht einschließen, um direkt auf die Interna dieser vorwärtsdeklarierten Datenstrukturen zuzugreifen. Dies führt zu CONFIG_MODVERSIONS CRC-Nichtübereinstimmungsproblemen (die ABI-Konformitätsprobleme erzeugen), wenn ein anderer Kernel (z. B. der GKI-Kernel) versucht, das Modul zu laden.

Beispielsweise ist struct fwnode_handle in include/linux/fwnode.h definiert, wird aber als struct fwnode_handle; in include/linux/device.h , weil der Kernel versucht, die Details von struct fwnode_handle vor den Benutzern von include/linux/device.h geheim zu halten. Fügen Sie in diesem Szenario nicht #include <linux/fwnode.h> in einem Modul hinzu, um Zugriff auf Mitglieder von struct fwnode_handle zu erhalten. Jedes Design, in das Sie solche Header-Dateien einbinden müssen, weist auf ein schlechtes Designmuster hin.

Greifen Sie nicht direkt auf Kernelstrukturen zu

Der direkte Zugriff auf oder die Änderung von Kernel-Datenstrukturen kann zu unerwünschtem Verhalten führen, einschließlich Speicherlecks, Abstürzen und beeinträchtigter Kompatibilität mit zukünftigen Kernel-Versionen. Eine Datenstruktur ist eine Kernel-Datenstruktur, wenn sie eine der folgenden Bedingungen erfüllt:

  • Die Datenstruktur ist unter KERNEL-DIR /include/ definiert. Zum Beispiel struct device und struct dev_links_info . Datenstrukturen, die in include/linux/soc definiert sind, sind ausgenommen.

  • Die Datenstruktur wird vom Modul zugewiesen oder initialisiert, aber für den Kernel sichtbar gemacht, indem sie indirekt (über einen Zeiger in einer Struktur) oder direkt als Eingabe in eine vom Kernel exportierte Funktion übergeben wird. Beispielsweise initialisiert ein cpufreq Treibermodul die struct cpufreq_driver und übergibt sie dann als Eingabe an cpufreq_register_driver() . Nach diesem Zeitpunkt sollte das cpufreq Treibermodul struct cpufreq_driver nicht mehr direkt ändern, da der Aufruf von cpufreq_register_driver() struct cpufreq_driver für den Kernel sichtbar macht.

  • Die Datenstruktur wird von Ihrem Modul nicht initialisiert. Beispielsweise wird struct regulator_dev von regulator_register() zurückgegeben.

Greifen Sie nur über vom Kernel exportierte Funktionen oder über Parameter, die explizit als Eingabe an Hersteller-Hooks übergeben werden, auf Kernel-Datenstrukturen zu. Wenn Sie nicht über eine API oder einen Hersteller-Hook zum Ändern von Teilen einer Kernel-Datenstruktur verfügen, ist dies wahrscheinlich beabsichtigt und Sie sollten die Datenstruktur nicht über Module ändern. Ändern Sie beispielsweise keine Felder in struct device oder struct device.links .

  • Um device.devres_head zu ändern, verwenden Sie eine devm_*() Funktion wie devm_clk_get() , devm_regulator_get() oder devm_kzalloc() .

  • Um Felder in struct device.links zu ändern, verwenden Sie eine Device-Link-API wie device_link_add() oder device_link_del() .

Analysieren Sie keine Devicetree-Knoten mit der Eigenschaft „Compatible“.

Wenn ein Gerätebaumknoten (DT) über eine compatible Eigenschaft verfügt, wird ihm automatisch oder beim Aufruf of_platform_populate() auf dem übergeordneten DT-Knoten ein struct device zugewiesen (normalerweise durch den Gerätetreiber des übergeordneten Geräts). Die Standarderwartung (mit Ausnahme einiger Geräte, die früh für den Scheduler initialisiert werden) ist, dass ein DT-Knoten mit einer compatible Eigenschaft über ein struct device und einen passenden Gerätetreiber verfügt. Alle anderen Ausnahmen werden bereits vom Upstream-Code behandelt.

Darüber hinaus betrachtet fw_devlink (zuvor of_devlink genannt) DT-Knoten mit der compatible Eigenschaft als Geräte mit einem zugewiesenen struct device , das von einem Treiber geprüft wird. Wenn ein DT-Knoten über eine compatible Eigenschaft verfügt, das zugewiesene struct device jedoch nicht geprüft wird, könnte fw_devlink die Prüfung seiner Verbrauchergeräte oder den Aufruf von sync_state() Aufrufen für seine Lieferantengeräte blockieren.

Wenn Ihr Treiber eine of_find_*() Funktion (z. B. of_find_node_by_name() oder of_find_compatible_node() ) verwendet, um direkt einen DT-Knoten zu finden, der eine compatible Eigenschaft hat, und diesen DT-Knoten dann zu analysieren, reparieren Sie das Modul, indem Sie einen Gerätetreiber schreiben, der Sondierungen durchführen kann Entfernen Sie das Gerät oder entfernen Sie die compatible Eigenschaft (nur möglich, wenn kein Upstream durchgeführt wurde). Um Alternativen zu besprechen, wenden Sie sich an das Android-Kernel-Team unter kernel-team@android.com und seien Sie bereit, Ihre Anwendungsfälle zu begründen.

Verwenden Sie DT-Phandles, um nach Lieferanten zu suchen

Verweisen Sie nach Möglichkeit auf einen Lieferanten, indem Sie in DT ein Phandle (eine Referenz/einen Zeiger auf einen DT-Knoten) verwenden. Durch die Verwendung standardmäßiger DT-Bindungen und Phandles zum Verweisen auf Lieferanten kann fw_devlink (früher of_devlink ) automatisch Abhängigkeiten zwischen Geräten ermitteln, indem der DT zur Laufzeit analysiert wird. Der Kernel kann dann Geräte automatisch in der richtigen Reihenfolge prüfen, sodass keine Modulladereihenfolge oder MODULE_SOFTDEP() erforderlich ist.

Legacy-Szenario (keine DT-Unterstützung im ARM-Kernel)

Zuvor, bevor DT-Unterstützung zu ARM-Kerneln hinzugefügt wurde, suchten Verbraucher wie Touch-Geräte mithilfe global eindeutiger Zeichenfolgen nach Lieferanten wie Regulierungsbehörden. Beispielsweise könnte der ACME-PMIC-Treiber mehrere Regler registrieren oder ankündigen (z. B. acme-pmic-ldo1 bis acme-pmic-ldo10 ), und ein Touch-Treiber könnte mithilfe regulator_get(dev, "acme-pmic-ldo10") einem Regler suchen. . Auf einer anderen Platine könnte jedoch der LDO8 das Touch-Gerät versorgen, wodurch ein umständliches System entsteht, bei dem derselbe Touch-Treiber die richtige Suchzeichenfolge für den Regler für jede Platine ermitteln muss, auf der das Touch-Gerät verwendet wird.

Aktuelles Szenario (DT-Unterstützung im ARM-Kernel)

Nachdem DT-Unterstützung zu ARM-Kerneln hinzugefügt wurde, können Verbraucher Lieferanten im DT identifizieren, indem sie mithilfe eines Phandle auf den Gerätebaumknoten des Lieferanten verweisen. Verbraucher können die Ressource auch nach dem Zweck benennen, für den sie verwendet wird, und nicht danach, wer sie bereitstellt. Der Touch-Treiber aus dem vorherigen Beispiel könnte beispielsweise regulator_get(dev, "core") und regulator_get(dev, "sensor") verwenden, um die Lieferanten abzurufen, die den Kern und Sensor des Touch-Geräts mit Strom versorgen. Der zugehörige DT für ein solches Gerät ähnelt dem folgenden Codebeispiel:

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 {
        ...
    };
};

Das schlimmste Szenario aus beiden Welten

Einige von älteren Kerneln portierte Treiber enthalten Legacy-Verhalten im DT, das den schlechtesten Teil des Legacy-Schemas übernimmt und es dem neueren Schema aufzwingt, was die Sache einfacher machen soll. In solchen Treibern liest der Verbrauchertreiber die für die Suche zu verwendende Zeichenfolge mithilfe einer gerätespezifischen DT-Eigenschaft, der Lieferant verwendet eine andere lieferantenspezifische Eigenschaft, um den Namen zu definieren, der für die Registrierung der Lieferantenressource verwendet werden soll, und verwendet dann Verbraucher und Lieferant weiter das gleiche alte Schema, Strings zum Nachschlagen des Lieferanten zu verwenden. In diesem schlimmsten Szenario:

  • Der Touch-Treiber verwendet Code ähnlich dem folgenden Code:

    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);
    
  • Das DT verwendet Code ähnlich dem folgenden:

    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"
      };
    };
    

Ändern Sie keine Framework-API-Fehler

Framework-APIs wie regulator , clocks , irq , gpio , phys und extcon geben -EPROBE_DEFER als Fehlerrückgabewert zurück, um anzuzeigen, dass ein Gerät versucht, eine Prüfung durchzuführen, dies jedoch zu diesem Zeitpunkt nicht kann, und der Kernel sollte die Prüfung erneut versuchen später. Um sicherzustellen, dass .probe() Funktion Ihres Geräts in solchen Fällen wie erwartet fehlschlägt, ersetzen Sie den Fehlerwert nicht und ordnen Sie ihn nicht neu zu. Das Ersetzen oder Neuzuordnen des Fehlerwerts kann dazu führen, dass -EPROBE_DEFER gelöscht wird und Ihr Gerät nie überprüft wird.

Verwenden Sie devm_*() API-Varianten

Wenn das Gerät eine Ressource mithilfe einer devm_*() -API abruft, wird die Ressource automatisch vom Kernel freigegeben, wenn die Prüfung durch das Gerät fehlschlägt oder die Prüfung erfolgreich ist und die Bindung später aufgehoben wird. Diese Funktionalität macht den Fehlerbehandlungscode in der Funktion probe() sauberer, da keine goto Sprünge erforderlich sind, um die von devm_*() erfassten Ressourcen freizugeben, und vereinfacht die Treiberaufhebungsvorgänge.

Behandeln Sie die Aufhebung der Bindung des Gerätetreibers

Gehen Sie bei der Aufhebung der Bindung von Gerätetreibern bewusst vor und lassen Sie die Aufhebung der Bindung nicht undefiniert, da undefiniert nicht bedeutet, dass sie nicht zulässig ist. Sie müssen die Gerätetreiber-Aufhebung entweder vollständig implementieren oder die Gerätetreiber-Aufhebung explizit deaktivieren.

Implementierung der Gerätetreiber-Entbindung

Wenn Sie sich für die vollständige Implementierung der Gerätetreiber-Entbindung entscheiden, müssen Sie die Gerätetreiber sauber entbinden, um Speicher- oder Ressourcenlecks und Sicherheitsprobleme zu vermeiden. Sie können ein Gerät an einen Treiber binden, indem Sie die Funktion probe() eines Treibers aufrufen, und die Bindung eines Geräts aufheben, indem Sie die Funktion remove() des Treibers aufrufen. Wenn keine Funktion remove() vorhanden ist, kann der Kernel das Gerät trotzdem entbinden; Der Treiberkern geht davon aus, dass der Treiber keine Aufräumarbeiten durchführen muss, wenn er die Verbindung zum Gerät aufhebt. Ein Treiber, der nicht an ein Gerät gebunden ist, muss keine expliziten Bereinigungsarbeiten durchführen, wenn beide der folgenden Bedingungen zutreffen:

  • Alle von der probe() Funktion eines Treibers erfassten Ressourcen erfolgen über devm_*() APIs.

  • Das Hardwaregerät benötigt keine Abschalt- oder Stilllegungssequenz.

In dieser Situation übernimmt der Treiberkern die Freigabe aller über devm_*() APIs erfassten Ressourcen. Wenn eine der vorhergehenden Aussagen nicht wahr ist, muss der Treiber eine Bereinigung durchführen (Ressourcen freigeben und die Hardware herunterfahren oder in den Ruhezustand versetzen), wenn er die Bindung zu einem Gerät aufhebt. Um sicherzustellen, dass ein Gerät ein Treibermodul sauber lösen kann, verwenden Sie eine der folgenden Optionen:

  • Wenn die Hardware keine Abschalt- oder Stilllegungssequenz benötigt, ändern Sie das Gerätemodul, um Ressourcen mithilfe devm_*() APIs abzurufen.

  • Implementieren Sie die Treiberoperation remove() in derselben Struktur wie die Funktion probe() und führen Sie dann die Bereinigungsschritte mit der Funktion remove() durch.

Explizites Deaktivieren der Gerätetreiber-Entbindung (nicht empfohlen)

Wenn Sie die Aufhebung der Bindung des Gerätetreibers explizit deaktivieren möchten, müssen Sie die Aufhebung der Bindung und das Entladen des Moduls verbieten.

  • Um das Aufheben der Bindung zu verhindern, setzen Sie das Flag suppress_bind_attrs “ in der struct device_driver des Treibers auf „ true “. Diese Einstellung verhindert, dass die bind und unbind Dateien im sysfs Verzeichnis des Treibers angezeigt werden. Die unbind -Datei ermöglicht es dem Benutzerraum, die Entbindung eines Treibers von seinem Gerät auszulösen.

  • Um das Entladen des Moduls zu verhindern, stellen Sie sicher, dass das Modul [permanent] in lsmod hat. Wenn module_exit() oder module_XXX_driver() nicht verwendet werden, wird das Modul als [permanent] markiert.

Laden Sie keine Firmware aus der Sondenfunktion heraus

Der Treiber sollte die Firmware nicht über die Funktion .probe() laden, da er möglicherweise keinen Zugriff auf die Firmware hat, wenn der Treiber eine Prüfung durchführt, bevor das Flash- oder Permanentspeicher-basierte Dateisystem gemountet ist. In solchen Fällen blockiert die request_firmware*() -API möglicherweise längere Zeit und schlägt dann fehl, was den Startvorgang unnötig verlangsamen kann. Verschieben Sie stattdessen das Laden der Firmware auf den Zeitpunkt, an dem ein Client das Gerät verwendet. Beispielsweise könnte ein Anzeigetreiber die Firmware laden, wenn das Anzeigegerät geöffnet wird.

Die Verwendung .probe() zum Laden der Firmware kann in einigen Fällen in Ordnung sein, z. B. bei einem Uhrentreiber, der zum Funktionieren Firmware benötigt, das Gerät jedoch nicht dem Benutzerbereich zugänglich ist. Andere geeignete Anwendungsfälle sind möglich.

Implementieren Sie asynchrone Sondierungen

Unterstützen und verwenden Sie asynchrone Prüfungen, um zukünftige Verbesserungen zu nutzen, z. B. paralleles Laden von Modulen oder Geräteprüfungen zur Beschleunigung der Startzeit, die möglicherweise in zukünftigen Versionen zu Android hinzugefügt werden. Treibermodule, die keine asynchrone Prüfung verwenden, könnten die Wirksamkeit solcher Optimierungen verringern.

Um einen Treiber als Unterstützung und Bevorzugung asynchroner Prüfungen zu kennzeichnen, legen Sie das Feld probe_type im struct device_driver des Treibers fest. Das folgende Beispiel zeigt eine solche Unterstützung, die für einen Plattformtreiber aktiviert ist:

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

Damit ein Treiber mit asynchroner Prüfung funktioniert, ist kein spezieller Code erforderlich. Beachten Sie jedoch Folgendes, wenn Sie Unterstützung für asynchrone Tests hinzufügen.

  • Machen Sie keine Annahmen über zuvor untersuchte Abhängigkeiten. Prüfen Sie direkt oder indirekt (die meisten Framework-Aufrufe) und geben Sie -EPROBE_DEFER zurück, wenn einer oder mehrere Lieferanten noch nicht bereit sind.

  • Wenn Sie untergeordnete Geräte in der Prüffunktion eines übergeordneten Geräts hinzufügen, gehen Sie nicht davon aus, dass die untergeordneten Geräte sofort geprüft werden.

  • Wenn ein Test fehlschlägt, führen Sie eine ordnungsgemäße Fehlerbehandlung und Bereinigung durch (siehe Verwenden von devm_*()-API-Varianten ).

Verwenden Sie nicht MODULE_SOFTDEP, um Geräte-Probes zu bestellen

Die Funktion MODULE_SOFTDEP() ist keine zuverlässige Lösung zur Gewährleistung der Reihenfolge von Gerätesonden und darf aus den folgenden Gründen nicht verwendet werden.

  • Aufgeschobene Untersuchung. Wenn ein Modul geladen wird, wird die Geräteprüfung möglicherweise verzögert, weil einer seiner Lieferanten nicht bereit ist. Dies kann zu einer Diskrepanz zwischen der Modulladereihenfolge und der Gerätesondenreihenfolge führen.

  • Ein Treiber, viele Geräte. Ein Treibermodul kann einen bestimmten Gerätetyp verwalten. Wenn das System mehr als eine Instanz eines Gerätetyps enthält und für diese Geräte jeweils eine andere Anforderung an die Sondenreihenfolge gilt, können Sie diese Anforderungen mithilfe der Modullastreihenfolge nicht berücksichtigen.

  • Asynchrone Sondierung. Treibermodule, die eine asynchrone Prüfung durchführen, prüfen ein Gerät nicht sofort, wenn das Modul geladen wird. Stattdessen übernimmt ein paralleler Thread die Geräteprüfung, was zu einer Nichtübereinstimmung zwischen der Modulladereihenfolge und der Geräteprüfungsreihenfolge führen kann. Wenn beispielsweise ein I2C-Haupttreibermodul eine asynchrone Abtastung durchführt und ein Touch-Treibermodul vom PMIC am I2C-Bus abhängig ist, wird möglicherweise zuvor versucht, den Touch-Treiber zu testen, selbst wenn der Touch-Treiber und der PMIC-Treiber in der richtigen Reihenfolge geladen werden die PMIC-Treibersonde.

Wenn Sie Treibermodule haben, die die Funktion MODULE_SOFTDEP() verwenden, korrigieren Sie sie, damit sie diese Funktion nicht verwenden. Um Ihnen zu helfen, hat das Android-Team Änderungen vorinstalliert, die es dem Kernel ermöglichen, Bestellprobleme zu bewältigen, ohne MODULE_SOFTDEP() zu verwenden. Insbesondere können Sie fw_devlink verwenden, um die Prüfreihenfolge sicherzustellen und (nachdem alle Verbraucher eines Geräts geprüft haben) den sync_state() Rückruf verwenden, um alle erforderlichen Aufgaben auszuführen.

Verwenden Sie für Konfigurationen #if IS_ENABLED() anstelle von #ifdef

Verwenden Sie #if IS_ENABLED(CONFIG_XXX) anstelle von #ifdef CONFIG_XXX , um sicherzustellen, dass der Code im #if Block weiterhin kompiliert wird, wenn sich die Konfiguration in Zukunft in eine Tristate-Konfiguration ändert. Die Unterschiede sind wie folgt:

  • #if IS_ENABLED(CONFIG_XXX) ergibt „ true , wenn CONFIG_XXX auf Modul ( =m ) oder integriert ( =y ) gesetzt ist.

  • #ifdef CONFIG_XXX wird als true ausgewertet, wenn CONFIG_XXX auf „built-in“ ( =y ) gesetzt ist, aber nicht, wenn CONFIG_XXX auf „module“ ( =m ) gesetzt ist. Verwenden Sie dies nur, wenn Sie sicher sind, dass Sie dasselbe tun möchten, wenn die Konfiguration auf „Modul“ eingestellt oder deaktiviert ist.

Verwenden Sie für bedingte Kompilierungen das richtige Makro

Wenn ein CONFIG_XXX auf Modul ( =m ) gesetzt ist, definiert das Build-System automatisch CONFIG_XXX_MODULE . Wenn Ihr Treiber von CONFIG_XXX gesteuert wird und Sie überprüfen möchten, ob Ihr Treiber als Modul kompiliert wird, verwenden Sie die folgenden Richtlinien:

  • Verwenden Sie in der C-Datei (oder einer anderen Quelldatei, die keine Header-Datei ist) für Ihren Treiber nicht #ifdef CONFIG_XXX_MODULE , da es unnötig restriktiv ist und kaputt geht, wenn die Konfiguration in CONFIG_XYZ umbenannt wird. Für jede Nicht-Header-Quelldatei, die in ein Modul kompiliert wird, definiert das Build-System automatisch MODULE für den Umfang dieser Datei. Um zu überprüfen, ob eine C-Datei (oder eine andere Quelldatei ohne Header) als Teil eines Moduls kompiliert wird, verwenden Sie daher #ifdef MODULE (ohne das Präfix CONFIG_ ).

  • Bei Header-Dateien ist die gleiche Prüfung schwieriger, da Header-Dateien nicht direkt in eine Binärdatei, sondern als Teil einer C-Datei (oder anderer Quelldateien) kompiliert werden. Verwenden Sie die folgenden Regeln für Header-Dateien:

    • Bei einer Header-Datei, die #ifdef MODULE verwendet, ändert sich das Ergebnis je nachdem, welche Quelldatei es verwendet. Dies bedeutet, dass in derselben Headerdatei im selben Build verschiedene Teile des Codes für verschiedene Quelldateien kompiliert werden können (Modul im Vergleich zu integrierten oder deaktivierten Dateien). Dies kann nützlich sein, wenn Sie ein Makro definieren möchten, das für integrierten Code auf eine Art und für ein Modul auf eine andere Art und Weise erweitert werden muss.

    • Für eine Header-Datei, die in einem Codestück kompiliert werden muss, wenn ein bestimmtes CONFIG_XXX auf Modul gesetzt ist (unabhängig davon, ob die Quelldatei, die es enthält, ein Modul ist), muss die Header-Datei #ifdef CONFIG_XXX_MODULE verwenden.