Pedoman modul vendor

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

Modul dapat berupa perpustakaan atau driver .

  • Modul perpustakaan adalah perpustakaan yang menyediakan API untuk digunakan modul lain. Modul seperti itu biasanya tidak spesifik pada perangkat keras. Contoh modul perpustakaan mencakup modul enkripsi AES, kerangka kerja remoteproc yang dikompilasi sebagai modul, dan modul logbuffer. Kode modul di module_init() dijalankan untuk menyiapkan struktur data, namun tidak ada kode lain yang berjalan kecuali dipicu oleh modul eksternal.

  • Modul driver adalah driver yang menyelidiki atau mengikat jenis perangkat tertentu. Modul semacam itu khusus untuk perangkat keras. Contoh modul driver termasuk perangkat keras UART, PCIe, dan 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 ke kerangka inti driver.

    • Jika perangkat ada dan driver berhasil menyelidiki atau mengikat perangkat tersebut, kode modul lain mungkin berjalan.

Gunakan modul init/exit dengan benar

Modul driver harus mendaftarkan driver di module_init() dan membatalkan pendaftaran driver di module_exit() . Cara sederhana untuk menerapkan pembatasan ini adalah dengan menggunakan makro wrapper, yang menghindari penggunaan langsung makro 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 tersebut ke dalam satu driver. Misalnya, Anda dapat membedakannya menggunakan string compatible atau data aux perangkat alih-alih mendaftarkan driver terpisah. Alternatifnya, Anda dapat membagi modul driver menjadi dua modul.

Pengecualian fungsi init dan exit

Modul perpustakaan tidak mendaftarkan driver dan dikecualikan dari pembatasan module_init() dan module_exit() karena modul tersebut mungkin memerlukan fungsi ini untuk menyiapkan struktur data, antrian kerja, atau thread 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, misalnya 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 dalam tipe data yang dideklarasikan ke depan. Beberapa struct, union, dan tipe data lain yang didefinisikan dalam file header ( header-Ah ) dapat dideklarasikan ke dalam file header berbeda ( header-Bh ) yang biasanya menggunakan pointer ke tipe data tersebut. Pola kode ini berarti bahwa kernel dengan sengaja mencoba menjaga struktur data tetap pribadi bagi pengguna header-Bh .

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

