استخدِم الإرشادات التالية لزيادة قوة وموثوقية وحدات المورّد. يمكن أن تساعد العديد من الإرشادات، عند اتّباعها، في تسهيل تحديد ترتيب تحميل الوحدات الصحيح والترتيب الذي يجب أن تبحث به برامج التشغيل عن الأجهزة.
يمكن أن يكون الوحدة مكتبة أو برنامج تشغيل.
وحدات المكتبة هي مكتبات توفّر واجهات برمجة تطبيقات يمكن للوحدات الأخرى استخدامها. ولا تكون هذه الوحدات عادةً خاصة بأجهزة معيّنة. تشمل أمثلة وحدات المكتبة وحدة تشفير AES وإطار عمل
remoteprocالذي يتم تجميعه كوحدة، ووحدة logbuffer. يتم تشغيل رمز الوحدة فيmodule_init()لإعداد بنى البيانات، ولكن لا يتم تشغيل أي رمز آخر ما لم يتم تشغيله بواسطة وحدة خارجية.وحدات برامج التشغيل هي برامج تشغيل تبحث عن نوع معيّن من الأجهزة أو ترتبط به. هذه الوحدات خاصة بالأجهزة. تشمل أمثلة وحدات برامج التشغيل أجهزة UART وPCIe وبرامج ترميز الفيديو. لا يتم تفعيل وحدات التحكّم في الأجهزة إلا عندما يكون الجهاز المرتبط بها متوفّرًا على النظام.
في حال عدم توفّر الجهاز، يكون رمز الوحدة الوحيد الذي يتم تنفيذه هو رمز
module_init()الذي يسجّل برنامج التشغيل في إطار عمل برنامج التشغيل الأساسي.إذا كان الجهاز متوفّرًا وتمكّن برنامج التشغيل من البحث عن هذا الجهاز أو ربطه به بنجاح، قد يتم تشغيل رمز وحدة آخر.
استخدام عمليات تهيئة الوحدات والخروج منها بشكل صحيح
يجب أن تسجّل وحدات برامج التشغيل برنامج تشغيل في 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معفاة.يتم تخصيص بنية البيانات أو تهيئتها بواسطة الوحدة، ولكن يتم إتاحتها للنواة من خلال تمريرها بشكل غير مباشر (عبر مؤشر في بنية) أو بشكل مباشر كإدخال في دالة يتم تصديرها بواسطة النواة. على سبيل المثال، يهيّئ برنامج تشغيل
cpufreqstruct cpufreq_driverثم يمرّره كإدخال إلىcpufreq_register_driver(). بعد هذه النقطة، يجب ألا تعدّل وحدة برنامج التشغيلcpufreqstruct 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()) للعثور مباشرةً على عقدة DT التي تتضمّن السمة compatible ثم تحليل عقدة DT هذه، عليك إصلاح الوحدة من خلال كتابة برنامج تشغيل جهاز يمكنه فحص الجهاز أو إزالة السمة compatible (لا يمكن إجراء ذلك إلا إذا لم يتم إرسالها إلى المصدر). لمناقشة البدائل، يُرجى التواصل مع فريق Android Kernel على kernel-team@android.com والاستعداد لتقديم مبرّرات لحالات الاستخدام.
استخدام معرّفات DT للبحث عن المورّدين
يجب الإشارة إلى المورّد باستخدام 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);يستخدم "المساعد الرقمي" رمزًا مشابهًا لما يلي:
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() لتحميل البرامج الثابتة مقبولاً في بعض الحالات، مثل برنامج تشغيل الساعة الذي يحتاج إلى برامج ثابتة ليعمل، ولكن الجهاز لا يكون متاحًا لمساحة المستخدم. من الممكن استخدام حالات أخرى مناسبة.
تنفيذ عملية التحقّق غير المتزامنة
يمكنك إتاحة استخدام ميزة الفحص غير المتزامن للاستفادة من التحسينات المستقبلية، مثل تحميل الوحدات النمطية المتوازية أو فحص الأجهزة لتسريع وقت التشغيل، والتي قد تتم إضافتها إلى 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.