Рекомендации по модулю поставщиков

Используйте следующие рекомендации, чтобы повысить надежность модулей вашего поставщика. При соблюдении многих рекомендаций можно упростить определение правильного порядка загрузки модулей и порядка, в котором драйверы должны проверять наличие устройств.

Модуль может быть библиотекой или драйвером .

  • Библиотечные модули — это библиотеки, которые предоставляют API для использования другими модулями. Такие модули обычно не зависят от аппаратного обеспечения. Примеры библиотечных модулей включают модуль шифрования AES, платформу remoteproc , скомпилированную как модуль, и модуль буфера журнала. Код модуля в module_init() запускается для настройки структур данных, но никакой другой код не запускается, если он не запущен внешним модулем.

  • Модули драйверов — это драйверы, которые проверяют или привязываются к определенному типу устройства. Такие модули зависят от аппаратного обеспечения. Примеры модулей драйверов включают UART, PCIe и аппаратное обеспечение видеокодера. Модули драйверов активируются только тогда, когда связанное с ними устройство присутствует в системе.

    • Если устройство отсутствует, единственным выполняемым кодом модуля является код module_init() , который регистрирует драйвер в базовой структуре драйвера.

    • Если устройство присутствует и драйвер успешно обнаруживает это устройство или привязывается к нему, может выполняться другой код модуля.

Правильно используйте инициализацию/выход модуля

Модули драйверов должны зарегистрировать драйвер в module_init() и отменить регистрацию драйвера в module_exit() . Простой способ обеспечить соблюдение этих ограничений — использовать макросы-оболочки, что позволяет избежать прямого использования макросов module_init() , *_initcall() или module_exit() .

  • Для модулей, которые можно выгружать, используйте module_ subsystem _driver() . Примеры: module_platform_driver() , module_i2c_driver() и module_pci_driver() .

  • Для модулей, которые не могут быть выгружены, используйте builtin_ subsystem _driver() Примеры: builtin_platform_driver() , builtin_i2c_driver() и builtin_pci_driver() .

Некоторые модули драйверов используют module_init() и module_exit() , поскольку они регистрируют более одного драйвера. Для модуля драйвера, который использует module_init() и module_exit() для регистрации нескольких драйверов, попробуйте объединить драйверы в один драйвер. Например, вы можете провести различие, используя compatible строку или дополнительные данные устройства вместо регистрации отдельных драйверов. Альтернативно вы можете разделить модуль драйвера на два модуля.

Исключения функций инициализации и выхода

Модули библиотеки не регистрируют драйверы и на них не распространяются ограничения на module_init() и module_exit() , поскольку эти функции могут им понадобиться для настройки структур данных, рабочих очередей или потоков ядра.

Используйте макрос MODULE_DEVICE_TABLE.

Модули драйвера должны включать макрос MODULE_DEVICE_TABLE , который позволяет пользовательскому пространству определять устройства, поддерживаемые модулем драйвера, перед загрузкой модуля. Android может использовать эти данные для оптимизации загрузки модулей, например, чтобы избежать загрузки модулей для устройств, которых нет в системе. Примеры использования макроса см. в исходном коде.

Избегайте несоответствий CRC из-за заранее объявленных типов данных.

Не включайте файлы заголовков, чтобы обеспечить видимость заранее объявленных типов данных. Некоторые структуры, объединения и другие типы данных, определенные в файле заголовка ( header-Ah ), могут быть объявлены в другом файле заголовка ( header-Bh ), который обычно использует указатели на эти типы данных. Этот шаблон кода означает, что ядро ​​намеренно пытается сохранить конфиденциальность структуры данных для пользователей header-Bh .

Пользователям header-Bh не следует включать header-Ah для прямого доступа к внутренним компонентам этих заранее объявленных структур данных. Это приводит к проблемам несоответствия CRC CONFIG_MODVERSIONS (что приводит к проблемам соответствия ABI), когда другое ядро ​​(например, ядро ​​GKI) пытается загрузить модуль.

