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

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

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

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

عدم تحليل عقد شجرة الأجهزة التي تحتوي على السمة compatible

إذا كانت عقدة شجرة الجهاز (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 للبحث عن المورّدين

يمكنك الإشارة إلى المورّد باستخدام معرّف phandle (مرجع أو مؤشر إلى عقدة DT) في DT كلما أمكن. من خلال استخدام عمليات ربط DT العادية وphandles للإشارة إلى المورّدين، يمكن لـ 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 كقيمة عرض خطأ للإشارة إلى أنّه يحاول الجهاز إجراء عملية فحص ولكن لا يمكنه إجراء ذلك في الوقت الحالي، ويجب أن يعيد الفحص في وقت لاحق. لضمان أنّ دالة .probe() في جهازك تُعرِض خطأ على النحو المتوقّع في مثل هذه الحالات، لا تستبدِل قيمة الخطأ أو تعيد تعيينها. قد يؤدي استبدال قيمة الخطأ أو إعادة ربطها إلى إسقاط -EPROBE_DEFER ونتيجةً لذلك، لن يتم فحص جهازك أبدًا.

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

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

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

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

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

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

  • يتم الحصول على جميع الموارد التي تحصل عليها دالة 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 لضمان orderly probing واستخدام 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.