本文主要參考這里 1’ 2 的解析和 linux
源碼 3。
此處推薦一個(gè)可以便捷查看 linux
源碼的網(wǎng)站 bootlin
4。
更新:2022 / 02 / 19
NVMe 的前世今生
NVMe
離不開(kāi) PCIe
,NVMe
SSD
是 PCIe
的 endpoint
。PCIe
是 x86
平臺(tái)上一種流行的外設(shè)總線,由于其 Plug and Play
的特性,目前很多外設(shè)都通過(guò) PCI Bus
與 Host
通信,甚至不少CPU
的集成外設(shè)都通過(guò) PCI Bus
連接,如 APIC
等。
NVMe SSD
在 PCIe
接口上使用新的標(biāo)準(zhǔn)協(xié)議 NVMe
,由大廠 Intel
推出并交由 nvmexpress
組織推廣,現(xiàn)在被全球大部分存儲(chǔ)企業(yè)采納。
從系統(tǒng)角度看 NVMe 驅(qū)動(dòng)
NVMe Command
NVMe
Host
( Server
)和 NVMe
Controller
( SSD
)通過(guò) NVMe
Command
進(jìn)行信息交互。NVMe
Spec
中定義了 NVMe
Command
的格式,占用 64
字節(jié)。
NVMe
Command
分為 Admin
Command
和 IO
Command
兩大類,前者主要是用于配置,后者用于數(shù)據(jù)傳輸。
NVMe
Command
是 Host
與 SSD
Controller
交流的基本單元,應(yīng)用的 I/O
請(qǐng)求也要轉(zhuǎn)化成NVMe
Command
。
或許可以將 NVMe Command
理解為英語(yǔ),host和SSD controller分別為韓國(guó)人和日本人,這兩個(gè) 對(duì)象
需要通過(guò)英語(yǔ)統(tǒng)一語(yǔ)法來(lái)進(jìn)行彼此的溝通和交流,Admin Command
是語(yǔ)法 負(fù)責(zé)語(yǔ)句的主謂賓結(jié)構(gòu),IO Command
是單詞 構(gòu)成語(yǔ)句的具體使用詞語(yǔ)。一句話需要語(yǔ)法組織語(yǔ)句結(jié)構(gòu)和單詞作為語(yǔ)句的填充。
PCI 總線
- 在操作系統(tǒng)啟動(dòng)時(shí),
BIOS
會(huì)枚舉整個(gè)PCI
的總線,之后將掃描到的設(shè)備通過(guò)ACPI
tables
傳給操作系統(tǒng)。 - 當(dāng)操作系統(tǒng)加載時(shí),
PCI
Bus
驅(qū)動(dòng)則會(huì)根據(jù)此信息讀取各個(gè)PCI
設(shè)備的PCI Header Config
空間,從class code
寄存器獲得一個(gè)特征值。
class code
是PCI
bus
用來(lái)選擇哪個(gè)驅(qū)動(dòng)加載設(shè)備的唯一根據(jù)。
NVMe
Spec
定義的class code
是010802h
。NVMe
SSD
內(nèi)部的Controller PCIe Header
中class code
都會(huì)設(shè)置成010802h
。
所以,需要在驅(qū)動(dòng)中指定 class code
為 010802h
,將 010802h
放入 pci_driver nvme_driver
的id_table
。
之后當(dāng) nvme_driver
注冊(cè)到 PCI
Bus
后,PCI
Bus
就知道這個(gè)驅(qū)動(dòng)是給 class code=010802h
的設(shè)備使用的。
nvme_driver
中有一個(gè) probe
函數(shù),nvme_probe()
,這個(gè)函數(shù)才是真正加載設(shè)備的處理函數(shù)。
從架構(gòu)角度看 NVMe 驅(qū)動(dòng)
學(xué)習(xí) Linux
NVMe
Driver
之前,先看一下 Driver
在 Linux
架構(gòu)中的位置,如下圖所示:
NVMe driver
在 Block Layer
之下,負(fù)責(zé)與 NVMe
設(shè)備交互。
為了緊跟時(shí)代的大趨勢(shì),現(xiàn)在的 NVMe
driver
已經(jīng)很強(qiáng)大了,也可以支持 NVMe over Fabric
相關(guān)設(shè)備,如下圖所示:
不過(guò),本文還是以 NVMe over PCIe
為主。
NVMe 驅(qū)動(dòng)的文件構(gòu)成
最新的代碼位于 linux/drivers/nvme/
5。
其文件目錄構(gòu)成如下所示:
.
├── Kconfig
├── Makefile
├── common
│ ├── Kconfig
│ ├── Makefile
│ └── auth.c
├── host
│ ├── Kconfig
│ ├── Makefile
│ ├── apple.c
│ ├── auth.c
│ ├── constants.c
│ ├── core.c
│ ├── fabrics.c
│ ├── fabrics.h
│ ├── fault_inject.c
│ ├── fc.c
│ ├── fc.h
│ ├── hwmon.c
│ ├── ioctl.c
│ ├── multipath.c
│ ├── nvme.h
│ ├── pci.c
│ ├── rdma.c
│ ├── tcp.c
│ ├── trace.c
│ ├── trace.h
│ └── zns.c
└── target
├── Kconfig
├── Makefile
├── admin-cmd.c
├── auth.c
├── configfs.c
├── core.c
├── discovery.c
├── fabrics-cmd-auth.c
├── fabrics-cmd.c
├── fc.c
├── fcloop.c
├── io-cmd-bdev.c
├── io-cmd-file.c
├── loop.c
├── nvmet.h
├── passthru.c
├── rdma.c
├── tcp.c
├── trace.c
├── trace.h
└── zns.c
4 directories, 47 files
在分析一個(gè) driver
時(shí),最好先看這個(gè) driver
相關(guān)的 kconfig
及 Makefile
文件,了解其文件架構(gòu),再閱讀相關(guān)的 source code
。
Kconfig
文件的作用是:
- 控制
make menuconfig
時(shí),出現(xiàn)的配置選項(xiàng); - 根據(jù)用戶配置界面的選擇,將配置結(jié)果保存在
.config
配置文件(該文件將提供給Makefile
使用,用以決定要編譯的內(nèi)核組件以及如何編譯)
先看一下 linux/drivers/nvme/host/Kconfig
6 的內(nèi)容 ( NVMeOF
相關(guān)內(nèi)容已省略,后續(xù)不再注明),如下所示:
# SPDX-License-Identifier: GPL-2.0-only
config NVME_CORE
tristate
select BLK_DEV_INTEGRITY_T10 if BLK_DEV_INTEGRITY
......
接著,再看一下 linux/drivers/nvme/host/Makefile
7 的內(nèi)容,如下所示:
# SPDX-License-Identifier: GPL-2.0
ccflags-y += -I$(src)
obj-$(CONFIG_NVME_CORE) += nvme-core.o
obj-$(CONFIG_BLK_DEV_NVME) += nvme.o
obj-$(CONFIG_NVME_FABRICS) += nvme-fabrics.o
obj-$(CONFIG_NVME_RDMA) += nvme-rdma.o
obj-$(CONFIG_NVME_FC) += nvme-fc.o
obj-$(CONFIG_NVME_TCP) += nvme-tcp.o
obj-$(CONFIG_NVME_APPLE) += nvme-apple.o
......
從 Kconfig
和 Makefile
來(lái)看,了解 NVMe over PCIe
相關(guān)的知識(shí)點(diǎn),我們主要關(guān)注 core.c
,pci.c
就好。
NVMe Driver 工作原理
core.c
通過(guò)該文件了解 NVMe
驅(qū)動(dòng)可以如何操作 NVMe
設(shè)備,文件中先后調(diào)用了哪些函數(shù)及其各自的作用可以參考下圖:
nvme_core_init
從 linux/drivers/nvme/host/core.c
8 找到程序入口 module_init(nvme_core_init);
,如下所示:
static int __init nvme_core_init(void)
{
int result = -ENOMEM;
......
// 1. 注冊(cè)字符設(shè)備 "nvme"
result = alloc_chrdev_region(&nvme_ctrl_base_chr_devt, 0,
NVME_MINORS, "nvme");
if (result < 0)
goto destroy_delete_wq;
// 2. 新建一個(gè)nvme class,擁有者(Owner)是為THIS_MODULE
nvme_class = class_create(THIS_MODULE, "nvme");
// 如果有Error發(fā)生,刪除字符設(shè)備nvme
if (IS_ERR(nvme_class)) {
result = PTR_ERR(nvme_class);
goto unregister_chrdev;
}
nvme_class->dev_uevent = nvme_class_uevent;
// 新建一個(gè)nvme_subsys_class
nvme_subsys_class = class_create(THIS_MODULE, "nvme-subsystem");
if (IS_ERR(nvme_subsys_class)) {
result = PTR_ERR(nvme_subsys_class);
goto destroy_class;
}
// 注冊(cè)字符設(shè)備 `nvme_generic`
result = alloc_chrdev_region(&nvme_ns_chr_devt, 0, NVME_MINORS,
"nvme-generic");
if (result < 0)
goto destroy_subsys_class;
// 新建nvme_ns_chr_class
nvme_ns_chr_class = class_create(THIS_MODULE, "nvme-generic");
if (IS_ERR(nvme_ns_chr_class)) {
result = PTR_ERR(nvme_ns_chr_class);
goto unregister_generic_ns;
}
//
result = nvme_init_auth();
if (result)
goto destroy_ns_chr;
return 0;
從上面來(lái)看,nvme_core_init
主要做了兩件事:
alloc_chrdev_region
- 調(diào)用
alloc_chrdev_region
9 函數(shù),如下所示,注冊(cè)名為nvme
和nvme_generic
的字符設(shè)備。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
class_create
- 調(diào)用
class_create
函數(shù) 10,如下所示,動(dòng)態(tài)創(chuàng)建設(shè)備的邏輯類,并完成部分字段的初始化,然后將其添加到內(nèi)核中。創(chuàng)建的邏輯類位于/sys/class/
。
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
#endif /* _DEVICE_CLASS_H_ */
在注冊(cè)字符設(shè)備時(shí),涉及到了設(shè)備號(hào)的知識(shí)點(diǎn):
一個(gè)字符設(shè)備或者塊設(shè)備都有一個(gè)主設(shè)備號(hào)(
Major
)和次設(shè)備號(hào)(Minor
)。主設(shè)備號(hào)用來(lái)表示一個(gè)特定的驅(qū)動(dòng)程序,次設(shè)備號(hào)用來(lái)表示使用該驅(qū)動(dòng)程序的各個(gè)設(shè)備。比如,我們?cè)?Linux
系統(tǒng)上掛了兩塊NVMe
SSD
,那么主設(shè)備號(hào)就可以自動(dòng)分配一個(gè)數(shù)字 (比如8
),次設(shè)備號(hào)分別為1
和2
。
例如,在32
位機(jī)子中,設(shè)備號(hào)共32
位,高12
位表示主設(shè)備號(hào),低20
位表示次設(shè)備號(hào)。
nvme_dev_fops
通過(guò) nvme_core_init
進(jìn)行字符設(shè)備的注冊(cè)后,我們就可以通過(guò) nvme_dev_fops
中的 open
,ioctrl
,release
接口對(duì)其進(jìn)行操作了。
nvme
字符設(shè)備的文件操作結(jié)構(gòu)體 nvme_dev_fops
8 定義如下:
static const struct file_operations nvme_dev_fops = {
.owner = THIS_MODULE,
// open()用來(lái)打開(kāi)一個(gè)設(shè)備,在該函數(shù)中可以對(duì)設(shè)備進(jìn)行初始化。
.open = nvme_dev_open,
// release()用來(lái)釋放open()函數(shù)中申請(qǐng)的資源。
.release = nvme_dev_release,
// nvme_dev_ioctl()提供一種執(zhí)行設(shè)備特定命令的方法。
// 這里的ioctl有兩個(gè)unlocked_ioctl和compat_ioctl。
// 如果這兩個(gè)同時(shí)存在的話,優(yōu)先調(diào)用unlocked_ioctl。
// 對(duì)于compat_ioctl只有打來(lái)了CONFIG_COMPAT 才會(huì)調(diào)用compat_ioctl。*/
.unlocked_ioctl = nvme_dev_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.uring_cmd = nvme_dev_uring_cmd,
};
nvme_dev_open
nvme_dev_open
8 的定義如下:
static int nvme_dev_open(struct inode *inode, struct file *file)
{
// 從incode中獲取次設(shè)備號(hào)
struct nvme_ctrl *ctrl =
container_of(inode->i_cdev, struct nvme_ctrl, cdev);
switch (ctrl->state) {
case NVME_CTRL_LIVE:
break;
default:
// -EWOULDBLOCK: Operation would block
return -EWOULDBLOCK;
}
nvme_get_ctrl(ctrl);
if (!try_module_get(ctrl->ops->module)) {
nvme_put_ctrl(ctrl);
// -EINVAL: Invalid argument
return -EINVAL;
}
file->private_data = ctrl;
return 0;
}
找到與 inode
次設(shè)備號(hào)對(duì)應(yīng)的 nvme
設(shè)備,接著判斷 nvme_ctrl_live
和 module
,最后找到的 nvme
設(shè)備放到 file->private_data
區(qū)域。
nvme_dev_release
nvme_dev_release
的定義如下:
static int nvme_dev_release(struct inode *inode, struct file *file)
{
struct nvme_ctrl *ctrl =
container_of(inode->i_cdev, struct nvme_ctrl, cdev);
module_put(ctrl->ops->module);
nvme_put_ctrl(ctrl);
return 0;
}
用來(lái)釋放 nvme_dev_open
中申請(qǐng)的資源。
nvme_dev_ioctl
nvme_dev_ioctl
11 的定義如下:
long nvme_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct nvme_ctrl *ctrl = file->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case NVME_IOCTL_ADMIN_CMD:
return nvme_user_cmd(ctrl, NULL, argp, 0, file->f_mode);
case NVME_IOCTL_ADMIN64_CMD:
return nvme_user_cmd64(ctrl, NULL, argp, 0, file->f_mode);
case NVME_IOCTL_IO_CMD:
return nvme_dev_user_cmd(ctrl, argp, file->f_mode);
case NVME_IOCTL_RESET:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
dev_warn(ctrl->device, "resetting controller\n");
return nvme_reset_ctrl_sync(ctrl);
case NVME_IOCTL_SUBSYS_RESET:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
return nvme_reset_subsystem(ctrl);
case NVME_IOCTL_RESCAN:
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
nvme_queue_scan(ctrl);
return 0;
default:
// 錯(cuò)誤的ioctrl命令
return -ENOTTY;
}
}
從上面可以發(fā)現(xiàn)在 nvme_dev_ioctl
中總共實(shí)現(xiàn)了 5
種 Command
:
命令 | 解釋 |
---|---|
NVME_IOCTL_ADMIN_CMD |
NVMe Spec 定義的 Admin 命令。 |
NVME_IOCTL_IO_CMD |
NVMe Spec 定義的 IO 命令。不支持 NVMe 設(shè)備有多個(gè)namespace 的情況。 |
NVME_IOCTL_RESET |
|
NVME_IOCTL_SUBSYS_RESET |
通過(guò)向相關(guān) bar 寄存器寫入指定值 0x4E564D65 來(lái)完成 |
NVME_IOCTL_RESCAN |
NVME_IO_RESET
先來(lái)看看 nvme_reset_ctrl_sync
的定義,如下:
int nvme_reset_ctrl_sync(struct nvme_ctrl *ctrl)
{
int ret;
ret = nvme_reset_ctrl(ctrl);
if (!ret) {
flush_work(&ctrl->reset_work);
if (ctrl->state != NVME_CTRL_LIVE)
// ENETRESET: Network dropped connection because of reset */
ret = -ENETRESET;
}
return ret;
}
再來(lái)看看 nvme_reset_ctrl
,如下:
int nvme_reset_ctrl(struct nvme_ctrl *ctrl)
{
if (!nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING))
return -EBUSY;
if (!queue_work(nvme_reset_wq, &ctrl->reset_work))
// EBUSH: Device or resource busy
return -EBUSY;
return 0;
}
EXPORT_SYMBOL_GPL(nvme_reset_ctrl);
從上面的定義中可以了解到,如果通過(guò)調(diào)用 nvme_reset_ctrl
獲取當(dāng)前設(shè)備和資源的狀態(tài):
- 如果處于
busy
狀態(tài),則不進(jìn)行相關(guān)的reset
的動(dòng)作; - 如果處于空閑狀態(tài),則調(diào)用
flush_work(&ctrl->reset_work)
來(lái)reset nvme controller
。
再來(lái)看一下 flush_work
的定義,如下:
static bool __flush_work(struct work_struct *work, bool from_cancel)
{
struct wq_barrier barr;
if (WARN_ON(!wq_online))
return false;
if (WARN_ON(!work->func))
return false;
lock_map_acquire(&work->lockdep_map);
lock_map_release(&work->lockdep_map);
if (start_flush_work(work, &barr, from_cancel)) {
wait_for_completion(&barr.done);
destroy_work_on_stack(&barr.work);
return true;
} else {
return false;
}
}
/**
* flush_work - wait for a work to finish executing the last queueing instance
* @work: the work to flush
*
* Wait until @work has finished execution. @work is guaranteed to be idle
* on return if it hasn't been requeued since flush started.
*
* Return:
* %true if flush_work() waited for the work to finish execution,
* %false if it was already idle.
*/
bool flush_work(struct work_struct *work)
{
return __flush_work(work, false);
}
EXPORT_SYMBOL_GPL(flush_work);
NVME_IOCTL_SUBSYS_RESET
先來(lái)看看 nvme_reset_subsystem
的定義,如下:
static inline int nvme_reset_subsystem(struct nvme_ctrl *ctrl)
{
int ret;
if (!ctrl->subsystem)
return -ENOTTY;
if (!nvme_wait_reset(ctrl))
// EBUSH: Device or resource busy
return -EBUSY;
ret = ctrl->ops->reg_write32(ctrl, NVME_REG_NSSR, 0x4E564D65);
if (ret)
return ret;
return nvme_try_sched_reset(ctrl);
}
先來(lái)看一下 nvme_wait_reset
的定義,如下:
/*
* Waits for the controller state to be resetting, or returns false if it is
* not possible to ever transition to that state.
*/
bool nvme_wait_reset(struct nvme_ctrl *ctrl)
{
wait_event(ctrl->state_wq,
nvme_change_ctrl_state(ctrl, NVME_CTRL_RESETTING) ||
nvme_state_terminal(ctrl));
return ctrl->state == NVME_CTRL_RESETTING;
}
EXPORT_SYMBOL_GPL(nvme_wait_reset);
通過(guò) nvme_wait_reset
獲取當(dāng)前設(shè)備或資源的狀態(tài):
- 如果
controller state
是resetting
,說(shuō)明當(dāng)前設(shè)備或資源處于繁忙狀態(tài),無(wú)法完成nvme_reset_subsystem
; - 如果
controller state
是resetting
,通過(guò)ctrl->ops->reg_write32
對(duì)NVME_REG_NSSR
寫入0x4E564D65
來(lái)完成nvme_reset_subsystem
。
再來(lái)看一下 reg_write32
的定義:
struct nvme_ctrl_ops {
const char *name;
struct module *module;
unsigned int flags;
#define NVME_F_FABRICS (1 << 0)
#define NVME_F_METADATA_SUPPORTED (1 << 1)
#define NVME_F_BLOCKING (1 << 2)
const struct attribute_group **dev_attr_groups;
int (*reg_read32)(struct nvme_ctrl *ctrl, u32 off, u32 *val);
int (*reg_write32)(struct nvme_ctrl *ctrl, u32 off, u32 val);
int (*reg_read64)(struct nvme_ctrl *ctrl, u32 off, u64 *val);
void (*free_ctrl)(struct nvme_ctrl *ctrl);
void (*submit_async_event)(struct nvme_ctrl *ctrl);
void (*delete_ctrl)(struct nvme_ctrl *ctrl);
void (*stop_ctrl)(struct nvme_ctrl *ctrl);
int (*get_address)(struct nvme_ctrl *ctrl, char *buf, int size);
void (*print_device_info)(struct nvme_ctrl *ctrl);
bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl);
};
從上面,不難看出,reg_write32
是 nvme_ctrl_ops
的其中一種操作。
再看一下 nvme_ctrl_ops
的定義,如下:
static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = {
.name = "pcie",
.module = THIS_MODULE,
.flags = NVME_F_METADATA_SUPPORTED,
.dev_attr_groups = nvme_pci_dev_attr_groups,
.reg_read32 = nvme_pci_reg_read32,
.reg_write32 = nvme_pci_reg_write32,
.reg_read64 = nvme_pci_reg_read64,
.free_ctrl = nvme_pci_free_ctrl,
.submit_async_event = nvme_pci_submit_async_event,
.get_address = nvme_pci_get_address,
.print_device_info = nvme_pci_print_device_info,
.supports_pci_p2pdma = nvme_pci_supports_pci_p2pdma,
};
從上面看,reg_write32
相當(dāng)于 nvme_pci_reg_write32
。
那么,再看 nvme_pci_reg_write32
的定義,即:
static int nvme_pci_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val)
{
writel(val, to_nvme_dev(ctrl)->bar + off);
return 0;
}
static inline struct nvme_dev *to_nvme_dev(struct nvme_ctrl *ctrl)
{
return container_of(ctrl, struct nvme_dev, ctrl);
}
從上面看,reg_write32
先通過(guò)調(diào)用 to_nvme_dev
獲取 nvme_ctrl
的地址并賦值于nvme_dev
,而后 writel
向指定 bar
寄存器中寫入指定的 val
值。
pci.c
通過(guò)該文件可以了解 NVMe
驅(qū)動(dòng)的初始化是怎樣進(jìn)行的,其中所先后調(diào)用的函數(shù)及其作用可參考下圖所示:
nvme_init
從 linux/drivers/nvme/host/pci.c
12 找到程序入口 module_init(nvme_init);
,如下所示:
static int __init nvme_init(void)
{
BUILD_BUG_ON(sizeof(struct nvme_create_cq) != 64);
BUILD_BUG_ON(sizeof(struct nvme_create_sq) != 64);
BUILD_BUG_ON(sizeof(struct nvme_delete_queue) != 64);
BUILD_BUG_ON(IRQ_AFFINITY_MAX_SETS < 2);
BUILD_BUG_ON(DIV_ROUND_UP(nvme_pci_npages_prp(), NVME_CTRL_PAGE_SIZE) >
S8_MAX);
// 注冊(cè)NVMe驅(qū)動(dòng)
return pci_register_driver(&nvme_driver);
}
從上面來(lái)看,初始化的過(guò)程只進(jìn)行了兩步:
- 通過(guò)
BUILD_BUG_ON
檢測(cè)一些相關(guān)參數(shù)是否為異常值; - 通過(guò)
pci_register_driver
注冊(cè)NVMe
驅(qū)動(dòng);
BUILD_BUG_ON
先看其定義 13,如下:
/**
* BUILD_BUG_ON - break compile if a condition is true.
* @condition: the condition which the compiler should know is false.
*
* If you have some code which relies on certain constants being equal, or
* some other compile-time-evaluated condition, you should use BUILD_BUG_ON to
* detect if someone changes it.
*/
#define BUILD_BUG_ON(condition) \
BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
所以,在初始化階段會(huì)通過(guò) BUILD_BUG_ON
監(jiān)測(cè) nvme_create_cq
,nvme_create_sq
,nvme_delete_queue
等參數(shù)是否同預(yù)期值一致。
pci_register_driver
先看其定義 14,如下:
static inline int pci_register_driver(struct pci_driver *drv)
{ return 0; }
/* pci_register_driver() must be a macro so KBUILD_MODNAME can be expanded */
#define pci_register_driver(driver) \
__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
在初始化過(guò)程中,調(diào)用 pci_register_driver
函數(shù)將 nvme_driver
注冊(cè)到 PCI Bus
。
然后,PCI Bus
是怎么將 NVMe
驅(qū)動(dòng)匹配到對(duì)應(yīng)的 NVMe
設(shè)備的呢?
nvme_id_table
系統(tǒng)啟動(dòng)時(shí),BIOS
會(huì)枚舉整個(gè) PCI Bus
, 之后將掃描到的設(shè)備通過(guò) ACPI tables
傳給操作系統(tǒng)。
當(dāng)操作系統(tǒng)加載時(shí),PCI Bus
驅(qū)動(dòng)則會(huì)根據(jù)此信息讀取各個(gè) PCI
設(shè)備的 PCIe Header Config
空間,從 class code
寄存器獲得一個(gè)特征值。class code
就是 PCI bus
用來(lái)選擇哪個(gè)驅(qū)動(dòng)加載設(shè)備的唯一根據(jù)。NVMe
Spec
定義 NVMe
設(shè)備的 Class code=0x010802h
, 如下圖。
然后,nvme driver
會(huì)將 class code
寫入 nvme_id_table
12,如下所示:
static struct pci_driver nvme_driver = {
.name = "nvme",
.id_table = nvme_id_table,
.probe = nvme_probe,
.remove = nvme_remove,
.shutdown = nvme_shutdown,
.driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
#ifdef CONFIG_PM_SLEEP
.pm = &nvme_dev_pm_ops,
#endif
},
.sriov_configure = pci_sriov_configure_simple,
.err_handler = &nvme_err_handler,
};
nvme_id_table
的內(nèi)容如下,
static const struct pci_device_id nvme_id_table[] = {
{ PCI_VDEVICE(INTEL, 0x0953), /* Intel 750/P3500/P3600/P3700 */
.driver_data = NVME_QUIRK_STRIPE_SIZE |
NVME_QUIRK_DEALLOCATE_ZEROES, },
......
{ PCI_DEVICE(PCI_VENDOR_ID_APPLE, 0x2005),
.driver_data = NVME_QUIRK_SINGLE_VECTOR |
NVME_QUIRK_128_BYTES_SQES |
NVME_QUIRK_SHARED_TAGS |
NVME_QUIRK_SKIP_CID_GEN |
NVME_QUIRK_IDENTIFY_CNS },
{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, nvme_id_table);
再看一下 PCI_CLASS_STORAGE_EXPRESS
的定義,如下:
/**
* PCI_DEVICE_CLASS - macro used to describe a specific PCI device class
* @dev_class: the class, subclass, prog-if triple for this device
* @dev_class_mask: the class mask for this device
*
* This macro is used to create a struct pci_device_id that matches a
* specific PCI class. The vendor, device, subvendor, and subdevice
* fields will be set to PCI_ANY_ID.
*/
#define PCI_DEVICE_CLASS(dev_class,dev_class_mask) \
.class = (dev_class), .class_mask = (dev_class_mask), \
.vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID
它是一個(gè)用來(lái)描述特定類的 PCI
設(shè)備的宏變量。
在 PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff)
中的 PCI_CLASS_STORAGE_EXPRESS
的值遵循 NVMe
Spec
中定義的 Class code=0x010802h
,在 linux/include/linux/pci_ids.h
15 中有所體現(xiàn),如下所示:
#define PCI_CLASS_STORAGE_EXPRESS 0x010802
因此,pci_register_driver
將 nvme_driver
注冊(cè)到 PCI Bus
之后,PCI Bus
就明白了這個(gè)驅(qū)動(dòng)是給NVMe
設(shè)備( Class code=0x010802h
) 用的。
到這里,只是找到 PCI Bus
上面驅(qū)動(dòng)與 NVMe
設(shè)備的對(duì)應(yīng)關(guān)系。
nvme_init
執(zhí)行完畢,返回后,nvme
驅(qū)動(dòng)就啥事不做了,直到 PCI
總線枚舉出了這個(gè) nvme
設(shè)備,就開(kāi)始調(diào)用 nvme_probe()
函數(shù)開(kāi)始 “干活”。
nvme_probe
現(xiàn)在,再回憶一下 nvme_driver
的結(jié)構(gòu)體,如下所示:
static struct pci_driver nvme_driver = {
.name = "nvme",
.id_table = nvme_id_table,
.probe = nvme_probe,
.remove = nvme_remove,
.shutdown = nvme_shutdown,
.driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
#ifdef CONFIG_PM_SLEEP
.pm = &nvme_dev_pm_ops,
#endif
},
.sriov_configure = pci_sriov_configure_simple,
.err_handler = &nvme_err_handler,
};
那么現(xiàn)在看一下 nvme_probe
都做了什么?如下所示:
static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct nvme_dev *dev;
int result = -ENOMEM;
// 1.
dev = nvme_pci_alloc_dev(pdev, id);
if (!dev)
return -ENOMEM;
// 2. 獲得PCI Bar的虛擬地址
result = nvme_dev_map(dev);
if (result)
goto out_uninit_ctrl;
// 3. 設(shè)置 DMA 需要的 PRP 內(nèi)存池
result = nvme_setup_prp_pools(dev);
if (result)
goto out_dev_unmap;
// 4.
result = nvme_pci_alloc_iod_mempool(dev);
if (result)
goto out_release_prp_pools;
// 5. 打印日志
dev_info(dev->ctrl.device, "pci function %s\n", dev_name(&pdev->dev));
// 6.
result = nvme_pci_enable(dev);
if (result)
goto out_release_iod_mempool;
// 7.
result = nvme_alloc_admin_tag_set(&dev->ctrl, &dev->admin_tagset,
&nvme_mq_admin_ops, sizeof(struct nvme_iod));
if (result)
goto out_disable;
/*
* Mark the controller as connecting before sending admin commands to
* allow the timeout handler to do the right thing.
*/
// 8.
if (!nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_CONNECTING)) {
dev_warn(dev->ctrl.device,
"failed to mark controller CONNECTING\n");
result = -EBUSY;
goto out_disable;
}
// 9. 初始化NVMe Controller結(jié)構(gòu)
result = nvme_init_ctrl_finish(&dev->ctrl, false);
if (result)
goto out_disable;
// 10.
nvme_dbbuf_dma_alloc(dev);
// 11.
result = nvme_setup_host_mem(dev);
if (result < 0)
goto out_disable;
// 12.
result = nvme_setup_io_queues(dev);
if (result)
goto out_disable;
// 13.
if (dev->online_queues > 1) {
nvme_alloc_io_tag_set(&dev->ctrl, &dev->tagset, &nvme_mq_ops,
nvme_pci_nr_maps(dev), sizeof(struct nvme_iod));
nvme_dbbuf_set(dev);
}
// 14.
if (!dev->ctrl.tagset)
dev_warn(dev->ctrl.device, "IO queues not created\n");
// 15.
if (!nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_LIVE)) {
dev_warn(dev->ctrl.device,
"failed to mark controller live state\n");
result = -ENODEV;
goto out_disable;
}
// 16. 為設(shè)備設(shè)置私有數(shù)據(jù)指針
pci_set_drvdata(pdev, dev);
nvme_start_ctrl(&dev->ctrl);
nvme_put_ctrl(&dev->ctrl);
flush_work(&dev->ctrl.scan_work);
return 0;
out_disable:
nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_DELETING);
nvme_dev_disable(dev, true);
nvme_free_host_mem(dev);
nvme_dev_remove_admin(dev);
nvme_dbbuf_dma_free(dev);
nvme_free_queues(dev, 0);
out_release_iod_mempool:
mempool_destroy(dev->iod_mempool);
out_release_prp_pools:
nvme_release_prp_pools(dev);
out_dev_unmap:
nvme_dev_unmap(dev);
out_uninit_ctrl:
nvme_uninit_ctrl(&dev->ctrl);
return result;
}
詳見(jiàn)另外一篇文章 16。
DMA
PCIe
有個(gè)寄存器位 Bus Master Enable
,這個(gè) bit
置 1
后,PCIe
設(shè)備就可以向Host
發(fā)送 DMA Read Memory
和 DMA Write Memory
請(qǐng)求。
當(dāng) Host
的 driver
需要跟 PCIe
設(shè)備傳輸數(shù)據(jù)的時(shí)候,只需要告訴 PCIe
設(shè)備存放數(shù)據(jù)的地址就可以。
NVMe Command
占用 64
個(gè)字節(jié),另外其 PCIe
BAR
空間被映射到虛擬內(nèi)存空間(其中包括用來(lái)通知 NVMe SSD Controller
讀取 Command
的 Doorbell
寄存器)。
NVMe
數(shù)據(jù)傳輸都是通過(guò) NVMe Command
,而 NVMe Command
則存放在 NVMe Queue
中,其配置如下圖所示:
其中隊(duì)列中有 Submission Queue
,Completion Queue
兩個(gè)。
參考鏈接
-
NVMe驅(qū)動(dòng)系列文章 ??
-
Linux NVMe Driver學(xué)習(xí)筆記之1:概述與nvme_core_init函數(shù)解析 ??
-
linux ??
-
bootlin ??
-
linux/drivers/nvme/ ??
-
linux/drivers/nvme/host/Kconfig ??
-
linux/drivers/nvme/host/Makefile ??
-
linux/drivers/nvme/host/core.c ?? ?? ??
-
linux/fs/char_dev.c ??
-
linux/include/linux/device.h ??
-
linux/drivers/nvme/host/ioctl.c ??
-
linux/drivers/nvme/host/pci.c ?? ??
-
linux/include/linux/build_bug.h ??
-
linux/include/linux/pci.h ??
-
linux/include/linux/pci_ids.h ??文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-599920.html
-
驅(qū)動(dòng) | Linux | NVMe | 2. nvme_probe ??文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-599920.html
到了這里,關(guān)于驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!