Linux virtualization list
 help / color / mirror / Atom feed
From: Alexander Graf <graf@amazon.com>
To: "Michael S. Tsirkin" <mst@redhat.com>, Jason Wang <jasowangio@gmail.com>
Cc: nh-open-source@amazon.com,
	"Xuan Zhuo" <xuanzhuo@linux.alibaba.com>,
	"Eugenio Pérez" <eperezma@redhat.com>,
	virtualization@lists.linux.dev, linux-kernel@vger.kernel.org,
	"Stefan Hajnoczi" <stefanha@redhat.com>,
	"Paolo Bonzini" <pbonzini@redhat.com>
Subject: [PATCH v2 12/12] virtio: expose device memory buffer occupancy over debugfs
Date: Tue, 18 Aug 2026 21:14:25 +0000	[thread overview]
Message-ID: <20260818211425.91009-13-graf@amazon.com> (raw)
In-Reply-To: <20260818211425.91009-1-graf@amazon.com>

A driver allocates from a pool bounded by the region a Device Memory
Buffer device reports, so running out of room in it is routine:
vring_map_errno() reports the shortage as the -ENOSPC a full queue
reports, and the driver retries and carries on. The allocator must not
log that at any level a working device prints, or an undersized region
turns into a log flood. That leaves an operator nothing but unexplained
throughput loss to go on.

swiotlb can afford dev_warn_ratelimited() because exhaustion there is a
misconfiguration, and it exports io_tlb_used and io_tlb_used_hiwater
through debugfs, which a log line cannot do for a sampled quantity.
Follow it, plus one file swiotlb has no need for:

  dmb/pages              pages the allocator can hand out
  dmb/used_pages         pages allocated now
  dmb/used_pages_hiwater the largest used_pages has been
  dmb/alloc_failed       buffer mappings the pool had no room for

used_pages comes from the allocator's own free count, so no counter is
maintained alongside it and no read can report a torn total. Writing 0 to
used_pages_hiwater restarts the measurement from the occupancy now, so a
peak never reads below the used_pages read alongside it.

used_pages_hiwater is the number to size a region against, because a
burst that fills the pool between two samples of used_pages leaves no
other trace.

Assisted-by: Kiro:claude-opus-5 checkpatch sparse
Signed-off-by: Alexander Graf <graf@amazon.com>
---

  - Rework description

---
 drivers/virtio/virtio_dmb.c | 163 +++++++++++++++++++++++++++++++++++-
 1 file changed, 162 insertions(+), 1 deletion(-)

diff --git a/drivers/virtio/virtio_dmb.c b/drivers/virtio/virtio_dmb.c
index 884f4017780f..01b7a3542828 100644
--- a/drivers/virtio/virtio_dmb.c
+++ b/drivers/virtio/virtio_dmb.c
@@ -20,6 +20,8 @@
  */
 
 #include <linux/align.h>
+#include <linux/atomic.h>
+#include <linux/debugfs.h>
 #include <linux/dma-mapping.h>
 #include <linux/export.h>
 #include <linux/genalloc.h>