Например, struct fwnode_handle определена в include/linux/fwnode.h , но заранее объявлена ​​как struct fwnode_handle; в include/linux/device.h , потому что ядро ​​пытается сохранить конфиденциальность деталей struct fwnode_handle от пользователей include/linux/device.h . В этом сценарии не добавляйте #include <linux/fwnode.h> в модуль, чтобы получить доступ к членам struct fwnode_handle . Любой дизайн, в который вам приходится включать такие файлы заголовков, указывает на плохой шаблон проектирования.

Не обращайтесь напрямую к основным структурам ядра.

Прямой доступ к основным структурам данных ядра или их изменение может привести к нежелательному поведению, включая утечки памяти, сбои и нарушение совместимости с будущими выпусками ядра. Структура данных является базовой структурой данных ядра, если она соответствует любому из следующих условий:

  • Структура данных определяется в KERNEL-DIR /include/ . Например, struct device и struct dev_links_info . Структуры данных, определенные в include/linux/soc исключаются.

  • Структура данных выделяется или инициализируется модулем, но становится видимой для ядра путем передачи косвенно (через указатель в структуре) или напрямую в качестве входных данных в функцию, экспортируемую ядром. Например, модуль драйвера cpufreq инициализирует struct cpufreq_driver , а затем передает ее в качестве входных данных в cpufreq_register_driver() . После этого модуль драйвера cpufreq не должен изменять struct cpufreq_driver напрямую, поскольку вызов cpufreq_register_driver() делает struct cpufreq_driver видимой для ядра.

  • Структура данных не инициализируется вашим модулем. Например, struct regulator_dev возвращаемая regulator_register() .

Доступ к основным структурам данных ядра осуществляется только через функции, экспортируемые ядром, или через параметры, явно передаваемые в качестве входных данных в перехватчики поставщика. Если у вас нет API или крючка поставщика для изменения частей базовой структуры данных ядра, вероятно, это сделано намеренно, и вам не следует изменять структуру данных из модулей. Например, не изменяйте никакие поля внутри struct device или struct device.links .

  • Чтобы изменить device.devres_head , используйте функцию devm_*() , например devm_clk_get() , devm_regulator_get() или devm_kzalloc() .

  • Чтобы изменить поля внутри struct device.links , используйте API ссылки на устройство, например device_link_add() или device_link_del() .

Не анализируйте узлы дерева устройств с совместимым свойством.

Если узел дерева устройств (DT) имеет compatible свойство, struct device выделяется для него автоматически или при вызове of_platform_populate() на родительском узле DT (обычно драйвером устройства родительского устройства). Ожидается, что по умолчанию (за исключением некоторых устройств, инициализированных планировщиком заранее) является то, что узел DT с compatible свойством имеет struct device и соответствующий драйвер устройства. Все остальные исключения уже обрабатываются вышестоящим кодом.

Кроме того, fw_devlink (ранее называвшийся of_devlink ) считает узлы DT со свойством compatible устройствами с выделенным struct device , которое проверяется драйвером. Если узел DT имеет compatible свойство, но выделенное struct device не проверено, fw_devlink может заблокировать проверку своих потребительских устройств или заблокировать вызовы sync_state() для своих устройств-поставщиков.

Если ваш драйвер использует функцию of_find_*() (например, of_find_node_by_name() или of_find_compatible_node() ) для непосредственного поиска узла DT, имеющего compatible свойство, а затем анализа этого узла DT, исправьте модуль, написав драйвер устройства, который может проверять устройство или удалить compatible свойство (возможно, только если оно не было передано в исходную версию). Чтобы обсудить альтернативы, обратитесь к команде Android Kernel по адресу kernel-team@android.com и будьте готовы обосновать свои варианты использования.

Используйте DT Phandles для поиска поставщиков

По возможности обращайтесь к поставщику, используя phandle (ссылку/указатель на узел DT) в DT. Использование стандартных привязок и хэндлов DT для обращения к поставщикам позволяет fw_devlink (ранее of_devlink ) автоматически определять зависимости между устройствами путем анализа DT во время выполнения. Затем ядро ​​может автоматически проверять устройства в правильном порядке, устраняя необходимость в упорядочивании загрузки модулей или MODULE_SOFTDEP() .