Misalnya, struct fwnode_handle didefinisikan dalam include/linux/fwnode.h , namun dinyatakan 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 . Desain apa pun di mana Anda harus 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, kerusakan, dan rusaknya kompatibilitas dengan rilis kernel 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 diteruskan, secara tidak langsung (melalui pointer di 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 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 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 vendor hooks. Jika Anda tidak memiliki API atau vendor hook untuk mengubah bagian struktur data kernel inti, hal itu mungkin disengaja dan Anda tidak boleh mengubah 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 mengubah kolom di dalam struct device.links , gunakan API tautan perangkat seperti device_link_add() atau device_link_del() .

Jangan parsing node pohon perangkat dengan properti yang kompatibel

Jika node pohon perangkat (DT) memiliki properti compatible , struct device dialokasikan untuknya secara otomatis atau ketika of_platform_populate() dipanggil pada node DT induk (biasanya oleh driver perangkat dari perangkat induk). Harapan 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 dan diperiksa oleh driver. Jika node DT memiliki properti compatible tetapi struct device yang dialokasikan tidak diperiksa, fw_devlink dapat memblokir perangkat konsumennya agar tidak melakukan penyelidikan 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 dan kemudian 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 lain, hubungi Tim Kernel Android di kernel-team@android.com dan bersiaplah untuk membenarkan kasus penggunaan Anda.

Gunakan phandle DT untuk mencari pemasok

Rujuk ke pemasok menggunakan phandle (referensi/penunjuk ke node DT) di DT bila memungkinkan. Menggunakan pengikatan dan phandle DT standar untuk merujuk ke pemasok memungkinkan fw_devlink (sebelumnya of_devlink ) untuk secara otomatis menentukan ketergantungan antar perangkat dengan menguraikan DT saat runtime. Kernel kemudian dapat secara otomatis memeriksa perangkat dalam urutan yang benar, menghilangkan kebutuhan akan pemesanan 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 dengan 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 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 mengacu pada node pohon perangkat pemasok menggunakan phandle . Konsumen juga dapat memberi nama sumber daya berdasarkan kegunaannya dan bukan berdasarkan 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 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 kedua dunia

Beberapa driver yang di-porting dari kernel lama menyertakan perilaku lama di DT yang mengambil bagian terburuk dari skema lama dan memaksakannya pada skema baru yang dimaksudkan untuk mempermudah segalanya. Dalam driver tersebut, driver konsumen membaca string yang akan digunakan untuk pencarian menggunakan properti DT khusus perangkat, pemasok menggunakan properti khusus pemasok lainnya 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 sedang mencoba melakukan probe tetapi tidak bisa saat ini, dan kernel harus mencoba kembali probe tersebut Nanti. Untuk memastikan bahwa fungsi .probe() perangkat Anda gagal seperti yang diharapkan dalam kasus seperti ini, jangan ganti atau petakan ulang nilai kesalahan. Mengganti atau memetakan ulang nilai kesalahan mungkin menyebabkan -EPROBE_DEFER dihilangkan dan mengakibatkan perangkat Anda tidak pernah diperiksa.

Gunakan varian API devm_*()

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

Menangani pelepasan ikatan driver perangkat

Berhati-hatilah saat melepas ikatan driver perangkat dan jangan biarkan pelepasan ikatan tidak terdefinisi karena tidak terdefinisi bukan berarti tidak diizinkan. Anda harus menerapkan pelepasan ikatan driver perangkat sepenuhnya atau menonaktifkan pelepasan ikatan driver perangkat secara eksplisit.

Menerapkan pelepasan driver perangkat

Saat memilih untuk sepenuhnya menerapkan pelepasan driver perangkat, lepaskan ikatan 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 melepas ikatan perangkat dengan memanggil fungsi remove() driver. Jika tidak ada fungsi remove() , kernel masih dapat melepas ikatan perangkat; inti driver mengasumsikan bahwa driver tidak memerlukan pekerjaan pembersihan saat melepaskan ikatan dari perangkat. Driver yang tidak terikat dari perangkat tidak perlu melakukan pekerjaan pembersihan apa pun secara eksplisit jika kedua hal berikut ini benar:

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

  • Perangkat keras tidak memerlukan urutan pematian atau penghentian.

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

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

  • Terapkan operasi driver remove() di struct yang sama dengan fungsi probe() , lalu lakukan langkah pembersihan menggunakan fungsi remove() .

Menonaktifkan pelepasan ikatan driver perangkat secara eksplisit (tidak disarankan)

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

  • Untuk melarang pelepasan ikatan, setel tanda suppress_bind_attrs ke true di struct device_driver ; pengaturan ini mencegah file bind dan unbind muncul di direktori sysfs driver. File unbind inilah 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

Pengemudi tidak boleh memuat firmware dari dalam fungsi .probe() karena mereka mungkin tidak memiliki akses ke firmware jika driver memeriksa sebelum sistem file berbasis flash atau penyimpanan permanen dipasang. Dalam kasus seperti ini, API request_firmware*() mungkin diblokir untuk waktu yang lama dan kemudian gagal, yang dapat memperlambat proses booting jika tidak diperlukan. Sebaliknya, tunda pemuatan firmware hingga klien mulai menggunakan perangkat. Misalnya, driver tampilan dapat memuat firmware saat perangkat tampilan dibuka.

Menggunakan .probe() untuk memuat firmware mungkin baik-baik saja dalam beberapa kasus, misalnya pada driver jam yang memerlukan firmware agar berfungsi tetapi perangkat tidak terkena ruang pengguna. Kasus penggunaan lain yang sesuai juga dimungkinkan.

Menerapkan pemeriksaan asinkron

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

Untuk menandai driver sebagai mendukung dan memilih pemeriksaan asinkron, setel bidang probe_type di anggota struct device_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 pemeriksaan asinkron tidak memerlukan kode khusus. Namun, ingatlah hal berikut saat menambahkan dukungan pemeriksaan asinkron.

  • Jangan membuat asumsi tentang dependensi yang telah diselidiki 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 tersebut langsung diperiksa.

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

Jangan gunakan MODULE_SOFTDEP untuk memesan probe perangkat

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

  • Pemeriksaan yang ditangguhkan. Saat modul dimuat, pemeriksaan perangkat mungkin ditunda 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 jenis perangkat dan perangkat tersebut masing-masing memiliki persyaratan urutan pemeriksaan yang berbeda, Anda tidak dapat memenuhi persyaratan tersebut menggunakan pengurutan beban modul.

  • Pemeriksaan asinkron. Modul driver yang melakukan pemeriksaan asinkron tidak langsung menyelidiki perangkat saat modul dimuat. Sebaliknya, thread paralel menangani pemeriksaan perangkat, yang dapat menyebabkan ketidakcocokan antara urutan pemuatan modul dan urutan pemeriksaan perangkat. Misalnya, ketika modul driver utama I2C melakukan pemeriksaan 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 sebelumnya. pemeriksaan driver PMIC.

Jika Anda memiliki modul driver yang menggunakan fungsi MODULE_SOFTDEP() , perbaiki modul tersebut agar tidak menggunakan fungsi tersebut. Untuk membantu Anda, tim Android telah melakukan perubahan upstream 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 callback sync_state() untuk melakukan tugas apa pun 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) bernilai true ketika CONFIG_XXX diatur ke modul ( =m ) atau bawaan ( =y ).

  • #ifdef CONFIG_XXX bernilai true ketika CONFIG_XXX disetel ke bawaan ( =y ) , namun tidak jika CONFIG_XXX disetel ke modul ( =m ). Gunakan ini hanya jika Anda yakin ingin melakukan hal yang sama ketika konfigurasi disetel ke modul atau dinonaktifkan.

Gunakan makro yang benar untuk kompilasi bersyarat

Jika CONFIG_XXX diatur ke modul ( =m ), sistem build secara otomatis mendefinisikan CONFIG_XXX_MODULE . Jika driver Anda dikontrol oleh CONFIG_XXX dan Anda ingin memeriksa apakah driver Anda dikompilasi sebagai modul, gunakan panduan berikut:

  • Di file C (atau file sumber apa pun yang bukan file header) untuk driver Anda, jangan gunakan #ifdef CONFIG_XXX_MODULE karena terlalu 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 apa pun) 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 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. Artinya, file header yang sama dalam build yang sama dapat memiliki bagian kode berbeda yang dikompilasi untuk file sumber berbeda (modul versus bawaan atau dinonaktifkan). Ini bisa berguna ketika Anda ingin mendefinisikan makro yang perlu diperluas dalam satu cara untuk kode bawaan dan diperluas dengan cara lain untuk sebuah modul.

    • Untuk file header yang perlu dikompilasi menjadi sepotong kode ketika CONFIG_XXX tertentu disetel ke modul (terlepas dari apakah file sumber yang menyertakannya adalah modul), file header harus menggunakan #ifdef CONFIG_XXX_MODULE .