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

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

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

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

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

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

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

ใช้โมดูล init และ exit อย่างถูกต้อง

โมดูลไดรเวอร์ต้องลงทะเบียนไดรเวอร์ใน 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 โมดูลก็ได้

ข้อยกเว้นของฟังก์ชัน Init และ Exit

โมดูลไลบรารีจะไม่ลงทะเบียนไดรเวอร์และได้รับการยกเว้นจากข้อจำกัดเกี่ยวกับ 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; ใน 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()

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

หากโหนด Device Tree (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-team@android.com และเตรียมพร้อมที่จะ อธิบาย Use Case ของคุณ

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

อ้างอิงถึงซัพพลายเออร์โดยใช้แฮนเดิล (การอ้างอิงหรือตัวชี้ไปยังโหนด DT) ใน DT ทุกครั้งที่เป็นไปได้ การใช้การเชื่อมโยง DT มาตรฐานและ phandle เพื่ออ้างอิงซัพพลายเออร์ ช่วยให้ fw_devlink (เดิมคือ of_devlink) สามารถกำหนดการอ้างอิงระหว่างอุปกรณ์โดยอัตโนมัติ ด้วยการแยกวิเคราะห์ 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 ได้โดยอ้างอิงโหนด Device Tree ของซัพพลายเออร์โดยใช้ 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 เฉพาะอุปกรณ์ ส่วนซัพพลายเออร์ จะใช้พร็อพเพอร์ตี้อื่นที่เฉพาะเจาะจงสำหรับซัพพลายเออร์เพื่อกำหนดชื่อที่จะใช้สำหรับ การลงทะเบียนทรัพยากรของซัพพลายเออร์ จากนั้นผู้บริโภคและซัพพลายเออร์จะใช้ รูปแบบเดิมในการใช้สตริงเพื่อค้นหาซัพพลายเออร์ต่อไป ในสถานการณ์ที่ แย่ที่สุดทั้ง 2 กรณี

  • ไดรเวอร์การสัมผัสใช้โค้ดที่คล้ายกับโค้ดต่อไปนี้

    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 เฟรมเวิร์ก

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

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

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

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

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

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

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

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

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

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

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

การทําให้ไดรเวอร์ทํางานกับการตรวจสอบแบบอะซิงโครนัสไม่จําเป็นต้องใช้โค้ดพิเศษ อย่างไรก็ตาม โปรดคำนึงถึงสิ่งต่อไปนี้เมื่อเพิ่มการรองรับการตรวจสอบแบบไม่พร้อมกัน

  • อย่าคาดเดาเกี่ยวกับทรัพยากร Dependency ที่ตรวจสอบก่อนหน้านี้ ตรวจสอบโดยตรงหรือ โดยอ้อม (การเรียกเฟรมเวิร์กส่วนใหญ่) และแสดงผล -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 เป็น built-in (=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