Устаревший сценарий (без поддержки DT в ядре ARM)

Раньше, до того как в ядра ARM была добавлена ​​поддержка DT, потребители, такие как сенсорные устройства, искали поставщиков, таких как регулирующие органы, используя глобально уникальные строки. Например, драйвер ACME PMIC может зарегистрировать или объявить несколько регуляторов (например, acme-pmic-ldo1 до acme-pmic-ldo10 ), а сенсорный драйвер может найти регулятор с помощью regulator_get(dev, "acme-pmic-ldo10") . Однако на другой плате LDO8 может поставлять сенсорное устройство, создавая громоздкую систему, в которой один и тот же драйвер сенсорного экрана должен определять правильную строку поиска для регулятора для каждой платы, в которой используется сенсорное устройство.

Текущий сценарий (поддержка DT в ядре ARM)

После того, как в ядра ARM была добавлена ​​поддержка DT, потребители могут идентифицировать поставщиков в DT, обращаясь к узлу дерева устройств поставщика с помощью phandle . Потребители также могут называть ресурс в зависимости от того, для чего он используется, а не от того, кто его поставляет. Например, драйвер сенсорного экрана из предыдущего примера может использовать regulator_get(dev, "core") и regulator_get(dev, "sensor") , чтобы получить информацию о поставщиках, обеспечивающих питание ядра и сенсора сенсорного устройства. Соответствующее ОУ для такого устройства аналогично следующему примеру кода:

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

Худший из обоих миров сценарий

Некоторые драйверы, перенесенные из старых ядер, включают устаревшее поведение в DT, которое берет худшую часть устаревшей схемы и принудительно применяет ее к новой схеме, предназначенной для упрощения работы. В таких драйверах драйвер потребителя считывает строку, которая будет использоваться для поиска с использованием свойства DT, специфичного для устройства, поставщик использует другое свойство, специфичное для поставщика, чтобы определить имя, которое будет использоваться для регистрации ресурса поставщика, затем потребитель и поставщик продолжают использовать та же старая схема использования строк для поиска поставщика. В этом худшем из обоих миров сценарии:

  • Драйвер сенсорного экрана использует код, аналогичный следующему:

    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 использует код, подобный следующему:

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

Не изменять ошибки API платформы

API-интерфейсы платформы, такие как regulator , clocks , irq , gpio , phys и extcon , возвращают -EPROBE_DEFER в качестве возвращаемого значения ошибки, чтобы указать, что устройство пытается выполнить проверку, но не может в данный момент, и ядро ​​должно повторить попытку проверки. позже. Чтобы гарантировать, что функция .probe() вашего устройства в таких случаях не будет работать должным образом, не заменяйте и не переназначайте значение ошибки. Замена или переназначение значения ошибки может привести к удалению -EPROBE_DEFER и к тому, что ваше устройство никогда не будет проверено.

Используйте варианты API devm_*()

Когда устройство получает ресурс с помощью API devm_*() , ресурс автоматически освобождается ядром, если устройству не удается выполнить проверку или проверка прошла успешно, а затем отсоединяется. Эта функциональность делает код обработки ошибок в функции probe() более чистым, поскольку он не требует переходов goto для освобождения ресурсов, полученных с помощью devm_*() , и упрощает операции по отвязке драйвера.

Обработка отмены привязки драйвера устройства

Будьте внимательны при отмене привязки драйверов устройств и не оставляйте отвязку неопределенной, поскольку неопределенность не означает запрет. Необходимо либо полностью реализовать отвязку драйверов устройств , либо явно отключить отвязку драйверов устройств.

Реализация отмены привязки драйвера устройства

Если вы решили полностью реализовать отвязку драйверов устройств, отмените привязку драйверов устройств аккуратно, чтобы избежать утечек памяти или ресурсов и проблем безопасности. Вы можете привязать устройство к драйверу, вызвав функцию драйвера probe() , и отменить привязку устройства, вызвав функцию драйвера remove() . Если функция remove() не существует, ядро ​​все равно может отсоединить устройство; ядро драйвера предполагает, что драйверу не требуется никаких действий по очистке при отвязке от устройства. Драйверу, который не привязан к устройству, не требуется выполнять какую-либо явную очистку, если выполняются оба следующих условия:

  • Все ресурсы, получаемые функцией драйвера probe() передаются через API-интерфейсы devm_*() .

  • Аппаратному устройству не требуется последовательность выключения или стабилизации.

