[PATCH v1 1/3] xen/grant-table: save page_count on map and use if during async unmapping

From: Oleksii Moisieiev
Date: Mon Jan 02 2023 - 08:42:21 EST


Save the reference count of the page before mapping and use this
value in gntdev_unmap_refs_async() call.
This is the enhancement of the
commit 3f9f1c67572f5e5e6dc84216d48d1480f3c4fcf6 ("xen/grant-table: add a
mechanism to safely unmap pages that are in use").
Safe unmapping mechanism defers page that may being use (ref count > 1).

This is needed to allow to map/unmap pages, which have more than 1
reference. For example, DRM_IOCTL_MODE_CREATE_DUMB creates dma buffer
with page_count = 2, which unmap call would be deferred while buffer
exists because ref count will never equals 1.
This means the buffer remains mapped during DRM_IOCTL_MODE_DESTROY_DUMB
call which causes an error:

Unable to handle kernel paging request at virtual address <addr>
....
Call trace:
check_move_unevictable_folios+0xb8/0x4d0
check_move_unevictable_pages+0x8c/0x110
drm_gem_put_pages+0x118/0x198
drm_gem_shmem_put_pages_locked+0x4c/0x70
drm_gem_shmem_unpin+0x30/0x50
virtio_gpu_cleanup_object+0x84/0x130
virtio_gpu_cmd_unref_cb+0x18/0x2c
virtio_gpu_dequeue_ctrl_func+0x124/0x290
process_one_work+0x1d0/0x320
worker_thread+0x14c/0x444
kthread+0x10c/0x110

This enhancement allows to provide the expected page_count during
map call so refs could be unmapped properly without unneeded defers.

Signed-off-by: Oleksii Moisieiev <oleksii_moisieiev@xxxxxxxx>
---
drivers/xen/grant-table.c | 16 +++++++++++++++-
include/xen/grant_table.h | 3 +++
2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/drivers/xen/grant-table.c b/drivers/xen/grant-table.c
index e1ec725c2819..d6576c8b6f0f 100644
--- a/drivers/xen/grant-table.c
+++ b/drivers/xen/grant-table.c
@@ -1241,11 +1241,23 @@ int gnttab_map_refs(struct gnttab_map_grant_ref *map_ops,
case GNTST_okay:
{
struct xen_page_foreign *foreign;
+ int page_cnt;

SetPageForeign(pages[i]);
foreign = xen_page_foreign(pages[i]);
foreign->domid = map_ops[i].dom;
foreign->gref = map_ops[i].ref;
+ page_cnt = page_count(pages[i]);
+ if (page_cnt > FOREIGN_MAX_PAGE_COUNT) {
+ /* foreign structure can't hold more than FOREIGN_MAX_PAGE_COUNT.
+ * That's why we save page_count = 1 so safe unmap mechanism will
+ * defer unmapping until all users stops using this page and let
+ * caller handle page users.
+ */
+ pr_warn_ratelimited("page have too many users. Will wait for 0 on umap\n");
+ foreign->private = 1;
+ } else
+ foreign->private = page_cnt;
break;
}

@@ -1308,9 +1320,11 @@ static void __gnttab_unmap_refs_async(struct gntab_unmap_queue_data* item)
{
int ret;
int pc;
+ struct xen_page_foreign *foreign;

for (pc = 0; pc < item->count; pc++) {
- if (page_count(item->pages[pc]) > 1) {
+ foreign = xen_page_foreign(item->pages[pc]);
+ if (page_count(item->pages[pc]) > foreign->private) {
unsigned long delay = GNTTAB_UNMAP_REFS_DELAY * (item->age + 1);
schedule_delayed_work(&item->gnttab_work,
msecs_to_jiffies(delay));
diff --git a/include/xen/grant_table.h b/include/xen/grant_table.h
index e279be353e3f..8e220edf44ab 100644
--- a/include/xen/grant_table.h
+++ b/include/xen/grant_table.h
@@ -49,6 +49,7 @@
#include <linux/mm_types.h>
#include <linux/page-flags.h>
#include <linux/kernel.h>
+#include <linux/limits.h>

/*
* Technically there's no reliably invalid grant reference or grant handle,
@@ -274,9 +275,11 @@ int gnttab_unmap_refs_sync(struct gntab_unmap_queue_data *item);
void gnttab_batch_map(struct gnttab_map_grant_ref *batch, unsigned count);
void gnttab_batch_copy(struct gnttab_copy *batch, unsigned count);

+#define FOREIGN_MAX_PAGE_COUNT U16_MAX

struct xen_page_foreign {
domid_t domid;
+ uint16_t private;
grant_ref_t gref;
};

--
2.25.1