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_set
和io_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队列。 -
tagset
和admin_tagset
: 这两个成员都是blk_mq_tag_set
结构,用于管理设备的块多队列(Blk-mq)标签集。tagset
用于表示普通I/O队列的标签集,而admin_tagset
用于表示管理队列的标签集。 -
dbs
: 指向一个门铃寄存器(Doorbell Register)数组的指针,用于管理设备的队列门铃。 -
dev
: 指向一个device
结构的指针,表示此NVMe设备对应的设备对象。 -
prp_page_pool
和prp_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。 -
cmbsz
和cmbloc
: 表示CMB大小和CMB位置。 -
ctrl
: 包含有关NVMe控制器的结构。 -
last_ps
: 上一次的电源状态。 -
hmb
: 表示是否支持HMB(Host Memory Buffer)。 -
iod_mempool
: 用于分配I/O描述符的内存池。 -
dbbuf_dbs
和dbbuf_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_queues
和nr_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_SIZE
和NVME_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_addr
、cq_dma_addr
: SQ和CQ的DMA地址。 -
q_db
: 指向队列的门铃寄存器。 -
q_depth
: 队列的深度。 -
cq_vector
: CQ的中断向量。 -
sq_tail
、cq_head
: SQ尾部指针和CQ头部指针。 -
qid
: 队列的ID。 -
cq_phase
: CQ的当前阶段。 -
sqes
: SQ Entry Size,表示每个SQ命令的大小。 -
flags
: 用于存储标志位,如NVMEQ_ENABLED
、NVMEQ_SQ_CMB
等。 -
dbbuf_sq_db
、dbbuf_cq_db
、dbbuf_sq_ei
、dbbuf_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的大小(以字节为单位)。