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