NVMe Linux driver series one: host side [fabrics.c]<69>

nvmf_check_allowed_opts

static int nvmf_check_allowed_opts(struct nvmf_ctrl_options *opts,
unsigned int allowed_opts)
{<!-- -->
if (opts->mask & amp; ~allowed_opts) {<!-- -->
unsigned int i;

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

return -EINVAL;
}

return 0;
}

This code defines a function named nvmf_check_allowed_opts that checks whether the given NVMe controller options contain any disallowed options. Here’s how the function works:

  1. Check for disallowed options by checking if opts->mask contains any options not in allowed_opts . If there are disallowed options, i.e. opts->mask & amp; ~allowed_opts is non-zero, perform the following steps:

    • Use a loop to go through all possible options, checking each option as follows:
      • If the option exists in opts->mask and is outside allowed_opts, ie (opt_tokens[i].token & amp; opts->mask) & amp; & amp; (opt_tokens[i].token & amp; ~allowed_opts) is true, a warning message is output, indicating that an invalid option was found.
  2. If an invalid option is present, -EINVAL is returned to indicate that an invalid option was found.

  3. If no invalid options are found, 0 is returned to indicate that all options are allowed.

This function is mainly used to ensure that only allowed options are included in the controller options to prevent the controller from being configured with unsupported or invalid options.

Furthermore, the function does not export any symbols, so it can only be used inside the module in which it is defined.

nvmf_free_options

void nvmf_free_options(struct nvmf_ctrl_options *opts)
{<!-- -->
nvmf_host_put(opts->host);
kfree(opts->transport);
kfree(opts->traddr);
kfree(opts->trsvcid);
kfree(opts->subsysnqn);
kfree(opts->host_traddr);
kfree(opts->host_iface);
kfree(opts->dhchap_secret);
kfree(opts->dhchap_ctrl_secret);
kfree(opts);
}
EXPORT_SYMBOL_GPL(nvmf_free_options);

This code defines a function called nvmf_free_options to free the memory of the NVMe controller options structure and perform necessary resource cleanup. Here’s how the function works:

  1. First, the nvmf_host_put function is called to decrement the reference count of the host data structure associated with the option. This is because previously the reference count of the host data structure could be increased when configuring options.

  2. Use the kfree function to free the memory of the string members of the options structure:

    • opts->transport
    • opts->traddr
    • opts->trsvcid
    • opts->subsysnqn
    • opts->host_traddr
    • opts->host_iface
    • opts->dhchap_secret
    • opts->dhchap_ctrl_secret
  3. Finally, use the kfree function to free the memory of the options structure itself.

The last line of the function is to use the EXPORT_SYMBOL_GPL macro to export this function so that other modules can access and use it.

This function is mainly used to release the memory and associated resources occupied by the NVMe controller option structure when it is no longer needed.

NVMF_REQUIRED_OPTS & NVMF_ALLOWED_OPTS

#define NVMF_REQUIRED_OPTS (NVMF_OPT_TRANSPORT | NVMF_OPT_NQN)
#define NVMF_ALLOWED_OPTS (NVMF_OPT_QUEUE_SIZE | NVMF_OPT_NR_IO_QUEUES | \
NVMF_OPT_KATO | NVMF_OPT_HOSTNQN | \
NVMF_OPT_HOST_ID | NVMF_OPT_DUP_CONNECT |\
NVMF_OPT_DISABLE_SQFLOW | NVMF_OPT_DISCOVERY |\
NVMF_OPT_FAIL_FAST_TMO | NVMF_OPT_DHCHAP_SECRET |\
NVMF_OPT_DHCHAP_CTRL_SECRET)

This code defines two macros:

  1. The NVMF_REQUIRED_OPTS macro specifies a mask of required options. These options must be provided in the controller options structure, otherwise an error will be returned before some action is performed.

  2. The NVMF_ALLOWED_OPTS macro specifies a mask of allowed options. These options are optional, but not mandatory, in the controller options structure. If options outside this mask are provided, a warning is issued and an error is returned.

The primary role of these macros is to provide support for validation of NVMe controller options to ensure that users follow the required and allowed range of options when setting options. This helps prevent users from making mistakes or invalid options when setting controller options.

nvmf_create_ctrl

