دستورالعمل های ماژول فروشنده

از دستورالعمل‌های زیر برای افزایش استحکام و قابلیت اطمینان ماژول‌های فروشنده خود استفاده کنید. بسیاری از دستورالعمل‌ها، در صورت رعایت، می‌توانند به تعیین ترتیب صحیح بارگذاری ماژول و ترتیبی که درایورها باید برای دستگاه‌ها جستجو کنند، کمک کنند.

یک ماژول می‌تواند یک کتابخانه یا یک درایور باشد.

  • ماژول‌های کتابخانه‌ای ، کتابخانه‌هایی هستند که APIهایی را برای استفاده سایر ماژول‌ها فراهم می‌کنند. چنین ماژول‌هایی معمولاً مختص سخت‌افزار نیستند. نمونه‌هایی از ماژول‌های کتابخانه‌ای شامل ماژول رمزگذاری AES، چارچوب remoteproc که به عنوان یک ماژول کامپایل می‌شود و یک ماژول logbuffer است. کد ماژول در module_init() برای تنظیم ساختارهای داده اجرا می‌شود، اما هیچ کد دیگری اجرا نمی‌شود مگر اینکه توسط یک ماژول خارجی فعال شود.

  • ماژول‌های درایور ، درایورهایی هستند که نوع خاصی از دستگاه را جستجو یا به آن متصل می‌شوند. چنین ماژول‌هایی مختص سخت‌افزار هستند. نمونه‌هایی از ماژول‌های درایور شامل UART، PCIe و سخت‌افزار رمزگذار ویدیو هستند. ماژول‌های درایور فقط زمانی فعال می‌شوند که دستگاه مرتبط با آنها در سیستم وجود داشته باشد.

    • اگر دستگاه موجود نباشد، تنها کد ماژولی که اجرا می‌شود، کد module_init() است که درایور را در چارچوب اصلی درایور ثبت می‌کند.

    • اگر دستگاه وجود داشته باشد و درایور با موفقیت آن دستگاه را جستجو کند یا به آن متصل شود، ممکن است کد ماژول دیگری اجرا شود.

از ماژول init و exit به درستی استفاده کنید

ماژول‌های درایور باید یک درایور را در module_init() ثبت کنند و یک درایور را در module_exit() لغو ثبت کنند. یکی از راه‌های اعمال این محدودیت‌ها، استفاده از ماکروهای wrapper است که از استفاده مستقیم ماکروهای 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 یا داده‌های aux دستگاه، آنها را از هم متمایز کنید. به طور جایگزین، می‌توانید ماژول درایور را به دو ماژول تقسیم کنید.

استثنائات تابع شروع و خروج

ماژول‌های کتابخانه درایورها را ثبت نمی‌کنند و از محدودیت‌های مربوط به module_init() و module_exit() معاف هستند، زیرا ممکن است برای تنظیم ساختارهای داده، صف‌های کاری یا نخ‌های هسته به این توابع نیاز داشته باشند.

از ماکروی MODULE_DEVICE_TABLE استفاده کنید

ماژول‌های درایور باید شامل ماکروی MODULE_DEVICE_TABLE باشند که به فضای کاربری اجازه می‌دهد قبل از بارگذاری ماژول، دستگاه‌های پشتیبانی‌شده توسط یک ماژول درایور را تعیین کند. اندروید می‌تواند از این داده‌ها برای بهینه‌سازی بارگذاری ماژول استفاده کند، مانند جلوگیری از بارگذاری ماژول‌ها برای دستگاه‌هایی که در سیستم وجود ندارند. برای مثال‌هایی در مورد استفاده از ماکرو، به کد بالادست مراجعه کنید.

از عدم تطابق 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 خصوصی نگه دارد. در این سناریو، برای دسترسی به اعضای struct fwnode_handle ، #include <linux/fwnode.h> در یک ماژول اضافه نکنید. هر طرحی که مجبور به درج چنین فایل‌های هدری در آن باشید، نشان دهنده یک الگوی طراحی نامناسب است.

مستقیماً به ساختارهای هسته اصلی دسترسی پیدا نکنید

