[PATCH v3 2/2] nvme: expose queue information via debugfs
Nilay Shroff
nilay at linux.ibm.com
Mon Oct 26 05:33:23 PDT 2026
Add a new debugfs attribute "io_queue_info" to expose per-queue
information for NVMe controllers. For NVMe-TCP, this includes the
CPU handling each I/O queue and the associated TCP flow (source and
destination address/port).
This information can be useful for understanding and tuning the
interaction between NVMe-TCP I/O queues and network stack components,
such as IRQ affinity, RPS/RFS, XPS, or NIC flow steering (ntuple).
The data is exported using seq_file interfaces to allow iteration
over all controller queues.
Signed-off-by: Nilay Shroff <nilay at linux.ibm.com>
---
drivers/nvme/host/core.c | 3 ++
drivers/nvme/host/debugfs.c | 76 ++++++++++++++++++++++++++++++++++++-
drivers/nvme/host/nvme.h | 2 +
drivers/nvme/host/tcp.c | 54 ++++++++++++++++++++++++++
4 files changed, 134 insertions(+), 1 deletion(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 531f9bb14bab..15334521c314 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -4316,6 +4316,8 @@ static void nvme_alloc_ns(struct nvme_ctrl *ctrl, struct nvme_ns_info *info)
if (device_add_disk(ctrl->device, ns->disk, nvme_ns_attr_groups))
goto out_cleanup_ns_from_list;
+ nvme_debugfs_register(ns->disk);
+
if (!nvme_ns_head_multipath(ns->head))
nvme_add_ns_cdev(ns);
@@ -4399,6 +4401,7 @@ static void nvme_ns_remove(struct nvme_ns *ns)
nvme_mpath_remove_sysfs_link(ns);
+ nvme_debugfs_unregister(ns->disk);
del_gendisk(ns->disk);
mutex_lock(&ns->ctrl->namespaces_lock);
diff --git a/drivers/nvme/host/debugfs.c b/drivers/nvme/host/debugfs.c
index 798fec4730f0..8ea0413f7b75 100644
--- a/drivers/nvme/host/debugfs.c
+++ b/drivers/nvme/host/debugfs.c
@@ -25,6 +25,61 @@ struct nvme_debugfs_ctx {
struct nvme_debugfs_attr *attr;
};
+static void *nvme_io_queue_info_start(struct seq_file *m, loff_t *pos)
+{
+ struct nvme_debugfs_ctx *ctx = m->private;
+ struct nvme_ns *ns = ctx->data;
+ struct nvme_ctrl *ctrl = ns->ctrl;
+
+ /*
+ * IO queues starts at offset 1.
+ */
+ return (++*pos < ctrl->queue_count) ? pos : NULL;
+}
+
+static void *nvme_io_queue_info_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct nvme_debugfs_ctx *ctx = m->private;
+ struct nvme_ns *ns = ctx->data;
+ struct nvme_ctrl *ctrl = ns->ctrl;
+
+ return (++*pos < ctrl->queue_count) ? pos : NULL;
+}
+
+static void nvme_io_queue_info_stop(struct seq_file *m, void *v)
+{
+ /* nothing to be done */
+}
+
+static int nvme_io_queue_info_show(struct seq_file *m, void *v)
+{
+ struct nvme_debugfs_ctx *ctx = m->private;
+ struct nvme_ns *ns = ctx->data;
+ struct nvme_ctrl *ctrl = ns->ctrl;
+
+ if (ctrl->ops->print_io_queue_info)
+ return ctrl->ops->print_io_queue_info(m, ctrl, *(loff_t *)v);
+
+ return 0;
+}
+
+const struct seq_operations nvme_io_queue_info_seq_ops = {
+ .start = nvme_io_queue_info_start,
+ .next = nvme_io_queue_info_next,
+ .stop = nvme_io_queue_info_stop,
+ .show = nvme_io_queue_info_show
+};
+
+static bool nvme_debugfs_get_ns(void *data)
+{
+ return nvme_get_ns(data);
+}
+
+static void nvme_debugfs_put_ns(void *data)
+{
+ nvme_put_ns(data);
+}
+
static int nvme_debugfs_show(struct seq_file *m, void *v)
{
struct nvme_debugfs_ctx *ctx = m->private;
@@ -100,6 +155,17 @@ static int nvme_debugfs_release(struct inode *inode, struct file *file)
return ret;
}
+static bool nvme_debugfs_io_queue_info_visible(void *data)
+{
+ struct nvme_ns *ns = data;
+ struct nvme_ctrl *ctrl = ns->ctrl;
+
+ if (ctrl->ops->print_io_queue_info)
+ return true;
+
+ return false;
+}
+
static const struct file_operations nvme_debugfs_fops = {
.owner = THIS_MODULE,
.open = nvme_debugfs_open,
@@ -109,7 +175,15 @@ static const struct file_operations nvme_debugfs_fops = {
};
static const struct nvme_debugfs_attr nvme_debugfs_attrs[] = {
- {},
+ {
+ .name = "io_queue_info",
+ .mode = 0400,
+ .seq_ops = &nvme_io_queue_info_seq_ops,
+ .get = nvme_debugfs_get_ns,
+ .put = nvme_debugfs_put_ns,
+ .is_visible = nvme_debugfs_io_queue_info_visible,
+ },
+ {}
};
static void nvme_debugfs_create_files(struct request_queue *q,
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index db2ab1469fd0..48995bc6669c 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -679,6 +679,8 @@ struct nvme_ctrl_ops {
void (*print_device_info)(struct nvme_ctrl *ctrl);
bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl);
unsigned long (*get_virt_boundary)(struct nvme_ctrl *ctrl, bool is_admin);
+ int (*print_io_queue_info)(struct seq_file *m, struct nvme_ctrl *ctrl,
+ int qid);
};
/*
diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
index f876375dffd2..2cb697d33c02 100644
--- a/drivers/nvme/host/tcp.c
+++ b/drivers/nvme/host/tcp.c
@@ -2701,6 +2701,59 @@ static void nvme_tcp_free_ctrl(struct nvme_ctrl *nctrl)
kfree(ctrl);
}
+static int nvme_tcp_print_io_queue_info(struct seq_file *m,
+ struct nvme_ctrl *ctrl, int qid)
+{
+ int cpu;
+ struct sockaddr_storage src, dst;
+ struct nvme_tcp_ctrl *tctrl = to_tcp_ctrl(ctrl);
+ struct nvme_tcp_queue *queue = &tctrl->queues[qid];
+ int ret = -EINVAL;
+
+ if (!qid || qid >= ctrl->queue_count)
+ return -EINVAL;
+
+ if (!test_bit(NVME_TCP_Q_LIVE, &queue->flags))
+ return 0;
+
+ mutex_lock(&queue->queue_lock);
+ if (!queue->sock)
+ goto unlock;
+
+ ret = kernel_getsockname(queue->sock, (struct sockaddr *)&src);
+ if (ret <= 0)
+ goto unlock;
+
+ ret = kernel_getpeername(queue->sock, (struct sockaddr *)&dst);
+ if (ret <= 0)
+ goto unlock;
+
+ cpu = (queue->io_cpu == WORK_CPU_UNBOUND) ? -1 : queue->io_cpu;
+
+ if (src.ss_family == AF_INET) {
+ struct sockaddr_in *sip = (struct sockaddr_in *)&src;
+ struct sockaddr_in *dip = (struct sockaddr_in *)&dst;
+
+ seq_printf(m, "qid=%d cpu=%d src_ip=%pI4 src_port=%u dst_ip=%pI4 dst_port=%u\n",
+ qid, cpu,
+ &sip->sin_addr.s_addr, ntohs(sip->sin_port),
+ &dip->sin_addr.s_addr, ntohs(dip->sin_port));
+ ret = 0;
+ } else if (src.ss_family == AF_INET6) {
+ struct sockaddr_in6 *sip6 = (struct sockaddr_in6 *)&src;
+ struct sockaddr_in6 *dip6 = (struct sockaddr_in6 *)&dst;
+
+ seq_printf(m, "qid=%d cpu=%d src_ip=%pI6c src_port=%u dst_ip=%pI6c dst_port=%u\n",
+ qid, cpu,
+ &sip6->sin6_addr, ntohs(sip6->sin6_port),
+ &dip6->sin6_addr, ntohs(dip6->sin6_port));
+ ret = 0;
+ }
+unlock:
+ mutex_unlock(&queue->queue_lock);
+ return ret;
+}
+
static void nvme_tcp_set_sg_null(struct nvme_command *c)
{
struct nvme_sgl_desc *sg = &c->common.dptr.sgl;
@@ -3002,6 +3055,7 @@ static const struct nvme_ctrl_ops nvme_tcp_ctrl_ops = {
.get_address = nvme_tcp_get_address,
.stop_ctrl = nvme_tcp_stop_ctrl,
.get_virt_boundary = nvmf_get_virt_boundary,
+ .print_io_queue_info = nvme_tcp_print_io_queue_info,
};
static bool
--
2.53.0
More information about the Linux-nvme
mailing list