nvme_disable_ctrl
/**
* nvme_disable_ctrl - 禁用NVMe控制器
* @ctrl: NVMe控制器的结构体指针
* @shutdown: 是否执行关机操作
*
* 此函数用于禁用NVMe控制器,根据 @shutdown 参数的值执行不同的操作。
* 如果 @shutdown 为 true,则将控制器配置中的关机选项设置为正常关机,并等待控制器完成关机。
* 如果 @shutdown 为 false,则将控制器配置中的启用选项清除,然后等待控制器重置为就绪状态。
*
* 参数:
* @ctrl: NVMe控制器的结构体指针
* @shutdown: 是否执行关机操作
*
* 返回值:
* 成功返回0,否则返回错误码
*/
int nvme_disable_ctrl(struct nvme_ctrl *ctrl, bool shutdown)
{
int ret;
/* 清除关机选项,根据 @shutdown 设置控制器的配置 */
ctrl->ctrl_config &= ~NVME_CC_SHN_MASK;
if (shutdown)
ctrl->ctrl_config |= NVME_CC_SHN_NORMAL;
else
ctrl->ctrl_config &= ~NVME_CC_ENABLE;
/* 更新控制器的配置寄存器 */
ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
if (ret)
return ret;
/* 如果需要关机,等待关机完成 */
if (shutdown) {
return nvme_wait_ready(ctrl, NVME_CSTS_SHST_MASK,
NVME_CSTS_SHST_CMPLT,
ctrl->shutdown_timeout, "shutdown");
}
/* 如果控制器有延迟等待特性,执行延迟等待 */
if (ctrl->quirks & NVME_QUIRK_DELAY_BEFORE_CHK_RDY)
msleep(NVME_QUIRK_DELAY_AMOUNT);
/* 等待控制器重置为就绪状态 */
return nvme_wait_ready(ctrl, NVME_CSTS_RDY, 0,
(NVME_CAP_TIMEOUT(ctrl->cap) + 1) / 2, "reset");
}
EXPORT_SYMBOL_GPL(nvme_disable_ctrl);
该函数用于禁用NVMe控制器,并根据参数来执行关机操作或重置操作。在禁用过程中,会根据控制器的配置进行相应的设置,并等待相关操作完成。函数提供了对控制器状态的轮询监测,以确保操作的完成或超时处理。最终返回值表示操作是否成功。
nvme_enable_ctrl
/**
* nvme_enable_ctrl - 启用NVMe控制器
* @ctrl: NVMe控制器的结构体指针
*
* 此函数用于启用NVMe控制器,根据控制器的能力和配置设置控制器的参数,然后等待控制器初始化完成。
*
* 参数:
* @ctrl: NVMe控制器的结构体指针
*
* 返回值:
* 成功返回0,否则返回错误码
*/
int nvme_enable_ctrl(struct nvme_ctrl *ctrl)
{
unsigned dev_page_min;
u32 timeout;
int ret;
/* 读取控制器能力寄存器值 */
ret = ctrl->ops->reg_read64(ctrl, NVME_REG_CAP, &ctrl->cap);
if (ret) {
dev_err(ctrl->device, "Reading CAP failed (%d)\n", ret);
return ret;
}
dev_page_min = NVME_CAP_MPSMIN(ctrl->cap) + 12;
/* 检查主机页大小是否足够支持设备要求 */
if (NVME_CTRL_PAGE_SHIFT < dev_page_min) {
dev_err(ctrl->device,
"Minimum device page size %u too large for host (%u)\n",
1 << dev_page_min, 1 << NVME_CTRL_PAGE_SHIFT);
return -ENODEV;
}
/* 根据控制器的能力和配置设置控制器的参数 */
if (NVME_CAP_CSS(ctrl->cap) & NVME_CAP_CSS_CSI)
ctrl->ctrl_config = NVME_CC_CSS_CSI;
else
ctrl->ctrl_config = NVME_CC_CSS_NVM;
if (ctrl->cap & NVME_CAP_CRMS_CRWMS) {
u32 crto;
ret = ctrl->ops->reg_read32(ctrl, NVME_REG_CRTO, &crto);
if (ret) {
dev_err(ctrl->device, "Reading CRTO failed (%d)\n",
ret);
return ret;
}
if (ctrl->cap & NVME_CAP_CRMS_CRIMS) {
ctrl->ctrl_config |= NVME_CC_CRIME;
timeout = NVME_CRTO_CRIMT(crto);
} else {
timeout = NVME_CRTO_CRWMT(crto);
}
} else {
timeout = NVME_CAP_TIMEOUT(ctrl->cap);
}
ctrl->ctrl_config |= (NVME_CTRL_PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
ctrl->ctrl_config |= NVME_CC_AMS_RR | NVME_CC_SHN_NONE;
ctrl->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;
/* 更新控制器的配置寄存器 */
ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
if (ret)
return ret;
/* 刷新设备上的写操作(对于PCI传输是必需的) */
ret = ctrl->ops->reg_read32(ctrl, NVME_REG_CC, &ctrl->ctrl_config);
if (ret)
return ret;
/* 设置启用控制器并等待控制器初始化完成 */
ctrl->ctrl_config |= NVME_CC_ENABLE;
ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
if (ret)
return ret;
return nvme_wait_ready(ctrl, NVME_CSTS_RDY, NVME_CSTS_RDY,
(timeout + 1) / 2, "initialisation");
}
EXPORT_SYMBOL_GPL(nvme_enable_ctrl);
该函数用于启用NVMe控制器,根据控制器的能力和配置设置控制器的参数,然后等待控制器初始化完成。函数首先读取控制器的能力寄存器值,检查主机页大小是否满足设备要求。然后,根据控制器的能力和配置设置控制器的配置寄存器,并执行一些必要的操作来确保配置的刷新。接着,函数设置控制器为启用状态,并等待控制器初始化完成。最终返回值表示操作是否成功
nvme_configure_timestamp
/**
* nvme_configure_timestamp - 配置NVMe控制器时间戳
* @ctrl: NVMe控制器的结构体指针
*
* 此函数用于配置NVMe控制器的时间戳特性。如果控制器支持时间戳特性,则将当前实时时间设置为时间戳的值。
*
* 参数:
* @ctrl: NVMe控制器的结构体指针
*
* 返回值:
* 成功返回0,否则返回错误码
*/
static int nvme_configure_timestamp(struct nvme_ctrl *ctrl)
{
__le64 ts;
int ret;
/* 如果控制器不支持时间戳特性,则直接返回 */
if (!(ctrl->oncs & NVME_CTRL_ONCS_TIMESTAMP))
return 0;
/* 获取当前实时时间并转换为毫秒 */
ts = cpu_to_le64(ktime_to_ms(ktime_get_real()));
/* 设置时间戳特性的值为当前时间 */
ret = nvme_set_features(ctrl, NVME_FEAT_TIMESTAMP, 0, &ts, sizeof(ts),
NULL);
if (ret)
dev_warn_once(ctrl->device,
"could not set timestamp (%d)\n", ret);
return ret;
}
该函数用于配置NVMe控制器的时间戳特性。如果控制器支持时间戳特性,函数会获取当前实时时间并将其设置为时间戳的值。函数首先检查控制器是否支持时间戳特性,如果不支持则直接返回。如果支持,则获取当前实时时间并将其转换为毫秒单位,然后将该值设置为时间戳特性的值。最终函数返回值表示操作是否成功。如果设置时间戳失败,会发出一条警告信息。。
nvme_configure_host_options
/**
* nvme_configure_host_options - 配置NVMe控制器的主机选项
* @ctrl: NVMe控制器的结构体指针
*
* 此函数用于配置NVMe控制器的主机选项特性。根据控制器的属性,可能会启用自动重试延迟和LBA格式扩展特性。
*
* 参数:
* @ctrl: NVMe控制器的结构体指针
*
* 返回值:
* 成功返回0,否则返回错误码
*/
static int nvme_configure_host_options(struct nvme_ctrl *ctrl)
{
struct nvme_feat_host_behavior *host;
u8 acre = 0, lbafee = 0;
int ret;
/* 如果控制器未报告重试延迟,不启用自动重试延迟特性 */
if (ctrl->crdt[0])
acre = NVME_ENABLE_ACRE;
/* 如果控制器支持LBA格式扩展特性,启用LBA格式扩展特性 */
if (ctrl->ctratt & NVME_CTRL_ATTR_ELBAS)
lbafee = NVME_ENABLE_LBAFEE;
/* 如果没有需要启用的特性,直接返回 */
if (!acre && !lbafee)
return 0;
/* 分配用于设置主机选项特性的内存空间 */
host = kzalloc(sizeof(*host), GFP_KERNEL);
if (!host)
return 0;
/* 配置自动重试延迟和LBA格式扩展特性的值 */
host->acre = acre;
host->lbafee = lbafee;
/* 设置主机选项特性 */
ret = nvme_set_features(ctrl, NVME_FEAT_HOST_BEHAVIOR, 0,
host, sizeof(*host), NULL);
kfree(host);
return ret;
}
该函数用于配置NVMe控制器的主机选项特性。函数根据控制器的属性决定是否启用自动重试延迟和LBA格式扩展特性。首先,函数检查控制器是否报告了重试延迟(ctrl->crdt[0]
是否非零),如果报告了则启用自动重试延迟特性(acre
设置为1)。然后,函数检查控制器是否支持LBA格式扩展特性(ctrl->ctratt & NVME_CTRL_ATTR_ELBAS
是否非零),如果支持则启用LBA格式扩展特性(lbafee
设置为1)。如果没有需要启用的特性,函数直接返回。接着,函数分配内存空间用于设置主机选项特性,然后将自动重试延迟和LBA格式扩展特性的值配置到内存中。最后,函数调用nvme_set_features
来设置主机选项特性。函数返回值表示操作是否成功。如果设置主机选项失败,会释放之前分配的内存并返回。
nvme_apst_get_transition_time
/**
* nvme_apst_get_transition_time - 检查给定的总延迟是否允许用作APST过渡目标
* @total_latency: 给定的总延迟(exlat + enlat)
* @transition_time: 输出参数,用于存储过渡时间
* @last_index: 输入/输出参数,上一个匹配的容忍度索引
*
* 该函数通过比较给定的总延迟(exlat + enlat)与模块参数定义的主要和次要延迟容忍度,来判断是否允许将后者用作APST过渡目标。
* 如果匹配成功,将返回相应的超时值,并报告匹配的容忍度索引(1或2)。
*
* 参数:
* @total_latency: 给定的总延迟(exlat + enlat)
* @transition_time: 输出参数,用于存储过渡时间
* @last_index: 输入/输出参数,上一个匹配的容忍度索引
*
* 返回值:
* 如果允许将给定的总延迟用作APST过渡目标,返回true;否则返回false
*/
static bool nvme_apst_get_transition_time(u64 total_latency,
u64 *transition_time, unsigned *last_index)
{
// 检查总延迟是否小于等于主要延迟容忍度
if (total_latency <= apst_primary_latency_tol_us) {
// 如果上一个匹配的容忍度索引是1,返回false
if (*last_index == 1)
return false;
// 更新上一个匹配的容忍度索引为1
*last_index = 1;
// 将主要超时值赋值给过渡时间,并返回true
*transition_time = apst_primary_timeout_ms;
return true;
}
// 检查次要超时值是否不为零,且总延迟小于等于次要延迟容忍度
if (apst_secondary_timeout_ms &&
total_latency <= apst_secondary_latency_tol_us) {
// 如果上一个匹配的容忍度索引小于等于2,返回false
if (*last_index <= 2)
return false;
// 更新上一个匹配的容忍度索引为2
*last_index = 2;
// 将次要超时值赋值给过渡时间,并返回true
*transition_time = apst_secondary_timeout_ms;
return true;
}
// 若都不满足条件,则返回false
return false;
}
这个函数用于检查给定的总延迟(exlat + enlat)是否允许将一个电源状态用作APST过渡目标。函数首先比较总延迟与模块参数中定义的主要延迟容忍度,如果小于等于主要容忍度,则判断上一个匹配的容忍度索引是否为1,如果是则返回false,否则将上一个匹配的容忍度索引更新为1,将主要超时值赋值给过渡时间,并返回true。
如果总延迟不满足主要延迟容忍度条件,函数会继续检查次要超时值是否不为零且总延迟小于等于次要延迟容忍度。如果满足次要容忍度条件,则判断上一个匹配的容忍度索引是否小于等于2,如果是则返回false,否则将上一个匹配的容忍度索引更新为2,将次要超时值赋值给过渡时间,并返回true。
如果总延迟既不满足主要容忍度条件,也不满足次要容忍度条件,则函数返回false。
nvme_configure_apst
/*
* nvme_configure_apst - 配置APST(Autonomous Power State Transition)
* @ctrl: NVMe控制器结构体指针
*
* 该函数用于配置APST(自主电源状态转换)。APST允许我们编程一张电源状态转换表,控制器会自动执行这些转换。
*
* 根据模块参数的设置,将使用以下两种支持的技术之一:
*
* - 如果参数提供了显式的超时和容忍度,将使用这些参数构建一个具有最多2个非操作状态的转换表。默认的参数值是基于Microsoft和Intel的NVMe驱动程序使用的值选择的。
* 由于我们不实现在切换外部电源和电池电源之间动态重建APST表,因此超时和容忍度反映了在交流电和电池场景下的值之间的折中。
* - 如果没有提供参数,将使用简单的启发式方法来配置表:我们愿意在电源状态之间的转换中最多花费总时间的2%。
* 因此,在运行在给定状态时,我们将在等待50 *(enlat + exlat)微秒后进入下一个较低功率的非操作状态,只要该状态的退出延迟低于所请求的最大延迟。
*
* 我们不会自动进入总延迟超过ps_max_latency_us的任何非操作状态。
*
* 用户可以将ps_max_latency_us设置为零来关闭APST。
*/
static int nvme_configure_apst(struct nvme_ctrl *ctrl)
{
struct nvme_feat_auto_pst *table;
unsigned apste = 0;
u64 max_lat_us = 0;
__le64 target = 0;
int max_ps = -1;
int state;
int ret;
unsigned last_lt_index = UINT_MAX;
/*
* 如果APST不受支持或者我们尚未初始化,不进行任何操作。
*/
if (!ctrl->apsta)
return 0;
if (ctrl->npss > 31) {
dev_warn(ctrl->device, "NPSS is invalid; not using APST\n");
return 0;
}
table = kzalloc(sizeof(*table), GFP_KERNEL);
if (!table)
return 0;
if (!ctrl->apst_enabled || ctrl->ps_max_latency_us == 0) {
/* 关闭APST。 */
dev_dbg(ctrl->device, "APST disabled\n");
goto done;
}
/*
* 从低到高遍历所有状态。
* 根据规范,较低的状态使用更多的功率。NPSS,尽管名称如此,是最低功率状态的索引,而不是状态数量。
*/
for (state = (int)ctrl->npss; state >= 0; state--) {
u64 total_latency_us, exit_latency_us, transition_ms;
if (target)
table->entries[state] = target;
/*
* 不允许转换到最深的状态,如果该状态被禁用。
*/
if (state == ctrl->npss &&
(ctrl->quirks & NVME_QUIRK_NO_DEEPEST_PS))
continue;
/*
* 这个状态是否是更高功率状态自主转换的有用的非操作状态?
*/
if (!(ctrl->psd[state].flags & NVME_PS_FLAGS_NON_OP_STATE))
continue;
exit_latency_us = (u64)le32_to_cpu(ctrl->psd[state].exit_lat);
if (exit_latency_us > ctrl->ps_max_latency_us)
continue;
total_latency_us = exit_latency_us +
le32_to_cpu(ctrl->psd[state].entry_lat);
/*
* 这个状态是好的。它可以作为更高功率状态的APST空闲目标。
*/
if (apst_primary_timeout_ms && apst_primary_latency_tol_us) {
if (!nvme_apst_get_transition_time(total_latency_us,
&transition_ms, &last_lt_index))
continue;
} else {
transition_ms = total_latency_us + 19;
do_div(transition_ms, 20);
if (transition_ms > (1 << 24) - 1)
transition_ms = (1 << 24) - 1;
}
target = cpu_to_le64((state << 3) | (transition_ms << 8));
if (max_ps == -1)
max_ps = state;
if (total_latency_us > max_lat_us)
max_lat_us = total_latency_us;
}
if (max_ps == -1)
dev_dbg(ctrl->device, "APST enabled but no non-operational states are available\n");
else
dev_dbg(ctrl->device, "APST enabled: max PS = %d, max round-trip latency = %lluus, table = %*phN\n",
max_ps, max_lat_us, (int)sizeof(*table), table);
apste = 1;
done:
ret = nvme_set_features(ctrl, NVME_FEAT_AUTO_PST, apste,
table, sizeof(*table), NULL);
if (ret)
dev_err(ctrl->device, "failed to set APST feature (%d)\n", ret);
kfree(table);
return ret;
}
nvme_set_latency_tolerance
/*
* nvme_set_latency_tolerance - 设置延迟容忍度
* @dev: 设备结构体指针
* @val: 延迟容忍度的值
*
* 该函数用于设置延迟容忍度,以影响APST(自主电源状态转换)的配置。
* 根据延迟容忍度的不同值,函数将相应地设置控制器的ps_max_latency_us字段,
* 并在控制器处于NVME_CTRL_LIVE状态时重新配置APST。
*/
static void nvme_set_latency_tolerance(struct device *dev, s32 val)
{
struct nvme_ctrl *ctrl = dev_get_drvdata(dev);
u64 latency;
switch (val) {
case PM_QOS_LATENCY_TOLERANCE_NO_CONSTRAINT:
case PM_QOS_LATENCY_ANY:
latency = U64_MAX;
break;
default:
latency = val;
}
if (ctrl->ps_max_latency_us != latency) {
ctrl->ps_max_latency_us = latency;
if (ctrl->state == NVME_CTRL_LIVE)
nvme_configure_apst(ctrl);
}
}
nvme_core_quirk_entry
/*
* struct nvme_core_quirk_entry - NVMe控制器的特性修复表项
* @vid: 设备的供应商ID
* @mn: 设备的型号名称(字符串)
* @fr: 设备的固件版本(字符串)
* @quirks: 特性修复标志位
*
* 该结构定义了一个NVMe控制器的特性修复表项,用于修复特定设备的问题或配置特性。
* 该表项包含了供应商ID、型号名称、固件版本和特性修复标志位等信息。
*/
struct nvme_core_quirk_entry {
u16 vid;
const char *mn;
const char *fr;
unsigned long quirks;
};
core_quirks
/*
* core_quirks - NVMe控制器特性修复表
*
* 该表包含了一些特定NVMe控制器的特性修复项,用于解决特定设备的问题或配置特性。
*/
static const struct nvme_core_quirk_entry core_quirks[] = {
{
/* Toshiba设备似乎在使用任何APST状态时会出现问题。
* 详情请参考:https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1678184/comments/11 */
.vid = 0x1179,
.mn = "THNSF5256GPUK TOSHIBA",
.quirks = NVME_QUIRK_NO_APST,
},
{
/* LiteON CL1-3D*-Q11固件版本存在与待机到空闲相关的竞争条件问题,
* 但LiteON已在未来的固件中解决了这个问题。 */
.vid = 0x14a4,
.fr = "22301111",
.quirks = NVME_QUIRK_SIMPLE_SUSPEND,
},
{
/* Kioxia CD6-V Series / HPE PE8030设备在任何负载下都会超时并中止I/O,
* 但通过修复APST状态禁用设备,使用"nvme set-feature"命令不起作用,
* 但使用nvme_core.default_ps_max_latency=0引导可以解决问题。 */
.vid = 0x1e0f,
.mn = "KCD6XVUL6T40",
.quirks = NVME_QUIRK_NO_APST,
},
{
/* 外部的Samsung X5 SSD在检查是否准备好之前需要一个延迟,
* 并且还有一系列其他问题。此外,它与不需要或不想要这些修复项的内部Samsung 970 Evo Plus共享PCI ID。 */
.vid = 0x144d,
.mn = "Samsung Portable SSD X5",
.quirks = NVME_QUIRK_DELAY_BEFORE_CHK_RDY |
NVME_QUIRK_NO_DEEPEST_PS |
NVME_QUIRK_IGNORE_DEV_SUBNQN,
}
};
总结:
core_quirks是一个用于存储NVMe控制器特性修复项的表。
每个修复项由设备的厂商ID、型号名称、固件版本号和特性修复标志位组成。
这些修复项用于解决特定设备的问题或应用特定的配置修复。
特性修复标志位定义了修复的类型,例如禁用APST状态、延迟检查设备是否准备好等。
string_matches
/*
- 函数名称: string_matches
- 说明: 比较两个字符串,其中match为以null结尾的字符串,而idstr为空格填充的字符串。
- 参数:
-
- idstr: 待比较的空格填充字符串
-
- match: 用于比较的以null结尾的字符串,如果为NULL,则认为匹配。
-
- len: idstr的长度
- 返回值:
-
- 如果match为NULL,则始终返回true。
-
- 如果match不为NULL,将比较idstr与match的前缀部分,如果相同,且idstr剩余部分为填充空格,则返回true,否则返回false。
*/
static bool string_matches(const char *idstr, const char *match, size_t len)
{
size_t matchlen;
if (!match)
return true;
matchlen = strlen(match);
WARN_ON_ONCE(matchlen > len);
if (memcmp(idstr, match, matchlen))
return false;
for (; matchlen < len; matchlen++)
if (idstr[matchlen] != ' ')
return false;
return true;
}
/*
- 总结: 这个函数用于比较两个字符串,其中一个字符串(match)是以null结尾的,另一个字符串(idstr)是在末尾填充了空格的。如果match为NULL,则认为匹配始终为true。如果match不为NULL,则比较idstr与match的前缀部分是否相同,且idstr剩余部分是否都是填充空格,如果是,则返回true,否则返回false。函数在比较过程中会确保matchlen不超过给定的len,并在某些条件下发出警告。
*/
quirk_matches
/*
- 函数名称: quirk_matches
- 说明: 检查给定的NVMe设备标识是否与给定的设备特殊情况条目匹配。
- 参数:
-
- id: 指向NVMe设备标识结构体的指针
-
- q: 指向NVMe核心特殊情况条目结构体的指针
- 返回值:
-
- 如果NVMe设备标识与设备特殊情况条目匹配,则返回true,否则返回false。
- 该函数用于检查给定的NVMe设备标识(id)是否与给定的设备特殊情况条目(q)匹配。匹配的条件为:
-
- 设备的厂商ID(vid)与特殊情况条目中的厂商ID匹配
-
- 设备的型号号(mn)与特殊情况条目中的型号号匹配(使用string_matches函数比较)
-
- 设备的固件版本号(fr)与特殊情况条目中的固件版本号匹配(使用string_matches函数比较)
*/
static bool quirk_matches(const struct nvme_id_ctrl *id,
const struct nvme_core_quirk_entry *q)
{
return q->vid == le16_to_cpu(id->vid) &&
string_matches(id->mn, q->mn, sizeof(id->mn)) &&
string_matches(id->fr, q->fr, sizeof(id->fr));
}
/*
- 总结: 这个函数用于检查给定的NVMe设备标识是否与给定的设备特殊情况条目匹配。函数会比较设备的厂商ID、型号号和固件版本号与特殊情况条目中的对应字段是否匹配。如果所有字段都匹配,则返回true,否则返回false。
*/