دسترسی مستقیم یا تغییر ساختارهای داده هسته اصلی می‌تواند منجر به رفتارهای نامطلوب، از جمله نشت حافظه، خرابی و عدم سازگاری با نسخه‌های آینده هسته شود. یک ساختار داده زمانی یک ساختار داده هسته اصلی است که هر یک از شرایط زیر را داشته باشد:

  • ساختار داده در زیر 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 را حذف کند (فقط در صورتی که upstream نشده باشد، امکان‌پذیر است) اصلاح کنید. برای بحث در مورد گزینه‌های دیگر، با تیم هسته اندروید به آدرس kernel-team@android.com تماس بگیرید و آماده باشید تا موارد استفاده خود را توجیه کنید.

از دسته‌های DT برای جستجوی تأمین‌کنندگان استفاده کنید

در صورت امکان، با استفاده از یک phandle (یک مرجع یا اشاره‌گر به گره DT) در DT به یک تأمین‌کننده ارجاع دهید. استفاده از اتصالات و phandleهای استاندارد DT برای ارجاع به تأمین‌کنندگان، fw_devlink (که قبلاً of_devlink ) را قادر می‌سازد تا با تجزیه DT در زمان اجرا، وابستگی‌های بین دستگاهی را به طور خودکار تعیین کند. سپس هسته می‌تواند به طور خودکار دستگاه‌ها را به ترتیب صحیح بررسی کند و نیاز به ترتیب بارگذاری ماژول یا MODULE_SOFTDEP() را از بین ببرد.

سناریوی قدیمی (عدم پشتیبانی از DT در هسته ARM)

پیش از این، قبل از اینکه پشتیبانی DT به هسته‌های ARM اضافه شود، مصرف‌کنندگانی مانند دستگاه‌های لمسی با استفاده از رشته‌های منحصر به فرد سراسری، تأمین‌کنندگانی مانند تنظیم‌کننده‌ها را جستجو می‌کردند. به عنوان مثال، درایور ACME PMIC می‌توانست چندین تنظیم‌کننده (مانند acme-pmic-ldo1 تا acme-pmic-ldo10 ) را ثبت یا تبلیغ کند و یک درایور لمسی می‌توانست با استفاده regulator_get(dev, "acme-pmic-ldo10") یک تنظیم‌کننده را جستجو کند. با این حال، در یک برد متفاوت، LDO8 ممکن است دستگاه لمسی را تغذیه کند و یک سیستم دست و پا گیر ایجاد کند که در آن همان درایور لمسی باید رشته جستجوی صحیح برای تنظیم‌کننده را برای هر بردی که دستگاه لمسی در آن استفاده می‌شود، تعیین کند.

سناریوی فعلی (پشتیبانی از DT در هسته ARM)

پس از اضافه شدن پشتیبانی DT به هسته‌های ARM، مصرف‌کنندگان می‌توانند با مراجعه به گره درخت دستگاه تأمین‌کننده با استفاده از یک phandle ، تأمین‌کنندگان را در DT شناسایی کنند. مصرف‌کنندگان همچنین می‌توانند منبع را بر اساس کاربرد آن، به جای اینکه چه کسی آن را تأمین می‌کند، نامگذاری کنند. به عنوان مثال، درایور لمسی از مثال قبلی می‌تواند از regulator_get(dev, "core") و regulator_get(dev, "sensor") برای دریافت تأمین‌کنندگانی که هسته و حسگر دستگاه لمسی را تغذیه می‌کنند، استفاده کند. DT مرتبط برای چنین دستگاهی مشابه نمونه کد زیر است:

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_*() ندارد و عملیات unbinding درایور را ساده می‌کند.

مدیریت عدم اتصال درایور دستگاه

در مورد unbinding درایورهای دستگاه عمدی عمل کنید و unbinding را undefined رها نکنید زیرا undefined به معنای غیرمجاز بودن نیست. شما باید یا unbinding درایور دستگاه را به طور کامل پیاده‌سازی کنید یا صریحاً unbinding درایور دستگاه را غیرفعال کنید.

پیاده‌سازی جداسازی درایور دستگاه

هنگام انتخاب پیاده‌سازی کامل unbinding درایور دستگاه، درایورهای دستگاه را به طور کامل unbind کنید تا از نشت حافظه یا منابع و مشکلات امنیتی جلوگیری شود. می‌توانید با فراخوانی تابع probe() درایور، یک دستگاه را به یک درایور متصل کنید و با فراخوانی تابع remove() درایور، آن را unbind کنید. اگر تابع remove() وجود نداشته باشد، هسته همچنان می‌تواند دستگاه را unbind کند. هسته درایور فرض می‌کند که هنگام unbind شدن از دستگاه، هیچ کار پاکسازی توسط درایور لازم نیست. درایوری که از دستگاه unbind شده است، در صورت صحیح بودن هر دو مورد زیر، نیازی به انجام هیچ کار پاکسازی صریحی ندارد:

  • تمام منابعی که توسط تابع probe() درایور به دست می‌آیند، از طریق رابط‌های برنامه‌نویسی کاربردی (API) devm_*() هستند.

  • دستگاه سخت‌افزاری نیازی به خاموش شدن یا خاموش شدن متوالی ندارد.