static struct nvme_ctrl *
nvmf_create_ctrl(struct device *dev, const char *buf)
{<!-- -->
struct nvmf_ctrl_options *opts;
struct nvmf_transport_ops *ops;
struct nvme_ctrl *ctrl;
int ret;

opts = kzalloc(sizeof(*opts), GFP_KERNEL);
if (!opts)
return ERR_PTR(-ENOMEM);

ret = nvmf_parse_options(opts, buf);
if (ret)
goto out_free_opts;


request_module("nvme-%s", opts->transport);

/*
* Check the generic options first as we need a valid transport for
* the lookup below. Then clear the generic flags so that transport
* drivers don't have to care about them.
*/
ret = nvmf_check_required_opts(opts, NVMF_REQUIRED_OPTS);
if (ret)
goto out_free_opts;
opts->mask &= ~NVMF_REQUIRED_OPTS;

down_read( & amp;nvmf_transports_rwsem);
ops = nvmf_lookup_transport(opts);
if (!ops) {<!-- -->
pr_info("no handler found for transport %s.\
",
opts->transport);
ret = -EINVAL;
goto out_unlock;
}

if (!try_module_get(ops->module)) {<!-- -->
ret = -EBUSY;
goto out_unlock;
}
up_read( & amp;nvmf_transports_rwsem);

ret = nvmf_check_required_opts(opts, ops->required_opts);
if (ret)
goto out_module_put;
ret = nvmf_check_allowed_opts(opts, NVMF_ALLOWED_OPTS |
ops->allowed_opts | ops->required_opts);
if (ret)
goto out_module_put;

ctrl = ops->create_ctrl(dev, opts);
if (IS_ERR(ctrl)) {<!-- -->
ret = PTR_ERR(ctrl);
goto out_module_put;
}

module_put(ops->module);
return ctrl;

out_module_put:
module_put(ops->module);
goto out_free_opts;
out_unlock:
up_read( & amp;nvmf_transports_rwsem);
out_free_opts:
nvmf_free_options(opts);
return ERR_PTR(ret);
}

This code defines a function nvmf_create_ctrl, which is used to create an NVMe controller instance. The workflow of this function is as follows:

  1. First, allocate and initialize the nvmf_ctrl_options structure (that is, the controller options structure) opts. If the allocation fails, an error pointer is returned.

  2. Populates the opts structure with the parsed options data buf. If parsing fails, the allocated opts structure is freed and an error is returned.

  3. Use the request_module function to attempt to load the kernel module associated with a given transport type. This is to ensure that support for the required transport types is loaded into the kernel.

  4. Check whether the common required options are provided. If required options are not provided, the opts structure is freed and an error is returned.

  5. Based on the given transfer type, find the corresponding transfer operation structure ops. If no matching transfer operation structure is found, the opts structure is freed and an error is returned.

  6. Try increasing the module reference count of the transfer action structure to ensure that the action structure’s module remains loaded during controller creation. If an attempt to increment the module’s reference count fails (the module has been unloaded or other reasons), an error is returned.

  7. Check whether the options required to transfer the operation structure are provided. If the required option is not provided, the module reference is released and an error is returned.

  8. Check that the provided options are within the allowed range. If the provided option is not within the allowed range, the module reference is released and an error is returned.

  9. Call the create_ctrl function of the transport operation structure to create a controller instance with the given device and options. If controller creation fails, release the module reference and return an error.

  10. The controller instance is successfully created, the module reference is released and the controller instance is returned.

  11. If an error occurs during the creation of the controller, release the module reference, unlock the read lock on the transfer operation structure, release the options structure and return an error.

The main purpose of this function is to create an NVMe controller instance, and in the process, verify that the transport type is valid, the options are as required, and perform the necessary module loading and unloading operations.

nvmf_dev_write

static DEFINE_MUTEX(nvmf_dev_mutex);

static ssize_t nvmf_dev_write(struct file *file, const char __user *ubuf,
size_t count, loff_t *pos)
{<!-- -->
struct seq_file *seq_file = file->private_data;
struct nvme_ctrl *ctrl;
const char *buf;
int ret = 0;

if (count > PAGE_SIZE)
return -ENOMEM;

buf = memdup_user_nul(ubuf, count);
if (IS_ERR(buf))
return PTR_ERR(buf);

mutex_lock( & amp;nvmf_dev_mutex);
if (seq_file->private) {<!-- -->
ret = -EINVAL;
goto out_unlock;
}

ctrl = nvmf_create_ctrl(nvmf_device, buf);
if (IS_ERR(ctrl)) {<!-- -->
ret = PTR_ERR(ctrl);
goto out_unlock;
}

seq_file->private = ctrl;

out_unlock:
mutex_unlock( & amp;nvmf_dev_mutex);
kfree(buf);
return ret ? ret : count;
}

