แนวทางโมดูลผู้จัดจำหน่าย

ใช้แนวทางต่อไปนี้เพื่อเพิ่มความทนทานและความน่าเชื่อถือของโมดูลผู้จำหน่ายของคุณ เมื่อปฏิบัติตามหลักเกณฑ์หลายประการ จะช่วยให้ระบุลำดับการโหลดโมดูลที่ถูกต้องและลำดับที่ไดรเวอร์ต้องตรวจสอบอุปกรณ์ได้ง่ายขึ้น

โมดูลสามารถเป็น ไลบรารี หรือ ไดรเวอร์ได้

  • โมดูลไลบรารี คือไลบรารีที่จัดเตรียม API สำหรับโมดูลอื่น ๆ ที่จะใช้ โดยทั่วไปโมดูลดังกล่าวจะไม่เฉพาะกับฮาร์ดแวร์ ตัวอย่างของโมดูลไลบรารีประกอบด้วยโมดูลการเข้ารหัส AES กรอบงาน remoteproc ที่คอมไพล์เป็นโมดูล และโมดูล Logbuffer โค้ดโมดูลใน module_init() รันเพื่อตั้งค่าโครงสร้างข้อมูล แต่ไม่มีโค้ดอื่นใดรัน เว้นแต่จะถูกทริกเกอร์โดยโมดูลภายนอก

  • โมดูลไดรเวอร์ คือไดรเวอร์ที่ตรวจสอบหรือเชื่อมโยงกับอุปกรณ์ประเภทเฉพาะ โมดูลดังกล่าวเป็นโมดูลเฉพาะสำหรับฮาร์ดแวร์ ตัวอย่างของโมดูลไดรเวอร์ได้แก่ UART, PCIe และฮาร์ดแวร์ตัวเข้ารหัสวิดีโอ โมดูลไดรเวอร์จะเปิดใช้งานเมื่อมีอุปกรณ์ที่เกี่ยวข้องอยู่ในระบบเท่านั้น

    • หากไม่มีอุปกรณ์อยู่ รหัสโมดูลเดียวที่ทำงานคือรหัส module_init() ที่ลงทะเบียนไดรเวอร์กับเฟรมเวิร์กหลักของไดรเวอร์

    • หากมีอุปกรณ์อยู่และไดรเวอร์ตรวจสอบหรือเชื่อมโยงกับอุปกรณ์นั้นได้สำเร็จ โค้ดโมดูลอื่นอาจทำงาน

ใช้โมดูลเข้า/ออกอย่างถูกต้อง

โมดูลไดรเวอร์จะต้องลงทะเบียนไดรเวอร์ใน module_init() และยกเลิกการลงทะเบียนไดรเวอร์ใน module_exit() วิธีง่ายๆ ในการบังคับใช้ข้อจำกัดเหล่านี้คือการใช้มาโคร wrapper ซึ่งหลีกเลี่ยงการใช้มาโคร 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 หรือข้อมูล aux ของอุปกรณ์แทนการลงทะเบียนไดรเวอร์แยกต่างหาก หรือคุณสามารถแยกโมดูลไดรเวอร์ออกเป็นสองโมดูลได้

ข้อยกเว้นของฟังก์ชันเริ่มต้นและออก

โมดูลไลบรารีไม่ได้ลงทะเบียนไดรเวอร์ และได้รับการยกเว้นจากข้อจำกัดใน module_init() และ module_exit() เนื่องจากอาจต้องใช้ฟังก์ชันเหล่านี้ในการตั้งค่าโครงสร้างข้อมูล คิวงาน หรือเธรดเคอร์เนล

ใช้มาโคร MODULE_DEVICE_TABLE

โมดูลไดรเวอร์ต้องมีมาโคร MODULE_DEVICE_TABLE ซึ่งช่วยให้ผู้ใช้สามารถระบุอุปกรณ์ที่โมดูลไดรเวอร์รองรับก่อนที่จะโหลดโมดูล Android สามารถใช้ข้อมูลนี้เพื่อเพิ่มประสิทธิภาพการโหลดโมดูล เช่น เพื่อหลีกเลี่ยงการโหลดโมดูลสำหรับอุปกรณ์ที่ไม่มีอยู่ในระบบ สำหรับตัวอย่างการใช้มาโคร โปรดดูโค้ดอัปสตรีม

