Panduan modul vendor

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 di module_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(), dan module_pci_driver().

  • Untuk modul yang tidak dapat di-unload, gunakan builtin_subsystem_driver() Contoh: builtin_platform_driver(), builtin_i2c_driver(), dan builtin_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 dan struct dev_links_info. Struktur data yang ditentukan di include/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 inisialisasi struct cpufreq_driver, lalu meneruskannya sebagai input ke cpufreq_register_driver(). Setelah tahap ini, modul driver cpufreq tidak boleh mengubah struct cpufreq_driver secara langsung karena memanggil cpufreq_register_driver() akan membuat struct cpufreq_driver terlihat oleh kernel.

  • Struktur data tidak diinisialisasi oleh modul Anda. Misalnya, struct regulator_dev ditampilkan oleh regulator_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 fungsi devm_*() seperti devm_clk_get(), devm_regulator_get(), atau devm_kzalloc().

  • Untuk mengubah kolom di dalam struct device.links, gunakan API link perangkat seperti device_link_add() atau device_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 API devm_*().

  • 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 fungsi probe(), lalu lakukan langkah-langkah pembersihan menggunakan fungsi remove().

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 ke true di struct device_driver driver; setelan ini mencegah file bind dan unbind ditampilkan di direktori sysfs driver. File unbind adalah yang memungkinkan ruang pengguna memicu pembatalan binding driver dari perangkatnya.

  • Untuk melarang penghapusan muatan modul, pastikan modul memiliki [permanent] di lsmod. Dengan tidak menggunakan module_exit() atau module_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) bernilai true saat CONFIG_XXX ditetapkan ke modul (=m) atau bawaan (=y).

  • #ifdef CONFIG_XXX dievaluasi menjadi true saat CONFIG_XXX disetel ke bawaan (=y) , tetapi tidak saat CONFIG_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 menjadi CONFIG_XYZ. Untuk file sumber non-header yang dikompilasi menjadi modul, sistem build akan otomatis menentukan MODULE 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 awalan CONFIG_).

  • 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.