国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述

這篇具有很好參考價(jià)值的文章主要介紹了驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

本文主要參考這里 12 的解析和 linux 源碼 3。
此處推薦一個(gè)可以便捷查看 linux 源碼的網(wǎng)站 bootlin 4。

更新:2022 / 02 / 19



NVMe 的前世今生

NVMe 離不開(kāi) PCIeNVMe SSDPCIeendpoint。PCIex86 平臺(tái)上一種流行的外設(shè)總線,由于其 Plug and Play 的特性,目前很多外設(shè)都通過(guò) PCI BusHost 通信,甚至不少CPU 的集成外設(shè)都通過(guò) PCI Bus 連接,如 APIC 等。

NVMe SSDPCIe 接口上使用新的標(biāo)準(zhǔn)協(xié)議 NVMe,由大廠 Intel 推出并交由 nvmexpress 組織推廣,現(xiàn)在被全球大部分存儲(chǔ)企業(yè)采納。


從系統(tǒng)角度看 NVMe 驅(qū)動(dòng)

NVMe Command

NVMe HostServer )和 NVMe ControllerSSD )通過(guò) NVMe Command 進(jìn)行信息交互。NVMe Spec 中定義了 NVMe Command 的格式,占用 64 字節(jié)。

NVMe Command 分為 Admin CommandIO Command 兩大類,前者主要是用于配置,后者用于數(shù)據(jù)傳輸。

NVMe CommandHostSSD 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 總線

  1. 在操作系統(tǒng)啟動(dòng)時(shí),BIOS 會(huì)枚舉整個(gè) PCI 的總線,之后將掃描到的設(shè)備通過(guò) ACPI tables 傳給操作系統(tǒng)。
  2. 當(dāng)操作系統(tǒng)加載時(shí),PCI Bus 驅(qū)動(dòng)則會(huì)根據(jù)此信息讀取各個(gè) PCI 設(shè)備的 PCI Header Config 空間,從 class code 寄存器獲得一個(gè)特征值。

class codePCI bus 用來(lái)選擇哪個(gè)驅(qū)動(dòng)加載設(shè)備的唯一根據(jù)。

NVMe Spec 定義的 class code010802h。NVMe SSD 內(nèi)部的 Controller PCIe Headerclass code 都會(huì)設(shè)置成010802h。

所以,需要在驅(qū)動(dòng)中指定 class code010802h,將 010802h 放入 pci_driver nvme_driverid_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之前,先看一下 DriverLinux 架構(gòu)中的位置,如下圖所示:

驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述,Linux,linux,驅(qū)動(dòng)開(kāi)發(fā)
NVMe driverBlock Layer 之下,負(fù)責(zé)與 NVMe 設(shè)備交互。

為了緊跟時(shí)代的大趨勢(shì),現(xiàn)在的 NVMe driver 已經(jīng)很強(qiáng)大了,也可以支持 NVMe over Fabric 相關(guān)設(shè)備,如下圖所示:

驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述,Linux,linux,驅(qū)動(dòng)開(kāi)發(fā)
不過(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)的 kconfigMakefile 文件,了解其文件架構(gòu),再閱讀相關(guān)的 source code。