В этой ситуации ядро ​​драйвера обрабатывает освобождение всех ресурсов, полученных через API devm_*() . Если какое-либо из предыдущих утверждений неверно, драйверу необходимо выполнить очистку (освободить ресурсы и выключить или приостановить работу оборудования) при отвязке от устройства. Чтобы гарантировать, что устройство сможет правильно отсоединить модуль драйвера, используйте один из следующих вариантов:

  • Если аппаратному обеспечению не требуется последовательность выключения или стабилизации, измените модуль устройства для получения ресурсов с помощью API-интерфейсов devm_*() .

  • Реализуйте операцию драйвера remove() в той же структуре, что и функцию probe() , затем выполните шаги очистки с помощью функции remove() .

Явное отключение отвязки драйвера устройства (не рекомендуется)

При выборе явного отключения отвязки драйвера устройства вам необходимо запретить отвязку и запретить выгрузку модуля.

  • Чтобы запретить отмену привязки, установите для флага suppress_bind_attrs значение true в struct device_driver ; этот параметр предотвращает отображение файлов bind и unbind в каталоге sysfs драйвера. Файл unbind — это то, что позволяет пользовательскому пространству инициировать отвязку драйвера от его устройства.

  • Чтобы запретить выгрузку модуля, убедитесь, что у модуля есть [permanent] в lsmod . Если не использовать module_exit() или module_XXX_driver() , модуль помечается как [permanent] .

Не загружайте прошивку из функции зонда

Драйвер не должен загружать прошивку из функции .probe() , поскольку у него может не быть доступа к прошивке, если драйвер выполняет проверку до того, как будет смонтирована файловая система на основе флэш-памяти или постоянного хранилища. В таких случаях API request_firmware*() может заблокироваться на долгое время, а затем выйти из строя, что может неоправданно замедлить процесс загрузки. Вместо этого отложите загрузку прошивки до того момента, когда клиент начнет использовать устройство. Например, драйвер дисплея может загружать встроенное ПО при открытии устройства отображения.

Использование .probe() для загрузки прошивки может быть приемлемым в некоторых случаях, например, в драйвере часов, которому для работы требуется встроенное ПО, но устройство не доступно пользовательскому пространству. Возможны и другие подходящие варианты использования.

Реализация асинхронного зондирования

Поддерживайте и используйте асинхронное тестирование, чтобы воспользоваться будущими улучшениями, такими как параллельная загрузка модулей или проверка устройств для ускорения загрузки, которые могут быть добавлены в Android в будущих выпусках. Модули драйверов, не использующие асинхронное тестирование, могут снизить эффективность такой оптимизации.

Чтобы пометить драйвер как поддерживающий или предпочитающий асинхронное зондирование, установите поле probe_type в члене struct device_driver . В следующем примере показана такая поддержка, включенная для драйвера платформы:

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

Чтобы драйвер работал с асинхронным зондированием, не требуется специальный код. Однако при добавлении поддержки асинхронного тестирования имейте в виду следующее.

  • Не делайте предположений о ранее исследованных зависимостях. Проверьте прямо или косвенно (большинство вызовов инфраструктуры) и верните -EPROBE_DEFER если один или несколько поставщиков еще не готовы.

  • Если вы добавляете дочерние устройства в функцию проверки родительского устройства, не думайте, что дочерние устройства будут проверены немедленно.

  • Если проверка не удалась, выполните правильную обработку ошибок и очистку (см. Использование вариантов API devm_*() ).

Не используйте MODULE_SOFTDEP для заказа зондов устройства.

