NVMe Linux驱动系列一:host端[pci.c]<41>

MARCO Define


#define SQ_SIZE(q)	((q)->q_depth << (q)->sqes)
#define CQ_SIZE(q)	((q)->q_depth * sizeof(struct nvme_completion))

#define SGES_PER_PAGE	(NVME_CTRL_PAGE_SIZE / sizeof(struct nvme_sgl_desc))

/*
 * These can be higher, but we need to ensure that any command doesn't
 * require an sg allocation that needs more than a page of data.
 */
#define NVME_MAX_KB_SZ	8192
#define NVME_MAX_SEGS	128
#define NVME_MAX_NR_ALLOCATIONS	5

这些宏定义是与NVMe驱动相关的一些参数和常量的定义。

  • SQ_SIZE(q): 定义了一个宏,用于计算队列的大小,其计算方式是队列的深度乘以每个条目的大小(通过 sqes 字段定义)。这个宏用于计算队列大小,以便在初始化队列时分配相应的内存。

  • CQ_SIZE(q): 定义了一个宏,用于计算完成队列(Completion Queue)的大小,其计算方式是队列的深度乘以每个完成条目的大小(结构体 nvme_completion 的大小)。这个宏用于计算完成队列的大小,以便在初始化队列时分配相应的内存。

  • SGES_PER_PAGE: 定义了一个宏,表示每个页面(NVME_CTRL_PAGE_SIZE)中可以容纳的struct nvme_sgl_desc结构体的数量。nvme_sgl_desc是描述散列(scatter-gather)列表的结构,用于指定数据在内存中的布局。

  • NVME_MAX_KB_SZ: 定义了一个宏,表示单个命令可以传输的最大数据量,以KB为单位。这个宏用于限制单个命令的数据传输量,以确保不会超过指定的限制。

  • NVME_MAX_SEGS: 定义了一个宏,表示单个散列(scatter-gather)列表中可以包含的最大段(segment)数量。散列列表用于描述数据在非连续内存中的布局。

  • NVME_MAX_NR_ALLOCATIONS: 定义了一个宏,表示分配NVMe命令所需的最大内存块数量。这个宏限制了NVMe命令可能使用的内存数量。

这些宏的目的是为了在NVMe驱动中定义和控制各种参数和常量,以便在初始化和管理NVMe队列、数据传输和内存分配时使用。

module_param



static int use_threaded_interrupts;
module_param(use_threaded_interrupts, int, 0444);

static bool use_cmb_sqes = true;
module_param(use_cmb_sqes, bool, 0444);
MODULE_PARM_DESC(use_cmb_sqes, "use controller's memory buffer for I/O SQes");

static unsigned int max_host_mem_size_mb = 128;
module_param(max_host_mem_size_mb, uint, 0444);
MODULE_PARM_DESC(max_host_mem_size_mb,
	"Maximum Host Memory Buffer (HMB) size per controller (in MiB)");

static unsigned int sgl_threshold = SZ_32K;
module_param(sgl_threshold, uint, 0644);
MODULE_PARM_DESC(sgl_threshold,
		"Use SGLs when average request segment size is larger or equal to "
		"this size. Use 0 to disable SGLs.");

#define NVME_PCI_MIN_QUEUE_SIZE 2
#define NVME_PCI_MAX_QUEUE_SIZE 4095
static int io_queue_depth_set(const char *val, const struct kernel_param *kp);
static const struct kernel_param_ops io_queue_depth_ops = {
	.set = io_queue_depth_set,
	.get = param_get_uint,
};

static unsigned int io_queue_depth = 1024;
module_param_cb(io_queue_depth, &io_queue_depth_ops, &io_queue_depth, 0644);
MODULE_PARM_DESC(io_queue_depth, "set io queue depth, should >= 2 and < 4096");