หลีกเลี่ยง CRC ที่ไม่ตรงกันเนื่องจากประเภทข้อมูลที่ประกาศล่วงหน้า

อย่ารวมไฟล์ส่วนหัวเพื่อให้มองเห็นประเภทข้อมูลที่ประกาศการส่งต่อ โครงสร้าง สหภาพแรงงาน และประเภทข้อมูลอื่นๆ ที่กำหนดในไฟล์ส่วนหัว ( header-Ah ) สามารถประกาศส่งต่อในไฟล์ส่วนหัวอื่น ( header-Bh ) ซึ่งโดยทั่วไปจะใช้พอยน์เตอร์ไปยังประเภทข้อมูลเหล่านั้น รูปแบบโค้ดนี้หมายความว่าเคอร์เนลพยายามรักษาโครงสร้างข้อมูลให้เป็นส่วนตัวสำหรับผู้ใช้ header-Bh เจตนา

ผู้ใช้ header-Bh ไม่ควรรวม header-Ah เพื่อเข้าถึงภายในของโครงสร้างข้อมูลที่ประกาศล่วงหน้าเหล่านี้โดยตรง การทำเช่นนี้ทำให้เกิดปัญหา CONFIG_MODVERSIONS CRC ที่ไม่ตรงกัน (ซึ่งสร้างปัญหาการปฏิบัติตามข้อกำหนด ABI) เมื่อเคอร์เนลอื่น (เช่น เคอร์เนล GKI) พยายามโหลดโมดูล

ตัวอย่างเช่น struct fwnode_handle ถูกกำหนดไว้ใน include/linux/fwnode.h แต่ถูกประกาศไปข้างหน้าเป็น struct fwnode_handle; ใน include/linux/device.h เนื่องจากเคอร์เนลพยายามเก็บรายละเอียดของ struct fwnode_handle private จากผู้ใช้ 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()

เข้าถึงโครงสร้างข้อมูลเคอร์เนลหลักผ่านฟังก์ชันที่ส่งออกโดยเคอร์เนลหรือผ่านพารามิเตอร์ที่ส่งอย่างชัดเจนเป็นอินพุตไปยัง vendor hooks หากคุณไม่มี API หรือ hook ของผู้ขายเพื่อแก้ไขบางส่วนของโครงสร้างข้อมูลเคอร์เนลหลัก อาจเป็นไปได้ว่าอาจเป็นไปโดยตั้งใจและคุณไม่ควรแก้ไขโครงสร้างข้อมูลจากโมดูล ตัวอย่างเช่น ห้ามแก้ไขฟิลด์ใดๆ ภายใน struct device หรือ struct device.links

  • หากต้องการแก้ไข device.devres_head ให้ใช้ฟังก์ชัน devm_*() เช่น devm_clk_get() , devm_regulator_get() หรือ devm_kzalloc()

  • หากต้องการแก้ไขฟิลด์ภายใน struct device.links ให้ใช้ API ลิงก์อุปกรณ์ เช่น device_link_add() หรือ device_link_del()

อย่าแยกวิเคราะห์โหนด Devicetree ด้วยคุณสมบัติที่เข้ากันได้

หากโหนดแผนผังอุปกรณ์ (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 นั้น ให้แก้ไขโมดูลโดยการเขียนไดรเวอร์อุปกรณ์ที่สามารถตรวจสอบได้ อุปกรณ์หรือลบคุณสมบัติ compatible (เป็นไปได้เฉพาะในกรณีที่ยังไม่ได้อัปสตรีม) หากต้องการหารือเกี่ยวกับทางเลือกอื่น โปรดติดต่อทีม Android Kernel ที่ kernel-team@android.com และเตรียมพร้อมที่จะชี้แจงกรณีการใช้งานของคุณ

ใช้ DT phandles เพื่อค้นหาซัพพลายเออร์

อ้างถึงซัพพลายเออร์ที่ใช้มือจับ (ตัวอ้างอิง/ตัวชี้ไปยังโหนด 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 ได้โดยการอ้างอิงถึงโหนดแผนผังอุปกรณ์ของซัพพลายเออร์โดยใช้ 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"
      };
    };
    

