ใช้แนวทางต่อไปนี้เพื่อเพิ่มความทนทานและความน่าเชื่อถือของโมดูลผู้จำหน่ายของคุณ เมื่อปฏิบัติตามหลักเกณฑ์หลายประการ จะช่วยให้ระบุลำดับการโหลดโมดูลที่ถูกต้องและลำดับที่ไดรเวอร์ต้องตรวจสอบอุปกรณ์ได้ง่ายขึ้น
โมดูลสามารถเป็น ไลบรารี หรือ ไดรเวอร์ได้
โมดูลไลบรารี คือไลบรารีที่จัดเตรียม 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()
เนื่องจากมีการลงทะเบียนไดรเวอร์มากกว่าหนึ่งตัว สำหรับโมดูลไดรเวอร์ที่ใช้ module_init()
และ module_exit()
เพื่อลงทะเบียนไดรเวอร์หลายตัว ให้ลองรวมไดรเวอร์ให้เป็นไดรเวอร์เดียว ตัวอย่างเช่น คุณสามารถแยกความแตกต่างโดยใช้สตริง compatible
หรือข้อมูล aux ของอุปกรณ์แทนการลงทะเบียนไดรเวอร์แยกต่างหาก หรือคุณสามารถแยกโมดูลไดรเวอร์ออกเป็นสองโมดูลได้
ข้อยกเว้นของฟังก์ชันเริ่มต้นและออก
โมดูลไลบรารีไม่ได้ลงทะเบียนไดรเวอร์ และได้รับการยกเว้นจากข้อจำกัดใน module_init()
และ module_exit()
เนื่องจากอาจต้องใช้ฟังก์ชันเหล่านี้ในการตั้งค่าโครงสร้างข้อมูล คิวงาน หรือเธรดเคอร์เนล
ใช้มาโคร MODULE_DEVICE_TABLE
โมดูลไดรเวอร์ต้องมีมาโคร MODULE_DEVICE_TABLE
ซึ่งช่วยให้ผู้ใช้สามารถระบุอุปกรณ์ที่โมดูลไดรเวอร์รองรับก่อนที่จะโหลดโมดูล Android สามารถใช้ข้อมูลนี้เพื่อเพิ่มประสิทธิภาพการโหลดโมดูล เช่น เพื่อหลีกเลี่ยงการโหลดโมดูลสำหรับอุปกรณ์ที่ไม่มีอยู่ในระบบ สำหรับตัวอย่างการใช้มาโคร โปรดดูโค้ดอัปสตรีม
หลีกเลี่ยง CRC ที่ไม่ตรงกันเนื่องจากประเภทข้อมูลที่ประกาศล่วงหน้า
อย่ารวมไฟล์ส่วนหัวเพื่อให้มองเห็นประเภทข้อมูลที่ประกาศการส่งต่อ โครงสร้าง สหภาพแรงงาน และประเภทข้อมูลอื่นๆ ที่กำหนดในไฟล์ส่วนหัว ( header-Ah
) สามารถประกาศส่งต่อในไฟล์ส่วนหัวอื่น ( header-Bh
) ซึ่งโดยทั่วไปจะใช้พอยน์เตอร์ไปยังประเภทข้อมูลเหล่านั้น รูปแบบโค้ดนี้หมายความว่าเคอร์เนลพยายามรักษาโครงสร้างข้อมูลให้เป็นส่วนตัวสำหรับผู้ใช้ header-Bh
เจตนา
ผู้ใช้ header-Bh
ไม่ควรรวม header-Ah
เพื่อเข้าถึงภายในของโครงสร้างข้อมูลที่ประกาศล่วงหน้าเหล่านี้โดยตรง การทำเช่นนี้ทำให้เกิดปัญหา CONFIG_MODVERSIONS
CRC ที่ไม่ตรงกัน (ซึ่งสร้างปัญหาการปฏิบัติตามข้อกำหนด ABI) เมื่อเคอร์เนลอื่น (เช่น เคอร์เนล GKI) พยายามโหลดโมดูล
ตัวอย่างเช่น struct fwnode_handle
ถูกกำหนดไว้ใน include/linux/fwnode.h
แต่ถูกประกาศไปข้างหน้าเป็น struct fwnode_handle;
ใน include/linux/device.h
เนื่องจากเคอร์เนลพยายามเก็บรายละเอียดของ struct fwnode_handle
private จากผู้ใช้ 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()
เข้าถึงโครงสร้างข้อมูลเคอร์เนลหลักผ่านฟังก์ชันที่ส่งออกโดยเคอร์เนลหรือผ่านพารามิเตอร์ที่ส่งอย่างชัดเจนเป็นอินพุตไปยัง vendor hooks หากคุณไม่มี 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 (โดยทั่วไปโดยไดรเวอร์อุปกรณ์ของอุปกรณ์หลัก) ความคาดหวังเริ่มต้น (ยกเว้นอุปกรณ์บางตัวที่เริ่มต้นล่วงหน้าสำหรับตัวกำหนดตารางเวลา) คือโหนด 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 ที่ kernel-team@android.com และเตรียมพร้อมที่จะชี้แจงกรณีการใช้งานของคุณ
ใช้ DT phandles เพื่อค้นหาซัพพลายเออร์
อ้างถึงซัพพลายเออร์ที่ใช้มือจับ (ตัวอ้างอิง/ตัวชี้ไปยังโหนด DT) ใน DT ทุกครั้งที่เป็นไปได้ การใช้การเชื่อมโยง DT และ phandles มาตรฐานเพื่ออ้างถึงซัพพลายเออร์ทำให้ fw_devlink
(ก่อนหน้านี้ of_devlink
) พิจารณาการพึ่งพาระหว่างอุปกรณ์โดยอัตโนมัติโดยแยกวิเคราะห์ DT ที่รันไทม์ เคอร์เนลสามารถตรวจสอบอุปกรณ์ในลำดับที่ถูกต้องได้โดยอัตโนมัติ โดยไม่จำเป็นต้องเรียงลำดับโหลดโมดูลหรือ MODULE_SOFTDEP()
สถานการณ์ดั้งเดิม (ไม่รองรับ DT ในเคอร์เนล ARM)
ก่อนหน้านี้ ก่อนที่จะเพิ่มการรองรับ DT ให้กับเคอร์เนล ARM ผู้บริโภค เช่น อุปกรณ์สัมผัส จะค้นหาซัพพลายเออร์ เช่น หน่วยงานกำกับดูแล โดยใช้สตริงที่ไม่ซ้ำกันทั่วโลก ตัวอย่างเช่น ไดรเวอร์ ACME PMIC สามารถลงทะเบียนหรือโฆษณาหน่วยงานกำกับดูแลหลายราย (เช่น 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 เฉพาะอุปกรณ์ ซัพพลายเออร์ใช้คุณสมบัติเฉพาะของซัพพลายเออร์อื่นเพื่อกำหนดชื่อที่จะใช้สำหรับการลงทะเบียนทรัพยากรของซัพพลายเออร์ จากนั้นผู้บริโภคและซัพพลายเออร์ยังคงใช้ รูปแบบเดิมของการใช้สตริงเพื่อค้นหาซัพพลายเออร์ ในสถานการณ์ที่เลวร้ายที่สุดของทั้งสองโลกนี้:
ไดรเวอร์ระบบสัมผัสใช้รหัสที่คล้ายกับรหัสต่อไปนี้:
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
หลุดออกไป และส่งผลให้อุปกรณ์ของคุณไม่ได้รับการสอบสวน
ใช้ตัวแปร devm_*() API
เมื่ออุปกรณ์ได้รับทรัพยากรโดยใช้ devm_*()
API เคอร์เนลจะปล่อยทรัพยากรโดยอัตโนมัติหากอุปกรณ์ไม่สามารถตรวจสอบได้ หรือตรวจสอบได้สำเร็จและถูกยกเลิกการเชื่อมโยงในภายหลัง ฟังก์ชันนี้ทำให้โค้ดการจัดการข้อผิดพลาดใน probe()
สะอาดขึ้น เนื่องจากไม่จำเป็น goto
ไปเพื่อปล่อยทรัพยากรที่ได้รับจาก devm_*()
และลดความซับซ้อนของการดำเนินการแยกไดรเวอร์
จัดการการยกเลิกการเชื่อมโยงไดรเวอร์อุปกรณ์
มีเจตนาเกี่ยวกับการเลิกผูกไดรเวอร์อุปกรณ์ และอย่าปล่อยให้การเลิกผูกไม่ได้กำหนดไว้ เนื่องจากไม่ได้กำหนดไม่ได้หมายความถึงไม่ได้รับอนุญาต คุณต้องใช้งานการแยกไดรเวอร์อุปกรณ์อย่างสมบูรณ์ หรือ ปิดใช้งานการแยกการเชื่อมโยงไดรเวอร์อุปกรณ์อย่างชัดเจน
การใช้การยกเลิกการเชื่อมโยงไดรเวอร์อุปกรณ์
เมื่อเลือกที่จะใช้งานการแยกไดรเวอร์อุปกรณ์อย่างสมบูรณ์ ให้ยกเลิกการเชื่อมโยงไดรเวอร์อุปกรณ์อย่างสมบูรณ์เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำหรือทรัพยากรและปัญหาด้านความปลอดภัย คุณสามารถผูกอุปกรณ์เข้ากับไดรเวอร์ได้โดยการเรียกใช้ฟังก์ชัน probe()
ของไดรเวอร์ และยกเลิกการผูกอุปกรณ์โดยเรียกใช้ฟังก์ชัน remove()
ของไดรเวอร์ หากไม่มีฟังก์ชัน remove()
เคอร์เนลยังคงสามารถแยกอุปกรณ์ได้ แกนไดรเวอร์ถือว่าไม่จำเป็นต้องทำความสะอาดโดยไดรเวอร์เมื่อแยกออกจากอุปกรณ์ โปรแกรมควบคุมที่ไม่ถูกผูกไว้จากอุปกรณ์ไม่จำเป็นต้องทำการล้างข้อมูลที่ชัดเจนเมื่อทั้งสองอย่างต่อไปนี้เป็นจริง:
ทรัพยากรทั้งหมดที่ได้รับจากฟังก์ชัน
probe()
ของไดรเวอร์นั้นผ่านdevm_*()
APIอุปกรณ์ฮาร์ดแวร์ไม่จำเป็นต้องมีลำดับการปิดระบบหรือการปิดเครื่อง
ในสถานการณ์นี้ ไดรเวอร์คอร์จะจัดการการเผยแพร่ทรัพยากรทั้งหมดที่ได้รับผ่าน devm_*()
API หากข้อความใดข้อความหนึ่งข้างต้นไม่เป็นความจริง ไดรเวอร์จำเป็นต้องดำเนินการล้างข้อมูล (ปล่อยทรัพยากรและปิดระบบหรือปิดฮาร์ดแวร์) เมื่อแยกออกจากอุปกรณ์ เพื่อให้แน่ใจว่าอุปกรณ์สามารถแยกโมดูลไดรเวอร์ออกได้อย่างสมบูรณ์ ให้ใช้ตัวเลือกใดตัวเลือกหนึ่งต่อไปนี้:
หากฮาร์ดแวร์ไม่ต้องการลำดับการปิดระบบหรือการปิดเครื่อง ให้เปลี่ยนโมดูลอุปกรณ์เพื่อรับทรัพยากรโดยใช้
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()
เนื่องจากอาจไม่สามารถเข้าถึงเฟิร์มแวร์ได้ หากไดรเวอร์ตรวจสอบก่อนที่จะติดตั้งระบบไฟล์แบบแฟลชหรือที่จัดเก็บข้อมูลถาวร ในกรณีเช่นนี้ 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
หากซัพพลายเออร์หนึ่งรายขึ้นไปยังไม่พร้อมหากคุณเพิ่มอุปกรณ์ลูกในฟังก์ชันโพรบของอุปกรณ์หลัก อย่าถือว่าอุปกรณ์ลูกนั้นถูกโพรบทันที
หากโพรบล้มเหลว ให้ดำเนินการจัดการข้อผิดพลาดอย่างเหมาะสมและล้างข้อมูล (โปรดดู ใช้ devm_*() API Variants )
อย่าใช้ MODULE_SOFTDEP เพื่อสั่งซื้อโพรบอุปกรณ์
ฟังก์ชัน MODULE_SOFTDEP()
ไม่ใช่โซลูชันที่เชื่อถือได้สำหรับการรับประกันลำดับของโพรบอุปกรณ์ และจะต้องไม่ใช้ด้วยเหตุผลต่อไปนี้
การสอบสวนเลื่อนออกไป เมื่อโมดูลโหลด โพรบอุปกรณ์อาจถูกเลื่อนออกไปเนื่องจากซัพพลายเออร์รายหนึ่งไม่พร้อม ซึ่งอาจนำไปสู่ความไม่ตรงกันระหว่างลำดับการโหลดโมดูลและลำดับโพรบอุปกรณ์
หนึ่งไดรเวอร์ อุปกรณ์มากมาย โมดูลไดรเวอร์สามารถจัดการประเภทอุปกรณ์เฉพาะได้ หากระบบมีประเภทอุปกรณ์มากกว่าหนึ่งอินสแตนซ์ และอุปกรณ์เหล่านั้นแต่ละอุปกรณ์มีข้อกำหนดลำดับโพรบที่แตกต่างกัน คุณไม่สามารถปฏิบัติตามข้อกำหนดเหล่านั้นโดยใช้ลำดับโหลดโมดูล
การตรวจสอบแบบอะซิงโครนัส โมดูลไดรเวอร์ที่ดำเนินการตรวจสอบแบบอะซิงโครนัสจะไม่ตรวจสอบอุปกรณ์ทันทีเมื่อมีการโหลดโมดูล เธรดแบบขนานจะจัดการการตรวจสอบอุปกรณ์แทน ซึ่งอาจนำไปสู่ความไม่ตรงกันระหว่างลำดับการโหลดโมดูลและลำดับโพรบอุปกรณ์ ตัวอย่างเช่น เมื่อโมดูลไดรเวอร์หลักของ 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
ยังคงคอมไพล์ต่อไป หากการกำหนดค่าเปลี่ยนเป็นการกำหนดค่าแบบ tristate ในอนาคต ความแตกต่างมีดังนี้:
#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