@@ -83,6 +85,10 @@ struct virtio_dmb_alloc {
  * @base_va: kernel address the pool starts at, inside the mapping
  * @base_off: region address the pool starts at
  * @nslots: pool size in PAGE_SIZE pages
+ * @used_hiwater: the largest occupancy has been since the last reset through
+ *	debugfs, or since init, in pages; CONFIG_VIRTIO_DEBUG
+ * @alloc_failed: buffer mappings the pool had no room for; CONFIG_VIRTIO_DEBUG
+ * @debugfs_dir: directory holding this region's debugfs files
  * @shm_id: shared memory id the device reported for the region
  */
 struct virtio_dmb {
@@ -98,6 +104,11 @@ struct virtio_dmb {
 	void			*base_va;
 	u64			 base_off;
 	unsigned int		 nslots;
+#ifdef CONFIG_VIRTIO_DEBUG
+	atomic_long_t		 used_hiwater;
+	atomic_long_t		 alloc_failed;
+#endif
+	struct dentry		*debugfs_dir;
 	u16			 shm_id;
 };
 
@@ -111,6 +122,52 @@ static size_t virtio_dmb_pool_size(const struct virtio_dmb *dmb)
 	return (size_t)dmb->nslots << PAGE_SHIFT;
 }
 
+#ifdef CONFIG_VIRTIO_DEBUG
+
+/*
+ * Pages allocated now.  gen_pool_avail() sums its chunks' free counts under
+ * RCU, so this needs no counter of its own and no lock, and it is exact
+ * between two claims rather than approximate.
+ */
+static unsigned long virtio_dmb_used(struct virtio_dmb *dmb)
+{
+	return (gen_pool_size(dmb->pool) - gen_pool_avail(dmb->pool))
+		>> PAGE_SHIFT;
+}
+
+/*
+ * Raise the high-water mark to the occupancy a claim has just established.
+ * Two racing claims each observe a real total and the larger wins, so the
+ * figure is a occupancy the pool genuinely held rather than a sum of readings
+ * taken at different moments.
+ */
+static void virtio_dmb_note_used(struct virtio_dmb *dmb)
+{
+	long old = atomic_long_read(&dmb->used_hiwater);
+	long now = virtio_dmb_used(dmb);
+
+	while (now > old &&
+	       !atomic_long_try_cmpxchg(&dmb->used_hiwater, &old, now))
+		;
+}
+
+static void virtio_dmb_inc_alloc_failed(struct virtio_dmb *dmb)
+{
+	atomic_long_inc(&dmb->alloc_failed);
+}
+
+#else /* !CONFIG_VIRTIO_DEBUG */
+
+static void virtio_dmb_note_used(struct virtio_dmb *dmb)
+{
+}
+
+static void virtio_dmb_inc_alloc_failed(struct virtio_dmb *dmb)
+{
+}
+
+#endif /* CONFIG_VIRTIO_DEBUG */
+
 /* Handle the driver publishes for the allocation starting at pool page @slot. */
 static dma_addr_t virtio_dmb_handle(const struct virtio_dmb *dmb,
 				    unsigned int slot)
@@ -178,6 +235,8 @@ static void *virtio_dmb_claim(struct virtio_dmb *dmb, size_t len,
 	rec->len = len;
 	rec->src = src;
 
+	virtio_dmb_note_used(dmb);
+
 	*out = rec;
 	return (void *)va;
 }
@@ -371,8 +430,22 @@ static dma_addr_t virtio_dmb_op_map_page(union virtio_map map,
 		return DMA_MAPPING_ERROR;
 
 	va = virtio_dmb_claim(dmb, size, src, &slot, &rec);
-	if (!va)
+	if (!va) {
+		/*
+		 * Counted here and not in virtio_dmb_claim(), which alloc()
+		 * reaches as well.  A virtqueue area that does not fit is a
+		 * step of vring_alloc_queue_split()'s search for a size that
+		 * does, so counting it would have a correctly sized region boot
+		 * with a failure for every attempt but the last, in the one
+		 * file whose purpose is to answer whether the region is too
+		 * small for the traffic.  A request over the per-mapping cap is
+		 * not counted either: the cap is what max_mapping_size()
+		 * advertises, so exceeding it is a caller bug rather than a
+		 * property of the region.
+		 */
+		virtio_dmb_inc_alloc_failed(dmb);
 		return DMA_MAPPING_ERROR;
+	}
 
 	/*
 	 * Copy in whatever the direction is, and without honouring
@@ -448,6 +521,91 @@ static const struct virtio_map_ops virtio_dmb_map_ops = {
 	.max_mapping_size	= virtio_dmb_op_max_mapping_size,
 };
 
+#ifdef CONFIG_VIRTIO_DEBUG
+
+static int virtio_dmb_used_get(void *data, u64 *val)
+{
+	struct virtio_dmb *dmb = data;
+
+	*val = virtio_dmb_used(dmb);
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(virtio_dmb_used_fops, virtio_dmb_used_get, NULL,
+			 "%llu\n");
+
+static int virtio_dmb_hiwater_get(void *data, u64 *val)
+{
+	struct virtio_dmb *dmb = data;
+
+	*val = atomic_long_read(&dmb->used_hiwater);
+
+	return 0;
+}
+
+/*
+ * Restart the measurement from the occupancy now, so that a peak never reads
+ * below the used_pages read alongside it.
+ */
+static int virtio_dmb_hiwater_set(void *data, u64 val)
+{
+	struct virtio_dmb *dmb = data;
+
+	if (val)
+		return -EINVAL;
+
+	atomic_long_set(&dmb->used_hiwater, virtio_dmb_used(dmb));
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(virtio_dmb_hiwater_fops, virtio_dmb_hiwater_get,
+			 virtio_dmb_hiwater_set, "%llu\n");
+
+static int virtio_dmb_alloc_failed_get(void *data, u64 *val)
+{
+	struct virtio_dmb *dmb = data;
+
+	*val = atomic_long_read(&dmb->alloc_failed);
+
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(virtio_dmb_alloc_failed_fops,
+			 virtio_dmb_alloc_failed_get, NULL, "%llu\n");
+
+static void virtio_dmb_debugfs_init(struct virtio_dmb *dmb)
+{
+	struct dentry *dir;
+
+	dir = debugfs_create_dir("dmb", dmb->vdev->debugfs_dir);
+	dmb->debugfs_dir = dir;
+
+	debugfs_create_u32("pages", 0400, dir, &dmb->nslots);
+	debugfs_create_file("used_pages", 0400, dir, dmb,
+			    &virtio_dmb_used_fops);
+	debugfs_create_file("used_pages_hiwater", 0600, dir, dmb,
+			    &virtio_dmb_hiwater_fops);
+	debugfs_create_file("alloc_failed", 0400, dir, dmb,
+			    &virtio_dmb_alloc_failed_fops);
+}
+
+static void virtio_dmb_debugfs_exit(struct virtio_dmb *dmb)
+{
+	debugfs_remove_recursive(dmb->debugfs_dir);
+	dmb->debugfs_dir = NULL;
+}
+
+#else /* !CONFIG_VIRTIO_DEBUG */
+
+static void virtio_dmb_debugfs_init(struct virtio_dmb *dmb)
+{
+}
+
+static void virtio_dmb_debugfs_exit(struct virtio_dmb *dmb)
+{
+}
+
+#endif /* CONFIG_VIRTIO_DEBUG */
+
 /*
  * Whether the device still has virtqueues.  No caller here can race an adder,
  * because every path that reaches this runs under the device lock, but the
@@ -515,6 +673,7 @@ void virtio_dmb_destroy(struct virtio_device *vdev)
 	 * virtqueue, and the refusal above establishes that none is left, which
 	 * is also why the records are empty by now.
 	 */
+	virtio_dmb_debugfs_exit(dmb);
 	gen_pool_destroy(dmb->pool);
 	kvfree(dmb->allocs);
 	memunmap(dmb->map_va);
@@ -773,6 +932,8 @@ int virtio_dmb_init(struct virtio_device *vdev)
 	vdev->vmap.dmb = dmb;
 	vdev->map = &virtio_dmb_map_ops;
 
+	virtio_dmb_debugfs_init(dmb);
+
 	dev_info(&vdev->dev,
 		 "device memory buffer %u at 0x%016llx, %u usable pages\n",
 		 shm_id, region.addr, nslots);

      parent reply	other threads:[~2026-08-18 21:16 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-18 21:14 [PATCH v2 00/12] virtio: support devices that own their virtqueue memory Alexander Graf
2026-08-18 21:14 ` [PATCH v2 01/12] virtio_ring: remove the unused map sync API Alexander Graf
2026-08-18 21:14 ` [PATCH v2 02/12] virtio: drop the sync operations from virtio_map_ops Alexander Graf
2026-08-18 21:14 ` [PATCH v2 03/12] vdpa: drop the VIRTIO_DEVICE_F_MASK example value Alexander Graf
2026-08-18 21:14 ` [PATCH v2 04/12] virtio_ring: return -ENOMEM when a packed ring mapping fails Alexander Graf
2026-08-18 21:14 ` [PATCH v2 05/12] virtio: add the VIRTIO_F_DMB feature bit Alexander Graf
2026-08-18 21:14 ` [PATCH v2 06/12] virtio_pci: read the device memory buffer registers Alexander Graf
2026-08-18 21:14 ` [PATCH v2 07/12] virtio_pci: create virtqueues with the device's mapping token Alexander Graf
2026-08-18 21:14 ` [PATCH v2 08/12] virtio: add a device memory buffer region allocator Alexander Graf
2026-08-18 21:14 ` [PATCH v2 09/12] virtio: locate the device memory buffer after feature negotiation Alexander Graf
2026-08-18 21:14 ` [PATCH v2 10/12] virtio: treat VIRTIO_F_DMB as implying VIRTIO_F_ACCESS_PLATFORM Alexander Graf
2026-08-18 21:14 ` [PATCH v2 11/12] virtio_pci: support VIRTIO_F_DMB Alexander Graf
2026-08-18 21:14 ` Alexander Graf [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260818211425.91009-13-graf@amazon.com \
    --to=graf@amazon.com \
    --cc=eperezma@redhat.com \
    --cc=jasowangio@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mst@redhat.com \
    --cc=nh-open-source@amazon.com \
    --cc=pbonzini@redhat.com \
    --cc=stefanha@redhat.com \
    --cc=virtualization@lists.linux.dev \
    --cc=xuanzhuo@linux.alibaba.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox