nvme_mps_to_sectors
/*
* 函数名称: nvme_mps_to_sectors
* 说明: 将NVMe控制器的多个页面大小(MPS)转换为扇区数。该函数用于将NVMe控制器的多个页面大小(MPS)值转换为对应的扇区数。
* 参数:
* - ctrl: 指向NVMe控制器结构的指针,表示要进行转换的控制器。
* - units: 表示要转换的多个页面大小(MPS)单位。
* 返回值:
* - 如果成功进行转换,返回转换后的扇区数。
* - 如果溢出或其他错误,返回UINT_MAX。
*/
static inline u32 nvme_mps_to_sectors(struct nvme_ctrl *ctrl, u32 units)
{
u32 page_shift = NVME_CAP_MPSMIN(ctrl->cap) + 12, val;
if (check_shl_overflow(1U, units + page_shift - 9, &val))
return UINT_MAX;
return val;
}
/*
* 总结: 这个函数用于将NVMe控制器的多个页面大小(MPS)值转换为对应的扇区数。
* 函数首先计算出页面大小的位移值,然后通过将2的单位数与位移值相加,将多个页面大小
* 转换为扇区数。函数会检查是否发生溢出,如果没有溢出,则返回转换后的扇区数。
* 否则,返回UINT_MAX表示转换失败。
*/
nvme_init_non_mdts_limits
/*
* 函数名称: nvme_init_non_mdts_limits
* 说明: 初始化NVMe控制器的非MDTS限制。该函数用于初始化NVMe控制器的非多队列线性写(MDTS)相关的限制,包括丢弃(discard)和写零(write-zeroes)操作的相关限制。
* 参数:
* - ctrl: 指向NVMe控制器结构的指针,表示要初始化非MDTS限制的控制器。
* 返回值:
* - 如果成功初始化非MDTS限制,返回0。
* - 如果发生错误,返回负数错误码。
*/
static int nvme_init_non_mdts_limits(struct nvme_ctrl *ctrl)
{
struct nvme_command c = { };
struct nvme_id_ctrl_nvm *id;
int ret;
if (ctrl->oncs & NVME_CTRL_ONCS_DSM) {
ctrl->max_discard_sectors = UINT_MAX;
ctrl->max_discard_segments = NVME_DSM_MAX_RANGES;
} else {
ctrl->max_discard_sectors = 0;
ctrl->max_discard_segments = 0;
}
/*
* 即使NVMe规范明确指出MDTS不适用于写零操作,我们仍然谨慎地将大小限制为
* 控制器的max_hw_sectors值,该值基于MDTS字段和可能的其他限制因素。
*/
if ((ctrl->oncs & NVME_CTRL_ONCS_WRITE_ZEROES) &&
!(ctrl->quirks & NVME_QUIRK_DISABLE_WRITE_ZEROES))
ctrl->max_zeroes_sectors = ctrl->max_hw_sectors;
else
ctrl->max_zeroes_sectors = 0;
if (ctrl->subsys->subtype != NVME_NQN_NVME ||
nvme_ctrl_limited_cns(ctrl) ||
test_bit(NVME_CTRL_SKIP_ID_CNS_CS, &ctrl->flags))
return 0;
id = kzalloc(sizeof(*id), GFP_KERNEL);
if (!id)
return -ENOMEM;
c.identify.opcode = nvme_admin_identify;
c.identify.cns = NVME_ID_CNS_CS_CTRL;
c.identify.csi = NVME_CSI_NVM;
ret = nvme_submit_sync_cmd(ctrl->admin_q, &c, id, sizeof(*id));
if (ret)
goto free_data;
if (id->dmrl)
ctrl->max_discard_segments = id->dmrl;
ctrl->dmrsl = le32_to_cpu(id->dmrsl);
if (id->wzsl)
ctrl->max_zeroes_sectors = nvme_mps_to_sectors(ctrl, id->wzsl);
free_data:
if (ret > 0)
set_bit(NVME_CTRL_SKIP_ID_CNS_CS, &ctrl->flags);
kfree(id);
return ret;
}
/*
* 总结: 这个函数用于初始化NVMe控制器的非多队列线性写(MDTS)相关的限制,包括丢弃和写零操作的相关限制。
* 函数首先根据控制器的支持情况设置丢弃和写零的最大扇区数和段数。然后,如果控制器满足一定条件,
* 函数将发送identify命令获取控制器的相关信息,并更新相应的限制值。函数可能会根据返回的错误码
* 更新控制器的flags位,最后释放分配的内存并返回。
*/
nvme_init_known_nvm_effects
/*
* 函数名称: nvme_init_known_nvm_effects
* 说明: 初始化已知的NVM效果。该函数用于初始化已知的NVM效果,包括命令的效果属性(ACS和IOCS)。
* 参数:
* - ctrl: 指向NVMe控制器结构的指针,表示要初始化已知NVM效果的控制器。
*/
static void nvme_init_known_nvm_effects(struct nvme_ctrl *ctrl)
{
struct nvme_effects_log *log = ctrl->effects;
log->acs[nvme_admin_format_nvm] |= cpu_to_le32(NVME_CMD_EFFECTS_LBCC |
NVME_CMD_EFFECTS_NCC |
NVME_CMD_EFFECTS_CSE_MASK);
log->acs[nvme_admin_sanitize_nvm] |= cpu_to_le32(NVME_CMD_EFFECTS_LBCC |
NVME_CMD_EFFECTS_CSE_MASK);
/*
* 规范指出安全接收命令的结果取决于先前的安全发送命令。
* 因此,许多厂商将此命令记录为仅在没有向同一命名空间提交其他命令时才提交的命令。
* 这是为了告诉主机防止安全发送和接收的混合。
*
* 但这个驱动程序只能对IO队列执行这种排他访问,对于管理队列,
* 我们无法强制执行这个规则,因此将其屏蔽掉。
*/
log->acs[nvme_admin_security_recv] &= cpu_to_le32(~NVME_CMD_EFFECTS_CSE_MASK);
log->iocs[nvme_cmd_write] |= cpu_to_le32(NVME_CMD_EFFECTS_LBCC);
log->iocs[nvme_cmd_write_zeroes] |= cpu_to_le32(NVME_CMD_EFFECTS_LBCC);
log->iocs[nvme_cmd_write_uncor] |= cpu_to_le32(NVME_CMD_EFFECTS_LBCC);
}
/*
* 总结: 这个函数用于初始化已知的NVM效果,包括命令的效果属性(ACS和IOCS)。
* 函数通过更新NVMe效果日志结构中的相应属性位来指定命令的效果。特别地,
* 初始化了format、sanitize、security_recv、write、write_zeroes和write_uncor命令的效果。
* 这些效果可能包括在命令中的一些属性标志,如LBCC(Lockdown Behavior Change Control)和CSE(Command Security Effects)。
*/
nvme_init_effects
/*
* 函数名称: nvme_init_effects
* 说明: 初始化NVMe效果。该函数用于初始化NVMe控制器的效果属性,包括获取效果日志并设置已知的NVM效果。
* 参数:
* - ctrl: 指向NVMe控制器结构的指针,表示要初始化效果属性的控制器。
* - id: 指向NVMe控制器ID信息结构的指针,用于判断是否支持效果日志。
* 返回值:
* - 如果成功初始化效果属性,返回0。
* - 如果发生错误,返回负数错误码。
*/
static int nvme_init_effects(struct nvme_ctrl *ctrl, struct nvme_id_ctrl *id)
{
int ret = 0;
if (ctrl->effects)
return 0;
if (id->lpa & NVME_CTRL_LPA_CMD_EFFECTS_LOG) {
ret = nvme_get_effects_log(ctrl, NVME_CSI_NVM, &ctrl->effects);
if (ret < 0)
return ret;
}
if (!ctrl->effects) {
ctrl->effects = kzalloc(sizeof(*ctrl->effects), GFP_KERNEL);
if (!ctrl->effects)
return -ENOMEM;
xa_store(&ctrl->cels, NVME_CSI_NVM, ctrl->effects, GFP_KERNEL);
}
nvme_init_known_nvm_effects(ctrl);
return 0;
}
/*
* 总结: 这个函数用于初始化NVMe控制器的效果属性,包括获取效果日志并设置已知的NVM效果。
* 函数首先检查控制器是否已经初始化了效果属性,如果已经初始化,则直接返回0。
* 如果支持效果日志,函数将尝试获取效果日志数据并存储在控制器的效果属性中。
* 如果未能获取到效果日志,函数将分配内存用于存储效果属性,并将其存储在控制器的效果日志缓存中。
* 接着,函数调用nvme_init_known_nvm_effects函数设置已知的NVM效果属性。
* 最后,函数返回0表示成功初始化效果属性,否则返回负数错误码。
*/
nvme_init_identify
/*
* 函数名称: nvme_init_identify
* 说明: 初始化NVMe控制器的识别信息。该函数用于从控制器的识别数据中提取和设置各种属性。
* 参数:
* - ctrl: 指向NVMe控制器结构的指针,表示要初始化的控制器。
* 返回值:
* - 如果成功初始化识别信息,返回0。
* - 如果发生错误,返回负数错误码。
*/
static int nvme_init_identify(struct nvme_ctrl *ctrl)
{
struct nvme_id_ctrl *id;
u32 max_hw_sectors;
bool prev_apst_enabled;
int ret;
ret = nvme_identify_ctrl(ctrl, &id);
if (ret) {
dev_err(ctrl->device, "Identify Controller failed (%d)\n", ret);
return -EIO;
}
if (!(ctrl->ops->flags & NVME_F_FABRICS))
ctrl->cntlid = le16_to_cpu(id->cntlid);
if (!ctrl->identified) {
unsigned int i;
for (i = 0; i < ARRAY_SIZE(core_quirks); i++) {
if (quirk_matches(id, &core_quirks[i]))
ctrl->quirks |= core_quirks[i].quirks;
}
ret = nvme_init_subsystem(ctrl, id);
if (ret)
goto out_free;
ret = nvme_init_effects(ctrl, id);
if (ret)
goto out_free;
}
memcpy(ctrl->subsys->firmware_rev, id->fr,
sizeof(ctrl->subsys->firmware_rev));
// ...(省略了部分属性设置)
if (ctrl->ops->flags & NVME_F_FABRICS) {
ctrl->icdoff = le16_to_cpu(id->icdoff);
ctrl->ioccsz = le32_to_cpu(id->ioccsz);
ctrl->iorcsz = le32_to_cpu(id->iorcsz);
ctrl->maxcmd = le16_to_cpu(id->maxcmd);
if (ctrl->cntlid != le16_to_cpu(id->cntlid)) {
dev_err(ctrl->device,
"Mismaching cntlid: Connect %u vs Identify "
"%u, rejecting\n",
ctrl->cntlid, le16_to_cpu(id->cntlid));
ret = -EINVAL;
goto out_free;
}
if (!nvme_discovery_ctrl(ctrl) && !ctrl->kas) {
dev_err(ctrl->device,
"keep-alive support is mandatory for fabrics\n");
ret = -EINVAL;
goto out_free;
}
} else {
ctrl->hmpre = le32_to_cpu(id->hmpre);
ctrl->hmmin = le32_to_cpu(id->hmmin);
ctrl->hmminds = le32_to_cpu(id->hmminds);
ctrl->hmmaxd = le16_to_cpu(id->hmmaxd);
}
ret = nvme_mpath_init_identify(ctrl, id);
if (ret < 0)
goto out_free;
if (ctrl->apst_enabled && !prev_apst_enabled)
dev_pm_qos_expose_latency_tolerance(ctrl->device);
else if (!ctrl->apst_enabled && prev_apst_enabled)
dev_pm_qos_hide_latency_tolerance(ctrl->device);
out_free:
kfree(id);
return ret;
}
/*
* 总结: 这个函数用于初始化NVMe控制器的识别信息,从控制器的识别数据中提取和设置各种属性。
* 函数首先调用nvme_identify_ctrl函数获取控制器的识别数据,并进行错误检查。
* 如果控制器不是Fabrics类型,函数设置控制器的cntlid属性。
* 如果控制器还未被识别,函数根据核心quirks数组匹配控制器的属性。
* 接着,函数调用nvme_init_subsystem和nvme_init_effects初始化控制器的子系统和效果属性。
* 然后,函数设置控制器的各种属性,如firmware_rev、oacs、oncs等等。
* 最后,函数根据控制器的类型设置不同的属性,如果是Fabrics类型则设置相应的属性,否则设置其他属性。
* 如果函数执行过程中发生错误,将释放已分配的内存并返回负数错误码,否则返回0表示成功初始化识别信息。
*/
nvme_init_ctrl_finish
/*
* 函数名称: nvme_init_ctrl_finish
* 说明: 在Admin队列完全启动和运行后,初始化NVMe控制器的缓存副本的识别数据和各种控制器寄存器。
* 参数:
* - ctrl: 指向NVMe控制器结构的指针,表示要初始化的控制器。
* - was_suspended: 表示控制器是否在挂起状态下。
* 返回值:
* - 如果成功完成控制器的初始化,返回0。
* - 如果发生错误,返回负数错误码。
*/
int nvme_init_ctrl_finish(struct nvme_ctrl *ctrl, bool was_suspended)
{
int ret;
ret = ctrl->ops->reg_read32(ctrl, NVME_REG_VS, &ctrl->vs);
if (ret) {
dev_err(ctrl->device, "Reading VS failed (%d)\n", ret);
return ret;
}
ctrl->sqsize = min_t(u16, NVME_CAP_MQES(ctrl->cap), ctrl->sqsize);
if (ctrl->vs >= NVME_VS(1, 1, 0))
ctrl->subsystem = NVME_CAP_NSSRC(ctrl->cap);
ret = nvme_init_identify(ctrl);
if (ret)
return ret;
ret = nvme_configure_apst(ctrl);
if (ret < 0)
return ret;
ret = nvme_configure_timestamp(ctrl);
if (ret < 0)
return ret;
ret = nvme_configure_host_options(ctrl);
if (ret < 0)
return ret;
nvme_configure_opal(ctrl, was_suspended);
if (!ctrl->identified && !nvme_discovery_ctrl(ctrl)) {
/*
* Do not return errors unless we are in a controller reset,
* the controller works perfectly fine without hwmon.
*/
ret = nvme_hwmon_init(ctrl);
if (ret == -EINTR)
return ret;
}
clear_bit(NVME_CTRL_DIRTY_CAPABILITY, &ctrl->flags);
ctrl->identified = true;
return 0;
}
/*
* 总结: 该函数用于初始化NVMe控制器,完成控制器的各种属性设置和配置工作。首先,函数读取控制器的版本寄存器值,并检查支持的命令队列大小。
* 如果控制器的版本号大于等于1.1.0,则设置子系统属性。接着,函数调用nvme_init_identify初始化识别数据,然后依次调用
* nvme_configure_apst、nvme_configure_timestamp、nvme_configure_host_options配置各种选项。
* 最后,如果控制器还未被识别且不是发现控制器,则初始化硬件监控(hwmon)模块。函数最后清除标志位,标志控制器已被识别。
* 如果函数执行过程中发生错误,将返回负数错误码,否则返回0表示成功初始化控制器。
*/
nvme_dev_open
/*
* 函数名称: nvme_dev_open
* 说明: 打开NVMe设备文件时调用的函数。该函数执行设备的初始化和状态检查操作,确保设备状态处于可用状态。
* 参数:
* - inode: 指向表示设备文件的inode结构的指针。
* - file: 指向表示打开的文件的file结构的指针。
* 返回值:
* - 如果设备处于可用状态,返回0。
* - 如果设备状态不允许打开,返回-EWOULDBLOCK(设备忙)。
* - 如果在尝试获取设备模块时出现问题,返回-EINVAL(无效参数)。
*/
static int nvme_dev_open(struct inode *inode, struct file *file)
{
struct nvme_ctrl *ctrl =
container_of(inode->i_cdev, struct nvme_ctrl, cdev);
switch (ctrl->state) {
case NVME_CTRL_LIVE:
break;
default:
return -EWOULDBLOCK;
}
nvme_get_ctrl(ctrl); // 增加对控制器的引用计数
if (!try_module_get(ctrl->ops->module)) {
nvme_put_ctrl(ctrl); // 减少对控制器的引用计数
return -EINVAL;
}
file->private_data = ctrl; // 将控制器指针存储在私有数据中
return 0;
}
/*
* 总结: 该函数用于打开NVMe设备文件时执行初始化和状态检查操作。首先,它从inode中获取控制器结构,并检查控制器的状态。
* 如果控制器处于NVME_CTRL_LIVE状态(即可用状态),则继续执行。否则,返回-EWOULDBLOCK表示设备忙。
* 接着,函数增加对控制器的引用计数,然后尝试获取控制器的模块。如果成功获取模块,则将控制器指针存储在私有数据中,并返回0表示成功打开设备。
* 如果尝试获取模块失败,则减少对控制器的引用计数并返回-EINVAL表示无效参数。
*/
nvme_dev_release
/*
* 函数名称: nvme_dev_release
* 说明: 关闭NVMe设备文件时调用的函数。该函数执行对控制器的引用计数和模块计数的操作,以释放资源。
* 参数:
* - inode: 指向表示设备文件的inode结构的指针。
* - file: 指向表示被关闭的文件的file结构的指针。
* 返回值: 始终返回0,表示成功完成。
*/
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;
}
/*
* 总结: 该函数用于关闭NVMe设备文件时执行资源释放操作。首先,它从inode中获取控制器结构,并调用module_put函数减少对控制器模块的引用计数,
* 这表示释放控制器所使用的内核模块。接着,函数调用nvme_put_ctrl减少对控制器的引用计数,这会逐步释放控制器的资源。
* 最后,函数始终返回0,表示成功完成资源释放。
*/
nvme_dev_fops
/*
* 结构体名称: nvme_dev_fops
* 说明: 定义了NVMe设备文件的操作函数,包括设备文件的打开、释放、IO控制等操作。
*/
static const struct file_operations nvme_dev_fops = {
.owner = THIS_MODULE, // 设定模块的拥有者
.open = nvme_dev_open, // 设备文件的打开函数
.release = nvme_dev_release, // 设备文件的释放函数
.unlocked_ioctl = nvme_dev_ioctl, // 设备文件的非加锁IO控制函数
.compat_ioctl = compat_ptr_ioctl, // 设备文件的兼容IO控制函数
.uring_cmd = nvme_dev_uring_cmd, // 设备文件的uring命令处理函数
};
/*
* 总结: 这个结构体定义了NVMe设备文件的操作函数,包括打开、释放、IO控制等操作。每个字段都指向相应的函数实现。这个结构体会被用于注册设备文件操作。
*/
这个结构体定义了一系列操作函数,这些函数在NVMe设备文件的操作期间会被调用。这些操作包括打开设备文件、释放设备文件、处理IO控制命令等。其中,.owner
字段指定了该文件操作结构体所属的内核模块,.open
和 .release
字段分别指定了打开和释放设备文件时调用的函数,.unlocked_ioctl
和 .compat_ioctl
字段指定了处理IO控制命令时调用的函数,.uring_cmd
字段指定了处理uring命令时调用的函数。
nvme_find_ns_head
函数 nvme_find_ns_head
,用于在给定的NVMe控制器(ctrl
)下,通过指定的命名空间ID(nsid
)查找并返回对应的命名空间头(nvme_ns_head
)。
/*
* 函数名称: nvme_find_ns_head
* 函数参数: ctrl - NVMe控制器结构体指针
* nsid - 命名空间ID
* 返回值: 成功 - 对应的命名空间头指针
* 失败 - NULL
* 说明: 在给定的NVMe控制器下,通过命名空间ID查找对应的命名空间头。
*/
static struct nvme_ns_head *nvme_find_ns_head(struct nvme_ctrl *ctrl, unsigned nsid)
{
struct nvme_ns_head *h;
lockdep_assert_held(&ctrl->subsys->lock); // 确保调用此函数时已持有控制器所属子系统的锁
list_for_each_entry(h, &ctrl->subsys->nsheads, entry) {
// 私有命名空间在特定条件下可能共享相同的NSID。在这种情况下,不能使用相同的ns_head。
if (h->ns_id != nsid || !nvme_is_unique_nsid(ctrl, h))
continue;
if (!list_empty(&h->list) && nvme_tryget_ns_head(h)) // 尝试获取命名空间头的引用计数
return h; // 返回找到的命名空间头
}
return NULL; // 未找到匹配的命名空间头,返回NULL
}
/*
* 总结: 这个函数在给定的NVMe控制器下,通过指定的命名空间ID查找对应的命名空间头。函数会遍历控制器所属子系统的命名空间头列表,
* 并根据条件判断是否匹配目标命名空间头。如果找到匹配的命名空间头且引用计数尝试获取成功,函数会返回该命名空间头;
* 否则,返回NULL。
*/
该函数通过遍历指定控制器所属子系统的命名空间头列表,根据条件匹配目标命名空间头,并尝试获取命名空间头的引用计数。如果找到匹配的命名空间头且引用计数获取成功,函数会返回该命名空间头;否则,返回NULL。这个函数在处理NVMe设备的命名空间时起到重要作用,用于在控制器中查找特定命名空间的头部。