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