概要
NVMeドライバの振る舞いを理解するためにLinuxのNVMeドライバのソースコードを読んでいきます。対象はlinux-4.19.4 kernelに含まれるNVMeドライバとなります。本記事では初期化処理を扱います。
初期化処理
対象ソースコードはlinux-4.19.4/drivers/nvme/host以下に存在します。今回はPCIeのNVMeデバイスを対象とします。初期化処理に主に関わるソースはcore.c
とpci.c
となります。core.c
は共通処理をまとめたもの、pci.c
はPCIeのNVMeデバイス専用の処理をまとめたソースとなります。モジュールのエントリポイントはmodule_init
で指定されcore.c
, pci.c
それぞれで初期化処理を行っています。core.c
ではnvme_init_core
関数内でalloc_workqueue
を用いてnvme_wq
, nvme_reset_wq
, nvme_delete_wq
の3つのワークキューを作成し、スキャン処理などの指示が来るのを待ち受けます。pci.c
ではpci_register_driver
を用いてPCIデバイス用のドライバとして登録をしています。
pci.cの初期化処理
pci.c
ではnvme_init
が初期化処理になります。pci_register_driver
でpciドライバを登録します。大事なのは.id_table
と.probe
エントリです。カーネルはPCIデバイスがロードされたときに.id_table
で指定されたテーブルを探索します。追加されたPCIデバイスがマッチしたら.probe
で指定した関数を実行します。NVMeデバイスが追加されたときにnvme_id_table
にマッチしたらnvme_probe
を実行します。
static struct pci_driver nvme_driver = {
.name = "nvme",
.id_table = nvme_id_table,
.probe = nvme_probe,
.remove = nvme_remove,
.shutdown = nvme_shutdown,
.driver = {
.pm = &nvme_dev_pm_ops,
},
.sriov_configure = pci_sriov_configure_simple,
.err_handler = &nvme_err_handler,
};
static int __init nvme_init(void)
{
return pci_register_driver(&nvme_driver);
}
nvme_probe
ではメモリの割り当てやmutexの初期化などを行います。
初期化処理のメインはnvme_init_ctrl
となります。この関数はcore.c
側に定義されています。
result = nvme_init_ctrl(&dev->ctrl, &pdev->dev, &nvme_pci_ctrl_ops,
quirks);
nvme_init_ctrl
ではワークキューへのハンドラー関数の登録やデバイスの登録などを行っています。
INIT_WORK(&ctrl->scan_work, nvme_scan_work);
INIT_WORK(&ctrl->async_event_work, nvme_async_event_work);
INIT_WORK(&ctrl->fw_act_work, nvme_fw_act_work);
INIT_WORK(&ctrl->delete_work, nvme_delete_ctrl_work);
INIT_DELAYED_WORK(&ctrl->ka_work, nvme_keep_alive_work);
ida_simple_getでnvme0やnvme1の通し番号にあたる数字を取得し、cdev_device_add
で/dev/nvme0
といったキャラクタデバイスを登録します。
ret = ida_simple_get(&nvme_instance_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto out;
ctrl->instance = ret;
ret = dev_set_name(ctrl->device, "nvme%d", ctrl->instance);
...
cdev_init(&ctrl->cdev, &nvme_dev_fops);
ctrl->cdev.owner = ops->module;
ret = cdev_device_add(&ctrl->cdev, ctrl->device);
今度はpci.c
側に戻りnvme_async_probe
を実行します。nvme_async_probe
からreset_ctrl_sync
経由でnvme_reset_ctrl
を呼ぶことでnvme controllerの初期化を行います。その後flush_work(&dev->ctrl.scan_work)
でscan_workの処理を実行させることで接続されているデバイスのスキャンを実施し、namespaceの設定などを行います。