Kconfig 文件的作用是:

  1. 控制 make menuconfig 時(shí),出現(xiàn)的配置選項(xiàng);
  2. 根據(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
......

KconfigMakefile 來(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
  1. 調(diào)用 alloc_chrdev_region 9 函數(shù),如下所示,注冊(cè)名為 nvmenvme_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
  1. 調(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)分別為 12。

例如,在 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,ioctrlrelease 接口對(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_livemodule,最后找到的 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)了 5Command

命令 解釋
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 stateresetting,說(shuō)明當(dāng)前設(shè)備或資源處于繁忙狀態(tài),無(wú)法完成 nvme_reset_subsystem;
  • 如果 controller stateresetting,通過(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_write32nvme_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, 如下圖。

驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述,Linux,linux,驅(qū)動(dòng)開(kāi)發(fā)
然后,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_drivernvme_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è) bit1 后,PCIe 設(shè)備就可以向Host 發(fā)送 DMA Read MemoryDMA Write Memory 請(qǐng)求。

當(dāng) Hostdriver 需要跟 PCIe 設(shè)備傳輸數(shù)據(jù)的時(shí)候,只需要告訴 PCIe 設(shè)備存放數(shù)據(jù)的地址就可以。

NVMe Command 占用 64 個(gè)字節(jié),另外其 PCIe BAR 空間被映射到虛擬內(nèi)存空間(其中包括用來(lái)通知 NVMe SSD Controller 讀取 CommandDoorbell 寄存器)。

NVMe 數(shù)據(jù)傳輸都是通過(guò) NVMe Command,而 NVMe Command 則存放在 NVMe Queue 中,其配置如下圖所示:驅(qū)動(dòng) | Linux | NVMe | 1. NVMe Driver的前世今生和工作原理概述,Linux,linux,驅(qū)動(dòng)開(kāi)發(fā)

其中隊(duì)列中有 Submission Queue,Completion Queue 兩個(gè)。


參考鏈接


  1. NVMe驅(qū)動(dòng)系列文章 ??

  2. Linux NVMe Driver學(xué)習(xí)筆記之1:概述與nvme_core_init函數(shù)解析 ??

  3. linux ??

  4. bootlin ??

  5. linux/drivers/nvme/ ??

  6. linux/drivers/nvme/host/Kconfig ??

  7. linux/drivers/nvme/host/Makefile ??

  8. linux/drivers/nvme/host/core.c ?? ?? ??

  9. linux/fs/char_dev.c ??

  10. linux/include/linux/device.h ??

  11. linux/drivers/nvme/host/ioctl.c ??

  12. linux/drivers/nvme/host/pci.c ?? ??

  13. linux/include/linux/build_bug.h ??

  14. linux/include/linux/pci.h ??

  15. linux/include/linux/pci_ids.h ??

  16. 驅(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深度學(xué)習(xí)的“前世今生”

    深度學(xué)習(xí)的“前世今生”

    20世紀(jì)50年代,人工智能派生出了這樣兩個(gè)學(xué)派,分別是“符號(hào)學(xué)派”及“連接學(xué)派”。前者的領(lǐng)軍學(xué)者有Marvin Minsky及John McCarthy,后者則是由Frank Rosenblatt所領(lǐng)導(dǎo)。 “符號(hào)學(xué)派”的人相信對(duì)機(jī)器從頭編程,一個(gè)模塊一個(gè)模塊組合最終可以得到比人類更智慧的機(jī)器; 而“連接學(xué)

    2024年02月12日
    瀏覽(20)
  • 小程序插件的前世今生

    首先,在開(kāi)始之前,我們需要了解小程序插件的概念。小程序插件可以理解為小程序的擴(kuò)展功能,類似于應(yīng)用商店中的插件。通過(guò)引入插件,我們可以給小程序添加一些特定的功能模塊,例如地圖、支付、分享等。這樣一來(lái),開(kāi)發(fā)者就可以更加靈活地為用戶提供豐富的體驗(yàn)。

    2024年02月03日
    瀏覽(20)
  • 提示工程的前世今生

    提示工程的前世今生

    原文鏈接:芝士AI吃魚 通過(guò)提示進(jìn)行情境學(xué)習(xí) 在生物學(xué)中,涌現(xiàn)是一種令人難以置信的特性,由于相互作用的結(jié)果,各個(gè)部分聚集在一起,表現(xiàn)出新的行為(稱為涌現(xiàn)),這是你在較小的尺度上看不到的。更令人難以置信的是,即使較小比例的版本看起來(lái)與較大比例相似,但

    2024年02月13日
    瀏覽(21)
  • JavaScript 發(fā)展的前世今生

    JavaScript 發(fā)展的前世今生

    專欄介紹 本專欄主要用作于開(kāi)放性知識(shí)點(diǎn)分享學(xué)習(xí),其主要知識(shí)點(diǎn)范圍是 以圍繞 原生 JavaScript 語(yǔ)法 從基礎(chǔ)知識(shí)到高階語(yǔ)法階段的學(xué)習(xí)分享。 導(dǎo)語(yǔ): 既然博主,計(jì)劃將此專欄打造為 JavaScript 的知識(shí)點(diǎn)學(xué)習(xí)分享集結(jié)地。所以,本章節(jié)就為大家?guī)?lái),有關(guān) JavaScript 這門語(yǔ)言的一

    2024年02月07日
    瀏覽(93)
  • Docker 的前世今生

    Docker 的前世今生

    ???? 博主 libin9iOak帶您 Go to New World.??? ?? 個(gè)人主頁(yè)——libin9iOak的博客?? ?? 《面試題大全》 文章圖文并茂??生動(dòng)形象??簡(jiǎn)單易學(xué)!歡迎大家來(lái)踩踩~?? ?? 《IDEA開(kāi)發(fā)秘籍》學(xué)會(huì)IDEA常用操作,工作效率翻倍~?? ???? 希望本文能夠給您帶來(lái)一定的幫助??文章粗淺,敬

    2024年02月16日
    瀏覽(23)
  • 1 Go的前世今生

    1 Go的前世今生

    概述 ????????Go語(yǔ)言正式發(fā)布于2009年11月,由Google主導(dǎo)開(kāi)發(fā)。它是一種針對(duì)多處理器系統(tǒng)應(yīng)用程序的編程語(yǔ)言,被設(shè)計(jì)成一種系統(tǒng)級(jí)語(yǔ)言,具有非常強(qiáng)大和有用的特性。Go語(yǔ)言的程序速度可以與C、C++相媲美,同時(shí)更加安全,支持并行進(jìn)程。此外,Go語(yǔ)言也支持面向?qū)ο缶幊?/p>

    2024年02月08日
    瀏覽(16)
  • OpenHarmony的前世今生

    OpenHarmony的前世今生

    目錄 1.1.1:OpenHarmony的背景 1.1.2:OpenHarmony的誕生 1.1.3:OpenHarmony與HarmonyOS的關(guān)系 1.1.4:OpenHarmony的技術(shù)架構(gòu) 1.1.5:OpenHarmony的技術(shù)特性 1.1.6:小結(jié) OpenHarmony 是由開(kāi)放原子開(kāi)源基金會(huì)(OpenAtom Foundation)孵化及運(yùn)營(yíng)的開(kāi)源項(xiàng)目,目標(biāo)是面向全場(chǎng)景、全連接、全智能時(shí)代,基于開(kāi)源

    2024年01月20日
    瀏覽(21)
  • ChatGPT的前世今生

    ChatGPT的前世今生

    作者????♂?:讓機(jī)器理解語(yǔ)言か 專欄??:NLP(自然語(yǔ)言處理) 描述??:讓機(jī)器理解語(yǔ)言,讓世界更加美好! 寄語(yǔ)??:??沒(méi)有白走的路,每一步都算數(shù)!?? ( 本文是chatGPT原理介紹,但沒(méi)有任何數(shù)學(xué)公式,可以放心食用 ) 這幾個(gè)月, chatGPT模型 真可謂稱得上是狂拽

    2023年04月09日
    瀏覽(19)
  • ChatGPT:GPT前世今生

    ????????2018年,OpenAI研究員Alec Radford提出了GPT(Generative Pre-trained Transformer)模型。這是人工智能歷史上的一個(gè)里程碑,因?yàn)樗堑谝粋€(gè)成功應(yīng)用Transformer網(wǎng)絡(luò)結(jié)構(gòu)到語(yǔ)言模型任務(wù)上的工作。 ????????GPT的核心創(chuàng)新在于利用Transformer的自注意力機(jī)制來(lái)建模語(yǔ)言的長(zhǎng)程依賴關(guān)系。

    2024年02月16日
    瀏覽(16)
  • Main()函數(shù)的前世今生

    Main()函數(shù)的前世今生

    ???????? 在開(kāi)始分析程序之前,我們第一個(gè)要解決的問(wèn)題,就是如何定位到 main函數(shù),想要從二進(jìn)制逆向的角度分析出main 函數(shù),就必須要了解正向的代碼下 main 函數(shù)的所有的細(xì)節(jié)和特 征。畢竟逆向的本質(zhì)就是正向。 VS C++開(kāi)發(fā)的程序在調(diào)試時(shí)總是從main或WinMain函數(shù)開(kāi)始,這

    2024年02月09日
    瀏覽(31)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包