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