Gunakan panduan berikut untuk meningkatkan keandalan dan keandalan modul vendor Anda. Banyak panduan, jika diikuti, dapat membantu memudahkan penentuan urutan pemuatan modul yang benar dan urutan yang harus diikuti driver untuk memeriksa perangkat.
Modul dapat berupa library atau driver.
Modul library adalah library yang menyediakan API untuk digunakan oleh modul lain. Modul tersebut biasanya tidak spesifik untuk hardware. Contoh modul library mencakup modul enkripsi AES, framework
remoteproc
yang dikompilasi sebagai modul, dan modul logbuffer. Kode modul dimodule_init()
berjalan untuk menyiapkan struktur data, tetapi tidak ada kode lain yang berjalan kecuali jika dipicu oleh modul eksternal.Modul driver adalah driver yang menyelidiki atau mengikat ke jenis perangkat tertentu. Modul tersebut bersifat khusus hardware. Contoh modul driver mencakup UART, PCIe, dan hardware encoder video. Modul driver hanya diaktifkan saat perangkat terkait 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 memeriksa atau mengikat ke perangkat tersebut, kode modul lain mungkin akan 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-unload, gunakan
module_subsystem_driver()
. Contoh:module_platform_driver()
,module_i2c_driver()
, danmodule_pci_driver()
.Untuk modul yang tidak dapat di-unload, gunakan
builtin_subsystem_driver()
Contoh:builtin_platform_driver()
,builtin_i2c_driver()
, danbuiltin_pci_driver()
.
Beberapa modul driver menggunakan module_init()
dan module_exit()
karena
mendaftarkan lebih dari satu driver. Untuk modul driver yang menggunakan module_init()
dan
module_exit()
untuk mendaftarkan beberapa driver, coba gabungkan driver menjadi
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 exit
Modul library tidak mendaftarkan driver dan dikecualikan dari pembatasan pada
module_init()
dan module_exit()
karena mungkin memerlukan fungsi ini untuk menyiapkan
struktur data, antrean pekerjaan, atau thread kernel.
Gunakan 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, misalnya 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 ke depan
Jangan sertakan file header untuk mendapatkan visibilitas ke jenis data yang dideklarasikan ke depan.
Beberapa struct, union, dan jenis data lainnya yang ditentukan dalam file header
(header-A.h
) dapat dideklarasikan ke depan dalam file header
yang berbeda (header-B.h
) yang biasanya menggunakan pointer ke
jenis data tersebut. Pola kode ini berarti kernel sengaja
mencoba menjaga struktur data tetap bersifat pribadi bagi pengguna
header-B.h
.
Pengguna header-B.h
tidak boleh menyertakan
header-A.h
untuk mengakses internal
struktur data yang dideklarasikan secara langsung ini secara langsung. Tindakan tersebut menyebabkan masalah ketidakcocokan
CRC CONFIG_MODVERSIONS
(yang menyebabkan masalah kepatuhan ABI) saat kernel lain
(seperti kernel GKI) mencoba memuat modul.
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
agar 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 seperti itu menunjukkan pola desain yang buruk.
Jangan langsung mengakses struktur kernel inti
Mengakses atau mengubah 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 device
danstruct dev_links_info
. Struktur data yang ditentukan diinclude/linux/soc
dikecualikan.Struktur data dialokasikan atau diinisialisasi oleh modul, tetapi dibuat terlihat oleh kernel dengan diteruskan, secara tidak langsung (melalui pointer dalam struct) atau secara langsung, sebagai input dalam fungsi yang diekspor oleh kernel. Misalnya, modul driver
cpufreq
melakukan inisialisasistruct cpufreq_driver
, lalu meneruskannya sebagai input kecpufreq_register_driver()
. Setelah tahap ini, modul drivercpufreq
tidak boleh mengubahstruct cpufreq_driver
secara langsung karena memanggilcpufreq_register_driver()
akan membuatstruct cpufreq_driver
terlihat oleh kernel.Struktur data tidak diinisialisasi oleh modul Anda. Misalnya,
struct regulator_dev
ditampilkan olehregulator_register()
.
Akses struktur data kernel inti hanya melalui fungsi yang diekspor oleh
kernel atau melalui parameter yang diteruskan secara eksplisit 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 link perangkat sepertidevice_link_add()
ataudevice_link_del()
.
Jangan mengurai node devicetree dengan properti yang kompatibel
Jika node hierarki perangkat (DT) memiliki properti compatible
, struct device
akan
dialokasikan 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 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 diperiksa oleh pengemudi. 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 secara langsung menemukan node DT yang memiliki
properti compatible
, lalu uraikan node DT tersebut, perbaiki modul dengan menulis
driver perangkat yang dapat menyelidiki perangkat atau menghapus properti compatible
(hanya dapat dilakukan jika belum di-upstream). Untuk membahas alternatif, hubungi
Tim Kernel Android di kernel-team@android.com dan bersiaplah untuk
menjelaskan kasus penggunaan Anda.
Menggunakan phandle DT untuk mencari pemasok
Rujuk pemasok menggunakan phandle (referensi atau pointer ke node DT) dalam DT
jika memungkinkan. Menggunakan binding DT dan phandle standar untuk merujuk ke pemasok
memungkinkan fw_devlink
(sebelumnya of_devlink
) untuk secara otomatis menentukan
dependensi antarperangkat dengan mengurai DT saat runtime. Kemudian, kernel dapat
otomatis menyelidiki perangkat dalam urutan yang benar, sehingga tidak perlu lagi
urutan pemuatan modul atau MODULE_SOFTDEP()
.
Skenario lama (tidak ada dukungan DT di kernel ARM)
Sebelumnya, sebelum dukungan DT ditambahkan ke kernel ARM, 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, pada board lain, LDO8 mungkin memasok perangkat sentuh, sehingga membuat
sistem yang rumit dengan driver sentuh yang sama harus menentukan string
penelusuran yang benar untuk regulator untuk setiap board 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 hierarki perangkat pemasok menggunakan phandle.
Konsumen juga dapat memberi nama resource berdasarkan tujuan penggunaannya, bukan berdasarkan
pihak yang menyediakannya. Misalnya, driver sentuh dari contoh sebelumnya dapat menggunakan
regulator_get(dev, "core")
dan regulator_get(dev, "sensor")
untuk mendapatkan
penyedia yang mendukung core 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 dari keduanya
Beberapa driver yang di-port dari kernel lama menyertakan perilaku lama di DT yang mengambil bagian terburuk dari skema lama dan memaksanya pada skema yang lebih baru yang dimaksudkan untuk 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 menggunakan string untuk 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 kode berikut:
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 mencoba menyelidiki, tetapi saat ini tidak dapat melakukannya, dan kernel harus
mencoba pemeriksaan lagi nanti. Untuk memastikan fungsi .probe()
perangkat Anda gagal seperti yang diharapkan dalam kasus tersebut, jangan ganti atau petakan ulang nilai error.
Mengganti atau memetakan ulang nilai error dapat menyebabkan -EPROBE_DEFER
dihapus
dan menyebabkan perangkat Anda tidak pernah diuji.
Menggunakan varian API devm_*()
Saat perangkat memperoleh resource menggunakan devm_*()
API, resource tersebut
akan otomatis dirilis oleh kernel jika perangkat gagal melakukan pemeriksaan, atau pemeriksaan
berhasil dan kemudian di-unbound. 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
pembatalan pengikatan driver.
Menangani pelepasan binding driver perangkat
Bertindaklah dengan sengaja saat melakukan unbinding driver perangkat dan jangan membiarkan pemisahan tersebut tidak ditentukan karena tidak ditentukan tidak berarti tidak diizinkan. Anda harus sepenuhnya menerapkan pelepasan binding driver perangkat atau menonaktifkan pelepasan binding driver perangkat secara eksplisit.
Mengimplementasikan pelepasan driver perangkat
Saat memilih untuk sepenuhnya menerapkan pelepasan driver perangkat, 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 membatalkan pengikatan perangkat
dengan memanggil fungsi remove()
driver. Jika tidak ada fungsi remove()
,
kernel masih dapat membatalkan pengikatan perangkat; core driver mengasumsikan bahwa tidak ada pekerjaan
pembersihan yang diperlukan oleh driver saat membatalkan pengikatan dari perangkat. Driver yang
tidak terikat dengan perangkat tidak perlu melakukan pekerjaan pembersihan eksplisit jika kedua
hal berikut benar:
Semua resource yang diperoleh oleh fungsi
probe()
driver adalah melalui APIdevm_*()
.Perangkat hardware tidak memerlukan urutan penonaktifan atau quiescing.
Dalam situasi ini, inti driver menangani pelepasan semua resource yang diperoleh
melalui devm_*()
API. Jika salah satu pernyataan sebelumnya tidak benar, driver
harus melakukan pembersihan (melepaskan resource dan mematikan atau
menonaktifkan hardware) saat melepaskan ikatan dari perangkat. Untuk memastikan perangkat dapat
melepaskan modul driver dengan bersih, gunakan salah satu opsi berikut:
Jika hardware tidak memerlukan urutan penonaktifan atau penonaktifan sementara, ubah modul perangkat untuk memperoleh resource menggunakan
devm_*()
API.Terapkan operasi driver
remove()
dalam struct yang sama dengan fungsiprobe()
, lalu lakukan langkah-langkah pembersihan menggunakan fungsiremove()
.
Menonaktifkan secara eksplisit pembatalan binding driver perangkat (tidak direkomendasikan)
Saat memilih untuk menonaktifkan pembatalan tautan driver perangkat secara eksplisit, Anda harus melarang pembatalan tautan dan melarang penghapusan muatan modul.
Untuk melarang pembatalan pengikatan, tetapkan tanda
suppress_bind_attrs
ketrue
distruct device_driver
driver; setelan ini mencegah filebind
danunbind
ditampilkan di direktorisysfs
driver. Fileunbind
adalah yang memungkinkan ruang pengguna memicu pembatalan binding driver dari perangkatnya.Untuk melarang penghapusan muatan modul, pastikan modul memiliki
[permanent]
dilsmod
. Dengan 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 driver mungkin
tidak memiliki akses ke firmware jika driver memindai sebelum flash atau
sistem file berbasis penyimpanan permanen dipasang. Dalam kasus seperti itu,
request_firmware*()
API mungkin memblokir selama waktu yang lama, lalu gagal, yang dapat
memperlambat proses booting secara tidak perlu. Sebagai gantinya, tunda pemuatan firmware
ke saat klien mulai menggunakan perangkat. Misalnya, driver tampilan dapat
memuat firmware saat perangkat tampilan dibuka.
Dalam beberapa kasus, penggunaan .probe()
untuk memuat firmware mungkin tidak masalah, seperti dalam driver
jam yang memerlukan firmware agar berfungsi, tetapi perangkat tidak diekspos ke ruang
pengguna. Kasus penggunaan lain yang sesuai juga dapat dilakukan.
Mengimplementasikan pemeriksaan asinkron
Mendukung dan menggunakan probing asinkron untuk memanfaatkan peningkatan di masa mendatang, seperti pemuatan modul paralel atau probing 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 probing 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 pengemudi bekerja dengan pemeriksaan asinkron tidak memerlukan kode khusus. Namun, perhatikan hal berikut saat menambahkan dukungan pemeriksaan asinkron.
Jangan membuat asumsi tentang dependensi yang diselidiki sebelumnya. Periksa secara langsung atau tidak langsung (sebagian besar panggilan framework) dan tampilkan
-EPROBE_DEFER
jika satu atau beberapa supplier belum siap.Jika Anda menambahkan perangkat turunan dalam fungsi pemeriksaan perangkat induk, jangan berasumsi bahwa perangkat turunan 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 probe perangkat dan tidak boleh digunakan karena alasan berikut.
Pemeriksaan yang ditangguhkan. Saat modul dimuat, pemeriksaan perangkat mungkin ditangguhkan karena salah satu pemasoknya tidak 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 probe yang berbeda, Anda tidak dapat memenuhi persyaratan tersebut menggunakan urutan pemuatan modul.
Pemindaian asinkron. Modul driver yang melakukan pemeriksaan asinkron tidak langsung memeriksa perangkat saat modul dimuat. Sebagai gantinya, thread paralel menangani pemindaian perangkat, yang dapat menyebabkan ketidakcocokan antara urutan pemuatan modul dan urutan probe 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, probe driver sentuh mungkin dicoba sebelum probe driver PMIC.
Jika Anda memiliki modul driver yang menggunakan fungsi MODULE_SOFTDEP()
, perbaiki agar
modul tersebut tidak menggunakan fungsi tersebut. Untuk membantu Anda, tim Android telah melakukan perubahan
upstream yang memungkinkan kernel menangani masalah pengurutan tanpa menggunakan
MODULE_SOFTDEP()
. Secara khusus, Anda dapat menggunakan fw_devlink
untuk memastikan pengurutan
probe dan (setelah semua konsumen perangkat di-probe) menggunakan
callback sync_state()
untuk melakukan tugas yang diperlukan.
Gunakan #if IS_ENABLED(), bukan #ifdef untuk konfigurasi
Gunakan #if IS_ENABLED(CONFIG_XXX)
, bukan #ifdef CONFIG_XXX
, untuk memastikan
kode di dalam blok #if
terus dikompilasi jika konfigurasi berubah menjadi
konfigurasi tristatus pada masa mendatang. Perbedaannya adalah sebagai berikut:
#if IS_ENABLED(CONFIG_XXX)
bernilaitrue
saatCONFIG_XXX
ditetapkan ke modul (=m
) atau bawaan (=y
).#ifdef CONFIG_XXX
dievaluasi menjaditrue
saatCONFIG_XXX
disetel ke bawaan (=y
) , tetapi tidak saatCONFIG_XXX
disetel ke modul (=m
). Gunakan ini hanya saat 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 dikontrol oleh CONFIG_XXX
dan
Anda ingin memeriksa apakah driver dikompilasi sebagai modul, gunakan
panduan berikut:
Dalam file C (atau file sumber apa pun yang bukan file header) untuk driver Anda, jangan gunakan
#ifdef CONFIG_XXX_MODULE
karena tidak perlu membatasi dan rusak jika konfigurasi diganti namanya menjadiCONFIG_XYZ
. Untuk file sumber non-header yang dikompilasi menjadi modul, sistem build akan otomatis menentukanMODULE
untuk cakupan file tersebut. Oleh karena itu, untuk memeriksa apakah file C (atau file sumber non-header) sedang dikompilasi sebagai bagian dari modul, gunakan#ifdef MODULE
(tanpa awalanCONFIG_
).Dalam file header, pemeriksaan yang sama lebih sulit karena file header tidak dikompilasi langsung menjadi 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 akan berubah berdasarkan file sumber yang menggunakannya. Ini berarti 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 satu arah untuk kode bawaan dan diperluas dengan cara yang berbeda untuk modul.Untuk file header yang perlu dikompilasi dalam potongan kode saat
CONFIG_XXX
tertentu ditetapkan ke modul (terlepas dari apakah file sumber termasuk modul), file header harus menggunakan#ifdef CONFIG_XXX_MODULE
.