Add a capacity field to page_reporting_dev_info so drivers can
control the maximum number of pages per report batch. This is
useful when the driver needs to reserve virtqueue descriptors for
metadata (e.g., a bitmap buffer) alongside the page buffers.

The value is capped at PAGE_REPORTING_CAPACITY and rounded down
to a power of 2. If unset (0), defaults to PAGE_REPORTING_CAPACITY.

The virtio_balloon driver sets capacity to the reporting virtqueue
size, letting page_reporting adapt to whatever the device provides.

Signed-off-by: Michael S. Tsirkin <[email protected]>
Assisted-by: Claude:claude-opus-4-6
---
 drivers/virtio/virtio_balloon.c |  5 +----
 include/linux/page_reporting.h  |  3 +++
 mm/page_reporting.c             | 26 +++++++++++++++-----------
 3 files changed, 19 insertions(+), 15 deletions(-)

diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
index d1fbc8fe8470..7ed024315539 100644
--- a/drivers/virtio/virtio_balloon.c
+++ b/drivers/virtio/virtio_balloon.c
@@ -1017,10 +1017,6 @@ static int virtballoon_probe(struct virtio_device *vdev)
                unsigned int capacity;
 
                capacity = virtqueue_get_vring_size(vb->reporting_vq);
-               if (capacity < PAGE_REPORTING_CAPACITY) {
-                       err = -ENOSPC;
-                       goto out_unregister_oom;
-               }
 
                /*
                 * The default page reporting order is @pageblock_order, which
@@ -1039,6 +1035,7 @@ static int virtballoon_probe(struct virtio_device *vdev)
                vb->pr_dev_info.order = 5;
 #endif
 
+               vb->pr_dev_info.capacity = capacity;
                err = page_reporting_register(&vb->pr_dev_info);
                if (err)
                        goto out_unregister_oom;
diff --git a/include/linux/page_reporting.h b/include/linux/page_reporting.h
index fe648dfa3a7c..306468b6c7d8 100644
--- a/include/linux/page_reporting.h
+++ b/include/linux/page_reporting.h
@@ -21,6 +21,9 @@ struct page_reporting_dev_info {
 
        /* Minimal order of page reporting */
        unsigned int order;
+
+       /* Max pages per report batch (default PAGE_REPORTING_CAPACITY) */
+       unsigned int capacity;
 };
 
 /* Tear-down and bring-up for page reporting devices */
diff --git a/mm/page_reporting.c b/mm/page_reporting.c
index f0042d5743af..247cda44e9de 100644
--- a/mm/page_reporting.c
+++ b/mm/page_reporting.c
@@ -174,10 +174,10 @@ page_reporting_cycle(struct page_reporting_dev_info 
*prdev, struct zone *zone,
         * list processed. This should result in us reporting all pages on
         * an idle system in about 30 seconds.
         *
-        * The division here should be cheap since PAGE_REPORTING_CAPACITY
-        * should always be a power of 2.
+        * The division here should be cheap since capacity should
+        * always be a power of 2.
         */
-       budget = DIV_ROUND_UP(area->nr_free, PAGE_REPORTING_CAPACITY * 16);
+       budget = DIV_ROUND_UP(area->nr_free, prdev->capacity * 16);
 
        /* loop through free list adding unreported pages to sg list */
        list_for_each_entry_safe(page, next, list, lru) {
@@ -222,10 +222,10 @@ page_reporting_cycle(struct page_reporting_dev_info 
*prdev, struct zone *zone,
                spin_unlock_irq(&zone->lock);
 
                /* begin processing pages in local list */
-               err = prdev->report(prdev, sgl, PAGE_REPORTING_CAPACITY);
+               err = prdev->report(prdev, sgl, prdev->capacity);
 
                /* reset offset since the full list was reported */
-               *offset = PAGE_REPORTING_CAPACITY;
+               *offset = prdev->capacity;
 
                /* update budget to reflect call to report function */
                budget--;
@@ -234,7 +234,7 @@ page_reporting_cycle(struct page_reporting_dev_info *prdev, 
struct zone *zone,
                spin_lock_irq(&zone->lock);
 
                /* flush reported pages from the sg list */
-               page_reporting_drain(prdev, sgl, PAGE_REPORTING_CAPACITY, !err);
+               page_reporting_drain(prdev, sgl, prdev->capacity, !err);
 
                /*
                 * Reset next to first entry, the old next isn't valid
@@ -260,13 +260,13 @@ static int
 page_reporting_process_zone(struct page_reporting_dev_info *prdev,
                            struct scatterlist *sgl, struct zone *zone)
 {
-       unsigned int order, mt, leftover, offset = PAGE_REPORTING_CAPACITY;
+       unsigned int order, mt, leftover, offset = prdev->capacity;
        unsigned long watermark;
        int err = 0;
 
        /* Generate minimum watermark to be able to guarantee progress */
        watermark = low_wmark_pages(zone) +
-                   (PAGE_REPORTING_CAPACITY << page_reporting_order);
+                   (prdev->capacity << page_reporting_order);
 
        /*
         * Cancel request if insufficient free memory or if we failed
@@ -290,7 +290,7 @@ page_reporting_process_zone(struct page_reporting_dev_info 
*prdev,
        }
 
        /* report the leftover pages before going idle */
-       leftover = PAGE_REPORTING_CAPACITY - offset;
+       leftover = prdev->capacity - offset;
        if (leftover) {
                sgl = &sgl[offset];
                err = prdev->report(prdev, sgl, leftover);
@@ -322,11 +322,11 @@ static void page_reporting_process(struct work_struct 
*work)
        atomic_set(&prdev->state, state);
 
        /* allocate scatterlist to store pages being reported on */
-       sgl = kmalloc_objs(*sgl, PAGE_REPORTING_CAPACITY);
+       sgl = kmalloc_objs(*sgl, prdev->capacity);
        if (!sgl)
                goto err_out;
 
-       sg_init_table(sgl, PAGE_REPORTING_CAPACITY);
+       sg_init_table(sgl, prdev->capacity);
 
        for_each_zone(zone) {
                err = page_reporting_process_zone(prdev, sgl, zone);
@@ -376,6 +376,10 @@ int page_reporting_register(struct page_reporting_dev_info 
*prdev)
                        page_reporting_order = pageblock_order;
        }
 
+       if (!prdev->capacity || prdev->capacity > PAGE_REPORTING_CAPACITY)
+               prdev->capacity = PAGE_REPORTING_CAPACITY;
+       prdev->capacity = rounddown_pow_of_two(prdev->capacity);
+
        /* initialize state and work structures */
        atomic_set(&prdev->state, PAGE_REPORTING_IDLE);
        INIT_DELAYED_WORK(&prdev->work, &page_reporting_process);
-- 
MST


Reply via email to