Gunakan panduan berikut untuk meningkatkan keandalan dan keandalan modul vendor Anda. Banyak panduan, jika diikuti, dapat membantu mempermudah penentuan urutan pemuatan modul yang benar dan urutan driver harus menyelidiki perangkat.
Modul dapat berupa library atau driver.
Modul library adalah library yang menyediakan API untuk digunakan oleh modul lain. Modul tersebut biasanya tidak khusus untuk hardware tertentu. Contoh modul library mencakup modul enkripsi AES, framework
remoteprocyang dikompilasi sebagai modul, dan modul logbuffer. Kode modul dimodule_init()berjalan untuk menyiapkan struktur data, tetapi tidak ada kode lain yang berjalan kecuali dipicu oleh modul eksternal.Modul driver adalah driver yang menyelidiki atau mengikat ke jenis perangkat tertentu. Modul tersebut khusus untuk hardware. Contoh modul driver mencakup UART, PCIe, dan hardware encoder video. Modul driver hanya diaktifkan saat perangkat terkaitnya ada di sistem.
Jika perangkat tidak ada, satu-satunya kode modul yang berjalan adalah kode
module_init()yang mendaftarkan driver dengan framework inti driver.Jika perangkat ada dan driver berhasil melakukan probing atau mengikat ke perangkat tersebut, kode modul lain mungkin berjalan.
Menggunakan inisialisasi dan keluar modul dengan benar
Modul driver harus mendaftarkan driver di module_init() dan membatalkan pendaftaran
driver di module_exit(). Salah satu cara untuk menerapkan batasan ini adalah dengan menggunakan
makro wrapper, yang menghindari penggunaan langsung makro module_init(), *_initcall(),
atau module_exit().
Untuk modul yang dapat di-unmount, gunakan
module_subsystem_driver(). Contoh:module_platform_driver(),module_i2c_driver(), danmodule_pci_driver().Untuk modul yang tidak dapat di-unmount, gunakan
builtin_subsystem_driver()Contoh:builtin_platform_driver(),builtin_i2c_driver(), danbuiltin_pci_driver().
Beberapa modul driver menggunakan module_init() dan module_exit() karena modul tersebut mendaftarkan lebih dari satu driver. Untuk modul driver yang menggunakan module_init() dan
module_exit() untuk mendaftarkan beberapa driver, coba gabungkan driver ke dalam
satu driver. Misalnya, Anda dapat membedakan menggunakan string compatible
atau data tambahan perangkat, bukan mendaftarkan driver terpisah.
Atau, Anda dapat membagi modul driver menjadi dua modul.
Pengecualian fungsi init dan keluar
Modul library tidak mendaftarkan driver dan dikecualikan dari batasan pada
module_init() dan module_exit() karena mungkin memerlukan fungsi ini untuk menyiapkan
struktur data, antrean kerja, atau thread kernel.
Menggunakan makro MODULE_DEVICE_TABLE
Modul driver harus menyertakan makro MODULE_DEVICE_TABLE, yang memungkinkan
ruang pengguna menentukan perangkat yang didukung oleh modul driver sebelum memuat
modul. Android dapat menggunakan data ini untuk mengoptimalkan pemuatan modul, seperti untuk
menghindari pemuatan modul untuk perangkat yang tidak ada dalam sistem. Untuk
contoh penggunaan makro, lihat kode upstream.
Menghindari ketidakcocokan CRC karena jenis data yang dideklarasikan terlebih dahulu
Jangan sertakan file header untuk mendapatkan visibilitas ke jenis data yang dideklarasikan terlebih dahulu.
Beberapa struct, union, dan jenis data lainnya yang ditentukan dalam file header
(header-A.h) dapat dideklarasikan terlebih dahulu dalam file header
lain (header-B.h) yang biasanya menggunakan pointer ke
jenis data tersebut. Pola kode ini berarti bahwa kernel sengaja
berusaha menjaga kerahasiaan struktur data bagi pengguna
header-B.h.
Pengguna header-B.h tidak boleh menyertakan
header-A.h untuk mengakses langsung internal dari struktur data yang
dideklarasikan ke depan ini. Tindakan ini menyebabkan masalah ketidakcocokan CRC (yang menimbulkan masalah kepatuhan ABI) saat kernel lain (seperti kernel GKI) mencoba memuat modul.CONFIG_MODVERSIONS
Misalnya, struct fwnode_handle ditentukan di include/linux/fwnode.h, tetapi
dideklarasikan ke depan sebagai struct fwnode_handle; di include/linux/device.h
karena kernel mencoba menjaga detail struct fwnode_handle
tetap bersifat pribadi dari pengguna include/linux/device.h. Dalam skenario ini, jangan
tambahkan #include <linux/fwnode.h> dalam modul untuk mendapatkan akses ke anggota
struct fwnode_handle. Desain apa pun yang mengharuskan Anda menyertakan file header tersebut menunjukkan pola desain yang buruk.
Jangan mengakses struktur kernel inti secara langsung
Mengakses atau memodifikasi struktur data kernel inti secara langsung dapat menyebabkan perilaku yang tidak diinginkan, termasuk kebocoran memori, error, dan kompatibilitas yang rusak dengan rilis kernel mendatang. Struktur data adalah struktur data kernel inti jika memenuhi salah satu kondisi berikut:
Struktur data ditentukan di bagian
KERNEL-DIR/include/. Misalnya,struct devicedanstruct dev_links_info. Struktur data yang ditentukan dalaminclude/linux/socdikecualikan.Struktur data dialokasikan atau diinisialisasi oleh modul, tetapi dibuat terlihat oleh kernel dengan diteruskan, secara tidak langsung (melalui pointer dalam struct) atau langsung, sebagai input dalam fungsi yang diekspor oleh kernel. Misalnya, modul driver
cpufreqmelakukan inisialisasistruct cpufreq_driverdan kemudian meneruskannya sebagai input kecpufreq_register_driver(). Setelah titik ini, modul drivercpufreqtidak boleh mengubahstruct cpufreq_driversecara langsung karena memanggilcpufreq_register_driver()membuatstruct cpufreq_driverterlihat oleh kernel.Struktur data tidak diinisialisasi oleh modul Anda. Misalnya,
struct regulator_devyang ditampilkan olehregulator_register().
Akses struktur data kernel inti hanya melalui fungsi yang diekspor oleh kernel atau melalui parameter yang secara eksplisit diteruskan sebagai input ke hook vendor. Jika Anda tidak memiliki API atau hook vendor untuk mengubah bagian struktur data kernel inti, hal ini mungkin disengaja dan Anda tidak boleh mengubah struktur data dari modul. Misalnya, jangan ubah kolom apa pun di dalam struct device atau
struct device.links.
Untuk mengubah
device.devres_head, gunakan fungsidevm_*()sepertidevm_clk_get(),devm_regulator_get(), ataudevm_kzalloc().Untuk mengubah kolom di dalam
struct device.links, gunakan API penautan perangkat sepertidevice_link_add()ataudevice_link_del().
Jangan mengurai node devicetree dengan properti yang kompatibel
Jika node pohon perangkat (DT) memiliki properti compatible, struct device akan dialokasikan untuknya secara otomatis atau saat of_platform_populate() dipanggil di node DT induk (biasanya oleh driver perangkat dari perangkat induk). Ekspektasi default (kecuali untuk beberapa perangkat yang diinisialisasi lebih awal untuk penjadwal) adalah bahwa node DT dengan properti compatible memiliki struct device dan driver perangkat yang cocok. Semua pengecualian lainnya sudah ditangani oleh kode upstream.
Selain itu, fw_devlink (sebelumnya disebut of_devlink) menganggap node DT dengan properti compatible sebagai perangkat dengan struct device yang dialokasikan yang diselidiki oleh driver. Jika node DT memiliki properti compatible, tetapi struct device yang dialokasikan tidak diselidiki, fw_devlink dapat memblokir perangkat konsumennya agar tidak menyelidiki atau dapat memblokir panggilan sync_state() agar tidak dipanggil untuk perangkat pemasoknya.
Jika driver Anda menggunakan fungsi of_find_*() (seperti of_find_node_by_name()
atau of_find_compatible_node()) untuk langsung menemukan node DT yang memiliki
properti compatible, lalu mengurai node DT tersebut, perbaiki modul dengan menulis
driver perangkat yang dapat menyelidiki perangkat atau menghapus properti compatible
(hanya mungkin jika belum di-upstream). Untuk mendiskusikan alternatif, hubungi
Tim Kernel Android di kernel-team@android.com dan bersiaplah untuk
membenarkan kasus penggunaan Anda.
Menggunakan phandle DT untuk mencari pemasok
Sebaiknya merujuk ke supplier menggunakan phandle (referensi atau penunjuk ke node DT) di DT. Penggunaan binding dan phandle DT standar untuk merujuk ke pemasok memungkinkan fw_devlink (sebelumnya of_devlink) menentukan dependensi antarperangkat secara otomatis dengan mengurai DT saat runtime. Kernel kemudian dapat menyelidiki perangkat secara otomatis dalam urutan yang benar, sehingga tidak perlu lagi mengurutkan pemuatan modul atau MODULE_SOFTDEP().
Skenario lama (tidak ada dukungan DT di kernel ARM)
Sebelumnya, sebelum dukungan DT ditambahkan ke kernel ARM, perangkat konsumen seperti perangkat sentuh mencari pemasok seperti regulator menggunakan string unik secara global.
Misalnya, driver PMIC ACME dapat mendaftarkan atau mengiklankan beberapa regulator (seperti acme-pmic-ldo1 hingga acme-pmic-ldo10) dan driver sentuh dapat mencari regulator menggunakan regulator_get(dev, "acme-pmic-ldo10").
Namun, di papan yang berbeda, LDO8 dapat memasok perangkat sentuh, sehingga menciptakan
sistem yang rumit di mana driver sentuh yang sama perlu menentukan
string pencarian yang benar untuk regulator bagi setiap papan tempat perangkat sentuh digunakan.
Skenario saat ini (dukungan DT di kernel ARM)
Setelah dukungan DT ditambahkan ke kernel ARM, konsumen dapat mengidentifikasi pemasok di DT dengan merujuk ke node pohon perangkat pemasok menggunakan phandle.
Konsumen juga dapat memberi nama resource berdasarkan penggunaannya, bukan berdasarkan siapa yang menyediakannya. Misalnya, driver sentuh dari contoh sebelumnya dapat menggunakan
regulator_get(dev, "core") dan regulator_get(dev, "sensor") untuk mendapatkan
pemasok yang mendukung inti dan sensor perangkat sentuh. DT terkait untuk
perangkat tersebut mirip dengan contoh kode berikut:
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 {
...
};
};
Skenario terburuk
Beberapa driver yang di-porting dari kernel lama mencakup perilaku lama di DT yang mengambil bagian terburuk dari skema lama dan memaksakannya pada skema baru yang seharusnya mempermudah. Dalam driver tersebut, driver konsumen membaca string yang akan digunakan untuk pencarian menggunakan properti DT khusus perangkat, pemasok menggunakan properti khusus pemasok lain untuk menentukan nama yang akan digunakan untuk mendaftarkan resource pemasok, lalu konsumen dan pemasok terus menggunakan skema lama yang sama untuk menggunakan string guna mencari pemasok. Dalam skenario terburuk ini:
Driver sentuh menggunakan kode yang mirip dengan kode berikut:
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 menggunakan kode yang mirip dengan berikut ini:
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" }; };
Jangan ubah error API framework
Framework API, seperti regulator, clocks, irq, gpio, phys, dan
extcon, menampilkan -EPROBE_DEFER sebagai nilai yang ditampilkan error untuk menunjukkan bahwa
perangkat sedang mencoba melakukan pemeriksaan, tetapi tidak dapat melakukannya saat ini, dan kernel harus
mencoba lagi pemeriksaan nanti. Untuk memastikan fungsi .probe() perangkat Anda gagal seperti yang diharapkan dalam kasus tersebut, jangan mengganti atau memetakan ulang nilai error.
Mengganti atau memetakan ulang nilai error dapat menyebabkan -EPROBE_DEFER dihilangkan
dan mengakibatkan perangkat Anda tidak pernah diperiksa.
Menggunakan varian API devm_*()
Saat perangkat mendapatkan resource menggunakan API devm_*(), resource akan dilepaskan secara otomatis oleh kernel jika perangkat gagal melakukan probing, atau berhasil melakukan probing dan kemudian tidak terikat. Kemampuan ini membuat kode penanganan error
dalam fungsi probe() lebih bersih karena tidak memerlukan lompatan goto
untuk melepaskan resource yang diperoleh oleh devm_*() dan menyederhanakan operasi
pelepasan driver.
Menangani pelepasan driver perangkat
Sengaja batalkan pengikatan driver perangkat dan jangan biarkan pembatalan pengikatan tidak ditentukan karena tidak ditentukan tidak berarti tidak diizinkan. Anda harus menerapkan pelepasan driver perangkat sepenuhnya atau menonaktifkan pelepasan driver perangkat secara eksplisit.
Menerapkan pelepasan driver perangkat
Saat memilih untuk menerapkan pelepasan driver perangkat sepenuhnya, lepaskan driver perangkat dengan benar untuk menghindari kebocoran memori atau resource dan masalah keamanan. Anda dapat mengikat
perangkat ke driver dengan memanggil fungsi probe() driver dan melepaskan ikatan perangkat
dengan memanggil fungsi remove() driver. Jika tidak ada fungsi remove(),
kernel masih dapat melepaskan perangkat; inti driver mengasumsikan bahwa tidak ada pekerjaan
pembersihan yang diperlukan oleh driver saat melepaskan diri dari perangkat. Driver yang
tidak terikat dari perangkat tidak perlu melakukan pekerjaan pembersihan eksplisit apa pun jika kedua
hal berikut benar:
Semua resource yang diperoleh oleh fungsi
probe()pengemudi dilakukan melalui APIdevm_*().Perangkat hardware tidak memerlukan urutan penonaktifan atau penghentian sementara.
Dalam situasi ini, inti driver menangani pelepasan semua resource yang diperoleh melalui API devm_*(). Jika salah satu pernyataan sebelumnya tidak benar, driver perlu melakukan pembersihan (melepaskan resource dan mematikan atau menghentikan sementara hardware) saat membatalkan pengikatan dari perangkat. Untuk memastikan perangkat dapat melepaskan modul driver dengan benar, gunakan salah satu opsi berikut:
Jika hardware tidak memerlukan urutan penonaktifan atau penghentian sementara, ubah modul perangkat untuk mendapatkan resource menggunakan API
devm_*().Terapkan operasi driver
remove()di struct yang sama dengan fungsiprobe(), lalu lakukan langkah-langkah pembersihan menggunakan fungsiremove().
Menonaktifkan pelepasan driver perangkat secara eksplisit (tidak direkomendasikan)
Saat memilih untuk menonaktifkan pelepasan driver perangkat secara eksplisit, Anda harus melarang pelepasan dan melarang pembongkaran modul.
Untuk melarang pelepasan, tetapkan flag
suppress_bind_attrsketruedistruct device_driverdriver; setelan ini mencegah filebinddanunbindditampilkan di direktorisysfsdriver. Fileunbindadalah yang memungkinkan ruang pengguna memicu pelepasan driver dari perangkatnya.Untuk melarang pelepasan modul, pastikan modul memiliki
[permanent]dilsmod. Jika tidak menggunakanmodule_exit()ataumodule_XXX_driver(), modul akan ditandai sebagai[permanent].
Jangan memuat firmware dari dalam fungsi probe
Driver tidak boleh memuat firmware dari dalam fungsi .probe() karena mungkin tidak memiliki akses ke firmware jika driver melakukan pemeriksaan sebelum sistem file berbasis flash atau penyimpanan permanen dipasang. Dalam kasus seperti itu, API request_firmware*() mungkin diblokir dalam waktu yang lama, lalu gagal, yang dapat memperlambat proses booting secara tidak perlu. Sebagai gantinya, tunda pemuatan firmware
hingga saat klien mulai menggunakan perangkat. Misalnya, driver layar dapat memuat firmware saat perangkat tampilan dibuka.
Menggunakan .probe() untuk memuat firmware mungkin tidak masalah dalam beberapa kasus, seperti pada driver jam yang memerlukan firmware agar berfungsi, tetapi perangkat tidak diekspos ke ruang pengguna. Kasus penggunaan lain yang sesuai juga memungkinkan.
Menerapkan pemeriksaan asinkron
Mendukung dan menggunakan penyelidikan asinkron untuk memanfaatkan peningkatan di masa mendatang, seperti pemuatan modul paralel atau penyelidikan perangkat untuk mempercepat waktu booting, yang mungkin ditambahkan ke Android dalam rilis mendatang. Modul driver yang tidak menggunakan pemeriksaan asinkron dapat mengurangi efektivitas pengoptimalan tersebut.
Untuk menandai driver sebagai mendukung dan lebih memilih pemeriksaan asinkron, tetapkan kolom
probe_type di anggota struct device_driver driver. Contoh berikut menunjukkan dukungan tersebut diaktifkan untuk driver platform:
static struct platform_driver acme_driver = {
.probe = acme_probe,
...
.driver = {
.name = "acme",
...
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
Membuat driver berfungsi dengan probing asinkron tidak memerlukan kode khusus. Namun, perhatikan hal berikut saat menambahkan dukungan pemeriksaan asinkron.
Jangan membuat asumsi tentang dependensi yang sebelumnya diselidiki. Periksa secara langsung atau tidak langsung (panggilan framework terbanyak) dan tampilkan
-EPROBE_DEFERjika satu atau beberapa pemasok belum siap.Jika Anda menambahkan perangkat anak dalam fungsi pemeriksaan perangkat induk, jangan menganggap bahwa perangkat anak akan segera diperiksa.
Jika probe gagal, lakukan penanganan error dan pembersihan yang tepat (lihat Menggunakan varian API devm_*()).
Jangan gunakan MODULE_SOFTDEP untuk mengurutkan probe perangkat
Fungsi MODULE_SOFTDEP() bukanlah solusi yang andal untuk menjamin
urutan pemeriksaan perangkat dan tidak boleh digunakan karena alasan berikut.
Pemeriksaan yang ditunda. Saat modul dimuat, pemeriksaan perangkat mungkin ditangguhkan karena salah satu pemasoknya belum siap. Hal ini dapat menyebabkan ketidakcocokan antara urutan pemuatan modul dan urutan pemeriksaan perangkat.
Satu driver, banyak perangkat. Modul driver dapat mengelola jenis perangkat tertentu. Jika sistem menyertakan lebih dari satu instance jenis perangkat dan setiap perangkat tersebut memiliki persyaratan urutan pemeriksaan yang berbeda, Anda tidak dapat memenuhi persyaratan tersebut menggunakan pengurutan pemuatan modul.
Pemeriksaan asinkron. Modul driver yang melakukan pemeriksaan asinkron tidak langsung memeriksa perangkat saat modul dimuat. Sebagai gantinya, thread paralel menangani pemeriksaan perangkat, yang dapat menyebabkan ketidakcocokan antara urutan pemuatan modul dan urutan pemeriksaan perangkat. Misalnya, saat modul driver utama I2C melakukan probing asinkron dan modul driver sentuh bergantung pada PMIC yang ada di bus I2C, meskipun driver sentuh dan driver PMIC dimuat dalam urutan yang benar, probing driver sentuh mungkin dicoba sebelum probing driver PMIC.
Jika Anda memiliki modul driver yang menggunakan fungsi MODULE_SOFTDEP(), perbaiki agar tidak menggunakan fungsi tersebut. Untuk membantu Anda, tim Android telah meng-upstream
perubahan yang memungkinkan kernel menangani masalah pengurutan tanpa menggunakan
MODULE_SOFTDEP(). Secara khusus, Anda dapat menggunakan fw_devlink untuk memastikan pengurutan pemeriksaan dan (setelah semua konsumen perangkat melakukan pemeriksaan) menggunakan callback
sync_state() untuk melakukan tugas yang diperlukan.
Gunakan #if IS_ENABLED() dan bukan #ifdef untuk konfigurasi
Gunakan #if IS_ENABLED(CONFIG_XXX), bukan #ifdef CONFIG_XXX, untuk memastikan bahwa
kode di dalam blok #if terus dikompilasi jika konfigurasi berubah menjadi
konfigurasi tiga status di masa mendatang. Perbedaannya adalah sebagai berikut:
#if IS_ENABLED(CONFIG_XXX)bernilaitruejikaCONFIG_XXXditetapkan ke modul (=m) atau bawaan (=y).#ifdef CONFIG_XXXdievaluasi menjaditruesaatCONFIG_XXXdisetel ke bawaan (=y) , tetapi tidak saatCONFIG_XXXdisetel ke modul (=m). Gunakan ini hanya jika Anda yakin ingin melakukan hal yang sama saat konfigurasi disetel ke modul atau dinonaktifkan.
Menggunakan makro yang benar untuk kompilasi bersyarat
Jika CONFIG_XXX ditetapkan ke modul (=m), sistem build akan otomatis
menentukan CONFIG_XXX_MODULE. Jika driver Anda dikontrol oleh CONFIG_XXX dan Anda ingin memeriksa apakah driver Anda dikompilasi sebagai modul, gunakan panduan berikut:
Dalam file C (atau file sumber yang bukan file header) untuk driver Anda, jangan gunakan
#ifdef CONFIG_XXX_MODULEkarena tidak perlu membatasi dan akan rusak jika config diganti namanya menjadiCONFIG_XYZ. Untuk file sumber non-header yang dikompilasi ke dalam modul, sistem build akan otomatis menentukanMODULEuntuk cakupan file tersebut. Oleh karena itu, untuk memeriksa apakah file C (atau file sumber non-header lainnya) sedang dikompilasi sebagai bagian dari modul, gunakan#ifdef MODULE(tanpa awalanCONFIG_).Dalam file header, pemeriksaan yang sama lebih rumit karena file header tidak dikompilasi langsung ke dalam biner, tetapi dikompilasi sebagai bagian dari file C (atau file sumber lainnya). Gunakan aturan berikut untuk file header:
Untuk file header yang menggunakan
#ifdef MODULE, hasilnya berubah berdasarkan file sumber mana yang menggunakannya. Artinya, file header yang sama dalam build yang sama dapat memiliki bagian kode yang berbeda yang dikompilasi untuk file sumber yang berbeda (modul versus bawaan atau dinonaktifkan). Hal ini dapat berguna saat Anda ingin menentukan makro yang perlu diperluas dengan satu cara untuk kode bawaan dan diperluas dengan cara yang berbeda untuk modul.Untuk file header yang perlu dikompilasi dalam sepotong kode saat
CONFIG_XXXtertentu disetel ke modul (terlepas dari apakah file sumber yang menyertakannya adalah modul), file header harus menggunakan#ifdef CONFIG_XXX_MODULE.