这段代码是关于Linux内核模块参数(Module Parameters)的定义。这些参数允许用户在加载模块时通过命令行设置不同的选项,从而调整驱动的行为和配置。

  • use_threaded_interrupts: 这是一个整数类型的内核模块参数,用于控制是否使用线程化中断处理。用户可以在加载模块时通过use_threaded_interrupts参数来设置是否启用线程化中断处理。

  • use_cmb_sqes: 这是一个布尔类型的内核模块参数,用于控制是否使用控制器的内存缓冲区(Controller Memory Buffer,CMB)来处理I/O SQes(Submission Queues)。用户可以通过use_cmb_sqes参数来设置是否启用使用CMB处理I/O SQes。

  • max_host_mem_size_mb: 这是一个无符号整数类型的内核模块参数,用于设置每个控制器的最大主机内存缓冲区(Host Memory Buffer,HMB)大小,以MB为单位。用户可以通过max_host_mem_size_mb参数来设置HMB的最大大小。

  • sgl_threshold: 这是一个无符号整数类型的内核模块参数,用于控制何时使用散列列表(SGLs)来描述数据传输。当平均请求段大小大于等于sgl_threshold时,将使用SGLs。用户可以通过sgl_threshold参数来设置SGL的使用阈值。

  • io_queue_depth: 这是一个无符号整数类型的内核模块参数,用于设置I/O队列的深度(即请求队列中可以同时排队的I/O请求的数量)。用户可以通过io_queue_depth参数来设置I/O队列的深度,取值范围应该在2到4095之间。

这些内核模块参数允许用户在加载NVMe驱动模块时灵活地调整不同的配置选项,以适应不同的系统需求和性能优化。


static int io_queue_count_set(const char *val, const struct kernel_param *kp)
{
	unsigned int n;
	int ret;

	ret = kstrtouint(val, 10, &n);
	if (ret != 0 || n > num_possible_cpus())
		return -EINVAL;
	return param_set_uint(val, kp);
}

static const struct kernel_param_ops io_queue_count_ops = {
	.set = io_queue_count_set,
	.get = param_get_uint,
};

static unsigned int write_queues;
module_param_cb(write_queues, &io_queue_count_ops, &write_queues, 0644);
MODULE_PARM_DESC(write_queues,
	"Number of queues to use for writes. If not set, reads and writes "
	"will share a queue set.");

static unsigned int poll_queues;
module_param_cb(poll_queues, &io_queue_count_ops, &poll_queues, 0644);
MODULE_PARM_DESC(poll_queues, "Number of queues to use for polled IO.");

static bool noacpi;
module_param(noacpi, bool, 0444);
MODULE_PARM_DESC(noacpi, "disable acpi bios quirks");

struct nvme_dev;
struct nvme_queue;

static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown);
static void nvme_delete_io_queues(struct nvme_dev *dev);
static void nvme_update_attrs(struct nvme_dev *dev);

这段代码涉及一些有关内核参数和NVMe设备的操作函数。我将为您解释每个部分的内容:

  • io_queue_count_setio_queue_count_ops: 这部分代码定义了一个自定义的内核参数操作,用于设置I/O队列的数量。io_queue_count_set 函数负责解析用户提供的参数值,并确保其有效性。io_queue_count_ops 定义了将这个操作函数与内核参数关联的操作。

  • write_queues: 这是一个无符号整数类型的内核模块参数,用于设置用于写入操作的队列数量。如果不设置该参数,则读取和写入操作将共享一个队列集。用户可以通过write_queues参数来设置写入队列的数量。

  • poll_queues: 这也是一个无符号整数类型的内核模块参数,用于设置用于轮询式I/O的队列数量。用户可以通过poll_queues参数来设置轮询式I/O的队列数量。

  • noacpi: 这是一个布尔类型的内核模块参数,用于禁用ACPI BIOS的特殊配置。用户可以通过设置noacpi参数来禁用ACPI BIOS的配置。

  • nvme_dev_disable: 这是一个函数原型声明,表示禁用NVMe设备的函数。

  • nvme_delete_io_queues: 这是一个函数原型声明,表示删除NVMe设备的I/O队列的函数。

  • nvme_update_attrs: 这是一个函数原型声明,表示更新NVMe设备属性的函数。

从这些代码片段中,可以看出涉及内核参数操作、NVMe设备管理和属性更新的功能。

nvme_dev


/*
 * Represents an NVM Express device.  Each nvme_dev is a PCI function.
 */
