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:
-
Check for disallowed options by checking if
opts->mask
contains any options not inallowed_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 outsideallowed_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.
- If the option exists in
- Use a loop to go through all possible options, checking each option as follows:
-
If an invalid option is present,
-EINVAL
is returned to indicate that an invalid option was found. -
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:
-
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. -
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
-
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:
-
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. -
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:
-
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. -
Populates the
opts
structure with the parsed options databuf
. If parsing fails, the allocatedopts
structure is freed and an error is returned. -
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. -
Check whether the common required options are provided. If required options are not provided, the
opts
structure is freed and an error is returned. -
Based on the given transfer type, find the corresponding transfer operation structure
ops
. If no matching transfer operation structure is found, theopts
structure is freed and an error is returned. -
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.
-
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.
-
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.
-
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. -
The controller instance is successfully created, the module reference is released and the controller instance is returned.
-
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:
-
Checks whether the input write data exceeds the size of one page, and returns an out of memory error if it does.
-
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. -
Acquires the global mutex
nvmf_dev_mutex
to ensure that no other writes or concurrent controller creations occur while the write is being processed. -
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. -
Call the
nvmf_create_ctrl
function to create an NVMe controller instance with the written data. If creation fails, an error is returned. -
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. -
Unlock the global mutex.
-
Release the copied data buffer.
-
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:
-
Add a string of “instance=-1,cntlid=-1” in
seq_file
to indicate invalid or non-existing controller instance and controller ID. -
Iterate over each token in the options tokens array.
-
For each valid option token, append its schema (i.e. the option string) to the
seq_file
. -
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:
-
Acquires the controller’s mutex to ensure that access to controller data is thread-safe.
-
Get a reference to the controller object from the
seq_file
. -
If the controller object does not exist (that is,
NULL
), call the__nvmf_concat_opt_tokens
function to splice all possible option patterns intoseq_file
, and skip Go to the unlocking steps. -
If the controller object exists, use the
seq_printf
function to write the instance number and controller ID of the controller intoseq_file
. -
Unlocks the mutex, releasing access to controller data.
-
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:
-
The function receives a pointer to
inode
and a pointer tofile
as parameters. -
The
private_data
field of the initialization file isNULL
. -
Call the
single_open
function, whose parameters include the callback functionnvmf_dev_show
to execute and an additional data pointer (hereNULL
). This will execute thenvmf_dev_show
function when the file is opened and write the output to the file. -
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:
-
The function receives a pointer to
inode
and a pointer tofile
as parameters. -
Get the associated
seq_file
structure via the file’sprivate_data
field, and then get the NVMe controller structure associated with the file from there. -
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. -
Call the
single_release
function, which takes as arguments the callback function to be executed (usuallyseq_release
) and theinode
andfile
. -
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:
-
.owner
: specifies the module owner, usuallyTHIS_MODULE
is used to represent the current module. -
.write
: A function pointer pointing to the write operation. This points to the functionnvmf_dev_write
. This function will be called when writing to the device file. -
.read
: A function pointer pointing to the read operation. This points to the standard functionseq_read
in the kernel, which is used to read the contents of the device file. -
.open
: A function pointer pointing to the open operation. This points to the custom functionnvmf_dev_open
. This function will be called when opening the device file. -
.release
: A function pointer pointing to the shutdown operation. This points to the custom functionnvmf_dev_release
. This function will be called when closing the device file.
Next, register a misc device by initializing a miscdevice
structure nvmf_misc
:
-
.minor
: UseMISC_DYNAMIC_MINOR
to dynamically allocate an unused minor number. -
.name
: Specifies the name of the device, here is “nvme-fabrics”. -
.fops
: Specifies the file operation structure, namelynvmf_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:
-
nvmf_default_host = nvmf_host_default();
- Get the default NVMe Fabrics host object.
-
if (!nvmf_default_host) return -ENOMEM;
- Check whether the default host object is successfully obtained, if not, return an error code of insufficient memory.
-
nvmf_class = class_create("nvme-fabrics");
- Create a device class named “nvme-fabrics”.
-
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.
-
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.
-
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.
-
ret = misc_register( & amp;nvmf_misc);
- Registers the misc device, which will enable device file operations defined in
nvmf_dev_fops
.
- Registers the misc device, which will enable device file operations defined in
-
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.
-
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:
-
misc_deregister( & amp;nvmf_misc);
- Unregister misc devices and cancel previously registered devices.
-
device_destroy(nvmf_class, MKDEV(0, 0));
- Destroy the previously created device node.
-
class_destroy(nvmf_class);
- Destroy the previously created device class.
-
nvmf_host_put(nvmf_default_host);
- Release the default NVMe Fabrics host object.
-
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);