در این شرایط، هسته درایور، آزادسازی تمام منابع به دست آمده از طریق APIهای devm_*() را مدیریت می‌کند. اگر هر یک از عبارات قبلی نادرست باشد، درایور هنگام جدا شدن از دستگاه، باید پاکسازی (رهاسازی منابع و خاموش کردن یا غیرفعال کردن سخت‌افزار) را انجام دهد. برای اطمینان از اینکه دستگاه می‌تواند ماژول درایور را به طور کامل جدا کند، از یکی از گزینه‌های زیر استفاده کنید:

  • اگر سخت‌افزار نیازی به خاموش شدن یا خاموش کردن متوالی ندارد، ماژول دستگاه را طوری تغییر دهید که با استفاده از APIهای devm_*() منابع را به دست آورد.

  • عملیات درایور remove() را در همان ساختاری که تابع probe() قرار دارد، پیاده‌سازی کنید، سپس مراحل پاکسازی را با استفاده از تابع remove() انجام دهید.

غیرفعال کردن صریح قابلیت unbinding درایور دستگاه (توصیه نمی‌شود)

هنگام انتخاب غیرفعال کردن صریح unbinding درایور دستگاه، باید unbinding و unloading ماژول را غیرفعال کنید.

  • برای غیرفعال کردن unbinding، پرچم suppress_bind_attrs را در struct device_driver روی true تنظیم کنید؛ این تنظیم از نمایش فایل‌های bind و unbind در دایرکتوری sysfs درایور جلوگیری می‌کند. فایل unbind چیزی است که به فضای کاربر اجازه می‌دهد unbinding یک درایور را از دستگاه خود فعال کند.

  • برای جلوگیری از تخلیه ماژول، مطمئن شوید که ماژول در lsmod دارای [permanent] است. با عدم استفاده از module_exit() یا module_XXX_driver() ، ماژول به عنوان [permanent] علامت گذاری می‌شود.

فریمور را از داخل تابع پروب بارگذاری نکنید

درایور نباید میان‌افزار را از داخل تابع .probe() بارگذاری کند، زیرا اگر درایور قبل از نصب سیستم فایل مبتنی بر فلش یا حافظه دائمی، کاوش کند، ممکن است به میان‌افزار دسترسی نداشته باشد. در چنین مواردی، API request_firmware*() ممکن است برای مدت طولانی مسدود شود و سپس از کار بیفتد، که می‌تواند فرآیند بوت را بی‌جهت کند کند. در عوض، بارگذاری میان‌افزار را به زمانی که کلاینت شروع به استفاده از دستگاه می‌کند، موکول کنید. به عنوان مثال، یک درایور نمایشگر می‌تواند میان‌افزار را هنگام باز شدن دستگاه نمایشگر بارگذاری کند.

استفاده از .probe() برای بارگذاری میان‌افزار ممکن است در برخی موارد، مانند درایور ساعت که برای عملکرد به میان‌افزار نیاز دارد اما دستگاه در معرض فضای کاربر قرار ندارد، اشکالی نداشته باشد. موارد استفاده مناسب دیگری نیز امکان‌پذیر است.

پیاده‌سازی کاوش ناهمزمان

از کاوش ناهمزمان پشتیبانی و استفاده کنید تا از پیشرفت‌های آینده، مانند بارگذاری موازی ماژول یا کاوش دستگاه برای افزایش سرعت بوت، که ممکن است در نسخه‌های آینده به اندروید اضافه شوند، بهره‌مند شوید. ماژول‌های درایوری که از کاوش ناهمزمان استفاده نمی‌کنند، می‌توانند اثربخشی چنین بهینه‌سازی‌هایی را کاهش دهند.

