إرشادات وحدة المورّد

استخدِم الإرشادات التالية لزيادة قوة وموثوقية وحدات المورّد. يمكن أن تساعد العديد من الإرشادات، عند اتّباعها، في تسهيل تحديد ترتيب تحميل الوحدات الصحيح والترتيب الذي يجب أن تبحث به برامج التشغيل عن الأجهزة.

يمكن أن تكون الوحدة مكتبة أو برنامج تشغيل.

  • وحدات المكتبة هي مكتبات توفّر واجهات برمجة تطبيقات يمكن للوحدات الأخرى استخدامها. وعادةً ما تكون هذه الوحدات غير مرتبطة بأجهزة معيّنة. تشمل أمثلة وحدات المكتبة وحدة تشفير 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، ولكن يتم الإعلان عن struct fwnode_handle بشكل مسبق على أنّه 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().

لا يمكن الوصول إلى بنيات بيانات النواة الأساسية إلا من خلال الدوال التي تصدّرها النواة أو من خلال المَعلمات التي يتم تمريرها بشكل صريح كمدخلات إلى نقاط ربط المورّد. إذا لم يكن لديك واجهة برمجة تطبيقات أو أداة ربط خاصة بمورّد لتعديل أجزاء من بنية بيانات أساسية في النواة، من المحتمل أن يكون ذلك مقصودًا، ويجب عدم تعديل بنية البيانات من الوحدات. على سبيل المثال، لا تعدّل أي حقول داخل 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 التي تتضمّن السمة 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()) للعثور مباشرةً على عقدة شجرة الأجهزة التي تتضمّن السمة compatible ثم تحليل عقدة شجرة الأجهزة هذه، عليك إصلاح الوحدة من خلال كتابة برنامج تشغيل جهاز يمكنه فحص الجهاز أو إزالة السمة compatible (لا يمكن إجراء ذلك إلا إذا لم يتم إرسالها إلى المصدر). لمناقشة البدائل، يُرجى التواصل مع فريق Android Kernel على kernel-team@android.com والاستعداد لتقديم مبرّرات لحالات الاستخدام.

استخدام معرّفات DT phandles للبحث عن المورّدين

يجب الإشارة إلى المورّد باستخدام معرّف phandle (مرجع أو مؤشر إلى عقدة 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 الأساسية، يمكن للمستهلكين تحديد المورّدين في DT من خلال الرجوع إلى عقدة شجرة الجهاز الخاصة بالمورّد باستخدام phandle. يمكن للمستهلكين أيضًا تسمية المورد استنادًا إلى الغرض من استخدامه بدلاً من الجهة التي توفّره. على سبيل المثال، يمكن لبرنامج تشغيل شاشة اللمس من المثال السابق استخدام 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"
      };
    };
    

عدم تعديل أخطاء واجهة برمجة التطبيقات للإطار

تعرض واجهات برمجة التطبيقات الخاصة بإطار العمل، مثل regulator وclocks وirq وgpio وphys وextcon، القيمة -EPROBE_DEFER كقيمة خطأ للإشارة إلى أنّ الجهاز يحاول إجراء عملية فحص ولكن لا يمكنه إتمامها في الوقت الحالي، ويجب أن يعيد النواة محاولة إجراء عملية الفحص لاحقًا. لضمان تعذُّر تنفيذ وظيفة .probe() على جهازك كما هو متوقّع في مثل هذه الحالات، لا تستبدِل قيمة الخطأ أو تعيد تعيينها. قد يؤدي استبدال قيمة الخطأ أو إعادة تعيينها إلى حذف -EPROBE_DEFER وعدم إرسال أي طلب إلى جهازك.

استخدام صيغ devm_*() لواجهة برمجة التطبيقات

عندما يحصل الجهاز على مورد باستخدام واجهة برمجة التطبيقات devm_*()، يتم تحرير المورد تلقائيًا بواسطة النواة إذا تعذّر على الجهاز إجراء عملية فحص، أو إذا تم إجراء عملية فحص بنجاح وتم إلغاء ربط المورد لاحقًا. تتيح هذه الإمكانية أن يكون رمز معالجة الأخطاء في الدالة probe() أكثر وضوحًا لأنّه لا يتطلّب عمليات انتقال goto لإتاحة الموارد التي تم الحصول عليها من خلال devm_*()، كما تسهّل عمليات إلغاء ربط برنامج التشغيل.

التعامل مع إلغاء ربط برنامج تشغيل الجهاز

يجب أن تكون حريصًا عند إلغاء ربط برامج تشغيل الأجهزة، ويجب ألا تترك عملية إلغاء الربط غير محدّدة لأنّ عدم التحديد لا يعني عدم السماح. يجب إما تنفيذ عملية إلغاء ربط برنامج تشغيل الجهاز بالكامل أو إيقافها بشكل صريح.

تنفيذ عملية إلغاء ربط برنامج تشغيل الجهاز