This code defines a function called nvmf_dev_write that handles writing to device files. The workflow of this function is as follows:

  1. Checks whether the input write data exceeds the size of one page, and returns an out of memory error if it does.

  2. Use the memdup_user_nul function to copy and write data from user space and add a null terminator at the end of the data. If the copy fails, an error is returned.

  3. Acquires the global mutex nvmf_dev_mutex to ensure that no other writes or concurrent controller creations occur while the write is being processed.

  4. Check whether the private data in the seq_file structure has been set. If it has been set, it means that write operations are not allowed at this time, so an invalid parameter error is returned.

  5. Call the nvmf_create_ctrl function to create an NVMe controller instance with the written data. If creation fails, an error is returned.

  6. If the controller is created successfully, store the controller instance in the private data field of the seq_file structure for use in subsequent read operations.

  7. Unlock the global mutex.

  8. Release the copied data buffer.

  9. Returns a possible error code or, if all goes well, the number of bytes written.

The main purpose of this function is to handle the write operation of the device file, create an NVMe controller instance, and store it in the private data field in the seq_file structure for use in subsequent read operations . This process uses a mutex to protect concurrent access from multiple write operations, ensuring that resources are locked and unlocked at the appropriate time.

__nvmf_concat_opt_tokens

static void __nvmf_concat_opt_tokens(struct seq_file *seq_file)
{<!-- -->
const struct match_token *tok;
int idx;

/*
* Add dummy entries for instance and cntlid to
* signal an invalid/non-existing controller
*/
seq_puts(seq_file, "instance=-1, cntlid=-1");
for (idx = 0; idx < ARRAY_SIZE(opt_tokens); idx + + ) {<!-- -->
tok = &opt_tokens[idx];
if (tok->token == NVMF_OPT_ERR)
continue;
seq_puts(seq_file, ",");
seq_puts(seq_file, tok->pattern);
}
seq_puts(seq_file, "\
");
}

This code defines a function __nvmf_concat_opt_tokens, which is used to concatenate the option patterns in the option token array into a sequence and write it into the given seq_file . The main purpose of the function is to generate a string containing all option patterns to represent the controller’s option information.

The execution steps of the function are as follows:

  1. Add a string of “instance=-1,cntlid=-1” in seq_file to indicate invalid or non-existing controller instance and controller ID.

  2. Iterate over each token in the options tokens array.

  3. For each valid option token, append its schema (i.e. the option string) to the seq_file .

  4. After all option patterns are added, add a newline in seq_file to indicate the end of option splicing.

In short, the function of this function is to concatenate all possible option modes into a string for subsequent use in controller creation to provide option information.

nvmf_dev_show

static int nvmf_dev_show(struct seq_file *seq_file, void *private)
{<!-- -->
struct nvme_ctrl *ctrl;

mutex_lock( &nvmf_dev_mutex);
ctrl = seq_file->private;
if (!ctrl) {<!-- -->
__nvmf_concat_opt_tokens(seq_file);
goto out_unlock;
}

seq_printf(seq_file, "instance=%d, cntlid=%d\
",
ctrl->instance, ctrl->cntlid);

out_unlock:
mutex_unlock( & amp;nvmf_dev_mutex);
return 0;
}

This code defines a function nvmf_dev_show for displaying NVMe controller related information in a given seq_file. The role of the function is to write the instance number and controller ID of the controller, and the option information of the controller in the file sequence.

The execution steps of the function are as follows:

  1. Acquires the controller’s mutex to ensure that access to controller data is thread-safe.

  2. Get a reference to the controller object from the seq_file.

  3. If the controller object does not exist (that is, NULL), call the __nvmf_concat_opt_tokens function to splice all possible option patterns into seq_file, and skip Go to the unlocking steps.

  4. If the controller object exists, use the seq_printf function to write the instance number and controller ID of the controller into seq_file.

  5. Unlocks the mutex, releasing access to controller data.

  6. Returns 0, indicating that the function executed successfully.

In summary, this function is used to display controller information in a file sequence, including instance number, controller ID, and option information. If the controller does not exist, the function will display all possible options modes.

nvmf_dev_open

static int nvmf_dev_open(struct inode *inode, struct file *file)
{<!-- -->
/*
* The miscdevice code initializes file->private_data, but doesn't
* make use of it later.
*/
file->private_data = NULL;
return single_open(file, nvmf_dev_show, NULL);
}

This code defines a function nvmf_dev_open for opening NVMe controller device files. This function is called by the kernel when a file is opened. The main purpose of the function is to set the private_data field of the file and call the single_open function to execute the callback function nvmf_dev_show specific to the open operation.