برای علامت‌گذاری یک درایور به عنوان پشتیبانی‌کننده و ترجیح‌دهنده‌ی کاوش ناهمزمان، فیلد 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() استفاده می‌کنند، آن‌ها را طوری اصلاح کنید که از این تابع استفاده نکنند. برای کمک به شما، تیم اندروید تغییراتی را در بالادست اعمال کرده است که به هسته امکان می‌دهد مشکلات ترتیب را بدون استفاده از MODULE_SOFTDEP() مدیریت کند. به طور خاص، می‌توانید از fw_devlink برای اطمینان از ترتیب پروب استفاده کنید و (پس از اینکه همه مصرف‌کنندگان یک دستگاه پروب کردند) از تابع فراخوانی sync_state() برای انجام هرگونه کار لازم استفاده کنید.

برای تنظیمات از #if_IS_ENABLED() به جای #ifdef استفاده کنید.

برای اطمینان از اینکه کد داخل بلوک #if در صورت تغییر پیکربندی به پیکربندی سه حالته در آینده، همچنان کامپایل می‌شود، به جای #ifdef CONFIG_XXX از #if IS_ENABLED(CONFIG_XXX) استفاده کنید. تفاوت‌ها به شرح زیر است:

  • #if IS_ENABLED(CONFIG_XXX) زمانی true ارزیابی می‌شود که CONFIG_XXX روی ماژول ( =m ) یا داخلی ( =y ) تنظیم شده باشد.

  • #ifdef CONFIG_XXX وقتی CONFIG_XXX روی مقدار داخلی ( =y ) تنظیم شده باشد، مقدار true (true) می‌دهد، اما وقتی CONFIG_XXX روی ماژول ( =m ) تنظیم شده باشد، مقدار درست (true) نمی‌دهد. این را فقط زمانی استفاده کنید که مطمئن هستید می‌خواهید همین کار را وقتی پیکربندی روی ماژول تنظیم شده یا غیرفعال است، انجام دهید.

از ماکروی صحیح برای کامپایل‌های شرطی استفاده کنید

اگر مقدار CONFIG_XXX برابر با module ( =m ) باشد، سیستم ساخت به طور خودکار CONFIG_XXX_MODULE را تعریف می‌کند. اگر درایور شما توسط CONFIG_XXX کنترل می‌شود و می‌خواهید بررسی کنید که آیا درایور شما به عنوان یک ماژول کامپایل می‌شود یا خیر، از دستورالعمل‌های زیر استفاده کنید:

  • در فایل C (یا هر فایل منبعی که فایل هدر نیست) برای درایور خود، #ifdef CONFIG_XXX_MODULE استفاده نکنید زیرا بی‌جهت محدودکننده است و اگر پیکربندی به CONFIG_XYZ تغییر نام دهد، از کار می‌افتد. برای هر فایل منبع غیر هدر که در یک ماژول کامپایل می‌شود، سیستم ساخت به طور خودکار MODULE برای دامنه آن فایل تعریف می‌کند. بنابراین، برای بررسی اینکه آیا یک فایل C (یا هر فایل منبع غیر هدر) به عنوان بخشی از یک ماژول کامپایل می‌شود یا خیر، از #ifdef MODULE (بدون پیشوند CONFIG_ ) استفاده کنید.

  • در فایل‌های هدر، همین بررسی پیچیده‌تر است زیرا فایل‌های هدر مستقیماً به فایل باینری کامپایل نمی‌شوند، بلکه به عنوان بخشی از یک فایل C (یا سایر فایل‌های منبع) کامپایل می‌شوند. برای فایل‌های هدر از قوانین زیر استفاده کنید:

    • برای یک فایل هدر که از #ifdef MODULE استفاده می‌کند، نتیجه بر اساس اینکه کدام فایل منبع از آن استفاده می‌کند، تغییر می‌کند. این بدان معناست که یک فایل هدر مشابه در همان ساخت می‌تواند بخش‌های مختلفی از کد خود را برای فایل‌های منبع مختلف کامپایل کند (ماژول در مقابل داخلی یا غیرفعال). این می‌تواند زمانی مفید باشد که می‌خواهید ماکرویی تعریف کنید که نیاز دارد برای کد داخلی به یک روش گسترش یابد و برای یک ماژول به روش دیگری گسترش یابد.

    • برای یک فایل هدر که نیاز دارد در یک قطعه کد کامپایل شود، زمانی که یک CONFIG_XXX خاص روی module تنظیم شده باشد (صرف نظر از اینکه فایل منبع شامل آن یک ماژول باشد یا خیر)، فایل هدر باید #ifdef CONFIG_XXX_MODULE استفاده کند.