อย่าแก้ไขข้อผิดพลาด API ของเฟรมเวิร์ก

Framework API เช่น regulator , clocks , irq , gpio , phys และ extcon ส่งคืน -EPROBE_DEFER เป็นค่าส่งคืนข้อผิดพลาดเพื่อระบุว่าอุปกรณ์กำลังพยายามตรวจสอบแต่ไม่สามารถทำได้ในขณะนี้ และเคอร์เนลควรลองตรวจสอบอีกครั้ง ภายหลัง. เพื่อให้แน่ใจว่าฟังก์ชัน .probe() ของอุปกรณ์ของคุณล้มเหลวตามที่คาดไว้ในกรณีเช่นนี้ อย่าแทนที่หรือแมปค่าข้อผิดพลาดใหม่ การเปลี่ยนหรือการแมปค่าข้อผิดพลาดใหม่อาจทำให้ -EPROBE_DEFER หลุดออกไป และส่งผลให้อุปกรณ์ของคุณไม่ได้รับการสอบสวน

ใช้ตัวแปร devm_*() API

เมื่ออุปกรณ์ได้รับทรัพยากรโดยใช้ devm_*() API เคอร์เนลจะปล่อยทรัพยากรโดยอัตโนมัติหากอุปกรณ์ไม่สามารถตรวจสอบได้ หรือตรวจสอบได้สำเร็จและถูกยกเลิกการเชื่อมโยงในภายหลัง ฟังก์ชันนี้ทำให้โค้ดการจัดการข้อผิดพลาดใน probe() สะอาดขึ้น เนื่องจากไม่จำเป็น goto ไปเพื่อปล่อยทรัพยากรที่ได้รับจาก devm_*() และลดความซับซ้อนของการดำเนินการแยกไดรเวอร์

จัดการการยกเลิกการเชื่อมโยงไดรเวอร์อุปกรณ์

มีเจตนาเกี่ยวกับการเลิกผูกไดรเวอร์อุปกรณ์ และอย่าปล่อยให้การเลิกผูกไม่ได้กำหนดไว้ เนื่องจากไม่ได้กำหนดไม่ได้หมายความถึงไม่ได้รับอนุญาต คุณต้องใช้งานการแยกไดรเวอร์อุปกรณ์อย่างสมบูรณ์ หรือ ปิดใช้งานการแยกการเชื่อมโยงไดรเวอร์อุปกรณ์อย่างชัดเจน

การใช้การยกเลิกการเชื่อมโยงไดรเวอร์อุปกรณ์

เมื่อเลือกที่จะใช้งานการแยกไดรเวอร์อุปกรณ์อย่างสมบูรณ์ ให้ยกเลิกการเชื่อมโยงไดรเวอร์อุปกรณ์อย่างสมบูรณ์เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำหรือทรัพยากรและปัญหาด้านความปลอดภัย คุณสามารถผูกอุปกรณ์เข้ากับไดรเวอร์ได้โดยการเรียกใช้ฟังก์ชัน probe() ของไดรเวอร์ และยกเลิกการผูกอุปกรณ์โดยเรียกใช้ฟังก์ชัน remove() ของไดรเวอร์ หากไม่มีฟังก์ชัน remove() เคอร์เนลยังคงสามารถแยกอุปกรณ์ได้ แกนไดรเวอร์ถือว่าไม่จำเป็นต้องทำความสะอาดโดยไดรเวอร์เมื่อแยกออกจากอุปกรณ์ โปรแกรมควบคุมที่ไม่ถูกผูกไว้จากอุปกรณ์ไม่จำเป็นต้องทำการล้างข้อมูลที่ชัดเจนเมื่อทั้งสองอย่างต่อไปนี้เป็นจริง:

  • ทรัพยากรทั้งหมดที่ได้รับจากฟังก์ชัน probe() ของไดรเวอร์นั้นผ่าน devm_*() API

  • อุปกรณ์ฮาร์ดแวร์ไม่จำเป็นต้องมีลำดับการปิดระบบหรือการปิดเครื่อง

