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

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

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

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

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

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

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

از init و exit واحد به‌درستی استفاده کنید

واحد راننده باید راننده را در 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-A.h) می‌توانند در فایل سرایند دیگری (header-B.h) که معمولاً از اشاره‌گرها به آن انواع داده استفاده می‌کند، ازقبل اعلام شوند. این الگوی کد به این معنی است که هسته عمداً تلاش می‌کند ساختار داده را برای کاربران header-B.h خصوصی نگه دارد.

کاربران header-B.h نباید header-A.h را برای دسترسی مستقیم به بخش‌های داخلی این ساختارهای داده پیش‌اعلام‌شده اضافه کنند. انجام این کار باعث ایجاد مشکلات عدم تطابق CONFIG_MODVERSIONS CRC می‌شود (که مشکلات سازگاری ABI را ایجاد می‌کند) زمانی که هسته دیگری (مانند هسته GKI) تلاش می‌کند ماژول را بار کند.

برای مثال، struct fwnode_handle در include/linux/fwnode.h تعریف شده است، اما در include/linux/device.h به‌عنوان struct fwnode_handle; اعلام شده است زیرا هسته تلاش می‌کند جزئیات 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().

دسترسی به ساختارهای داده هسته اصلی فقط ازطریق توابع صادرشده توسط هسته یا ازطریق پارامترهایی که به‌طور صریح به‌عنوان ورودی به قلاب‌های فروشنده منتقل می‌شوند. اگر واسط برنامه‌سازی کاربردی یا قلاب فروشنده‌ای برای اصلاح بخش‌هایی از ساختار داده هسته اصلی ندارید، احتمالاً این کار عمدی است و نباید ساختار داده را از واحدها اصلاح کنید. برای مثال، هیچ فیلدی را در struct device یا struct device.links تغییر ندهید.

  • برای تغییر دادن device.devres_head، از تابع devm_*() مثل devm_clk_get()، devm_regulator_get()، یا devm_kzalloc() استفاده کنید.

  • برای اصلاح فیلدهای داخل struct device.links، از میانای برنامه‌سازی کاربردی پیوند دستگاه مثل device_link_add() یا device_link_del() استفاده کنید.

گره‌های devicetree با ویژگی سازگار تجزیه نشود

اگر گره درخت دستگاه (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-team@android.com تماس بگیرید و آماده باشید تا موارد استفاده خود را توجیه کنید.

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

درصورت امکان، بااستفاده از یک نام کاربری (ارجاع یا اشاره‌گر به گره DT) در DT به تأمین‌کننده ارجاع دهید. استفاده از اتصال‌های DT استاندارد و phandle برای ارجاع به تأمین‌کنندگان 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);
    
  • «جدول داده» از کدی مشابه کد زیر استفاده می‌کند:

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

خطاهای میانای برنامه‌سازی کاربردی چارچوب را اصلاح نکنید

میاناهای برنامه‌سازی کاربردی چارچوب، مثل regulator، clocks، irq، gpio، phys، و extcon، مقدار برگشتی خطای -EPROBE_DEFER را برمی‌گردانند تا نشان دهند که دستگاهی درحال تلاش برای کاوش است اما درحال‌حاضر نمی‌تواند این کار را انجام دهد، و هسته باید کاوش را بعداً دوباره امتحان کند. برای اطمینان از اینکه عملکرد .probe() دستگاهتان در چنین مواردی طبق انتظار با خطا مواجه می‌شود، مقدار خطا را جایگزین نکنید یا آن را دوباره تخصیص ندهید. جایگزین کردن یا بازنگاشت کردن مقدار خطا ممکن است باعث شود -EPROBE_DEFER حذف شود و درنتیجه دستگاهتان هرگز کاوش نشود.

از انواع میانای برنامه‌سازی کاربردی devm_*()‎ استفاده کنید

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

رسیدگی به لغو اتصال درایور دستگاه

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

پیاده‌سازی لغو اتصال درایور دستگاه

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

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

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

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

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

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

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

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

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

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

سفت‌افزار را از درون تابع کاوشگر بار نکنید

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

استفاده از .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() برای انجام هرگونه تکلیف لازم استفاده کنید.

برای پیکربندی‌ها، به‌جای ‎ #ifdef از ‎ #if IS_ENABLED()‎ استفاده کنید

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

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

  • #ifdef CONFIG_XXX evaluates to true when CONFIG_XXX is set to built-in (=y) , but doesn't when CONFIG_XXX is set to module (=m). Use this only when you're certain you want to do the same thing when the config is set to module or is disabled.

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

اگر 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 استفاده کند.