nvmf_parse_options
static int nvmf_parse_options(struct nvmf_ctrl_options *opts,
const char *buf)
{
substring_t args[MAX_OPT_ARGS];
char *options, *o, *p;
int token, ret = 0;
size_t nqnlen = 0;
int ctrl_loss_tmo = NVMF_DEF_CTRL_LOSS_TMO;
uuid_t hostid;
char hostnqn[NVMF_NQN_SIZE];
/* Set defaults */
opts->queue_size = NVMF_DEF_QUEUE_SIZE;
opts->nr_io_queues = num_online_cpus();
opts->reconnect_delay = NVMF_DEF_RECONNECT_DELAY;
opts->kato = 0;
opts->duplicate_connect = false;
opts->fast_io_fail_tmo = NVMF_DEF_FAIL_FAST_TMO;
opts->hdr_digest = false;
opts->data_digest = false;
opts->tos = -1; /* < 0 == use transport default */
options = o = kstrdup(buf, GFP_KERNEL);
if (!options)
return -ENOMEM;
/* use default host if not given by user space */
uuid_copy(&hostid, &nvmf_default_host->id);
strscpy(hostnqn, nvmf_default_host->nqn, NVMF_NQN_SIZE);
while ((p = strsep(&o, ",\n")) != NULL) {
if (!*p)
continue;
token = match_token(p, opt_tokens, args);
opts->mask |= token;
switch (token) {
case NVMF_OPT_TRANSPORT:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
kfree(opts->transport);
opts->transport = p;
break;
case NVMF_OPT_NQN:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
kfree(opts->subsysnqn);
opts->subsysnqn = p;
nqnlen = strlen(opts->subsysnqn);
if (nqnlen >= NVMF_NQN_SIZE) {
pr_err("%s needs to be < %d bytes\n",
opts->subsysnqn, NVMF_NQN_SIZE);
ret = -EINVAL;
goto out;
}
opts->discovery_nqn =
!(strcmp(opts->subsysnqn,
NVME_DISC_SUBSYS_NAME));
break;
case NVMF_OPT_TRADDR:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
kfree(opts->traddr);
opts->traddr = p;
break;
case NVMF_OPT_TRSVCID:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
kfree(opts->trsvcid);
opts->trsvcid = p;
break;
case NVMF_OPT_QUEUE_SIZE:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token < NVMF_MIN_QUEUE_SIZE ||
token > NVMF_MAX_QUEUE_SIZE) {
pr_err("Invalid queue_size %d\n", token);
ret = -EINVAL;
goto out;
}
opts->queue_size = token;
break;
case NVMF_OPT_NR_IO_QUEUES:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token <= 0) {
pr_err("Invalid number of IOQs %d\n", token);
ret = -EINVAL;
goto out;
}
if (opts->discovery_nqn) {
pr_debug("Ignoring nr_io_queues value for discovery controller\n");
break;
}
opts->nr_io_queues = min_t(unsigned int,
num_online_cpus(), token);
break;
case NVMF_OPT_KATO:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token < 0) {
pr_err("Invalid keep_alive_tmo %d\n", token);
ret = -EINVAL;
goto out;
} else if (token == 0 && !opts->discovery_nqn) {
/* Allowed for debug */
pr_warn("keep_alive_tmo 0 won't execute keep alives!!!\n");
}
opts->kato = token;
break;
case NVMF_OPT_CTRL_LOSS_TMO:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token < 0)
pr_warn("ctrl_loss_tmo < 0 will reconnect forever\n");
ctrl_loss_tmo = token;
break;
case NVMF_OPT_FAIL_FAST_TMO:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token >= 0)
pr_warn("I/O fail on reconnect controller after %d sec\n",
token);
else
token = -1;
opts->fast_io_fail_tmo = token;
break;
case NVMF_OPT_HOSTNQN:
if (opts->host) {
pr_err("hostnqn already user-assigned: %s\n",
opts->host->nqn);
ret = -EADDRINUSE;
goto out;
}
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
nqnlen = strlen(p);
if (nqnlen >= NVMF_NQN_SIZE) {
pr_err("%s needs to be < %d bytes\n",
p, NVMF_NQN_SIZE);
kfree(p);
ret = -EINVAL;
goto out;
}
strscpy(hostnqn, p, NVMF_NQN_SIZE);
kfree(p);
break;
case NVMF_OPT_RECONNECT_DELAY:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token <= 0) {
pr_err("Invalid reconnect_delay %d\n", token);
ret = -EINVAL;
goto out;
}
opts->reconnect_delay = token;
break;
case NVMF_OPT_HOST_TRADDR:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
kfree(opts->host_traddr);
opts->host_traddr = p;
break;
case NVMF_OPT_HOST_IFACE:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
kfree(opts->host_iface);
opts->host_iface = p;
break;
case NVMF_OPT_HOST_ID:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
ret = uuid_parse(p, &hostid);
if (ret) {
pr_err("Invalid hostid %s\n", p);
ret = -EINVAL;
kfree(p);
goto out;
}
kfree(p);
break;
case NVMF_OPT_DUP_CONNECT:
opts->duplicate_connect = true;
break;
case NVMF_OPT_DISABLE_SQFLOW:
opts->disable_sqflow = true;
break;
case NVMF_OPT_HDR_DIGEST:
opts->hdr_digest = true;
break;
case NVMF_OPT_DATA_DIGEST:
opts->data_digest = true;
break;
case NVMF_OPT_NR_WRITE_QUEUES:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token <= 0) {
pr_err("Invalid nr_write_queues %d\n", token);
ret = -EINVAL;
goto out;
}
opts->nr_write_queues = token;
break;
case NVMF_OPT_NR_POLL_QUEUES:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token <= 0) {
pr_err("Invalid nr_poll_queues %d\n", token);
ret = -EINVAL;
goto out;
}
opts->nr_poll_queues = token;
break;
case NVMF_OPT_TOS:
if (match_int(args, &token)) {
ret = -EINVAL;
goto out;
}
if (token < 0) {
pr_err("Invalid type of service %d\n", token);
ret = -EINVAL;
goto out;
}
if (token > 255) {
pr_warn("Clamping type of service to 255\n");
token = 255;
}
opts->tos = token;
break;
case NVMF_OPT_DISCOVERY:
opts->discovery_nqn = true;
break;
case NVMF_OPT_DHCHAP_SECRET:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
if (strlen(p) < 11 || strncmp(p, "DHHC-1:", 7)) {
pr_err("Invalid DH-CHAP secret %s\n", p);
ret = -EINVAL;
goto out;
}
kfree(opts->dhchap_secret);
opts->dhchap_secret = p;
break;
case NVMF_OPT_DHCHAP_CTRL_SECRET:
p = match_strdup(args);
if (!p) {
ret = -ENOMEM;
goto out;
}
if (strlen(p) < 11 || strncmp(p, "DHHC-1:", 7)) {
pr_err("Invalid DH-CHAP secret %s\n", p);
ret = -EINVAL;
goto out;
}
kfree(opts->dhchap_ctrl_secret);
opts->dhchap_ctrl_secret = p;
break;
default:
pr_warn("unknown parameter or missing value '%s' in ctrl creation request\n",
p);
ret = -EINVAL;
goto out;
}
}
if (opts->discovery_nqn) {
opts->nr_io_queues = 0;
opts->nr_write_queues = 0;
opts->nr_poll_queues = 0;
opts->duplicate_connect = true;
} else {
if (!opts->kato)
opts->kato = NVME_DEFAULT_KATO;
}
if (ctrl_loss_tmo < 0) {
opts->max_reconnects = -1;
} else {
opts->max_reconnects = DIV_ROUND_UP(ctrl_loss_tmo,
opts->reconnect_delay);
if (ctrl_loss_tmo < opts->fast_io_fail_tmo)
pr_warn("failfast tmo (%d) larger than controller loss tmo (%d)\n",
opts->fast_io_fail_tmo, ctrl_loss_tmo);
}
opts->host = nvmf_host_add(hostnqn, &hostid);
if (IS_ERR(opts->host)) {
ret = PTR_ERR(opts->host);
opts->host = NULL;
goto out;
}
out:
kfree(options);
return ret;
}
这段代码定义了一个函数 nvmf_parse_options
,用于解析 NVMe 控制器选项字符串,并将解析后的选项值存储在一个 nvmf_ctrl_options
结构体中。下面是这个函数的主要步骤:
-
首先,为了保护原始选项字符串,它对输入的
buf
进行了一次内存拷贝,生成了一个名为options
的字符串。 -
然后,函数开始解析选项字符串。它使用
strsep
函数将字符串按照逗号,
或换行符\n
分割成各个子字符串,并遍历这些子字符串。 -
对于每个子字符串,函数使用
match_token
函数将其与opt_tokens
匹配表中的标识符进行匹配,得到对应的标识符。然后,根据标识符进行相应的处理。 -
对于每个标识符,函数执行相应的操作。例如,对于
NVMF_OPT_TRANSPORT
,它将选项的值(字符串)分配给opts->transport
字段。 -
函数在解析选项的过程中,根据情况更新了
nvmf_ctrl_options
结构体的各个字段。 -
解析完成后,函数根据解析结果,对一些字段进行了一些额外的处理。例如,根据是否启用了发现控制器,调整了一些字段的值。
-
最后,函数通过调用
nvmf_host_add
函数,根据主机名和主机 ID 添加了一个主机,这是nvmf_ctrl_options
结构体中的一个字段。 -
函数释放了之前为保护选项字符串而创建的
options
字符串,并返回解析过程中的结果。
总体来说,这个函数的作用是将 NVMe 控制器选项字符串解析为一个结构体,以供后续在初始化 NVMe 控制器时使用。
nvmf_set_io_queues
void nvmf_set_io_queues(struct nvmf_ctrl_options *opts, u32 nr_io_queues,
u32 io_queues[HCTX_MAX_TYPES])
{
if (opts->nr_write_queues && opts->nr_io_queues < nr_io_queues) {
/*
* separate read/write queues
* hand out dedicated default queues only after we have
* sufficient read queues.
*/
io_queues[HCTX_TYPE_READ] = opts->nr_io_queues;
nr_io_queues -= io_queues[HCTX_TYPE_READ];
io_queues[HCTX_TYPE_DEFAULT] =
min(opts->nr_write_queues, nr_io_queues);
nr_io_queues -= io_queues[HCTX_TYPE_DEFAULT];
} else {
/*
* shared read/write queues
* either no write queues were requested, or we don't have
* sufficient queue count to have dedicated default queues.
*/
io_queues[HCTX_TYPE_DEFAULT] =
min(opts->nr_io_queues, nr_io_queues);
nr_io_queues -= io_queues[HCTX_TYPE_DEFAULT];
}
if (opts->nr_poll_queues && nr_io_queues) {
/* map dedicated poll queues only if we have queues left */
io_queues[HCTX_TYPE_POLL] =
min(opts->nr_poll_queues, nr_io_queues);
}
}
EXPORT_SYMBOL_GPL(nvmf_set_io_queues);
这段代码定义了一个函数 nvmf_set_io_queues
,用于根据 NVMe 控制器选项设置 I/O 队列的分配方式。该函数会更新传入的 io_queues
数组,指定每种类型的 I/O 队列数量。
函数的主要思路如下:
-
函数根据传入的
nr_io_queues
和io_queues
数组来设置不同类型的 I/O 队列数量。 -
首先,函数会检查是否有写队列(
opts->nr_write_queues
)且分配的总队列数opts->nr_io_queues
小于传入的nr_io_queues
。 -
如果有写队列,函数会将读队列(
HCTX_TYPE_READ
)的数量设为opts->nr_io_queues
,然后将剩余队列分配给默认队列(HCTX_TYPE_DEFAULT
)。 -
如果没有写队列,或者写队列数量超过剩余队列数,函数会将默认队列(
HCTX_TYPE_DEFAULT
)的数量设为opts->nr_io_queues
。 -
接下来,函数会检查是否有轮询队列(
opts->nr_poll_queues
)且剩余队列数不为零。 -
如果有轮询队列且剩余队列数大于零,函数会将轮询队列(
HCTX_TYPE_POLL
)的数量设为opts->nr_poll_queues
。 -
最终,
io_queues
数组中的各个元素会被更新为相应的队列数量。
总的来说,该函数的目的是根据 NVMe 控制器选项以及可用的队列数,设置不同类型的 I/O 队列数量,以便在初始化 NVMe 控制器时进行合适的队列分配。
nvmf_map_queues
void nvmf_map_queues(struct blk_mq_tag_set *set, struct nvme_ctrl *ctrl,
u32 io_queues[HCTX_MAX_TYPES])
{
struct nvmf_ctrl_options *opts = ctrl->opts;
if (opts->nr_write_queues && io_queues[HCTX_TYPE_READ]) {
/* separate read/write queues */
set->map[HCTX_TYPE_DEFAULT].nr_queues =
io_queues[HCTX_TYPE_DEFAULT];
set->map[HCTX_TYPE_DEFAULT].queue_offset = 0;
set->map[HCTX_TYPE_READ].nr_queues =
io_queues[HCTX_TYPE_READ];
set->map[HCTX_TYPE_READ].queue_offset =
io_queues[HCTX_TYPE_DEFAULT];
} else {
/* shared read/write queues */
set->map[HCTX_TYPE_DEFAULT].nr_queues =
io_queues[HCTX_TYPE_DEFAULT];
set->map[HCTX_TYPE_DEFAULT].queue_offset = 0;
set->map[HCTX_TYPE_READ].nr_queues =
io_queues[HCTX_TYPE_DEFAULT];
set->map[HCTX_TYPE_READ].queue_offset = 0;
}
blk_mq_map_queues(&set->map[HCTX_TYPE_DEFAULT]);
blk_mq_map_queues(&set->map[HCTX_TYPE_READ]);
if (opts->nr_poll_queues && io_queues[HCTX_TYPE_POLL]) {
/* map dedicated poll queues only if we have queues left */
set->map[HCTX_TYPE_POLL].nr_queues = io_queues[HCTX_TYPE_POLL];
set->map[HCTX_TYPE_POLL].queue_offset =
io_queues[HCTX_TYPE_DEFAULT] +
io_queues[HCTX_TYPE_READ];
blk_mq_map_queues(&set->map[HCTX_TYPE_POLL]);
}
dev_info(ctrl->device,
"mapped %d/%d/%d default/read/poll queues.\n",
io_queues[HCTX_TYPE_DEFAULT],
io_queues[HCTX_TYPE_READ],
io_queues[HCTX_TYPE_POLL]);
}
EXPORT_SYMBOL_GPL(nvmf_map_queues);
这段代码定义了一个函数 nvmf_map_queues
,用于在块多队列(blk-mq)中映射 NVMe 控制器的不同类型的 I/O 队列。这个函数将根据传入的 NVMe 控制器选项和队列信息来配置 blk-mq 的队列映射。
函数的主要流程如下:
-
首先,函数根据 NVMe 控制器选项和传入的
io_queues
数组,判断是否需要分离读写队列。 -
如果存在写队列且读队列数量不为零,那么将读和写队列分开。
-
设置默认队列(
HCTX_TYPE_DEFAULT
)的映射信息,包括队列数量和队列偏移。 -
设置读队列(
HCTX_TYPE_READ
)的映射信息,包括队列数量和队列偏移。 -
如果没有分离读写队列,或者分离后写队列数量为零,那么将读队列和默认队列合并。
-
调用
blk_mq_map_queues
函数来实际完成队列映射。 -
如果存在轮询队列且轮询队列数量不为零,设置轮询队列(
HCTX_TYPE_POLL
)的映射信息,包括队列数量和队列偏移。 -
最后,打印日志信息,指示映射了多少个默认、读和轮询队列。
总的来说,这个函数负责根据 NVMe 控制器选项和队列信息,在块多队列(blk-mq)中设置适当的队列映射,以便在 NVMe 控制器初始化过程中能够正确地使用这些队列。
nvmf_check_required_opts
static int nvmf_check_required_opts(struct nvmf_ctrl_options *opts,
unsigned int required_opts)
{
if ((opts->mask & required_opts) != required_opts) {
unsigned int i;
for (i = 0; i < ARRAY_SIZE(opt_tokens); i++) {
if ((opt_tokens[i].token & required_opts) &&
!(opt_tokens[i].token & opts->mask)) {
pr_warn("missing parameter '%s'\n",
opt_tokens[i].pattern);
}
}
return -EINVAL;
}
return 0;
}
这段代码定义了一个函数 nvmf_check_required_opts
,用于检查是否存在所需的 NVMe 控制器选项。函数接收一个包含选项掩码和所需选项掩码的参数,然后检查给定的选项掩码中是否包含了所有所需的选项掩码。
函数的主要步骤如下:
-
首先,函数检查给定的选项掩码中是否包含了所有所需选项掩码。如果包含了所有所需选项,那么函数返回 0,表示选项是完整的。
-
如果给定的选项掩码缺少某个所需选项,那么函数会遍历预定义的
opt_tokens
数组,该数组包含所有可识别的选项信息。对于每个所需选项,函数会检查是否存在对应的选项掩码,并且该选项掩码是否缺失在给定的选项掩码中。 -
如果发现某个所需选项缺失,函数会打印警告消息,指示缺失的参数。
-
最后,函数返回
-EINVAL
,表示选项不完整且存在缺失的参数。
总的来说,这个函数用于验证是否存在所有所需的 NVMe 控制器选项,并在发现缺失的参数时打印警告消息。这可以帮助确保在使用 NVMe Fabrics 驱动程序时,所有必要的选项都已正确设置。
nvmf_ip_options_match
bool nvmf_ip_options_match(struct nvme_ctrl *ctrl,
struct nvmf_ctrl_options *opts)
{
if (!nvmf_ctlr_matches_baseopts(ctrl, opts) ||
strcmp(opts->traddr, ctrl->opts->traddr) ||
strcmp(opts->trsvcid, ctrl->opts->trsvcid))
return false;
/*
* Checking the local address or host interfaces is rough.
*
* In most cases, none is specified and the host port or
* host interface is selected by the stack.
*
* Assume no match if:
* - local address or host interface is specified and address
* or host interface is not the same
* - local address or host interface is not specified but
* remote is, or vice versa (admin using specific
* host_traddr/host_iface when it matters).
*/
if ((opts->mask & NVMF_OPT_HOST_TRADDR) &&
(ctrl->opts->mask & NVMF_OPT_HOST_TRADDR)) {
if (strcmp(opts->host_traddr, ctrl->opts->host_traddr))
return false;
} else if ((opts->mask & NVMF_OPT_HOST_TRADDR) ||
(ctrl->opts->mask & NVMF_OPT_HOST_TRADDR)) {
return false;
}
if ((opts->mask & NVMF_OPT_HOST_IFACE) &&
(ctrl->opts->mask & NVMF_OPT_HOST_IFACE)) {
if (strcmp(opts->host_iface, ctrl->opts->host_iface))
return false;
} else if ((opts->mask & NVMF_OPT_HOST_IFACE) ||
(ctrl->opts->mask & NVMF_OPT_HOST_IFACE)) {
return false;
}
return true;
}
EXPORT_SYMBOL_GPL(nvmf_ip_options_match);
这段代码定义了一个名为 nvmf_ip_options_match
的函数,用于比较给定的 NVMe 控制器选项和控制器实例的选项是否匹配。这可以用来确定是否可以建立连接。以下是函数的工作方式:
-
首先,通过调用
nvmf_ctlr_matches_baseopts(ctrl, opts)
函数检查控制器选项和实例选项的基本选项是否匹配,如果不匹配,则返回false
。 -
接下来,比较
traddr
(传输地址)和trsvcid
(传输服务 ID)是否匹配。如果它们不匹配,则返回false
。 -
针对本地地址或主机接口的匹配情况,做如下判断:
-
如果给定的选项中同时包含
NVMF_OPT_HOST_TRADDR
并且实例选项中也包含NVMF_OPT_HOST_TRADDR
,则比较host_traddr
是否相同,如果不相同则返回false
。 -
如果只有一个选项中包含
NVMF_OPT_HOST_TRADDR
,则返回false
。 -
如果给定的选项中同时包含
NVMF_OPT_HOST_IFACE
并且实例选项中也包含NVMF_OPT_HOST_IFACE
,则比较host_iface
是否相同,如果不相同则返回false
。 -
如果只有一个选项中包含
NVMF_OPT_HOST_IFACE
,则返回false
。
-
-
如果上述所有检查都通过,则返回
true
,表示选项匹配。
此外,该函数使用 EXPORT_SYMBOL_GPL
宏将 nvmf_ip_options_match
函数导出,以便其他模块可以使用该函数。