Функция MODULE_SOFTDEP() не является надежным решением для обеспечения порядка опросов устройств и не должна использоваться по следующим причинам.

  • Отложенный зонд. Когда модуль загружается, проверка устройства может быть отложена, поскольку один из его поставщиков не готов. Это может привести к несоответствию порядка загрузки модуля и порядка проверки устройства.

  • Один драйвер, множество устройств. Модуль драйвера может управлять определенным типом устройства. Если система включает более одного экземпляра типа устройства и каждое из этих устройств имеет разные требования к порядку проверки, вы не сможете соблюсти эти требования, используя порядок загрузки модулей.

  • Асинхронное зондирование. Модули драйверов, выполняющие асинхронное тестирование, не проверяют устройство сразу после загрузки модуля. Вместо этого параллельный поток обрабатывает проверку устройства, что может привести к несоответствию между порядком загрузки модуля и порядком проверки устройства. Например, когда основной модуль драйвера I2C выполняет асинхронное зондирование, а модуль сенсорного драйвера зависит от PMIC, находящегося на шине I2C, даже если сенсорный драйвер и драйвер PMIC загружаются в правильном порядке, попытка сенсорного драйвера может быть предпринята раньше. датчик драйвера PMIC.

Если у вас есть модули драйверов, использующие функцию MODULE_SOFTDEP() , исправьте их, чтобы они не использовали эту функцию. Чтобы помочь вам, команда Android внесла изменения, которые позволяют ядру решать проблемы с упорядочиванием без использования MODULE_SOFTDEP() . В частности, вы можете использовать fw_devlink чтобы обеспечить порядок проверки и (после того, как все потребители устройства выполнили проверку) использовать обратный вызов sync_state() для выполнения любых необходимых задач.

Используйте #if IS_ENABLED() вместо #ifdef для конфигураций.

Используйте #if IS_ENABLED(CONFIG_XXX) вместо #ifdef CONFIG_XXX , чтобы гарантировать, что код внутри блока #if продолжит компилироваться, если в будущем конфигурация изменится на конфигурацию с тремя состояниями. Различия заключаются в следующем:

  • #if IS_ENABLED(CONFIG_XXX) имеет значение true , если CONFIG_XXX установлено значение модуля ( =m ) или встроенного ( =y ).

  • #ifdef CONFIG_XXX имеет значение true , если CONFIG_XXX установлено значение встроенный ( =y ), но не имеет значения, если для CONFIG_XXX установлено значение модуль ( =m ). Используйте это только в том случае, если вы уверены, что хотите сделать то же самое, когда в конфигурации установлен модуль или отключен.

Используйте правильный макрос для условной компиляции.

Если CONFIG_XXX установлен на модуль ( =m ), система сборки автоматически определяет CONFIG_XXX_MODULE . Если ваш драйвер контролируется CONFIG_XXX и вы хотите проверить, компилируется ли ваш драйвер как модуль, используйте следующие рекомендации:

  • В файле C (или любом исходном файле, который не является заголовочным файлом) вашего драйвера не используйте #ifdef CONFIG_XXX_MODULE , поскольку он излишне ограничителен и нарушает работу, если конфигурация переименовывается в CONFIG_XYZ . Для любого исходного файла без заголовка, скомпилированного в модуль, система сборки автоматически определяет MODULE для области действия этого файла. Поэтому, чтобы проверить, компилируется ли файл C (или любой исходный файл без заголовка) как часть модуля, используйте #ifdef MODULE (без префикса CONFIG_ ).

  • В файлах заголовков та же проверка сложнее, поскольку файлы заголовков не компилируются непосредственно в двоичный файл, а скорее компилируются как часть файла C (или других исходных файлов). Используйте следующие правила для файлов заголовков:

    • Для файла заголовка, использующего #ifdef MODULE , результат меняется в зависимости от того, какой исходный файл его использует. Это означает, что один и тот же заголовочный файл в одной сборке может содержать разные части кода, скомпилированные для разных исходных файлов (модуль, встроенный или отключенный). Это может быть полезно, если вы хотите определить макрос, который необходимо раскрыть в одну сторону для встроенного кода и в другую сторону для модуля.

    • Для файла заголовка, который необходимо скомпилировать в фрагмент кода, когда для конкретного CONFIG_XXX установлено значение модуля (независимо от того, является ли исходный файл, включающий его, модулем), файл заголовка должен использовать #ifdef CONFIG_XXX_MODULE .