struct nvme_dev {
	struct nvme_queue *queues;
	struct blk_mq_tag_set tagset;
	struct blk_mq_tag_set admin_tagset;
	u32 __iomem *dbs;
	struct device *dev;
	struct dma_pool *prp_page_pool;
	struct dma_pool *prp_small_pool;
	unsigned online_queues;
	unsigned max_qid;
	unsigned io_queues[HCTX_MAX_TYPES];
	unsigned int num_vecs;
	u32 q_depth;
	int io_sqes;
	u32 db_stride;
	void __iomem *bar;
	unsigned long bar_mapped_size;
	struct mutex shutdown_lock;
	bool subsystem;
	u64 cmb_size;
	bool cmb_use_sqes;
	u32 cmbsz;
	u32 cmbloc;
	struct nvme_ctrl ctrl;
	u32 last_ps;
	bool hmb;

	mempool_t *iod_mempool;

	/* shadow doorbell buffer support: */
	__le32 *dbbuf_dbs;
	dma_addr_t dbbuf_dbs_dma_addr;
	__le32 *dbbuf_eis;
	dma_addr_t dbbuf_eis_dma_addr;

	/* host memory buffer support: */
	u64 host_mem_size;
	u32 nr_host_mem_descs;
	dma_addr_t host_mem_descs_dma;
	struct nvme_host_mem_buf_desc *host_mem_descs;
	void **host_mem_desc_bufs;
	unsigned int nr_allocated_queues;
	unsigned int nr_write_queues;
	unsigned int nr_poll_queues;
};

这段代码定义了一个名为 nvme_dev 的数据结构,表示一个NVM Express(NVMe)设备。下面我将为您解释这个结构的各个成员:

  • queues: 指向一个 nvme_queue 结构的指针,表示与此设备相关联的NVMe队列。

  • tagsetadmin_tagset: 这两个成员都是 blk_mq_tag_set 结构,用于管理设备的块多队列(Blk-mq)标签集。tagset 用于表示普通I/O队列的标签集,而 admin_tagset 用于表示管理队列的标签集。

  • dbs: 指向一个门铃寄存器(Doorbell Register)数组的指针,用于管理设备的队列门铃。

  • dev: 指向一个 device 结构的指针,表示此NVMe设备对应的设备对象。

  • prp_page_poolprp_small_pool: 这两个成员分别表示用于页面大小PRP(Physical Region Page)内存池和小型PRP内存池的DMA内存池。

  • online_queues: 表示当前激活的队列数。

  • max_qid: 表示最大队列ID。

  • io_queues: 数组,用于存储不同类型I/O队列的数量。

  • num_vecs: 表示支持的中断向量数量。

  • q_depth: 表示队列深度。

  • io_sqes: 表示I/O SQES(Submission Queue Entry Size)的大小。

  • db_stride: 表示门铃寄存器的跨度。

  • bar: 指向设备的基址寄存器(Base Address Register)。

  • bar_mapped_size: 映射到内存的基址寄存器的大小。

  • shutdown_lock: 用于设备关闭操作的互斥锁。

  • subsystem: 表示是否为子系统设备。

  • cmb_size: 表示CMB(Controller Memory Buffer)的大小。

  • cmb_use_sqes: 表示是否使用CMB存储SQES。

  • cmbszcmbloc: 表示CMB大小和CMB位置。

  • ctrl: 包含有关NVMe控制器的结构。

  • last_ps: 上一次的电源状态。

  • hmb: 表示是否支持HMB(Host Memory Buffer)。

  • iod_mempool: 用于分配I/O描述符的内存池。

  • dbbuf_dbsdbbuf_eis: 用于支持阴影门铃缓冲区的Doorbell寄存器。

  • host_mem_size: 主机内存缓冲区的大小。

  • nr_host_mem_descs: 主机内存缓冲区描述符的数量。

  • host_mem_descs_dma: 主机内存缓冲区描述符的DMA地址。

  • host_mem_descs: 主机内存缓冲区描述符数组。

  • host_mem_desc_bufs: 主机内存缓冲区描述符对应的数据缓冲区数组。

  • nr_allocated_queues: 已分配的队列数。

  • nr_write_queuesnr_poll_queues: 写入队列和轮询队列的数量。

