Panduan modul vendor

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

  • Untuk modul yang tidak dapat di-unmount, 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 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 device dan struct dev_links_info. Struktur data yang ditentukan dalam 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 langsung, sebagai input dalam fungsi yang diekspor oleh kernel. Misalnya, modul driver cpufreq melakukan inisialisasi struct cpufreq_driver dan kemudian meneruskannya sebagai input ke cpufreq_register_driver(). Setelah titik ini, modul driver cpufreq tidak boleh mengubah struct cpufreq_driver secara langsung karena memanggil cpufreq_register_driver() membuat struct cpufreq_driver terlihat oleh kernel.

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

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

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

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_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 pelepasan driver dari perangkatnya.

  • Untuk melarang pelepasan modul, pastikan modul memiliki [permanent] di lsmod. Jika 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 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_DEFER jika 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) bernilai true jika 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 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_MODULE karena tidak perlu membatasi dan akan rusak jika config diganti namanya menjadi CONFIG_XYZ. Untuk file sumber non-header yang dikompilasi ke dalam modul, sistem build akan otomatis menentukan MODULE untuk 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 awalan CONFIG_).

  • 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_XXX tertentu disetel ke modul (terlepas dari apakah file sumber yang menyertakannya adalah modul), file header harus menggunakan #ifdef CONFIG_XXX_MODULE.