The specific execution steps are as follows:

  1. The function receives a pointer to inode and a pointer to file as parameters.

  2. The private_data field of the initialization file is NULL.

  3. Call the single_open function, whose parameters include the callback function nvmf_dev_show to execute and an additional data pointer (here NULL). This will execute the nvmf_dev_show function when the file is opened and write the output to the file.

  4. Returns the return value of the single_open function, usually the file descriptor after successfully opening the file.

In short, this function is used to open the NVMe controller device file, and set the corresponding callback function and data to display the controller information in the file.

nvmf_dev_release


static int nvmf_dev_release(struct inode *inode, struct file *file)
{<!-- -->
struct seq_file *seq_file = file->private_data;
struct nvme_ctrl *ctrl = seq_file->private;

if(ctrl)
nvme_put_ctrl(ctrl);
return single_release(inode, file);
}

This code defines a function nvmf_dev_release that releases the NVMe controller device file. The kernel calls this function when closing the file. The main task of the function is to release related resources, including releasing the NVMe controller and calling the single_release function to execute the callback function specific to the shutdown operation.

The specific execution steps are as follows:

  1. The function receives a pointer to inode and a pointer to file as parameters.

  2. Get the associated seq_file structure via the file’s private_data field, and then get the NVMe controller structure associated with the file from there.

  3. If the controller exists (not empty), call the nvme_put_ctrl function to release the controller. This is to ensure that related resources are properly released when the controller is no longer needed.

  4. Call the single_release function, which takes as arguments the callback function to be executed (usually seq_release) and the inode and file.

  5. Returns the return value of the single_release function, usually the return value after successfully closing the file.

In summary, this function is used to release the NVMe controller device file and the resources associated with it, including the NVMe controller itself.

nvmf_dev_fops & amp; nvmf_misc

static const struct file_operations nvmf_dev_fops = {<!-- -->
.owner = THIS_MODULE,
.write = nvmf_dev_write,
.read = seq_read,
.open = nvmf_dev_open,
.release = nvmf_dev_release,
};

static struct miscdevice nvmf_misc = {<!-- -->
.minor = MISC_DYNAMIC_MINOR,
.name = "nvme-fabrics",
.fops = &nvmf_dev_fops,
};

This code defines a file operation structure nvmf_dev_fops, which contains various function pointers for file operations. It then uses this file operation structure to initialize a miscdevice structure nvmf_misc that represents a misc device and is used to create the NVMe Fabrics controller device file.

Specifically, the meanings of each field here are as follows:

  1. .owner: specifies the module owner, usually THIS_MODULE is used to represent the current module.

  2. .write: A function pointer pointing to the write operation. This points to the function nvmf_dev_write. This function will be called when writing to the device file.

  3. .read: A function pointer pointing to the read operation. This points to the standard function seq_read in the kernel, which is used to read the contents of the device file.

  4. .open: A function pointer pointing to the open operation. This points to the custom function nvmf_dev_open. This function will be called when opening the device file.

  5. .release: A function pointer pointing to the shutdown operation. This points to the custom function nvmf_dev_release. This function will be called when closing the device file.

Next, register a misc device by initializing a miscdevice structure nvmf_misc:

  1. .minor: Use MISC_DYNAMIC_MINOR to dynamically allocate an unused minor number.

  2. .name: Specifies the name of the device, here is “nvme-fabrics”.

  3. .fops: Specifies the file operation structure, namely nvmf_dev_fops, which will associate device files with file operation functions.

Through these steps, a misc device associated with the NVMe Fabrics controller is created and the corresponding file operation functions are specified. In this way, when the user operates the device file in the file system, the kernel will perform corresponding operations according to the file operation function.

nvmf_init

static int __init nvmf_init(void)
{<!-- -->
int ret;

nvmf_default_host = nvmf_host_default();
if (!nvmf_default_host)
return -ENOMEM;

nvmf_class = class_create("nvme-fabrics");
if (IS_ERR(nvmf_class)) {<!-- -->
pr_err("couldn't register class nvme-fabrics\
");
ret = PTR_ERR(nvmf_class);
goto out_free_host;
}

nvmf_device =
device_create(nvmf_class, NULL, MKDEV(0, 0), NULL, "ctl");
if (IS_ERR(nvmf_device)) {<!-- -->
pr_err("couldn't create nvme-fabrics device!\
");
ret = PTR_ERR(nvmf_device);
goto out_destroy_class;
}

ret = misc_register( & amp;nvmf_misc);
if (ret) {<!-- -->
pr_err("couldn't register misc device: %d\
", ret);
goto out_destroy_device;
}

return 0;

out_destroy_device:
device_destroy(nvmf_class, MKDEV(0, 0));
out_destroy_class:
class_destroy(nvmf_class);
out_free_host:
nvmf_host_put(nvmf_default_host);
return ret;
}