io_queue_depth_set & sq_idx & cq_idx & to_nvme_dev


static int io_queue_depth_set(const char *val, const struct kernel_param *kp)
{
	return param_set_uint_minmax(val, kp, NVME_PCI_MIN_QUEUE_SIZE,
			NVME_PCI_MAX_QUEUE_SIZE);
}

static inline unsigned int sq_idx(unsigned int qid, u32 stride)
{
	return qid * 2 * stride;
}

static inline unsigned int cq_idx(unsigned int qid, u32 stride)
{
	return (qid * 2 + 1) * stride;
}

static inline struct nvme_dev *to_nvme_dev(struct nvme_ctrl *ctrl)
{
	return container_of(ctrl, struct nvme_dev, ctrl);
}

这部分代码实现了几个辅助函数和一个参数设置函数,让我为您解释一下:

  • io_queue_depth_set: 这个函数用于设置I/O队列的深度参数。它在模块参数的设置操作中调用,将输入的值限制在预定义的最小和最大队列深度之间(NVME_PCI_MIN_QUEUE_SIZENVME_PCI_MAX_QUEUE_SIZE)。它会返回设置的结果。

  • sq_idx: 这个函数用于计算指定队列ID和门铃寄存器跨度的Submission Queue(SQ)的索引。在多队列系统中,每个队列有一个对应的Submission Queue和Completion Queue,因此需要根据队列ID来计算门铃寄存器中对应的位置。

  • cq_idx: 类似于 sq_idx,这个函数用于计算指定队列ID和门铃寄存器跨度的Completion Queue(CQ)的索引。

  • to_nvme_dev: 这个宏用于将 struct nvme_ctrl 类型的指针转换为对应的 struct nvme_dev 类型的指针。在代码中,struct nvme_ctrl 是用于表示NVMe控制器的结构,而 struct nvme_dev 是在上面的代码中定义的用于表示NVMe设备的结构。由于 struct nvme_dev 中嵌套了一个 struct nvme_ctrl,可以通过这种方式在两者之间进行相互转换。

nvme_queue


/*
 * An NVM Express queue.  Each device has at least two (one for admin
 * commands and one for I/O commands).
 */
struct nvme_queue {
	struct nvme_dev *dev;
	spinlock_t sq_lock;
	void *sq_cmds;
	 /* only used for poll queues: */
	spinlock_t cq_poll_lock ____cacheline_aligned_in_smp;
	struct nvme_completion *cqes;
	dma_addr_t sq_dma_addr;
	dma_addr_t cq_dma_addr;
	u32 __iomem *q_db;
	u32 q_depth;
	u16 cq_vector;
	u16 sq_tail;
	u16 last_sq_tail;
	u16 cq_head;
	u16 qid;
	u8 cq_phase;
	u8 sqes;
	unsigned long flags;
#define NVMEQ_ENABLED		0
#define NVMEQ_SQ_CMB		1
#define NVMEQ_DELETE_ERROR	2
#define NVMEQ_POLLED		3
	__le32 *dbbuf_sq_db;
	__le32 *dbbuf_cq_db;
	__le32 *dbbuf_sq_ei;
	__le32 *dbbuf_cq_ei;
	struct completion delete_done;
};

这段代码定义了 struct nvme_queue 结构体,表示一个NVM Express队列,每个设备至少有两个这样的队列,一个用于管理命令(admin commands),另一个用于I/O命令。

