ما هي شجرة الأجهزة؟

August 7, 2026 · زوار

إن سبق لك أن حاولت تشغيل لينكس على لوحة مدمجة، فستصادف عاجلًا أم آجلًا ملفًا ينتهي بـ ‎.dts‎. وغالبًا في أسوأ لحظة ممكنة: اللوحة لا تقلع، لا شيء يظهر على الشاشة، ثم يقول لك أحدهم «راجع شجرة الأجهزة». هذا المقال عن ماهية ذلك الملف، ولماذا يوجد، وعمّا تبحث فيه فعلًا.

المشكلة التي يحلّها

على حاسوب مكتبي، تعثر النواة على العتاد بنفسها إلى حد بعيد. فـ PCI و USB ناقلان موصَّفان رسميًا وقابلان للتعداد (1). تعرّف الأجهزة عن نفسها بمعرّف مُصنِّع ومعرّف منتج؛ ويسرد المشغّل المعرّفات التي يدعمها في جدول، وتستدعي النواة دالة ‎probe()‎ لذلك المشغّل لكل جهاز يطابق مدخلًا في الجدول (2). وعلى جانب USB يجري الأمر نفسه عبر جدول معرّفات خاص به (3). لا يحتاج أحد إلى إخبار أحد بشيء مسبقًا.

اللوحات المدمجة لا تملك هذا الترف. توثيق النواة نفسه يرسم الحد بوضوح: الأطراف المدمجة في نظام على شريحة تتعلّق بنواقل ذات بنية تحتية دنيا، «على النقيض من النواقل الكبيرة الموصَّفة رسميًا مثل PCI أو USB»، والقاسم المشترك بين تلك الأجهزة هو العنونة المباشرة من ناقل المعالج (1). حساس I²C أو متحكم شاشة معلّق على SPI لا يعرّف عن نفسه؛ هو ببساطة هناك، عند ذلك العنوان، موصولًا بخط المقاطعة ذاك. والسبيل الوحيد لتعرف النواة بوجوده هو أن يخبرها أحد.

لزمن طويل كان ذلك «الأحد» هو الشيفرة نفسها. كان لكل لوحة ملف داخل شجرة النواة يصف بلغة C أي جهاز موصول أين — ولا تزال النواة توثّق هذا المسار، حيث تسجّل شيفرة التهيئة الخاصة باللوحة الأجهزة واحدًا تلو الآخر (1). على ARM تكاثرت هذه الملفات إلى حد دفع لينوس تورفالدس عام 2011 إلى تفكيك الوضع في رسالة بريدية، قائلًا إن شيفرة ARM تتجه إلى ما لا يمكن الاستمرار عليه. وكان المخرج الذي اعتُمد هو شجرة الأجهزة.

الفكرة

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

الصيغة نفسها تستند إلى مواصفة رسمية (4). وتُسمّى شجرة لأنها شجرة فعلًا. تبدأ من عقدة جذر، وتتفرّع منها النواقل، ومن النواقل تتفرّع الأجهزة الموصولة بها — بما يعكس التوصيل الفيزيائي للعتاد.

بدلًا من مثال مُختلَق، إليك مثالًا حقيقيًا. العقدة التالية مأخوذة من ‎k3-am62p-main.dtsi‎ في شجرة U-Boot لعائلة J722S/AM67A من TI — أي الوصف الذي تقرأه لوحة AM67A فعليًا عند الإقلاع:

main_i2c0: i2c@20000000 {
    compatible = "ti,am64-i2c", "ti,omap4-i2c";
    reg = <0x00 0x20000000 0x00 0x100>;
    interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH>;
    #address-cells = <1>;
    #size-cells = <0>;
    power-domains = <&k3_pds 102 TI_SCI_PD_EXCLUSIVE>;
    clocks = <&k3_clks 102 2>;
    clock-names = "fck";
    status = "disabled";
};

كل سطر هنا يعني شيئًا. ‎reg‎ يعطي عنوان المتحكم في خريطة الذاكرة — ‎0x20000000‎ بطول ‎0x100‎. وتُكتب العناوين على هيئة أزواج من الأرقام لأن فضاء العنونة 64 بت؛ الرقم الأول يحمل الـ 32 بت العليا. و‎interrupts‎ يبيّن أي خط مقاطعة يُستخدم؛ وأسماء مثل ‎GIC_SPI‎ و‎IRQ_TYPE_LEVEL_HIGH‎ تأتي من ملفات ترويسة مُضمَّنة — أي أن مصدر شجرة الأجهزة، مع كونه بيانات صرفة، يمر رغم ذلك عبر معالج C الأولي.

