mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-30 22:03:17 +01:00
Add a 'reservation' debugfs file under each namespace directory that
shows the persistent reservation state, including enable status,
generation counter, notify mask, current holder info, and the full
registrant list with hostid and reservation key.
Each attribute is emitted as a single "key=value" line so the output is
easy to parse from scripts. The registrant list is emitted as repeated
"reg=" lines. The notify mask is emitted as a comma-separated list of
masked notification names. Empty values are reported as "none". Example
output:
enable=1
generation=2
notify_mask=reg_preempted,resv_released,resv_preempted
rtype=write_exclusive
holder=11111111-1111-1111-1111-111111111111,0x1111
reg=11111111-1111-1111-1111-111111111111,0x1111
reg=22222222-2222-2222-2222-222222222222,0x2222
When reservation is not enabled only "enable=0" is printed.
The output uses rcu_read_lock() for safe access to the holder and
registrant_list, consistent with other PR read paths.
Reviewed-by: Daniel Wagner <[email protected]>
Reviewed-by: Christoph Hellwig <[email protected]>
Signed-off-by: Guixin Liu <[email protected]>
Signed-off-by: Keith Busch <[email protected]>
333 lines
8.1 KiB
C
333 lines
8.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* DebugFS interface for the NVMe target.
|
|
* Copyright (c) 2022-2024 Shadow
|
|
* Copyright (c) 2024 SUSE LLC
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
|
|
#include "nvmet.h"
|
|
#include "debugfs.h"
|
|
|
|
static struct dentry *nvmet_debugfs;
|
|
|
|
#define NVMET_DEBUGFS_ATTR(field) \
|
|
static int field##_open(struct inode *inode, struct file *file) \
|
|
{ return single_open(file, field##_show, inode->i_private); } \
|
|
\
|
|
static const struct file_operations field##_fops = { \
|
|
.open = field##_open, \
|
|
.read = seq_read, \
|
|
.release = single_release, \
|
|
}
|
|
|
|
#define NVMET_DEBUGFS_RW_ATTR(field) \
|
|
static int field##_open(struct inode *inode, struct file *file) \
|
|
{ return single_open(file, field##_show, inode->i_private); } \
|
|
\
|
|
static const struct file_operations field##_fops = { \
|
|
.open = field##_open, \
|
|
.read = seq_read, \
|
|
.write = field##_write, \
|
|
.release = single_release, \
|
|
}
|
|
|
|
static int nvmet_ctrl_hostnqn_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
|
|
seq_puts(m, ctrl->hostnqn);
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ctrl_hostnqn);
|
|
|
|
static int nvmet_ctrl_kato_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
|
|
seq_printf(m, "%d\n", ctrl->kato);
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ctrl_kato);
|
|
|
|
static int nvmet_ctrl_port_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
|
|
seq_printf(m, "%d\n", le16_to_cpu(ctrl->port->disc_addr.portid));
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ctrl_port);
|
|
|
|
static const char *const csts_state_names[] = {
|
|
[NVME_CSTS_RDY] = "ready",
|
|
[NVME_CSTS_CFS] = "fatal",
|
|
[NVME_CSTS_NSSRO] = "reset",
|
|
[NVME_CSTS_SHST_OCCUR] = "shutdown",
|
|
[NVME_CSTS_SHST_CMPLT] = "completed",
|
|
[NVME_CSTS_PP] = "paused",
|
|
};
|
|
|
|
static int nvmet_ctrl_state_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
bool sep = false;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(csts_state_names); i++) {
|
|
int state = BIT(i);
|
|
|
|
if (!(ctrl->csts & state))
|
|
continue;
|
|
if (sep)
|
|
seq_puts(m, "|");
|
|
sep = true;
|
|
if (csts_state_names[state])
|
|
seq_puts(m, csts_state_names[state]);
|
|
else
|
|
seq_printf(m, "%d", state);
|
|
}
|
|
if (sep)
|
|
seq_printf(m, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t nvmet_ctrl_state_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct seq_file *m = file->private_data;
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
char reset[16];
|
|
|
|
if (count >= sizeof(reset))
|
|
return -EINVAL;
|
|
if (copy_from_user(reset, buf, count))
|
|
return -EFAULT;
|
|
if (!memcmp(reset, "fatal", 5))
|
|
nvmet_ctrl_fatal_error(ctrl);
|
|
else
|
|
return -EINVAL;
|
|
return count;
|
|
}
|
|
NVMET_DEBUGFS_RW_ATTR(nvmet_ctrl_state);
|
|
|
|
static int nvmet_ctrl_host_traddr_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
ssize_t size;
|
|
char buf[NVMF_TRADDR_SIZE + 1];
|
|
|
|
size = nvmet_ctrl_host_traddr(ctrl, buf, NVMF_TRADDR_SIZE);
|
|
if (size < 0) {
|
|
buf[0] = '\0';
|
|
size = 0;
|
|
}
|
|
buf[size] = '\0';
|
|
seq_printf(m, "%s\n", buf);
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ctrl_host_traddr);
|
|
|
|
#ifdef CONFIG_NVME_TARGET_TCP_TLS
|
|
static int nvmet_ctrl_tls_key_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
key_serial_t keyid = nvmet_queue_tls_keyid(ctrl->sqs[0]);
|
|
|
|
seq_printf(m, "%08x\n", keyid);
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ctrl_tls_key);
|
|
|
|
static int nvmet_ctrl_tls_concat_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ctrl *ctrl = m->private;
|
|
|
|
seq_printf(m, "%d\n", ctrl->concat);
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ctrl_tls_concat);
|
|
#endif
|
|
|
|
static const char *const nvmet_pr_type_names[] = {
|
|
[NVME_PR_WRITE_EXCLUSIVE] = "write_exclusive",
|
|
[NVME_PR_EXCLUSIVE_ACCESS] = "exclusive_access",
|
|
[NVME_PR_WRITE_EXCLUSIVE_REG_ONLY] = "write_exclusive_reg_only",
|
|
[NVME_PR_EXCLUSIVE_ACCESS_REG_ONLY] = "exclusive_access_reg_only",
|
|
[NVME_PR_WRITE_EXCLUSIVE_ALL_REGS] = "write_exclusive_all_regs",
|
|
[NVME_PR_EXCLUSIVE_ACCESS_ALL_REGS] = "exclusive_access_all_regs",
|
|
};
|
|
|
|
static const char *nvmet_pr_type_to_str(enum nvme_pr_type type)
|
|
{
|
|
if (type < ARRAY_SIZE(nvmet_pr_type_names) &&
|
|
nvmet_pr_type_names[type])
|
|
return nvmet_pr_type_names[type];
|
|
return "unknown";
|
|
}
|
|
|
|
static const char *const nvmet_pr_notify_names[] = {
|
|
[NVME_PR_NOTIFY_BIT_REG_PREEMPTED] = "reg_preempted",
|
|
[NVME_PR_NOTIFY_BIT_RESV_RELEASED] = "resv_released",
|
|
[NVME_PR_NOTIFY_BIT_RESV_PREEMPTED] = "resv_preempted",
|
|
};
|
|
|
|
static void nvmet_pr_notify_mask_to_str(struct seq_file *m, unsigned long mask)
|
|
{
|
|
bool sep = false;
|
|
int i;
|
|
|
|
if (!mask) {
|
|
seq_puts(m, "none");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(nvmet_pr_notify_names); i++) {
|
|
if (!test_bit(i, &mask) || !nvmet_pr_notify_names[i])
|
|
continue;
|
|
if (sep)
|
|
seq_putc(m, ',');
|
|
seq_puts(m, nvmet_pr_notify_names[i]);
|
|
sep = true;
|
|
}
|
|
}
|
|
|
|
static int nvmet_ns_pr_show(struct seq_file *m, void *p)
|
|
{
|
|
struct nvmet_ns *ns = m->private;
|
|
struct nvmet_pr *pr = &ns->pr;
|
|
struct nvmet_pr_registrant *holder, *reg;
|
|
|
|
seq_printf(m, "enable=%d\n", pr->enable);
|
|
if (!pr->enable)
|
|
return 0;
|
|
|
|
seq_printf(m, "generation=%u\n", atomic_read(&pr->generation));
|
|
seq_puts(m, "notify_mask=");
|
|
nvmet_pr_notify_mask_to_str(m, pr->notify_mask);
|
|
seq_putc(m, '\n');
|
|
|
|
rcu_read_lock();
|
|
holder = rcu_dereference(pr->holder);
|
|
if (holder) {
|
|
seq_printf(m, "rtype=%s\n",
|
|
nvmet_pr_type_to_str(holder->rtype));
|
|
seq_printf(m, "holder=%pUb,0x%llx\n",
|
|
&holder->hostid, holder->rkey);
|
|
} else {
|
|
seq_puts(m, "rtype=none\n");
|
|
seq_puts(m, "holder=none\n");
|
|
}
|
|
|
|
list_for_each_entry_rcu(reg, &pr->registrant_list, entry) {
|
|
seq_printf(m, "reg=%pUb,0x%llx\n",
|
|
®->hostid, reg->rkey);
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|
|
NVMET_DEBUGFS_ATTR(nvmet_ns_pr);
|
|
|
|
void nvmet_debugfs_ns_setup(struct nvmet_ns *ns)
|
|
{
|
|
char name[16];
|
|
struct dentry *parent = ns->subsys->debugfs_dir;
|
|
|
|
if (!parent)
|
|
return;
|
|
snprintf(name, sizeof(name), "ns%u", ns->nsid);
|
|
ns->debugfs_dir = debugfs_create_dir(name, parent);
|
|
if (IS_ERR(ns->debugfs_dir)) {
|
|
ns->debugfs_dir = NULL;
|
|
return;
|
|
}
|
|
debugfs_create_file("reservation", 0400, ns->debugfs_dir, ns,
|
|
&nvmet_ns_pr_fops);
|
|
}
|
|
|
|
void nvmet_debugfs_ns_free(struct nvmet_ns *ns)
|
|
{
|
|
debugfs_remove_recursive(ns->debugfs_dir);
|
|
ns->debugfs_dir = NULL;
|
|
}
|
|
|
|
int nvmet_debugfs_ctrl_setup(struct nvmet_ctrl *ctrl)
|
|
{
|
|
char name[32];
|
|
struct dentry *parent = ctrl->subsys->debugfs_dir;
|
|
int ret;
|
|
|
|
if (!parent)
|
|
return -ENODEV;
|
|
snprintf(name, sizeof(name), "ctrl%d", ctrl->cntlid);
|
|
ctrl->debugfs_dir = debugfs_create_dir(name, parent);
|
|
if (IS_ERR(ctrl->debugfs_dir)) {
|
|
ret = PTR_ERR(ctrl->debugfs_dir);
|
|
ctrl->debugfs_dir = NULL;
|
|
return ret;
|
|
}
|
|
debugfs_create_file("port", S_IRUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_port_fops);
|
|
debugfs_create_file("hostnqn", S_IRUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_hostnqn_fops);
|
|
debugfs_create_file("kato", S_IRUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_kato_fops);
|
|
debugfs_create_file("state", S_IRUSR | S_IWUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_state_fops);
|
|
debugfs_create_file("host_traddr", S_IRUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_host_traddr_fops);
|
|
#ifdef CONFIG_NVME_TARGET_TCP_TLS
|
|
debugfs_create_file("tls_concat", S_IRUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_tls_concat_fops);
|
|
debugfs_create_file("tls_key", S_IRUSR, ctrl->debugfs_dir, ctrl,
|
|
&nvmet_ctrl_tls_key_fops);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void nvmet_debugfs_ctrl_free(struct nvmet_ctrl *ctrl)
|
|
{
|
|
debugfs_remove_recursive(ctrl->debugfs_dir);
|
|
}
|
|
|
|
int nvmet_debugfs_subsys_setup(struct nvmet_subsys *subsys)
|
|
{
|
|
int ret = 0;
|
|
|
|
subsys->debugfs_dir = debugfs_create_dir(subsys->subsysnqn,
|
|
nvmet_debugfs);
|
|
if (IS_ERR(subsys->debugfs_dir)) {
|
|
ret = PTR_ERR(subsys->debugfs_dir);
|
|
subsys->debugfs_dir = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void nvmet_debugfs_subsys_free(struct nvmet_subsys *subsys)
|
|
{
|
|
debugfs_remove_recursive(subsys->debugfs_dir);
|
|
}
|
|
|
|
int __init nvmet_init_debugfs(void)
|
|
{
|
|
struct dentry *parent;
|
|
|
|
parent = debugfs_create_dir("nvmet", NULL);
|
|
if (IS_ERR(parent)) {
|
|
pr_warn("%s: failed to create debugfs directory\n", "nvmet");
|
|
return PTR_ERR(parent);
|
|
}
|
|
nvmet_debugfs = parent;
|
|
return 0;
|
|
}
|
|
|
|
void nvmet_exit_debugfs(void)
|
|
{
|
|
debugfs_remove_recursive(nvmet_debugfs);
|
|
}
|