Panduan modul vendor

Gunakan panduan berikut untuk meningkatkan ketahanan dan keandalan modul vendor Anda. Banyak pedoman, bila diikuti, dapat membantu mempermudah menentukan urutan pemuatan modul yang benar dan urutan driver yang harus diperiksa untuk perangkat.

Modul dapat berupa perpustakaan atau driver .

  • Modul library adalah library yang menyediakan API untuk digunakan modul lain. Modul semacam itu biasanya tidak spesifik untuk perangkat keras. Contoh modul library termasuk modul enkripsi AES, framework remoteproc yang dikompilasi sebagai modul, dan modul logbuffer. Kode modul di module_init() berjalan untuk mengatur 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 semacam itu khusus untuk perangkat keras. Contoh modul driver termasuk UART, PCIe, dan perangkat keras encoder video. Modul driver hanya aktif jika perangkat terkaitnya ada di sistem.

    • Jika perangkat tidak ada, satu-satunya kode modul yang berjalan adalah kode module_init() yang mendaftarkan driver dengan kerangka inti driver.

    • Jika perangkat ada dan driver berhasil memeriksa atau mengikat ke perangkat itu, kode modul lain mungkin berjalan.

Gunakan modul init/keluar dengan benar

Modul driver harus mendaftarkan driver di module_init() dan membatalkan registrasi driver di module_exit() . Cara sederhana untuk menerapkan pembatasan ini adalah dengan menggunakan makro pembungkus, yang menghindari penggunaan makro module_init( module_init() , *_initcall() , atau module_exit() .

  • Untuk modul yang dapat dibongkar, gunakan module_ subsystem _driver() . Contoh: module_platform_driver() , module_i2c_driver() , dan module_pci_driver() .

  • Untuk modul yang tidak dapat dibongkar, 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 mereka 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 yang compatible atau data aux perangkat alih-alih 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 module_init() dan module_exit() karena mungkin memerlukan fungsi ini untuk menyiapkan struktur data, antrian kerja, atau utas kernel.

Gunakan makro MODULE_DEVICE_TABLE

Modul driver harus menyertakan makro MODULE_DEVICE_TABLE , yang memungkinkan ruang pengguna untuk menentukan perangkat yang didukung oleh modul driver sebelum memuat modul. Android dapat menggunakan data ini untuk mengoptimalkan pemuatan modul, seperti menghindari pemuatan modul untuk perangkat yang tidak ada dalam sistem. Untuk contoh penggunaan makro, lihat kode upstream.

Hindari ketidakcocokan CRC karena tipe data yang dideklarasikan ke depan

Jangan sertakan file header untuk mendapatkan visibilitas ke tipe data yang dideklarasikan ke depan. Beberapa struct, union, dan tipe data lain yang didefinisikan dalam file header ( header-Ah ) dapat dideklarasikan ke depan dalam file header yang berbeda ( header-Bh ) yang biasanya menggunakan pointer ke tipe data tersebut. Pola kode ini berarti bahwa kernel secara sengaja mencoba menjaga kerahasiaan struktur data bagi pengguna header-Bh .

Pengguna header-Bh tidak boleh menyertakan header-Ah untuk langsung mengakses internal struktur data yang dideklarasikan ke depan ini. Melakukannya menyebabkan masalah ketidakcocokan CONFIG_MODVERSIONS CRC (yang menghasilkan masalah kepatuhan ABI) ketika kernel yang berbeda (seperti kernel GKI) mencoba memuat modul.

Sebagai contoh, struct fwnode_handle didefinisikan di include/linux/fwnode.h , tetapi dideklarasikan sebagai struct fwnode_handle; di include/linux/device.h karena kernel mencoba merahasiakan detail struct fwnode_handle 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 . Setiap desain di mana Anda harus menyertakan file header tersebut menunjukkan pola desain yang buruk.

Jangan langsung mengakses struktur kernel inti

Mengakses atau memodifikasi struktur data kernel inti secara langsung dapat menyebabkan perilaku yang tidak diinginkan, termasuk kebocoran memori, crash, dan rusaknya kompatibilitas dengan rilis kernel di masa mendatang. Struktur data adalah struktur data kernel inti jika memenuhi salah satu kondisi berikut:

  • Struktur data didefinisikan di bawah KERNEL-DIR /include/ . Misalnya, struct device dan struct dev_links_info . Struktur data yang didefinisikan dalam include/linux/soc dikecualikan.

  • Struktur data dialokasikan atau diinisialisasi oleh modul tetapi dibuat terlihat oleh kernel dengan dilewatkan, secara tidak langsung (melalui pointer dalam struct) atau secara langsung, sebagai input dalam fungsi yang diekspor oleh kernel. Misalnya, modul driver cpufreq menginisialisasi struct cpufreq_driver dan kemudian meneruskannya sebagai input ke cpufreq_register_driver() . Setelah titik ini, modul driver cpufreq tidak boleh memodifikasi 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 dikembalikan 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 kait vendor. Jika Anda tidak memiliki API atau kait vendor untuk memodifikasi bagian dari struktur data kernel inti, itu mungkin disengaja dan Anda tidak boleh memodifikasi struktur data dari modul. Misalnya, jangan ubah bidang apa pun di dalam struct device atau struct device.links .

  • Untuk memodifikasi device.devres_head , gunakan fungsi devm_*() seperti devm_clk_get() , devm_regulator_get() , atau devm_kzalloc() .

  • Untuk memodifikasi bidang di dalam struct device.links , gunakan API tautan perangkat seperti device_link_add() atau device_link_del() .

Jangan mengurai node devicetree dengan properti yang kompatibel

Jika simpul pohon perangkat (DT) memiliki properti yang compatible , struct device dialokasikan untuknya secara otomatis atau ketika of_platform_populate() dipanggil pada simpul DT induk (biasanya oleh driver perangkat perangkat induk). Harapan default (kecuali untuk beberapa perangkat yang diinisialisasi lebih awal untuk penjadwal) adalah bahwa simpul DT dengan properti yang 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 yang compatible sebagai perangkat dengan struct device yang dialokasikan yang diperiksa oleh driver. Jika node DT memiliki properti yang compatible tetapi struct device yang dialokasikan tidak diperiksa, fw_devlink dapat memblokir perangkat konsumennya dari probing 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 simpul DT yang memiliki properti yang compatible dan kemudian menguraikan simpul DT tersebut, perbaiki modul dengan menulis driver perangkat yang dapat menyelidiki perangkat atau hapus properti yang 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.

Gunakan gagang DT untuk mencari pemasok

Rujuk ke pemasok menggunakan phandle (referensi/penunjuk ke simpul DT) di DT bila memungkinkan. Menggunakan binding dan phandle DT standar untuk merujuk ke pemasok memungkinkan fw_devlink (sebelumnya of_devlink ) untuk secara otomatis menentukan dependensi antar-perangkat dengan menguraikan DT saat runtime. Kernel kemudian dapat secara otomatis memeriksa perangkat dalam urutan yang benar, menghilangkan kebutuhan untuk pemesanan beban 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 global. Misalnya, driver ACME PMIC 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 papan yang berbeda, LDO8 mungkin memasok perangkat sentuh, menciptakan sistem yang rumit di mana driver sentuh yang sama perlu menentukan string pencarian yang benar untuk regulator untuk 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 sumber daya berdasarkan untuk apa digunakan, bukan siapa yang memasoknya. Misalnya, driver sentuh dari contoh sebelumnya dapat menggunakan regulator_get(dev, "core") dan regulator_get(dev, "sensor") untuk mendapatkan pemasok yang memberi daya pada inti dan sensor perangkat sentuh. DT terkait untuk perangkat semacam itu 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 kedua dunia

Beberapa driver yang di-porting dari kernel yang lebih lama menyertakan perilaku warisan di DT yang mengambil bagian terburuk dari skema warisan dan memaksanya pada skema yang lebih baru yang dimaksudkan untuk membuat segalanya lebih mudah. 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 sumber daya pemasok, kemudian konsumen dan pemasok terus menggunakan skema lama yang sama menggunakan string untuk mencari pemasok. Dalam skenario terburuk dari kedua dunia 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 kesalahan API kerangka kerja

Framework API, seperti regulator , clocks , irq , gpio , phys , dan extcon , mengembalikan -EPROBE_DEFER sebagai nilai pengembalian kesalahan untuk menunjukkan bahwa perangkat mencoba untuk menyelidiki tetapi tidak dapat saat ini, dan kernel harus mencoba kembali penyelidikan nanti. Untuk memastikan bahwa fungsi .probe() perangkat Anda gagal seperti yang diharapkan dalam kasus tersebut, jangan mengganti atau memetakan ulang nilai kesalahan. Mengganti atau memetakan ulang nilai kesalahan dapat menyebabkan -EPROBE_DEFER dijatuhkan dan mengakibatkan perangkat Anda tidak pernah diperiksa.

Gunakan devm_*() varian API

Ketika perangkat memperoleh sumber daya menggunakan devm_*() API, sumber daya secara otomatis dilepaskan oleh kernel jika perangkat gagal untuk menyelidiki, atau berhasil menyelidiki dan kemudian tidak terikat. Fungsi ini membuat kode penanganan kesalahan dalam fungsi probe() lebih bersih karena tidak memerlukan lompatan goto untuk melepaskan sumber daya yang diperoleh oleh devm_*() dan menyederhanakan operasi pelepasan driver.

Menangani pelepasan driver perangkat

Berhati-hatilah untuk melepaskan driver perangkat dan jangan biarkan yang tidak mengikat tidak terdefinisi karena tidak terdefinisi tidak berarti tidak diizinkan. Anda harus sepenuhnya menerapkan pelepasan driver perangkat atau secara eksplisit menonaktifkan pelepasan driver perangkat.

Menerapkan pelepasan driver perangkat

Saat memilih untuk sepenuhnya menerapkan pelepasan driver perangkat, lepaskan driver perangkat dengan bersih untuk menghindari kebocoran memori atau sumber daya dan masalah keamanan. Anda dapat mengikat perangkat ke driver dengan memanggil fungsi probe() driver dan melepaskan perangkat dengan memanggil fungsi remove() driver. Jika tidak ada fungsi remove() , kernel masih dapat melepaskan ikatan perangkat; inti driver mengasumsikan bahwa tidak ada pekerjaan pembersihan yang diperlukan oleh driver saat terlepas dari perangkat. Driver yang tidak terikat dari perangkat tidak perlu melakukan pekerjaan pembersihan eksplisit jika kedua hal berikut ini benar:

  • Semua sumber daya yang diperoleh oleh fungsi probe() driver melalui devm_*() API.

  • Perangkat keras tidak memerlukan urutan shutdown atau quiescing.

Dalam situasi ini, inti driver menangani pelepasan semua sumber daya yang diperoleh melalui devm_*() API. Jika salah satu dari pernyataan sebelumnya tidak benar, driver perlu melakukan pembersihan (melepaskan sumber daya dan mematikan atau menghentikan perangkat keras) saat melepaskan ikatan dari perangkat. Untuk memastikan bahwa perangkat dapat melepaskan modul driver dengan bersih, gunakan salah satu opsi berikut:

  • Jika perangkat keras tidak memerlukan urutan penonaktifan atau penghentian, ubah modul perangkat untuk memperoleh sumber daya menggunakan devm_*() API.

  • Implementasikan operasi driver remove() dalam struct yang sama dengan fungsi probe() , lalu lakukan langkah-langkah pembersihan menggunakan fungsi remove() .

Menonaktifkan pelepasan driver perangkat secara eksplisit (tidak disarankan)

Saat memilih untuk secara eksplisit menonaktifkan pelepasan driver perangkat, Anda harus melarang pelepasan ikatan dan pelepasan modul.

  • Untuk melarang pelepasan ikatan, setel tanda suppress_bind_attrs ke true di struct device_driver driver; pengaturan ini mencegah file bind dan unbind ditampilkan di direktori sysfs driver. File unbind adalah apa yang memungkinkan ruang pengguna untuk memicu pelepasan driver dari perangkatnya.

  • Untuk melarang pembongkaran modul, pastikan modul memiliki [permanent] di lsmod . Dengan tidak menggunakan module_exit() atau module_XXX_driver() , modul ditandai sebagai [permanent] .

Jangan memuat firmware dari dalam fungsi probe

Driver tidak boleh memuat firmware dari dalam fungsi .probe() karena mereka mungkin tidak memiliki akses ke firmware jika driver memeriksa sebelum flash atau sistem file berbasis penyimpanan permanen dipasang. Dalam kasus seperti itu, API request_firmware*() mungkin memblokir untuk waktu yang lama dan kemudian gagal, yang dapat memperlambat proses boot secara tidak perlu. Sebagai gantinya, tunda pemuatan firmware ke saat klien mulai menggunakan perangkat. Misalnya, driver tampilan 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 untuk berfungsi tetapi perangkat tidak terpapar ke ruang pengguna. Kasus penggunaan lain yang sesuai dimungkinkan.

Terapkan pemeriksaan asinkron

Dukung dan gunakan probing asinkron untuk memanfaatkan penyempurnaan di masa mendatang, seperti pemuatan modul paralel atau probing perangkat untuk mempercepat waktu booting, yang mungkin ditambahkan ke Android di rilis mendatang. Modul driver yang tidak menggunakan pemeriksaan asinkron dapat mengurangi efektivitas pengoptimalan tersebut.

Untuk menandai driver sebagai pendukung dan lebih memilih probing asinkron, setel bidang probe_type di anggota struct device_driver driver. Contoh berikut menunjukkan dukungan yang diaktifkan untuk driver platform:

static struct platform_driver acme_driver = {
        .probe          = acme_probe,
        ...
        .driver         = {
                .name   = "acme",
                ...
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

Membuat driver bekerja dengan pemeriksaan asinkron tidak memerlukan kode khusus. Namun, ingatlah hal berikut saat menambahkan dukungan probing asinkron.

  • Jangan membuat asumsi tentang dependensi yang diperiksa sebelumnya. Periksa secara langsung atau tidak langsung (sebagian besar panggilan kerangka kerja) dan kembalikan -EPROBE_DEFER jika satu atau lebih pemasok belum siap.

  • Jika Anda menambahkan perangkat anak dalam fungsi pemeriksaan perangkat induk, jangan berasumsi bahwa perangkat anak langsung diperiksa.

  • Jika pemeriksaan gagal, lakukan penanganan kesalahan yang tepat dan bersihkan (lihat Menggunakan varian API devm_*() ).

Jangan gunakan MODULE_SOFTDEP untuk memesan probe perangkat

Fungsi MODULE_SOFTDEP() bukanlah solusi yang andal untuk menjamin urutan pemeriksaan perangkat dan tidak boleh digunakan karena alasan berikut.

  • Pemeriksaan yang ditangguhkan. 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 instans dari jenis perangkat dan masing-masing perangkat tersebut memiliki persyaratan urutan pemeriksaan yang berbeda, Anda tidak dapat mematuhi persyaratan tersebut menggunakan urutan pemuatan modul.

  • Penyelidikan asinkron. Modul driver yang melakukan pemeriksaan asinkron tidak langsung memeriksa perangkat saat modul dimuat. Sebagai gantinya, utas paralel menangani pemeriksaan perangkat, yang dapat menyebabkan ketidakcocokan antara urutan pemuatan modul dan urutan pemeriksaan perangkat. Misalnya, ketika modul driver master I2C melakukan pemeriksaan asinkron dan modul driver sentuh bergantung pada PMIC yang ada di bus I2C, bahkan jika driver sentuh dan driver PMIC memuat 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 meningkatkan perubahan yang memungkinkan kernel menangani masalah pemesanan tanpa menggunakan MODULE_SOFTDEP() . Secara khusus, Anda dapat menggunakan fw_devlink untuk memastikan pemesanan pemeriksaan dan (setelah semua konsumen perangkat melakukan pemeriksaan) menggunakan panggilan balik sync_state() untuk melakukan tugas yang diperlukan.

Gunakan #if IS_ENABLED() alih-alih #ifdef untuk konfigurasi

Gunakan #if IS_ENABLED(CONFIG_XXX) alih-alih #ifdef CONFIG_XXX untuk memastikan bahwa kode di dalam blok #if terus dikompilasi jika konfigurasi berubah menjadi konfigurasi tristate di masa mendatang. Perbedaannya adalah sebagai berikut:

  • #if IS_ENABLED(CONFIG_XXX) dievaluasi menjadi true ketika CONFIG_XXX diatur ke modul ( =m ) atau bawaan ( =y ).

  • #ifdef CONFIG_XXX mengevaluasi ke true ketika CONFIG_XXX diatur ke built-in ( =y ) , tetapi tidak ketika CONFIG_XXX diatur ke modul ( =m ). Gunakan ini hanya jika Anda yakin ingin melakukan hal yang sama ketika konfigurasi diatur ke modul atau dinonaktifkan.

Gunakan makro yang benar untuk kompilasi bersyarat

Jika CONFIG_XXX diatur ke modul ( =m ), sistem pembangunan secara otomatis mendefinisikan CONFIG_XXX_MODULE . Jika driver Anda dikendalikan oleh CONFIG_XXX dan Anda ingin memeriksa apakah driver Anda sedang 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 diubah namanya menjadi CONFIG_XYZ . Untuk file sumber non-header apa pun yang dikompilasi ke dalam modul, sistem build secara otomatis mendefinisikan 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 ke dalam biner melainkan 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. 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 built-in atau dinonaktifkan). Ini bisa berguna saat Anda ingin mendefinisikan makro yang perlu diperluas satu cara untuk kode bawaan dan memperluas dengan cara berbeda untuk modul.

    • Untuk file header yang perlu dikompilasi dalam potongan kode ketika CONFIG_XXX tertentu diatur ke modul (terlepas dari apakah file sumber termasuk modul), file header harus menggunakan #ifdef CONFIG_XXX_MODULE .