NVMe Linux driver series 1: host side [fabrics.c]<68>

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( & amp;hostid, & amp;nvmf_default_host->id);
strscpy(hostnqn, nvmf_default_host->nqn, NVMF_NQN_SIZE);

while ((p = strsep( & amp;o, ",\\
")) != 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\\
",
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, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}
if (token < NVMF_MIN_QUEUE_SIZE ||
token > NVMF_MAX_QUEUE_SIZE) {<!-- -->
pr_err("Invalid queue_size %d\\
", token);
ret = -EINVAL;
goto out;
}
opts->queue_size = token;
break;
case NVMF_OPT_NR_IO_QUEUES:
if (match_int(args, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}
if (token <= 0) {<!-- -->
pr_err("Invalid number of IOQs %d\\
", token);
ret = -EINVAL;
goto out;
}
if (opts->discovery_nqn) {<!-- -->
pr_debug("Ignoring nr_io_queues value for discovery controller\\
");
break;
}

opts->nr_io_queues = min_t(unsigned int,
num_online_cpus(), token);
break;
case NVMF_OPT_KATO:
if (match_int(args, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}

if (token < 0) {<!-- -->
pr_err("Invalid keep_alive_tmo %d\\
", token);
ret = -EINVAL;
goto out;
} else if (token == 0 & amp; & amp; !opts->discovery_nqn) {<!-- -->
/* Allowed for debug */
pr_warn("keep_alive_tmo 0 won't execute keep alives!!!\\
");
}
opts->kato = token;
break;
case NVMF_OPT_CTRL_LOSS_TMO:
if (match_int(args, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}

if (token < 0)
pr_warn("ctrl_loss_tmo < 0 will reconnect forever\\
");
ctrl_loss_tmo = token;
break;
case NVMF_OPT_FAIL_FAST_TMO:
if (match_int(args, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}

if (token >= 0)
pr_warn("I/O fail on reconnect controller after %d sec\\
",
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\\
",
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\\
",
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, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}
if (token <= 0) {<!-- -->
pr_err("Invalid reconnect_delay %d\\
", 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\\
", 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, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}
if (token <= 0) {<!-- -->
pr_err("Invalid nr_write_queues %d\\
", token);
ret = -EINVAL;
goto out;
}
opts->nr_write_queues = token;
break;
case NVMF_OPT_NR_POLL_QUEUES:
if (match_int(args, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}
if (token <= 0) {<!-- -->
pr_err("Invalid nr_poll_queues %d\\
", token);
ret = -EINVAL;
goto out;
}
opts->nr_poll_queues = token;
break;
case NVMF_OPT_TOS:
if (match_int(args, & amp;token)) {<!-- -->
ret = -EINVAL;
goto out;
}
if (token < 0) {<!-- -->
pr_err("Invalid type of service %d\\
", token);
ret = -EINVAL;
goto out;
}
if (token > 255) {<!-- -->
pr_warn("Clamping type of service to 255\\
");
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\\
", 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\\
", 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\\
",
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)\\
",
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;
}

This code defines a function nvmf_parse_options that parses the NVMe controller option string and stores the parsed option value in a nvmf_ctrl_options structure. Here are the main steps of this function:

  1. First, in order to protect the original options string, it makes a memory copy of the input buf, generating a string named options.

  2. Then, the function starts parsing the option string. It uses the strsep function to split the string into substrings according to commas , or newline characters \\
    , and iterate over these substrings .

  3. For each substring, the function uses the match_token function to match it with the identifier in the opt_tokens match table to get the corresponding identifier. Then, perform corresponding processing according to the identifier.

  4. For each identifier, the function performs the corresponding operation. For example, for NVMF_OPT_TRANSPORT, it assigns the value (string) of the option to the opts->transport field.

  5. In the process of parsing the options, the function updates the fields of the nvmf_ctrl_options structure according to the situation.

  6. After the parsing is complete, the function performs some additional processing on some fields according to the parsing results. For example, the values of some fields are adjusted depending on whether the discovery controller is enabled or not.

  7. Finally, the function adds a host based on the hostname and host ID by calling the nvmf_host_add function, which is a field in the nvmf_ctrl_options structure.

  8. The function frees the options string previously created to protect the options string and returns the result of the parsing process.

Generally speaking, the function of this function is to parse the NVMe controller option string into a structure for subsequent use when initializing the NVMe controller.

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 & amp; & amp; 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 & amp; & amp; 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);

This code defines a function nvmf_set_io_queues, which is used to set the allocation method of I/O queues according to NVMe controller options. This function updates the passed in io_queues array, specifying the number of I/O queues of each type.

The main idea of the function is as follows:

  1. The function sets the number of different types of I/O queues according to the nr_io_queues and io_queues arrays passed in.

  2. First, the function checks if there is a write queue (opts->nr_write_queues) and the total number of queues allocated opts->nr_io_queues is less than the passed in nr_io_queues >.

  3. If there is a write queue, the function sets the number of read queues (HCTX_TYPE_READ) to opts->nr_io_queues, and then allocates the remaining queues to the default queue (HCTX_TYPE_DEFAULT).

  4. If there is no write queue, or the number of write queues exceeds the number of remaining queues, the function sets the number of default queues (HCTX_TYPE_DEFAULT) to opts->nr_io_queues.

  5. Next, the function checks if there are polling queues (opts->nr_poll_queues) and the number of remaining queues is not zero.

  6. If there is a poll queue and the number of remaining queues is greater than zero, the function sets the number of poll queues (HCTX_TYPE_POLL) to opts->nr_poll_queues.

  7. Eventually, each element in the io_queues array will be updated with the corresponding number of queues.

Overall, the purpose of this function is to set the number of different types of I/O queues based on the NVMe controller options and the number of queues available, so that appropriate queue allocation occurs when the NVMe controller is initialized.

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 & amp; & amp; 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( & amp;set->map[HCTX_TYPE_DEFAULT]);
blk_mq_map_queues( & amp;set->map[HCTX_TYPE_READ]);
if (opts->nr_poll_queues & amp; & amp; 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( & amp;set->map[HCTX_TYPE_POLL]);
}

dev_info(ctrl->device,
"mapped %d/%d/%d default/read/poll queues.\\
",
io_queues[HCTX_TYPE_DEFAULT],
io_queues[HCTX_TYPE_READ],
io_queues[HCTX_TYPE_POLL]);
}
EXPORT_SYMBOL_GPL(nvmf_map_queues);

This code defines a function nvmf_map_queues for mapping the different types of I/O queues of an NVMe controller in Block Multiqueue (blk-mq). This function will configure blk-mq’s queue map according to the NVMe controller options and queue information passed in.

The main process of the function is as follows:

  1. First, the function judges whether to separate read and write queues according to the NVMe controller options and the incoming io_queues array.

  2. If there is a write queue and the number of read queues is non-zero, separate the read and write queues.

  3. Set the mapping information of the default queue (HCTX_TYPE_DEFAULT), including the queue number and queue offset.

  4. Set the mapping information of the read queue (HCTX_TYPE_READ), including the queue number and queue offset.

  5. If the read and write queues are not separated, or the number of write queues is zero after separation, the read queue and the default queue will be merged.

  6. Call the blk_mq_map_queues function to actually do the queue mapping.

  7. If there is a polling queue and the number of polling queues is not zero, set the mapping information of the polling queue (HCTX_TYPE_POLL), including the queue number and queue offset.

  8. Finally, log information is printed indicating how many default, read, and poll queues are mapped.

Overall, this function is responsible for setting up the appropriate queue mappings in the block multiqueue (blk-mq) based on the NVMe controller options and queue information, so that these queues can be used correctly during the NVMe controller initialization process.

nvmf_check_required_opts

static int nvmf_check_required_opts(struct nvmf_ctrl_options *opts,
unsigned int required_opts)
{<!-- -->
if ((opts->mask & amp; required_opts) != required_opts) {<!-- -->
unsigned int i;

for (i = 0; i < ARRAY_SIZE(opt_tokens); i + + ) {<!-- -->
if ((opt_tokens[i].token & amp; required_opts) & amp; & amp;
!(opt_tokens[i].token & amp; opts->mask)) {<!-- -->
pr_warn("missing parameter '%s'\\
",
opt_tokens[i].pattern);
}
}

return -EINVAL;
}

return 0;
}

This code defines a function nvmf_check_required_opts that checks whether the required NVMe controller options are present. The function receives an argument containing an option mask and a required option mask, and then checks whether all required option masks are included in the given option mask.

The main steps of the function are as follows:

  1. First, the function checks whether all required option masks are included in the given option mask. If all required options are included, the function returns 0, indicating that the options are complete.

  2. If a required option is missing from the given option mask, the function iterates over the predefined opt_tokens array, which contains all recognized option information. For each required option, the function checks whether a corresponding option mask exists and is missing from the given option mask.

  3. If a required option is found to be missing, the function prints a warning message indicating the missing argument.

  4. Finally, the function returns -EINVAL, indicating that the option is incomplete and has missing parameters.

Overall, this function is used to verify that all required NVMe controller options are present and print a warning message if missing parameters are found. This can help ensure that all necessary options are set correctly when using the NVMe Fabrics driver.

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 & amp; NVMF_OPT_HOST_TRADDR) & amp; & amp;
(ctrl->opts->mask & amp; NVMF_OPT_HOST_TRADDR)) {<!-- -->
if (strcmp(opts->host_traddr, ctrl->opts->host_traddr))
return false;
} else if ((opts->mask & amp; NVMF_OPT_HOST_TRADDR) ||
(ctrl->opts->mask & amp; NVMF_OPT_HOST_TRADDR)) {<!-- -->
return false;
}

if ((opts->mask & amp; NVMF_OPT_HOST_IFACE) & amp; & amp;
(ctrl->opts->mask & amp; NVMF_OPT_HOST_IFACE)) {<!-- -->
if (strcmp(opts->host_iface, ctrl->opts->host_iface))
return false;
} else if ((opts->mask & amp; NVMF_OPT_HOST_IFACE) ||
(ctrl->opts->mask & amp; NVMF_OPT_HOST_IFACE)) {<!-- -->
return false;
}

return true;
}
EXPORT_SYMBOL_GPL(nvmf_ip_options_match);

This code defines a function called nvmf_ip_options_match that compares the given NVMe controller options to the controller instance’s options for a match. This can be used to determine if a connection can be established. Here’s how the function works:

  1. First, check if the base options of the controller options and instance options match by calling the nvmf_ctlr_matches_baseopts(ctrl, opts) function, returning false if they do not match.

  2. Next, compare traddr (transport address) and trsvcid (transport service ID) for a match. If they don’t match, return false.

  3. For the matching of local addresses or host interfaces, make the following judgments:

    • If the given option contains both NVMF_OPT_HOST_TRADDR and the instance option also contains NVMF_OPT_HOST_TRADDR, compare host_traddr to see if they are the same. If they are not the same, return false.

    • Returns false if only one option contains NVMF_OPT_HOST_TRADDR.

    • If the given option contains both NVMF_OPT_HOST_IFACE and the instance option also contains NVMF_OPT_HOST_IFACE, compare host_iface to see if they are the same. If they are not the same, return false.

    • Returns false if only one option contains NVMF_OPT_HOST_IFACE.

  4. If all of the above checks pass, true is returned, indicating that the options matched.

Additionally, this function exports the nvmf_ip_options_match function using the EXPORT_SYMBOL_GPL macro so that other modules can use the function.