ในสถานการณ์นี้ ไดรเวอร์คอร์จะจัดการการเผยแพร่ทรัพยากรทั้งหมดที่ได้รับผ่าน devm_*() API หากข้อความใดข้อความหนึ่งข้างต้นไม่เป็นความจริง ไดรเวอร์จำเป็นต้องดำเนินการล้างข้อมูล (ปล่อยทรัพยากรและปิดระบบหรือปิดฮาร์ดแวร์) เมื่อแยกออกจากอุปกรณ์ เพื่อให้แน่ใจว่าอุปกรณ์สามารถแยกโมดูลไดรเวอร์ออกได้อย่างสมบูรณ์ ให้ใช้ตัวเลือกใดตัวเลือกหนึ่งต่อไปนี้:

  • หากฮาร์ดแวร์ไม่ต้องการลำดับการปิดระบบหรือการปิดเครื่อง ให้เปลี่ยนโมดูลอุปกรณ์เพื่อรับทรัพยากรโดยใช้ devm_*() API

  • ใช้การดำเนินการไดรเวอร์ 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*() API อาจบล็อกเป็นเวลานานแล้วล้มเหลว ซึ่งทำให้กระบวนการบูตช้าลงโดยไม่จำเป็น ให้เลื่อนการโหลดเฟิร์มแวร์ไปเป็นเวลาที่ไคลเอ็นต์เริ่มใช้อุปกรณ์แทน ตัวอย่างเช่น โปรแกรมควบคุมการแสดงผลสามารถโหลดเฟิร์มแวร์ได้เมื่อเปิดอุปกรณ์แสดงผล

การใช้ .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_*() API Variants )

อย่าใช้ 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 ยังคงคอมไพล์ต่อไป หากการกำหนดค่าเปลี่ยนเป็นการกำหนดค่าแบบ tristate ในอนาคต ความแตกต่างมีดังนี้:

  • #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_ )

  • ในไฟล์ส่วนหัว การตรวจสอบแบบเดียวกันนั้นใช้กลอุบาย เนื่องจากไฟล์ส่วนหัวไม่ได้คอมไพล์โดยตรงเป็นไบนารี่ แต่จะคอมไพล์เป็นส่วนหนึ่งของไฟล์ C (หรือไฟล์ต้นฉบับอื่น ๆ ) ใช้กฎต่อไปนี้สำหรับไฟล์ส่วนหัว:

    • สำหรับไฟล์ส่วนหัวที่ใช้ #ifdef MODULE ผลลัพธ์จะเปลี่ยนไปตามไฟล์ต้นฉบับที่ใช้งาน ซึ่งหมายความว่าไฟล์ส่วนหัวเดียวกันในรุ่นเดียวกันสามารถมีส่วนต่างๆ ของโค้ดที่คอมไพล์สำหรับไฟล์ต้นฉบับที่แตกต่างกัน (โมดูลเทียบกับในตัวหรือปิดใช้งาน) สิ่งนี้มีประโยชน์เมื่อคุณต้องการกำหนดแมโครที่ต้องขยายวิธีหนึ่งสำหรับโค้ดในตัวและขยายวิธีอื่นสำหรับโมดูล

    • สำหรับไฟล์ส่วนหัวที่ต้องคอมไพล์เป็นโค้ดเมื่อ CONFIG_XXX เฉพาะถูกตั้งค่าเป็นโมดูล (ไม่ว่าไฟล์ต้นฉบับจะเป็นโมดูลหรือไม่ก็ตาม) ไฟล์ส่วนหัวจะต้องใช้ #ifdef CONFIG_XXX_MODULE