إرشادات حول وحدات المورّدين

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

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

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

استخدام ماكرو 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; في 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().

لا يمكنك الوصول إلى هياكل بيانات kernel الأساسية إلا من خلال الدوال التي يصدّرها kernel أو من خلال المَعلمات التي يتم تمريرها صراحةً كمدخلات إلى أدوات الربط الخاصة بالمورّدين. إذا لم يكن لديك واجهة برمجة تطبيقات أو ربط موفِّر لتعديل أجزاء من بنية data الأساسية للنواة، من المحتمل أن يكون ذلك مقصودًا، ويجب عدم تعديل بنية data من الوحدات. على سبيل المثال، لا تعدِّل أي حقول داخل struct device أو struct device.links.

  • لتعديل device.devres_head، استخدِم دالة devm_*() مثل devm_clk_get() أو devm_regulator_get() أو devm_kzalloc().

  • لتعديل الحقول داخل "struct device.links"، استخدِم واجهة برمجة تطبيقات Device link 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 هذه، يمكنك إصلاح الوحدة عن طريق كتابة of_find_*()برنامج تشغيل جهاز يمكنه فحص الجهاز أو إزالة السمة compatible (لا يمكن إجراء ذلك إلا إذا لم يتم نقله إلى الإصدار العلني). لمناقشة البدائل، يُرجى التواصل مع فريق Android Kernel على العنوان kernel-team@android.com والاستعداد لمحاولة توضيح حالات الاستخدام.

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

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

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

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

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

عندما يحصل الجهاز على مورد باستخدام واجهة برمجة تطبيقات devm_*()، يُطلق kernel المورد تلقائيًا إذا تعذّر على الجهاز إجراء عملية فحص أو إذا أجرى عملية فحص بنجاح وتم إلغاء ربطه لاحقًا. تجعل هذه الميزة رمز معالجة الأخطاء في دالة 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*() لفترة طويلة ثم يتعذّر استخدامها، ما قد يؤدي إلى تباطؤ عملية الإقلاع بدون داعٍ. بدلاً من ذلك، يمكنك تأجيل تحميل البرامج الثابتة إلى وقت بدء العميل استخدام الجهاز. على سبيل المثال، يمكن لبرنامج تشغيل الشاشة carregar تحميل البرامج الثابتة عند فتح جهاز العرض.

قد يكون استخدام .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 إذا لم يكن أحد المورّدين أو أكثر جاهزًا بعد.

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

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

لا تستخدِم MODULE_SOFTDEP لطلب عمليات فحص الأجهزة.

ويُرجى العِلم بأنّ دالة MODULE_SOFTDEP() ليست حلاً موثوقًا به لضمان ترتيب المسابرات الخاصة بالأجهزة، ويجب عدم استخدامها للأسباب التالية.

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

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

  • الاستكشاف غير المتزامن: وحدات برامج التشغيل التي تُجري اختبارات غير متزامنة لا تتحقق فورًا من بيانات الجهاز عند تحميل الوحدة. بدلاً من ذلك، يعالج خيوط برمجية متعامِدة عملية فحص الجهاز، ما قد يؤدي إلى عدم تطابق بين ترتيب تحميل الوحدة وترتيب فحص الجهاز. على سبيل المثال، عندما تُجري وحدة برنامج التشغيل الرئيسية I2C استقصاء غير متزامن وتعتمد وحدة برنامج التشغيل التي تعمل باللمس على وحدة PMIC الموجودة في ناقل I2C، حتى إذا كان برنامج تشغيل اللمس وتحميل برنامج التشغيل PMIC بالترتيب الصحيح، قد تتم محاولة استقصاء برنامج تشغيل اللمس قبل فحص برنامج تشغيل PMIC.

إذا كانت لديك وحدات برامج تشغيل تستخدِم الدالة MODULE_SOFTDEP()، عليك إصلاحها كي تتمكّن من عدم استخدام هذه الدالة. لمساعدتك، أرسل فريق Android تغييرات إلى الإصدار العلني تتيح للنواة التعامل مع مشاكل الترتيب بدون استخدام MODULE_SOFTDEP(). على وجه التحديد، يمكنك استخدام fw_devlink لضمان ترتيب عمليات الاستكشاف و (بعد أن يتم استكشاف جميع مستخدِمي جهاز معيّن) استخدام callback sync_state() لتنفيذ أي مهام ضرورية.

استخدام #if IS_ENABLED()‎ بدلاً من #ifdef للإعدادات

استخدِم #if IS_ENABLED(CONFIG_XXX) بدلاً من #ifdef CONFIG_XXX لضمان مواصلة تجميع الرمز البرمجي داخل البنية #if في حال تغيُّر الإعداد إلى #if IS_ENABLED(CONFIG_XXX) في المستقبل. في ما يلي الاختلافات:

  • يتم تقييم #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_).

  • في ملفات الرأس، يكون التحقّق نفسه أكثر تعقيدًا لأنّ ملفات الرأس لا تتم compiling مباشرةً إلى ملف ثنائي، بل تتم compiling كجزء من ملف C (أو ملفات مصدر أخرى). استخدِم القواعد التالية لملفات الرأس:

    • بالنسبة إلى ملف العنوان الذي يستخدم #ifdef MODULE، تتغير النتيجة بناءً على ملف المصدر الذي يستخدمه. وهذا يعني أنّ ملف الرأس نفسه في الإصدار نفسه يمكن أن يحتوي على أجزاء مختلفة من رمزه تم تجميعها لملفات مصدر مختلفة (الوحدة مقابل مضمّنة أو غير مفعَّلة). يمكن أن يكون ذلك مفيدًا عندما تريد تحديد وحدة ماكرو تحتاج إلى التوسيع بطريقة واحدة للرمز المضمّن وتوسيع بطريقة مختلفة للوحدة.

    • بالنسبة إلى ملف الرأس الذي يجب تجميعه في قطعة من الرمز البرمجي عند ضبط قيمة محددة CONFIG_XXX على وحدة (بغض النظر عمّا إذا كان ملف المصدر بما في ذلك هو وحدة)، يجب أن يستخدم ملف الرأس #ifdef CONFIG_XXX_MODULE.