يستحق الانتباه أن ‎compatible‎ يحمل قيمتين: ‎"ti,am64-i2c"‎ و‎"ti,omap4-i2c"‎. وتُقرأ القائمة من الأخص إلى الأعم. تبحث النواة عن مشغّل خاص بـ am64، فإن لم تجده تعود إلى مشغّل omap4 الأقدم والأعم (5). وبهذا يمكن لشريحة جديدة أن تعمل دون أن يكتب أحد مشغّلًا خاصًا بها.

و‎clocks‎ و‎clock-names‎ يبيّنان من أين يأخذ الجهاز ساعته: المخرج 2 من الجهاز 102 على ‎&k3_clks‎، وسيجده المشغّل تحت الاسم ‎"fck"‎. و‎power-domains‎ يربط نطاق الطاقة بالمنطق نفسه. هذه ليست ضبطًا للتردد — إنها وصف للتوصيل، أي ساعة تذهب إلى أين.

السطر الأخير مهم: ‎status = "disabled"‎. يصف مُصنِّع الشريحة الطرف لكنه يتركه مطفأً، لأنه لا يستطيع أن يعرف ما إذا كان ناقل I²C ذاك مستخدَمًا على لوحتك. وتشغيله من مهمة ملف ‎.dts‎ الخاص باللوحة. في ملف لوحة J722S EVM يبدو الأمر هكذا:

&main_i2c0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&main_i2c0_pins_default>;
    clock-frequency = <400000>;

    exp1: gpio@23 {
        compatible = "ti,tca6424";
        reg = <0x23>;
        gpio-controller;
        #gpio-cells = <2>;
    };
};

الـ ‎&main_i2c0‎ في المقدمة إشارة إلى العقدة أعلاه — لا ينشئ عقدة جديدة بل يكتب فوق الموجودة. يُشغَّل ‎status‎، وتُسنَد الأطراف، وتُضبط السرعة على 400 كيلوهرتز، ويُضاف تحته جهاز حقيقي: موسّع GPIO من طراز TCA6424 يجلس عند العنوان ‎0x23‎ على ناقل I²C. ولأن المتحكم أعلن ‎#address-cells = <1>‎، يُكتب هذا العنوان برقم واحد، بخلاف ‎reg‎ الخاص بالمتحكم نفسه.

هكذا تعمل شجرة الأجهزة عمليًا: يصف المُصنِّع الـ SoC مرة واحدة، وتقول أنت ما الموصول على لوحتك.

الخاصية التي تهم فعلًا هي ‎compatible‎. هنا تنظر النواة لتزاوج بين مشغّل وقطعة عتاد. يقول المشغّل «أنا متوافق مع ‎ti,tca6424‎»، وتحمل عقدة في شجرة الأجهزة السلسلة نفسها، فتجمع النواة بينهما. ولهذا تفشل الأخطاء المطبعية بصمت: إن لم تتطابق السلسلة فلا خطأ يظهر — الجهاز ببساطة لا يظهر أبدًا.

مما يجدر تذكّره: شجرة الأجهزة تصف العتاد لا إعدادات المشغّل. ودليل النواة لكتابة الـ bindings يشدّد على هذا الحد تحديدًا (6). ويتشوّش هذا الخط باستمرار عمليًا، وهو أكثر المواضيع إثارة للجدل في قوائم النواة البريدية — «هل هذه حقًا خاصية في العتاد، أم أنها تفضيلك أنت؟»

الملفات

سترى ثلاث لواحق، ويسهل الخلط بينها (11).

.dts‎ هو المصدر الذي تكتبه، الصيغة التي يقرأها الإنسان. و‎.dtsi‎ جزء يمكن لملفات أخرى تضمينه — و«i» من include. يصف مُصنِّع الشريحة عادةً الـ SoC كاملًا في ملف ‎.dtsi‎؛ وتكتب أنت ‎.dts‎ قصيرًا للوحتك، تضمّنه، ثم تضيف ما يخص لوحتك وحدها.

.dtb‎ هو الصيغة المصرَّفة — كتلة شجرة الأجهزة. وهذا ما تقرأه النواة فعلًا. والمصرِّف هو ‎dtc‎ (7):

dtc -I dts -O dtb -o my-board.dtb my-board.dts

ويعمل بالاتجاه المعاكس أيضًا، وهو أمر بالغ النفع عند تعقّب مشكلة. فإن كان لديك ‎.dtb‎ يعمل، يمكنك فتحه والنظر في داخله:

dtc -I dtb -O dts -o decoded.dts my-board.dtb

وعلى نظام قيد التشغيل يمكنك قراءة شجرة الأجهزة مباشرة من نظام الملفات. تعرض النواة هنا الشجرة التي تستخدمها فعليًا:

ls /proc/device-tree/

هذه أسرع إجابة عن سؤال: «هل الملف الذي حرّرته هو الملف الذي حُمِّل؟» وفي نسبة مدهشة من المرات تكون الإجابة: لا.

ماذا يحدث عند الإقلاع

