หลักเกณฑ์ของโมดูลผู้ให้บริการ

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

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

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

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

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

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

ใช้โมดูล 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() เนื่องจากลงทะเบียนไดรเวอร์มากกว่า 1 รายการ สําหรับโมดูลไดรเวอร์ที่ใช้ module_init() และ module_exit() เพื่อลงทะเบียนไดรเวอร์หลายรายการ ให้ลองรวมไดรเวอร์เข้าด้วยกันเป็นไดรเวอร์เดียว เช่น คุณอาจแยกความแตกต่างโดยใช้สตริง compatible หรือข้อมูลเสริมของอุปกรณ์แทนการลงทะเบียนไดรเวอร์แยกต่างหาก หรือจะแยกโมดูลไดรเวอร์เป็น 2 โมดูลก็ได้

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

โมดูลไลบรารีจะไม่ลงทะเบียนไดรเวอร์และได้รับการยกเว้นจากข้อจำกัดเกี่ยวกับ 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 เพื่อเข้าถึงข้อมูลภายในของโครงสร้างข้อมูลที่ประกาศไว้ล่วงหน้าเหล่านี้โดยตรง การทำเช่นนี้จะทำให้เกิดปัญหาเกี่ยวกับ CRC ไม่ตรงกัน CONFIG_MODVERSIONS รายการ (ซึ่งทำให้เกิดปัญหาการปฏิบัติตามข้อกำหนดของ 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()

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

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

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

อ้างอิงถึงซัพพลายเออร์โดยใช้ phandle (การอ้างอิงหรือพอยน์เตอร์ไปยังโหนด DT) ใน DT ทุกครั้งที่เป็นไปได้ การใช้การเชื่อมโยง DT และแฮนเดิลแบบมาตรฐานเพื่ออ้างถึงซัพพลายเออร์จะทำให้ fw_devlink (ก่อนหน้านี้เรียกว่า of_devlink) ระบุทรัพยากร Dependency ระหว่างอุปกรณ์ได้โดยอัตโนมัติด้วยการแยกวิเคราะห์ DT ขณะรันไทม์ จากนั้นเคอร์เนลจะสแกนและตรวจสอบอุปกรณ์โดยอัตโนมัติตามลําดับที่ถูกต้อง ซึ่งทำให้ไม่จําเป็นต้องจัดลําดับการโหลดโมดูลหรือ MODULE_SOFTDEP()

สถานการณ์เดิม (ไม่รองรับ DT ในเคอร์เนล ARM)

ก่อนหน้านี้ ก่อนที่จะมีการเพิ่มการรองรับ DT ลงในเคอร์เนล ARM ผู้บริโภค เช่น อุปกรณ์แบบสัมผัส จะค้นหาซัพพลายเออร์ เช่น หน่วยงานกำกับดูแล โดยใช้สตริงที่ไม่ซ้ำกันทั่วโลก เช่น ไดร์เวอร์ PMIC ของ ACME อาจลงทะเบียนหรือโฆษณาตัวควบคุมหลายตัว (เช่น 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"
      };
    };
    

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

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

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

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

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

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

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

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

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

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

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

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

  • ใช้การดำเนินการของไดรเวอร์ remove() ในโครงสร้างเดียวกับฟังก์ชัน probe() จากนั้นทำตามขั้นตอนการล้างโดยใช้ฟังก์ชัน remove()

ปิดใช้การยกเลิกการเชื่อมโยงอุปกรณ์กับไดรเวอร์อย่างชัดเจน (ไม่แนะนำ)

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

  • หากไม่อนุญาตให้ยกเลิกการเชื่อมโยง ให้ตั้งค่า Flag 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 หากซัพพลายเออร์อย่างน้อย 1 รายยังไม่พร้อม

  • หากคุณเพิ่มอุปกรณ์ย่อยในฟังก์ชันการตรวจสอบของอุปกรณ์หลัก อย่าเพิ่งคิดว่าระบบจะตรวจสอบอุปกรณ์ย่อยทันที

  • หากการสอดแนมไม่สําเร็จ ให้จัดการข้อผิดพลาดและล้างข้อมูลอย่างเหมาะสม (ดูใช้ตัวแปร API devm_*())

อย่าใช้ MODULE_SOFTDEP เพื่อสั่งซื้อการตรวจสอบอุปกรณ์

ฟังก์ชัน MODULE_SOFTDEP() ไม่ใช่โซลูชันที่เชื่อถือได้สำหรับการรับประกันลําดับของการตรวจสอบอุปกรณ์ และไม่ควรนำมาใช้เนื่องจากเหตุผลต่อไปนี้

  • การตรวจสอบที่เลื่อน เมื่อโหลดโมดูล การสอดแนมอุปกรณ์อาจเลื่อนออกไปเนื่องจากพาร์ทเนอร์รายใดรายหนึ่งยังไม่พร้อม ซึ่งอาจทําให้ลําดับการโหลดโมดูลไม่ตรงกับลําดับการสำรวจอุปกรณ์

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

  • การสอดแนมแบบไม่เรียลไทม์ โมดูลไดรเวอร์ที่ทำการสำรวจแบบไม่พร้อมกันจะไม่สำรวจอุปกรณ์ทันทีเมื่อโหลดโมดูล แต่จะใช้เธรดแบบขนานเพื่อจัดการการสำรวจอุปกรณ์แทน ซึ่งอาจทําให้ลําดับการโหลดโมดูลไม่ตรงกับลําดับการสำรวจอุปกรณ์ ตัวอย่างเช่น เมื่อโมดูลไดรเวอร์หลัก 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 จะคอมไพล์ต่อไปได้หากการกําหนดค่าเปลี่ยนเป็นการกําหนดค่าแบบ 3 ค่าในอนาคต ความแตกต่างมีดังนี้

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