عند اختيار تنفيذ عملية إلغاء ربط برامج تشغيل الأجهزة بالكامل، يجب إلغاء ربط برامج تشغيل الأجهزة بشكل سليم لتجنُّب حدوث تسرُّب في الذاكرة أو الموارد ومشاكل الأمان. يمكنك ربط جهاز ببرنامج تشغيل من خلال استدعاء دالة probe() خاصة ببرنامج التشغيل، وإلغاء ربط جهاز من خلال استدعاء دالة remove() خاصة ببرنامج التشغيل. إذا لم تكن هناك وظيفة remove()، سيظل بإمكان النواة إلغاء ربط الجهاز، ويفترض برنامج تشغيل النواة أنّه لا حاجة إلى أي عملية تنظيف من قِبل برنامج التشغيل عند إلغاء ربطه بالجهاز. لا يحتاج برنامج التشغيل الذي تم إلغاء ربطه بجهاز إلى تنفيذ أي عملية تنظيف صريحة عندما يتحقّق الشرطان التاليان:

  • يتم الحصول على جميع الموارد التي تكتسبها وظيفة probe() في برنامج التشغيل من خلال واجهات برمجة التطبيقات devm_*().

  • لا يحتاج الجهاز إلى تسلسل إيقاف أو تعليق.

في هذه الحالة، يتولّى برنامج التشغيل الأساسي تحرير جميع الموارد التي تم الحصول عليها من خلال واجهات برمجة التطبيقات devm_*(). إذا كان أي من البيانين السابقين غير صحيح، يجب أن ينفّذ برنامج التشغيل عملية تنظيف (إصدار الموارد وإيقاف تشغيل الأجهزة أو إيقافها مؤقتًا) عند إلغاء ربطها بجهاز. لضمان إمكانية إلغاء ربط وحدة برنامج تشغيل بجهاز بشكل سليم، استخدِم أحد الخيارَين التاليَين:

  • إذا لم يكن الجهاز بحاجة إلى تسلسل إيقاف أو إيقاف مؤقت، غيِّر وحدة الجهاز للحصول على الموارد باستخدام واجهات برمجة التطبيقات devm_*().

  • نفِّذ عملية برنامج التشغيل remove() في البنية نفسها التي تتضمّن الدالة probe()، ثم نفِّذ خطوات التنظيف باستخدام الدالة remove().

إيقاف ربط برنامج تشغيل الجهاز بشكل صريح (لا يُنصح بذلك)

عند اختيار إيقاف ربط برنامج تشغيل الجهاز بشكل صريح، عليك عدم السماح بإلغاء الربط وعدم السماح بإلغاء تحميل الوحدة.

  • لعدم السماح بإلغاء الربط، اضبط العلامة suppress_bind_attrs على true في struct device_driver الخاص ببرنامج التشغيل، ويمنع هذا الإعداد ظهور الملفَين bind وunbind في دليل sysfs الخاص ببرنامج التشغيل. ملف unbind هو ما يسمح لمساحة المستخدم بتفعيل إلغاء ربط برنامج التشغيل بالجهاز.

  • لمنع إلغاء تحميل الوحدة، تأكَّد من أنّ الوحدة تتضمّن [permanent] في lsmod. في حال عدم استخدام module_exit() أو module_XXX_driver()، يتم وضع علامة [permanent] على الوحدة.

عدم تحميل البرامج الثابتة من داخل دالة التحقيق

يجب ألا يحمّل برنامج التشغيل البرامج الثابتة من داخل الدالة .probe() لأنّه قد لا يتمكّن من الوصول إلى البرامج الثابتة إذا كان برنامج التشغيل يتحقّق من الأجهزة قبل تحميل نظام الملفات المستند إلى الذاكرة المؤقتة أو التخزين الدائم. في مثل هذه الحالات، قد يتم حظر واجهة برمجة التطبيقات request_firmware*() لفترة طويلة ثم يتعذّر تنفيذها، ما قد يؤدي إلى إبطاء عملية التشغيل بدون داعٍ. بدلاً من ذلك، أجِّل تحميل البرنامج الثابت إلى حين بدء أحد العملاء في استخدام الجهاز. على سبيل المثال، يمكن لبرنامج تشغيل الشاشة تحميل البرامج الثابتة عند فتح جهاز العرض.

قد يكون استخدام .probe() لتحميل البرامج الثابتة مقبولاً في بعض الحالات، مثل برنامج تشغيل الساعة الذي يحتاج إلى برامج ثابتة ليعمل، ولكن الجهاز لا يكون متاحًا لمساحة المستخدم. من الممكن استخدام حالات أخرى مناسبة.

تنفيذ عملية التحقّق غير المتزامنة

يمكنك إتاحة استخدام ميزة &quot;التحقّق غير المتزامن&quot; للاستفادة من التحسينات المستقبلية، مثل تحميل الوحدات النمطية المتوازية أو التحقّق من الأجهزة لتسريع وقت التشغيل، والتي قد تتم إضافتها إلى 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 إذا لم يكن مورد واحد أو أكثر جاهزًا بعد.

  • إذا أضفت أجهزة الأطفال في وظيفة البحث لجهاز أحد الوالدَين، لا تفترض أنّه سيتم البحث عن أجهزة الأطفال على الفور.

  • في حال تعذُّر إجراء اختبار، عليك تنفيذ معالجة الأخطاء والتنظيف بشكل صحيح (راجِع استخدام صيغ واجهة برمجة التطبيقات 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 هي built-in (=y)، ولكن لا تكون كذلك عندما تكون قيمة CONFIG_XXX هي module (=m). استخدِم هذا الخيار فقط عندما تكون متأكّدًا من أنّك تريد تنفيذ الإجراء نفسه عندما يكون الإعداد مضبوطًا على module أو عندما يكون غير مفعّل.

استخدام الماكرو الصحيح لعمليات التجميع الشرطية

إذا تم ضبط 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.