الترتيب كالتالي: يحمّل محمّل الإقلاع — وغالبًا U-Boot (8) — ملف ‎.dtb‎ إلى الذاكرة، ويحمّل النواة، ثم يسلّمها عنوان الكتلة في الذاكرة عبر مسجّل عند تشغيلها. وعلى ARM، هذا المسجّل والشروط التي يجب أن يستوفيها محمّل الإقلاع محدَّدة في وثيقة إقلاع النواة (9). وقبل أن تشغّل النواة أي مشغّل، تقرأ تلك الشجرة، وتستخرج منها الأجهزة، وتزاوج المشغّلات بحسب سلاسل ‎compatible‎.

والنتيجة العملية أن النواة وشجرة الأجهزة يمكن تحديثهما منفصلين. فإضافة حساس نادرًا ما تعني إعادة بناء النواة؛ وتحديث ‎.dtb‎ يكفي عادة. لكن هذا سلاح ذو حدين — فلأنهما مستقلان، قد يتباعدان. أقلِع بنواة جديدة و‎.dtb‎ قديم، فتقوم اللوحة ناقصةً في صمت، ويستغرق فهم السبب وقتًا.

وهناك أيضًا فكرة الـ overlays: أجزاء تُطبَّق فوق شجرة قائمة أثناء التشغيل (10). وهذا ما يحدث حين توصّل لوحة HAT براسبيري باي — تبقى الشجرة الرئيسية كما هي ويوضع فوقها جزء صغير. وهي مفيدة حين تكون اللوحة الأساس ثابتة ولا يتغير إلا الوحدة الموصولة بها.

حين لا يعمل شيء ما

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

ابدأ بالتحقق مما إذا كان الجهاز قد أُنشئ أصلًا. هل العقدة موجودة تحت ‎/proc/device-tree‎؟ إن لم تكن، فالمشكلة ليست في الملف الذي حرّرته — على الأرجح يُحمَّل ‎.dtb‎ خاطئ، أو أن العقدة مطفأة عند مستوى أعلى بـ ‎status = "disabled"‎. والثانية تحدث باستمرار: فالأطراف تأتي معطّلة عادةً في ملفات ‎.dtsi‎ الخاصة بالمُصنِّع، ويُتوقّع منك تشغيلها واحدة تلو الأخرى بـ ‎status = "okay"‎ في ملف ‎.dts‎ الخاص بلوحتك.

وإن كانت العقدة موجودة ولم يتعلّق بها مشغّل، فوجّه عينك إلى سلسلة ‎compatible‎. ابحث عن جدول ‎of_device_id‎ في مصدر المشغّل وقارن السلاسل حرفًا حرفًا. فاصلة واحدة أو شَرطة واحدة تكفي.

وأمر آخر: تحذيرات ‎dtc‎ تأتي مطفأة في معظم المشاريع، وهي تقول أشياء نافعة حقًا. عدم تطابق بين العنوان و‎reg‎، أو ‎#address-cells‎ ناقص — وهي بالضبط نوع العطل الذي يتحوّل إلى فشل صامت عند الإقلاع. ويستحق الأمر إبقاء التحذيرات مفعّلة عند البناء.

خاتمة

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

وحين تستقر في هذا الإطار يتبعها الباقي. لماذا ‎compatible‎ بهذه الحرجية، ولماذا يعيش ‎.dtb‎ منفصلًا عن النواة، ولماذا ‎/proc/device-tree‎ أول ما يُنظر فيه حين يغيب شيء — كل ذلك ينبع من الفكرة نفسها.

قراءات ذات صلة

المراجع

  1. Platform Devices and Drivers — لماذا لا تكون أطراف الـ SoC قابلة للتعداد كـ PCI و USB، وكيف تسجّلها الشيفرة الخاصة باللوحة.
  2. How To Write Linux PCI Drivers — جدول ‎pci_device_id‎ واستدعاء ‎probe()‎ للأجهزة المطابقة.
  3. USB Device Drivers — مزاوجة المشغّلات بالمعرّفات على جانب USB.
  4. Devicetree Specification — التعريف الرسمي للصيغة.
  5. Linux and the Devicetree — كيف تقرأ النواة الشجرة وتزاوج المشغّلات.
  6. Writing Devicetree Bindings — الحد بين وصف العتاد وضبط المشغّل.
  7. dtc — Device Tree Compiler — مستودع المصرِّف وطريقة استخدامه.
  8. U-Boot: Devicetree Control — كيف يتعامل U-Boot مع شجرة الأجهزة.
  9. Booting ARM Linux — أي مسجّل يستخدمه محمّل الإقلاع لتسليم الكتلة للنواة.
  10. Devicetree Overlay Notes — كيف تُطبَّق الـ overlays أثناء التشغيل.
  11. Device Tree Reference (eLinux) — مرجع مجتمعي بأمثلة معالَجة.

التعليقات