以下是结构体的各个成员的说明:

  • dev: 指向 struct nvme_dev 结构,表示该队列所属的设备。

  • sq_lock: 用于保护Submission Queue(SQ)的自旋锁。

  • sq_cmds: 指向SQ命令的缓冲区。

  • cq_poll_lock: 仅用于轮询队列的锁,用于保护Completion Queue(CQ)。

  • cqes: 指向CQ入口(Completion Queue Entry)的缓冲区。

  • sq_dma_addrcq_dma_addr: SQ和CQ的DMA地址。

  • q_db: 指向队列的门铃寄存器。

  • q_depth: 队列的深度。

  • cq_vector: CQ的中断向量。

  • sq_tailcq_head: SQ尾部指针和CQ头部指针。

  • qid: 队列的ID。

  • cq_phase: CQ的当前阶段。

  • sqes: SQ Entry Size,表示每个SQ命令的大小。

  • flags: 用于存储标志位,如 NVMEQ_ENABLEDNVMEQ_SQ_CMB 等。

  • dbbuf_sq_dbdbbuf_cq_dbdbbuf_sq_eidbbuf_cq_ei: 用于支持Shadow Doorbell Buffer的缓冲区。

  • delete_done: 用于删除队列时的完成信号。

总之,struct nvme_queue 表示了NVM Express的队列,包含了与队列操作相关的信息。

nvme_descriptor


union nvme_descriptor {
	struct nvme_sgl_desc	*sg_list;
	__le64			*prp_list;
};

这段代码定义了一个 union nvme_descriptor 联合体,用于描述NVM Express命令的数据传输描述符。

在NVM Express中,命令可以包含不同类型的数据传输,例如,使用Scatter-Gather List(SG List)传输数据或使用物理页列表(PRP List)传输数据。这个联合体允许命令使用不同类型的描述符来传输数据。

  • sg_list: 如果命令使用SG List传输数据,则该成员指向一个SG List描述符。

  • prp_list: 如果命令使用PRP List传输数据,则该成员指向一个PRP List描述符。

通过这个联合体,可以根据命令的数据传输类型来选择合适的成员。

nvme_iod


/*
 * The nvme_iod describes the data in an I/O.
 *
 * The sg pointer contains the list of PRP/SGL chunk allocations in addition
 * to the actual struct scatterlist.
 */
struct nvme_iod {
	struct nvme_request req;
	struct nvme_command cmd;
	bool aborted;
	s8 nr_allocations;	/* PRP list pool allocations. 0 means small
				   pool in use */
	unsigned int dma_len;	/* length of single DMA segment mapping */
	dma_addr_t first_dma;
	dma_addr_t meta_dma;
	struct sg_table sgt;
	union nvme_descriptor list[NVME_MAX_NR_ALLOCATIONS];
};

这段代码定义了一个结构体 struct nvme_iod,用于描述NVM Express的I/O操作数据。

  • req: 一个 struct nvme_request 结构体,用于描述NVM Express请求。

  • cmd: 一个 struct nvme_command 结构体,用于描述NVM Express命令。

  • aborted: 一个布尔值,表示I/O操作是否已被中止。

  • nr_allocations: 表示PRP列表池中的分配数量,为0表示使用小的池。

  • dma_len: 单个DMA段映射的长度。

  • first_dma: 第一个DMA段的DMA地址。

  • meta_dma: 元数据的DMA地址。

  • sgt: 一个 struct sg_table 结构体,用于描述散列列表。

  • list[NVME_MAX_NR_ALLOCATIONS]: 一个 union nvme_descriptor 的数组,用于保存PRP或SGL描述符。这个数组可以存储最多NVME_MAX_NR_ALLOCATIONS个描述符,这些描述符用于I/O操作的数据传输。

通过这个结构体,可以组织和管理NVM Express的I/O操作所需的数据和描述符。

nvme_dbbuf_size


static inline unsigned int nvme_dbbuf_size(struct nvme_dev *dev)
{
	return dev->nr_allocated_queues * 8 * dev->db_stride;
}

这是一个内联函数,用于计算给定NVMe设备的Doorbell Buffer的大小。

  • dev: 指向 struct nvme_dev 的指针,表示NVMe设备。

函数根据以下公式计算Doorbell Buffer的大小(以字节为单位):

Doorbell Buffer Size = (已分配的队列数) * 8 * (队列步进大小)

其中:

  • 已分配的队列数 是在设备上已经分配的队列数。
  • 8 代表每个队列需要两个门铃寄存器(一个用于SQ,一个用于CQ),每个门铃寄存器占用4字节。
  • 队列步进大小 是设备中队列门铃之间的间隔(通常为1)。

该函数的返回值是Doorbell Buffer的大小(以字节为单位)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值