forked from Minki/linux
513a4befae
Pull block layer updates from Jens Axboe: "This is the main pull request for block layer changes in 4.9. As mentioned at the last merge window, I've changed things up and now do just one branch for core block layer changes, and driver changes. This avoids dependencies between the two branches. Outside of this main pull request, there are two topical branches coming as well. This pull request contains: - A set of fixes, and a conversion to blk-mq, of nbd. From Josef. - Set of fixes and updates for lightnvm from Matias, Simon, and Arnd. Followup dependency fix from Geert. - General fixes from Bart, Baoyou, Guoqing, and Linus W. - CFQ async write starvation fix from Glauber. - Add supprot for delayed kick of the requeue list, from Mike. - Pull out the scalable bitmap code from blk-mq-tag.c and make it generally available under the name of sbitmap. Only blk-mq-tag uses it for now, but the blk-mq scheduling bits will use it as well. From Omar. - bdev thaw error progagation from Pierre. - Improve the blk polling statistics, and allow the user to clear them. From Stephen. - Set of minor cleanups from Christoph in block/blk-mq. - Set of cleanups and optimizations from me for block/blk-mq. - Various nvme/nvmet/nvmeof fixes from the various folks" * 'for-4.9/block' of git://git.kernel.dk/linux-block: (54 commits) fs/block_dev.c: return the right error in thaw_bdev() nvme: Pass pointers, not dma addresses, to nvme_get/set_features() nvme/scsi: Remove power management support nvmet: Make dsm number of ranges zero based nvmet: Use direct IO for writes admin-cmd: Added smart-log command support. nvme-fabrics: Add host_traddr options field to host infrastructure nvme-fabrics: revise host transport option descriptions nvme-fabrics: rework nvmf_get_address() for variable options nbd: use BLK_MQ_F_BLOCKING blkcg: Annotate blkg_hint correctly cfq: fix starvation of asynchronous writes blk-mq: add flag for drivers wanting blocking ->queue_rq() blk-mq: remove non-blocking pass in blk_mq_map_request blk-mq: get rid of manual run of queue with __blk_mq_run_hw_queue() block: export bio_free_pages to other modules lightnvm: propagate device_add() error code lightnvm: expose device geometry through sysfs lightnvm: control life of nvm_dev in driver blk-mq: register device instead of disk ...
983 lines
25 KiB
C
983 lines
25 KiB
C
/*
|
|
* NVMe over Fabrics common host code.
|
|
* Copyright (c) 2015-2016 HGST, a Western Digital Company.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*/
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
#include <linux/init.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/parser.h>
|
|
#include <linux/seq_file.h>
|
|
#include "nvme.h"
|
|
#include "fabrics.h"
|
|
|
|
static LIST_HEAD(nvmf_transports);
|
|
static DEFINE_MUTEX(nvmf_transports_mutex);
|
|
|
|
static LIST_HEAD(nvmf_hosts);
|
|
static DEFINE_MUTEX(nvmf_hosts_mutex);
|
|
|
|
static struct nvmf_host *nvmf_default_host;
|
|
|
|
static struct nvmf_host *__nvmf_host_find(const char *hostnqn)
|
|
{
|
|
struct nvmf_host *host;
|
|
|
|
list_for_each_entry(host, &nvmf_hosts, list) {
|
|
if (!strcmp(host->nqn, hostnqn))
|
|
return host;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct nvmf_host *nvmf_host_add(const char *hostnqn)
|
|
{
|
|
struct nvmf_host *host;
|
|
|
|
mutex_lock(&nvmf_hosts_mutex);
|
|
host = __nvmf_host_find(hostnqn);
|
|
if (host) {
|
|
kref_get(&host->ref);
|
|
goto out_unlock;
|
|
}
|
|
|
|
host = kmalloc(sizeof(*host), GFP_KERNEL);
|
|
if (!host)
|
|
goto out_unlock;
|
|
|
|
kref_init(&host->ref);
|
|
memcpy(host->nqn, hostnqn, NVMF_NQN_SIZE);
|
|
uuid_be_gen(&host->id);
|
|
|
|
list_add_tail(&host->list, &nvmf_hosts);
|
|
out_unlock:
|
|
mutex_unlock(&nvmf_hosts_mutex);
|
|
return host;
|
|
}
|
|
|
|
static struct nvmf_host *nvmf_host_default(void)
|
|
{
|
|
struct nvmf_host *host;
|
|
|
|
host = kmalloc(sizeof(*host), GFP_KERNEL);
|
|
if (!host)
|
|
return NULL;
|
|
|
|
kref_init(&host->ref);
|
|
uuid_be_gen(&host->id);
|
|
snprintf(host->nqn, NVMF_NQN_SIZE,
|
|
"nqn.2014-08.org.nvmexpress:NVMf:uuid:%pUb", &host->id);
|
|
|
|
mutex_lock(&nvmf_hosts_mutex);
|
|
list_add_tail(&host->list, &nvmf_hosts);
|
|
mutex_unlock(&nvmf_hosts_mutex);
|
|
|
|
return host;
|
|
}
|
|
|
|
static void nvmf_host_destroy(struct kref *ref)
|
|
{
|
|
struct nvmf_host *host = container_of(ref, struct nvmf_host, ref);
|
|
|
|
mutex_lock(&nvmf_hosts_mutex);
|
|
list_del(&host->list);
|
|
mutex_unlock(&nvmf_hosts_mutex);
|
|
|
|
kfree(host);
|
|
}
|
|
|
|
static void nvmf_host_put(struct nvmf_host *host)
|
|
{
|
|
if (host)
|
|
kref_put(&host->ref, nvmf_host_destroy);
|
|
}
|
|
|
|
/**
|
|
* nvmf_get_address() - Get address/port
|
|
* @ctrl: Host NVMe controller instance which we got the address
|
|
* @buf: OUTPUT parameter that will contain the address/port
|
|
* @size: buffer size
|
|
*/
|
|
int nvmf_get_address(struct nvme_ctrl *ctrl, char *buf, int size)
|
|
{
|
|
int len = 0;
|
|
|
|
if (ctrl->opts->mask & NVMF_OPT_TRADDR)
|
|
len += snprintf(buf, size, "traddr=%s", ctrl->opts->traddr);
|
|
if (ctrl->opts->mask & NVMF_OPT_TRSVCID)
|
|
len += snprintf(buf + len, size - len, "%strsvcid=%s",
|
|
(len) ? "," : "", ctrl->opts->trsvcid);
|
|
if (ctrl->opts->mask & NVMF_OPT_HOST_TRADDR)
|
|
len += snprintf(buf + len, size - len, "%shost_traddr=%s",
|
|
(len) ? "," : "", ctrl->opts->host_traddr);
|
|
len += snprintf(buf + len, size - len, "\n");
|
|
|
|
return len;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_get_address);
|
|
|
|
/**
|
|
* nvmf_get_subsysnqn() - Get subsystem NQN
|
|
* @ctrl: Host NVMe controller instance which we got the NQN
|
|
*/
|
|
const char *nvmf_get_subsysnqn(struct nvme_ctrl *ctrl)
|
|
{
|
|
return ctrl->opts->subsysnqn;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_get_subsysnqn);
|
|
|
|
/**
|
|
* nvmf_reg_read32() - NVMe Fabrics "Property Get" API function.
|
|
* @ctrl: Host NVMe controller instance maintaining the admin
|
|
* queue used to submit the property read command to
|
|
* the allocated NVMe controller resource on the target system.
|
|
* @off: Starting offset value of the targeted property
|
|
* register (see the fabrics section of the NVMe standard).
|
|
* @val: OUTPUT parameter that will contain the value of
|
|
* the property after a successful read.
|
|
*
|
|
* Used by the host system to retrieve a 32-bit capsule property value
|
|
* from an NVMe controller on the target system.
|
|
*
|
|
* ("Capsule property" is an "PCIe register concept" applied to the
|
|
* NVMe fabrics space.)
|
|
*
|
|
* Return:
|
|
* 0: successful read
|
|
* > 0: NVMe error status code
|
|
* < 0: Linux errno error code
|
|
*/
|
|
int nvmf_reg_read32(struct nvme_ctrl *ctrl, u32 off, u32 *val)
|
|
{
|
|
struct nvme_command cmd;
|
|
struct nvme_completion cqe;
|
|
int ret;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.prop_get.opcode = nvme_fabrics_command;
|
|
cmd.prop_get.fctype = nvme_fabrics_type_property_get;
|
|
cmd.prop_get.offset = cpu_to_le32(off);
|
|
|
|
ret = __nvme_submit_sync_cmd(ctrl->admin_q, &cmd, &cqe, NULL, 0, 0,
|
|
NVME_QID_ANY, 0, 0);
|
|
|
|
if (ret >= 0)
|
|
*val = le64_to_cpu(cqe.result64);
|
|
if (unlikely(ret != 0))
|
|
dev_err(ctrl->device,
|
|
"Property Get error: %d, offset %#x\n",
|
|
ret > 0 ? ret & ~NVME_SC_DNR : ret, off);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_reg_read32);
|
|
|
|
/**
|
|
* nvmf_reg_read64() - NVMe Fabrics "Property Get" API function.
|
|
* @ctrl: Host NVMe controller instance maintaining the admin
|
|
* queue used to submit the property read command to
|
|
* the allocated controller resource on the target system.
|
|
* @off: Starting offset value of the targeted property
|
|
* register (see the fabrics section of the NVMe standard).
|
|
* @val: OUTPUT parameter that will contain the value of
|
|
* the property after a successful read.
|
|
*
|
|
* Used by the host system to retrieve a 64-bit capsule property value
|
|
* from an NVMe controller on the target system.
|
|
*
|
|
* ("Capsule property" is an "PCIe register concept" applied to the
|
|
* NVMe fabrics space.)
|
|
*
|
|
* Return:
|
|
* 0: successful read
|
|
* > 0: NVMe error status code
|
|
* < 0: Linux errno error code
|
|
*/
|
|
int nvmf_reg_read64(struct nvme_ctrl *ctrl, u32 off, u64 *val)
|
|
{
|
|
struct nvme_command cmd;
|
|
struct nvme_completion cqe;
|
|
int ret;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.prop_get.opcode = nvme_fabrics_command;
|
|
cmd.prop_get.fctype = nvme_fabrics_type_property_get;
|
|
cmd.prop_get.attrib = 1;
|
|
cmd.prop_get.offset = cpu_to_le32(off);
|
|
|
|
ret = __nvme_submit_sync_cmd(ctrl->admin_q, &cmd, &cqe, NULL, 0, 0,
|
|
NVME_QID_ANY, 0, 0);
|
|
|
|
if (ret >= 0)
|
|
*val = le64_to_cpu(cqe.result64);
|
|
if (unlikely(ret != 0))
|
|
dev_err(ctrl->device,
|
|
"Property Get error: %d, offset %#x\n",
|
|
ret > 0 ? ret & ~NVME_SC_DNR : ret, off);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_reg_read64);
|
|
|
|
/**
|
|
* nvmf_reg_write32() - NVMe Fabrics "Property Write" API function.
|
|
* @ctrl: Host NVMe controller instance maintaining the admin
|
|
* queue used to submit the property read command to
|
|
* the allocated NVMe controller resource on the target system.
|
|
* @off: Starting offset value of the targeted property
|
|
* register (see the fabrics section of the NVMe standard).
|
|
* @val: Input parameter that contains the value to be
|
|
* written to the property.
|
|
*
|
|
* Used by the NVMe host system to write a 32-bit capsule property value
|
|
* to an NVMe controller on the target system.
|
|
*
|
|
* ("Capsule property" is an "PCIe register concept" applied to the
|
|
* NVMe fabrics space.)
|
|
*
|
|
* Return:
|
|
* 0: successful write
|
|
* > 0: NVMe error status code
|
|
* < 0: Linux errno error code
|
|
*/
|
|
int nvmf_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val)
|
|
{
|
|
struct nvme_command cmd;
|
|
int ret;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.prop_set.opcode = nvme_fabrics_command;
|
|
cmd.prop_set.fctype = nvme_fabrics_type_property_set;
|
|
cmd.prop_set.attrib = 0;
|
|
cmd.prop_set.offset = cpu_to_le32(off);
|
|
cmd.prop_set.value = cpu_to_le64(val);
|
|
|
|
ret = __nvme_submit_sync_cmd(ctrl->admin_q, &cmd, NULL, NULL, 0, 0,
|
|
NVME_QID_ANY, 0, 0);
|
|
if (unlikely(ret))
|
|
dev_err(ctrl->device,
|
|
"Property Set error: %d, offset %#x\n",
|
|
ret > 0 ? ret & ~NVME_SC_DNR : ret, off);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_reg_write32);
|
|
|
|
/**
|
|
* nvmf_log_connect_error() - Error-parsing-diagnostic print
|
|
* out function for connect() errors.
|
|
*
|
|
* @ctrl: the specific /dev/nvmeX device that had the error.
|
|
*
|
|
* @errval: Error code to be decoded in a more human-friendly
|
|
* printout.
|
|
*
|
|
* @offset: For use with the NVMe error code NVME_SC_CONNECT_INVALID_PARAM.
|
|
*
|
|
* @cmd: This is the SQE portion of a submission capsule.
|
|
*
|
|
* @data: This is the "Data" portion of a submission capsule.
|
|
*/
|
|
static void nvmf_log_connect_error(struct nvme_ctrl *ctrl,
|
|
int errval, int offset, struct nvme_command *cmd,
|
|
struct nvmf_connect_data *data)
|
|
{
|
|
int err_sctype = errval & (~NVME_SC_DNR);
|
|
|
|
switch (err_sctype) {
|
|
|
|
case (NVME_SC_CONNECT_INVALID_PARAM):
|
|
if (offset >> 16) {
|
|
char *inv_data = "Connect Invalid Data Parameter";
|
|
|
|
switch (offset & 0xffff) {
|
|
case (offsetof(struct nvmf_connect_data, cntlid)):
|
|
dev_err(ctrl->device,
|
|
"%s, cntlid: %d\n",
|
|
inv_data, data->cntlid);
|
|
break;
|
|
case (offsetof(struct nvmf_connect_data, hostnqn)):
|
|
dev_err(ctrl->device,
|
|
"%s, hostnqn \"%s\"\n",
|
|
inv_data, data->hostnqn);
|
|
break;
|
|
case (offsetof(struct nvmf_connect_data, subsysnqn)):
|
|
dev_err(ctrl->device,
|
|
"%s, subsysnqn \"%s\"\n",
|
|
inv_data, data->subsysnqn);
|
|
break;
|
|
default:
|
|
dev_err(ctrl->device,
|
|
"%s, starting byte offset: %d\n",
|
|
inv_data, offset & 0xffff);
|
|
break;
|
|
}
|
|
} else {
|
|
char *inv_sqe = "Connect Invalid SQE Parameter";
|
|
|
|
switch (offset) {
|
|
case (offsetof(struct nvmf_connect_command, qid)):
|
|
dev_err(ctrl->device,
|
|
"%s, qid %d\n",
|
|
inv_sqe, cmd->connect.qid);
|
|
break;
|
|
default:
|
|
dev_err(ctrl->device,
|
|
"%s, starting byte offset: %d\n",
|
|
inv_sqe, offset);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(ctrl->device,
|
|
"Connect command failed, error wo/DNR bit: %d\n",
|
|
err_sctype);
|
|
break;
|
|
} /* switch (err_sctype) */
|
|
}
|
|
|
|
/**
|
|
* nvmf_connect_admin_queue() - NVMe Fabrics Admin Queue "Connect"
|
|
* API function.
|
|
* @ctrl: Host nvme controller instance used to request
|
|
* a new NVMe controller allocation on the target
|
|
* system and establish an NVMe Admin connection to
|
|
* that controller.
|
|
*
|
|
* This function enables an NVMe host device to request a new allocation of
|
|
* an NVMe controller resource on a target system as well establish a
|
|
* fabrics-protocol connection of the NVMe Admin queue between the
|
|
* host system device and the allocated NVMe controller on the
|
|
* target system via a NVMe Fabrics "Connect" command.
|
|
*
|
|
* Return:
|
|
* 0: success
|
|
* > 0: NVMe error status code
|
|
* < 0: Linux errno error code
|
|
*
|
|
*/
|
|
int nvmf_connect_admin_queue(struct nvme_ctrl *ctrl)
|
|
{
|
|
struct nvme_command cmd;
|
|
struct nvme_completion cqe;
|
|
struct nvmf_connect_data *data;
|
|
int ret;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.connect.opcode = nvme_fabrics_command;
|
|
cmd.connect.fctype = nvme_fabrics_type_connect;
|
|
cmd.connect.qid = 0;
|
|
|
|
/*
|
|
* fabrics spec sets a minimum of depth 32 for admin queue,
|
|
* so set the queue with this depth always until
|
|
* justification otherwise.
|
|
*/
|
|
cmd.connect.sqsize = cpu_to_le16(NVMF_AQ_DEPTH - 1);
|
|
|
|
/*
|
|
* Set keep-alive timeout in seconds granularity (ms * 1000)
|
|
* and add a grace period for controller kato enforcement
|
|
*/
|
|
cmd.connect.kato = ctrl->opts->discovery_nqn ? 0 :
|
|
cpu_to_le32((ctrl->kato + NVME_KATO_GRACE) * 1000);
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
memcpy(&data->hostid, &ctrl->opts->host->id, sizeof(uuid_be));
|
|
data->cntlid = cpu_to_le16(0xffff);
|
|
strncpy(data->subsysnqn, ctrl->opts->subsysnqn, NVMF_NQN_SIZE);
|
|
strncpy(data->hostnqn, ctrl->opts->host->nqn, NVMF_NQN_SIZE);
|
|
|
|
ret = __nvme_submit_sync_cmd(ctrl->admin_q, &cmd, &cqe,
|
|
data, sizeof(*data), 0, NVME_QID_ANY, 1,
|
|
BLK_MQ_REQ_RESERVED | BLK_MQ_REQ_NOWAIT);
|
|
if (ret) {
|
|
nvmf_log_connect_error(ctrl, ret, le32_to_cpu(cqe.result),
|
|
&cmd, data);
|
|
goto out_free_data;
|
|
}
|
|
|
|
ctrl->cntlid = le16_to_cpu(cqe.result16);
|
|
|
|
out_free_data:
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_connect_admin_queue);
|
|
|
|
/**
|
|
* nvmf_connect_io_queue() - NVMe Fabrics I/O Queue "Connect"
|
|
* API function.
|
|
* @ctrl: Host nvme controller instance used to establish an
|
|
* NVMe I/O queue connection to the already allocated NVMe
|
|
* controller on the target system.
|
|
* @qid: NVMe I/O queue number for the new I/O connection between
|
|
* host and target (note qid == 0 is illegal as this is
|
|
* the Admin queue, per NVMe standard).
|
|
*
|
|
* This function issues a fabrics-protocol connection
|
|
* of a NVMe I/O queue (via NVMe Fabrics "Connect" command)
|
|
* between the host system device and the allocated NVMe controller
|
|
* on the target system.
|
|
*
|
|
* Return:
|
|
* 0: success
|
|
* > 0: NVMe error status code
|
|
* < 0: Linux errno error code
|
|
*/
|
|
int nvmf_connect_io_queue(struct nvme_ctrl *ctrl, u16 qid)
|
|
{
|
|
struct nvme_command cmd;
|
|
struct nvmf_connect_data *data;
|
|
struct nvme_completion cqe;
|
|
int ret;
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.connect.opcode = nvme_fabrics_command;
|
|
cmd.connect.fctype = nvme_fabrics_type_connect;
|
|
cmd.connect.qid = cpu_to_le16(qid);
|
|
cmd.connect.sqsize = cpu_to_le16(ctrl->sqsize);
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
memcpy(&data->hostid, &ctrl->opts->host->id, sizeof(uuid_be));
|
|
data->cntlid = cpu_to_le16(ctrl->cntlid);
|
|
strncpy(data->subsysnqn, ctrl->opts->subsysnqn, NVMF_NQN_SIZE);
|
|
strncpy(data->hostnqn, ctrl->opts->host->nqn, NVMF_NQN_SIZE);
|
|
|
|
ret = __nvme_submit_sync_cmd(ctrl->connect_q, &cmd, &cqe,
|
|
data, sizeof(*data), 0, qid, 1,
|
|
BLK_MQ_REQ_RESERVED | BLK_MQ_REQ_NOWAIT);
|
|
if (ret) {
|
|
nvmf_log_connect_error(ctrl, ret, le32_to_cpu(cqe.result),
|
|
&cmd, data);
|
|
}
|
|
kfree(data);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_connect_io_queue);
|
|
|
|
/**
|
|
* nvmf_register_transport() - NVMe Fabrics Library registration function.
|
|
* @ops: Transport ops instance to be registered to the
|
|
* common fabrics library.
|
|
*
|
|
* API function that registers the type of specific transport fabric
|
|
* being implemented to the common NVMe fabrics library. Part of
|
|
* the overall init sequence of starting up a fabrics driver.
|
|
*/
|
|
void nvmf_register_transport(struct nvmf_transport_ops *ops)
|
|
{
|
|
mutex_lock(&nvmf_transports_mutex);
|
|
list_add_tail(&ops->entry, &nvmf_transports);
|
|
mutex_unlock(&nvmf_transports_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_register_transport);
|
|
|
|
/**
|
|
* nvmf_unregister_transport() - NVMe Fabrics Library unregistration function.
|
|
* @ops: Transport ops instance to be unregistered from the
|
|
* common fabrics library.
|
|
*
|
|
* Fabrics API function that unregisters the type of specific transport
|
|
* fabric being implemented from the common NVMe fabrics library.
|
|
* Part of the overall exit sequence of unloading the implemented driver.
|
|
*/
|
|
void nvmf_unregister_transport(struct nvmf_transport_ops *ops)
|
|
{
|
|
mutex_lock(&nvmf_transports_mutex);
|
|
list_del(&ops->entry);
|
|
mutex_unlock(&nvmf_transports_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_unregister_transport);
|
|
|
|
static struct nvmf_transport_ops *nvmf_lookup_transport(
|
|
struct nvmf_ctrl_options *opts)
|
|
{
|
|
struct nvmf_transport_ops *ops;
|
|
|
|
lockdep_assert_held(&nvmf_transports_mutex);
|
|
|
|
list_for_each_entry(ops, &nvmf_transports, entry) {
|
|
if (strcmp(ops->name, opts->transport) == 0)
|
|
return ops;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const match_table_t opt_tokens = {
|
|
{ NVMF_OPT_TRANSPORT, "transport=%s" },
|
|
{ NVMF_OPT_TRADDR, "traddr=%s" },
|
|
{ NVMF_OPT_TRSVCID, "trsvcid=%s" },
|
|
{ NVMF_OPT_NQN, "nqn=%s" },
|
|
{ NVMF_OPT_QUEUE_SIZE, "queue_size=%d" },
|
|
{ NVMF_OPT_NR_IO_QUEUES, "nr_io_queues=%d" },
|
|
{ NVMF_OPT_RECONNECT_DELAY, "reconnect_delay=%d" },
|
|
{ NVMF_OPT_KATO, "keep_alive_tmo=%d" },
|
|
{ NVMF_OPT_HOSTNQN, "hostnqn=%s" },
|
|
{ NVMF_OPT_HOST_TRADDR, "host_traddr=%s" },
|
|
{ NVMF_OPT_ERR, NULL }
|
|
};
|
|
|
|
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;
|
|
|
|
/* Set defaults */
|
|
opts->queue_size = NVMF_DEF_QUEUE_SIZE;
|
|
opts->nr_io_queues = num_online_cpus();
|
|
opts->reconnect_delay = NVMF_DEF_RECONNECT_DELAY;
|
|
|
|
options = o = kstrdup(buf, GFP_KERNEL);
|
|
if (!options)
|
|
return -ENOMEM;
|
|
|
|
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;
|
|
}
|
|
opts->transport = p;
|
|
break;
|
|
case NVMF_OPT_NQN:
|
|
p = match_strdup(args);
|
|
if (!p) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
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));
|
|
if (opts->discovery_nqn)
|
|
opts->nr_io_queues = 0;
|
|
break;
|
|
case NVMF_OPT_TRADDR:
|
|
p = match_strdup(args);
|
|
if (!p) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
opts->traddr = p;
|
|
break;
|
|
case NVMF_OPT_TRSVCID:
|
|
p = match_strdup(args);
|
|
if (!p) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
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;
|
|
}
|
|
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 (opts->discovery_nqn) {
|
|
pr_err("Discovery controllers cannot accept keep_alive_tmo != 0\n");
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (token < 0) {
|
|
pr_err("Invalid keep_alive_tmo %d\n", token);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
} else if (token == 0) {
|
|
/* Allowed for debug */
|
|
pr_warn("keep_alive_tmo 0 won't execute keep alives!!!\n");
|
|
}
|
|
opts->kato = 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);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
opts->host = nvmf_host_add(p);
|
|
if (!opts->host) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
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;
|
|
}
|
|
opts->host_traddr = p;
|
|
break;
|
|
default:
|
|
pr_warn("unknown parameter or missing value '%s' in ctrl creation request\n",
|
|
p);
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!opts->host) {
|
|
kref_get(&nvmf_default_host->ref);
|
|
opts->host = nvmf_default_host;
|
|
}
|
|
|
|
out:
|
|
if (!opts->discovery_nqn && !opts->kato)
|
|
opts->kato = NVME_DEFAULT_KATO;
|
|
kfree(options);
|
|
return ret;
|
|
}
|
|
|
|
static int nvmf_check_required_opts(struct nvmf_ctrl_options *opts,
|
|
unsigned int required_opts)
|
|
{
|
|
if ((opts->mask & required_opts) != required_opts) {
|
|
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;
|
|
}
|
|
|
|
static int nvmf_check_allowed_opts(struct nvmf_ctrl_options *opts,
|
|
unsigned int allowed_opts)
|
|
{
|
|
if (opts->mask & ~allowed_opts) {
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(opt_tokens); i++) {
|
|
if (opt_tokens[i].token & ~allowed_opts) {
|
|
pr_warn("invalid parameter '%s'\n",
|
|
opt_tokens[i].pattern);
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvmf_free_options);
|
|
|
|
#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)
|
|
|
|
static struct nvme_ctrl *
|
|
nvmf_create_ctrl(struct device *dev, const char *buf, size_t count)
|
|
{
|
|
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;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
mutex_lock(&nvmf_transports_mutex);
|
|
ops = nvmf_lookup_transport(opts);
|
|
if (!ops) {
|
|
pr_info("no handler found for transport %s.\n",
|
|
opts->transport);
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
ret = nvmf_check_required_opts(opts, ops->required_opts);
|
|
if (ret)
|
|
goto out_unlock;
|
|
ret = nvmf_check_allowed_opts(opts, NVMF_ALLOWED_OPTS |
|
|
ops->allowed_opts | ops->required_opts);
|
|
if (ret)
|
|
goto out_unlock;
|
|
|
|
ctrl = ops->create_ctrl(dev, opts);
|
|
if (IS_ERR(ctrl)) {
|
|
ret = PTR_ERR(ctrl);
|
|
goto out_unlock;
|
|
}
|
|
|
|
mutex_unlock(&nvmf_transports_mutex);
|
|
return ctrl;
|
|
|
|
out_unlock:
|
|
mutex_unlock(&nvmf_transports_mutex);
|
|
out_free_opts:
|
|
nvmf_host_put(opts->host);
|
|
kfree(opts);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
static struct class *nvmf_class;
|
|
static struct device *nvmf_device;
|
|
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(&nvmf_dev_mutex);
|
|
if (seq_file->private) {
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
ctrl = nvmf_create_ctrl(nvmf_device, buf, count);
|
|
if (IS_ERR(ctrl)) {
|
|
ret = PTR_ERR(ctrl);
|
|
goto out_unlock;
|
|
}
|
|
|
|
seq_file->private = ctrl;
|
|
|
|
out_unlock:
|
|
mutex_unlock(&nvmf_dev_mutex);
|
|
kfree(buf);
|
|
return ret ? ret : count;
|
|
}
|
|
|
|
static int nvmf_dev_show(struct seq_file *seq_file, void *private)
|
|
{
|
|
struct nvme_ctrl *ctrl;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&nvmf_dev_mutex);
|
|
ctrl = seq_file->private;
|
|
if (!ctrl) {
|
|
ret = -EINVAL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
seq_printf(seq_file, "instance=%d,cntlid=%d\n",
|
|
ctrl->instance, ctrl->cntlid);
|
|
|
|
out_unlock:
|
|
mutex_unlock(&nvmf_dev_mutex);
|
|
return ret;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
static int __init nvmf_init(void)
|
|
{
|
|
int ret;
|
|
|
|
nvmf_default_host = nvmf_host_default();
|
|
if (!nvmf_default_host)
|
|
return -ENOMEM;
|
|
|
|
nvmf_class = class_create(THIS_MODULE, "nvme-fabrics");
|
|
if (IS_ERR(nvmf_class)) {
|
|
pr_err("couldn't register class nvme-fabrics\n");
|
|
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-fabris device!\n");
|
|
ret = PTR_ERR(nvmf_device);
|
|
goto out_destroy_class;
|
|
}
|
|
|
|
ret = misc_register(&nvmf_misc);
|
|
if (ret) {
|
|
pr_err("couldn't register misc device: %d\n", 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;
|
|
}
|
|
|
|
static void __exit nvmf_exit(void)
|
|
{
|
|
misc_deregister(&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_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_connect_data) != 1024);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
module_init(nvmf_init);
|
|
module_exit(nvmf_exit);
|