This code is the module initialization function nvmf_init, which performs these operations when loading the module. Here is an explanation of this function:

  1. nvmf_default_host = nvmf_host_default();

    • Get the default NVMe Fabrics host object.
  2. if (!nvmf_default_host) return -ENOMEM;

    • Check whether the default host object is successfully obtained, if not, return an error code of insufficient memory.
  3. nvmf_class = class_create("nvme-fabrics");

    • Create a device class named “nvme-fabrics”.
  4. if (IS_ERR(nvmf_class)) { ... }

    • Check whether the device class is successfully created, if it fails, output an error message and clean up, and then return the corresponding error code.
  5. nvmf_device = device_create(nvmf_class, NULL, MKDEV(0, 0), NULL, "ctl");

    • Create a device under the “nvme-fabrics” class named “ctl” and associate it with the device node with a major device number of 0 and a minor device number of 0.
  6. if (IS_ERR(nvmf_device)) { ... }

    • Check whether the device is successfully created. If it fails, output an error message and clean it up, and then return the corresponding error code.
  7. ret = misc_register( & amp;nvmf_misc);

    • Registers the misc device, which will enable device file operations defined in nvmf_dev_fops.
  8. if (ret) { ... }

    • Check whether the device registration is successful. If it fails, output an error message and clean it up, and then return the corresponding error code.
  9. If the above steps are completed successfully, 0 is returned to indicate successful module initialization.

If a step fails, the code will clean up in reverse order based on where the error occurred to ensure that resources are released correctly.

In short, this code is responsible for initializing NVMe Fabrics-related device classes, device nodes and registering misc devices when the module is loaded, thereby providing corresponding device files and operation interfaces for user space.

nvmf_exit

static void __exit nvmf_exit(void)
{<!-- -->
misc_deregister( & amp;nvmf_misc);
device_destroy(nvmf_class, MKDEV(0, 0));
class_destroy(nvmf_class);
nvmf_host_put(nvmf_default_host);

BUILD_BUG_ON(sizeof(struct nvmf_common_command) != 64);
BUILD_BUG_ON(sizeof(struct nvmf_connect_command) != 64);
BUILD_BUG_ON(sizeof(struct nvmf_property_get_command) != 64);
BUILD_BUG_ON(sizeof(struct nvmf_property_set_command) != 64);
BUILD_BUG_ON(sizeof(struct nvmf_auth_send_command) != 64);
BUILD_BUG_ON(sizeof(struct nvmf_auth_receive_command) != 64);
BUILD_BUG_ON(sizeof(struct nvmf_connect_data) != 1024);
BUILD_BUG_ON(sizeof(struct nvmf_auth_dhchap_negotiate_data) != 8);
BUILD_BUG_ON(sizeof(struct nvmf_auth_dhchap_challenge_data) != 16);
BUILD_BUG_ON(sizeof(struct nvmf_auth_dhchap_reply_data) != 16);
BUILD_BUG_ON(sizeof(struct nvmf_auth_dhchap_success1_data) != 16);
BUILD_BUG_ON(sizeof(struct nvmf_auth_dhchap_success2_data) != 16);
}

This code is the module unloading function nvmf_exit, which performs these operations when the module is unloaded. Here is an explanation of this function:

  1. misc_deregister( & amp;nvmf_misc);

    • Unregister misc devices and cancel previously registered devices.
  2. device_destroy(nvmf_class, MKDEV(0, 0));

    • Destroy the previously created device node.
  3. class_destroy(nvmf_class);

    • Destroy the previously created device class.
  4. nvmf_host_put(nvmf_default_host);

    • Release the default NVMe Fabrics host object.
  5. The following series of BUILD_BUG_ON macros are used to check whether the size of the structure meets the specified size limit at compile time. If the sizes do not match, a compilation error will result.

In short, this code is responsible for cleaning up previously initialized resources when the module is unloaded, including deregistering devices, destroying device nodes and device classes, releasing host objects, and performing some static size checks using the BUILD_BUG_ON macro.

MODULE_LICENSE("GPL v2");

module_init(nvmf_init);
module_exit(nvmf_exit);