* [PATCH 10/13] mm/vma: convert miscellaneous uses of VMA flags in core mm
From: Lorenzo Stoakes @ 2026-06-29 19:25 UTC (permalink / raw)
To: Andrew Morton
Cc: Thomas Bogendoerfer, Madhavan Srinivasan, Michael Ellerman,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Lucas Stach, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Rob Clark,
Dmitry Baryshkov, Lyude Paul, Danilo Krummrich, Tomi Valkeinen,
Sandy Huang, Heiko Stübner, Andy Yan, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann, Dmitry Osipenko,
Zack Rusin, Matthew Brost, Thomas Hellstrom,
Oleksandr Andrushchenko, Helge Deller, Benjamin LaHaise,
Alexander Viro, Christian Brauner, Muchun Song, Oscar Salvador,
David Hildenbrand, Zi Yan, Baolin Wang, Liam R . Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Hugh Dickins, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Michal Hocko, Jann Horn, Pedro Falcato, Kees Cook,
Jaroslav Kysela, Takashi Iwai, linux-mips, linux-kernel,
linuxppc-dev, dri-devel, etnaviv, linux-arm-kernel,
linux-samsung-soc, intel-gfx, linux-arm-msm, freedreno, nouveau,
linux-rockchip, linux-tegra, virtualization, intel-xe, xen-devel,
linux-fbdev, linux-aio, linux-fsdevel, linux-mm, linux-sound
In-Reply-To: <cover.1782760670.git.ljs@kernel.org>
Update various uses of legacy flags in vma.c and mmap.c to the new
vma_flags_t type, updating comments alongside them to be consistent.
Also update __install_special_mapping() to rearrange things slightly to
accommodate the changes.
Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
mm/mmap.c | 39 +++++++++++++++++++++------------------
mm/vma.c | 16 ++++++++++------
2 files changed, 31 insertions(+), 24 deletions(-)
diff --git a/mm/mmap.c b/mm/mmap.c
index e834ec9273e3..a8546c9250a0 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -557,8 +557,8 @@ unsigned long do_mmap(struct file *file, unsigned long addr,
}
/*
- * Set 'VM_NORESERVE' if we should not account for the
- * memory use of this mapping.
+ * Set VMA_NORESERVE_BIT if we should not account for the memory use
+ * of this mapping.
*/
if (flags & MAP_NORESERVE) {
/* We honor MAP_NORESERVE if allowed to overcommit */
@@ -985,7 +985,7 @@ struct vm_area_struct *find_extend_vma_locked(struct mm_struct *mm, unsigned lon
return NULL;
if (expand_stack_locked(prev, addr))
return NULL;
- if (prev->vm_flags & VM_LOCKED)
+ if (vma_test(prev, VMA_LOCKED_BIT))
populate_vma_page_range(prev, addr, prev->vm_end, NULL);
return prev;
}
@@ -1009,7 +1009,7 @@ struct vm_area_struct *find_extend_vma_locked(struct mm_struct *mm, unsigned lon
start = vma->vm_start;
if (expand_stack_locked(vma, addr))
return NULL;
- if (vma->vm_flags & VM_LOCKED)
+ if (vma_test(vma, VMA_LOCKED_BIT))
populate_vma_page_range(vma, addr, start, NULL);
return vma;
}
@@ -1134,18 +1134,18 @@ SYSCALL_DEFINE5(remap_file_pages, unsigned long, start, unsigned long, size,
*/
vma = vma_lookup(mm, start);
- if (!vma || !(vma->vm_flags & VM_SHARED)) {
+ if (!vma || !vma_test(vma, VMA_SHARED_BIT)) {
mmap_read_unlock(mm);
return -EINVAL;
}
- prot |= vma->vm_flags & VM_READ ? PROT_READ : 0;
- prot |= vma->vm_flags & VM_WRITE ? PROT_WRITE : 0;
- prot |= vma->vm_flags & VM_EXEC ? PROT_EXEC : 0;
+ prot |= vma_test(vma, VMA_READ_BIT) ? PROT_READ : 0;
+ prot |= vma_test(vma, VMA_WRITE_BIT) ? PROT_WRITE : 0;
+ prot |= vma_test(vma, VMA_EXEC_BIT) ? PROT_EXEC : 0;
flags &= MAP_NONBLOCK;
flags |= MAP_SHARED | MAP_FIXED | MAP_POPULATE;
- if (vma->vm_flags & VM_LOCKED)
+ if (vma_test(vma, VMA_LOCKED_BIT))
flags |= MAP_LOCKED;
/* Save vm_flags used to calculate prot and flags, and recheck later. */
@@ -1271,7 +1271,7 @@ unsigned long tear_down_vmas(struct mm_struct *mm, struct vma_iterator *vmi,
mmap_assert_write_locked(mm);
vma_iter_set(vmi, vma->vm_end);
do {
- if (vma->vm_flags & VM_ACCOUNT)
+ if (vma_test(vma, VMA_ACCOUNT_BIT))
nr_accounted += vma_pages(vma);
vma_mark_detached(vma);
remove_vma(vma);
@@ -1420,7 +1420,7 @@ static int special_mapping_split(struct vm_area_struct *vma, unsigned long addr)
{
/*
* Forbid splitting special mappings - kernel has expectations over
- * the number of pages in mapping. Together with VM_DONTEXPAND
+ * the number of pages in mapping. Together with VMA_DONTEXPAND_BIT
* the size of vma should stay the same over the special mapping's
* lifetime.
*/
@@ -1692,7 +1692,7 @@ bool mmap_read_lock_maybe_expand(struct mm_struct *mm,
return true;
}
- if (!(new_vma->vm_flags & VM_GROWSDOWN))
+ if (!vma_test(new_vma, VMA_GROWSDOWN_BIT))
return false;
mmap_write_lock(mm);
@@ -1742,7 +1742,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
retval = vma_start_write_killable(mpnt);
if (retval < 0)
goto loop_out;
- if (mpnt->vm_flags & VM_DONTCOPY) {
+ if (vma_test(mpnt, VMA_DONTCOPY_BIT)) {
retval = vma_iter_clear_gfp(&vmi, mpnt->vm_start,
mpnt->vm_end, GFP_KERNEL);
if (retval)
@@ -1752,7 +1752,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
continue;
}
charge = 0;
- if (mpnt->vm_flags & VM_ACCOUNT) {
+ if (vma_test(mpnt, VMA_ACCOUNT_BIT)) {
unsigned long len = vma_pages(mpnt);
if (security_vm_enough_memory_mm(oldmm, len)) /* sic */
@@ -1770,16 +1770,19 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
retval = dup_userfaultfd(tmp, &uf);
if (retval)
goto fail_nomem_anon_vma_fork;
- if (tmp->vm_flags & VM_WIPEONFORK) {
+
+ if (vma_test(tmp, VMA_WIPEONFORK_BIT)) {
/*
- * VM_WIPEONFORK gets a clean slate in the child.
+ * VMA_WIPEONFORK_BIT gets a clean slate in the child.
* Don't prepare anon_vma until fault since we don't
* copy page for current vma.
*/
tmp->anon_vma = NULL;
} else if (anon_vma_fork(tmp, mpnt))
goto fail_nomem_anon_vma_fork;
- vm_flags_clear(tmp, VM_LOCKED_MASK);
+
+ vma_start_write(tmp);
+ vma_clear_flags_mask(tmp, VMA_LOCKED_MASK);
/*
* Copy/update hugetlb private vma information.
*/
@@ -1812,7 +1815,7 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
i_mmap_unlock_write(mapping);
}
- if (!(tmp->vm_flags & VM_WIPEONFORK))
+ if (!vma_test(tmp, VMA_WIPEONFORK_BIT))
retval = copy_page_range(tmp, mpnt);
if (retval) {
diff --git a/mm/vma.c b/mm/vma.c
index b81c05e67a61..ab2ef0f04420 100644
--- a/mm/vma.c
+++ b/mm/vma.c
@@ -3417,23 +3417,27 @@ struct vm_area_struct *__install_special_mapping(
vm_flags_t vm_flags, void *priv,
const struct vm_operations_struct *ops)
{
- int ret;
+ vma_flags_t vma_flags = legacy_to_vma_flags(vm_flags);
struct vm_area_struct *vma;
+ int ret;
vma = vm_area_alloc(mm);
- if (unlikely(vma == NULL))
+ if (unlikely(!vma))
return ERR_PTR(-ENOMEM);
- vma_set_range(vma, addr, addr + len, 0);
- vm_flags |= vma_flags_to_legacy(mm->def_vma_flags) | VM_DONTEXPAND;
+ vma_flags_set_mask(&vma_flags, mm->def_vma_flags);
+ vma_flags_set(&vma_flags, VMA_DONTEXPAND_BIT);
if (pgtable_supports_soft_dirty())
- vm_flags |= VM_SOFTDIRTY;
- vm_flags_init(vma, vm_flags & ~VM_LOCKED_MASK);
+ vma_flags_set(&vma_flags, VMA_SOFTDIRTY_BIT);
+ vma_flags_clear_mask(&vma_flags, VMA_LOCKED_MASK);
+ vma->flags = vma_flags;
vma->vm_page_prot = vma_get_page_prot(vma);
vma->vm_ops = ops;
vma->vm_private_data = priv;
+ vma_set_range(vma, addr, addr + len, 0);
+
ret = insert_vm_struct(mm, vma);
if (ret)
goto out;
--
2.54.0
^ permalink raw reply related
* [PATCH 11/13] mm/mlock: convert mlock code to use vma_flags_t
From: Lorenzo Stoakes @ 2026-06-29 19:25 UTC (permalink / raw)
To: Andrew Morton
Cc: Thomas Bogendoerfer, Madhavan Srinivasan, Michael Ellerman,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Lucas Stach, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Rob Clark,
Dmitry Baryshkov, Lyude Paul, Danilo Krummrich, Tomi Valkeinen,
Sandy Huang, Heiko Stübner, Andy Yan, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann, Dmitry Osipenko,
Zack Rusin, Matthew Brost, Thomas Hellstrom,
Oleksandr Andrushchenko, Helge Deller, Benjamin LaHaise,
Alexander Viro, Christian Brauner, Muchun Song, Oscar Salvador,
David Hildenbrand, Zi Yan, Baolin Wang, Liam R . Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Hugh Dickins, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Michal Hocko, Jann Horn, Pedro Falcato, Kees Cook,
Jaroslav Kysela, Takashi Iwai, linux-mips, linux-kernel,
linuxppc-dev, dri-devel, etnaviv, linux-arm-kernel,
linux-samsung-soc, intel-gfx, linux-arm-msm, freedreno, nouveau,
linux-rockchip, linux-tegra, virtualization, intel-xe, xen-devel,
linux-fbdev, linux-aio, linux-fsdevel, linux-mm, linux-sound
In-Reply-To: <cover.1782760670.git.ljs@kernel.org>
Replace use of the legacy vm_flags_t flags with vma_flags_t values
throughout the mlock logic.
Additionally update comments to reflect the changes to be consistent.
No functional change intended.
Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
mm/mlock.c | 86 +++++++++++++++++++++++++++++-------------------------
1 file changed, 46 insertions(+), 40 deletions(-)
diff --git a/mm/mlock.c b/mm/mlock.c
index 9c87b3ced65f..1634b125a519 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -329,7 +329,7 @@ static inline bool allow_mlock_munlock(struct folio *folio,
* be split. And the pages are not in VM_LOCKed VMA
* can be reclaimed.
*/
- if (!(vma->vm_flags & VM_LOCKED))
+ if (!vma_test(vma, VMA_LOCKED_BIT))
return true;
/* folio_within_range() cannot take KSM, but any small folio is OK */
@@ -368,7 +368,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
folio = pmd_folio(*pmd);
if (folio_is_zone_device(folio))
goto out;
- if (vma->vm_flags & VM_LOCKED)
+ if (vma_test(vma, VMA_LOCKED_BIT))
mlock_folio(folio);
else
munlock_folio(folio);
@@ -393,7 +393,7 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
if (!allow_mlock_munlock(folio, vma, start, end, step))
goto next_entry;
- if (vma->vm_flags & VM_LOCKED)
+ if (vma_test(vma, VMA_LOCKED_BIT))
mlock_folio(folio);
else
munlock_folio(folio);
@@ -417,8 +417,8 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
* @end - end of range in @vma
* @new_vma_flags - the new set of flags for @vma.
*
- * Called for mlock(), mlock2() and mlockall(), to set @vma VM_LOCKED;
- * called for munlock() and munlockall(), to clear VM_LOCKED from @vma.
+ * Called for mlock(), mlock2() and mlockall(), to set @vma VMA_LOCKED_BIT;
+ * called for munlock() and munlockall(), to clear VMA_LOCKED_BIT from @vma.
*/
static void mlock_vma_pages_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end,
@@ -431,14 +431,14 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,
/*
* There is a slight chance that concurrent page migration,
- * or page reclaim finding a page of this now-VM_LOCKED vma,
+ * or page reclaim finding a page of this now-VMA_LOCKED_BIT vma,
* will call mlock_vma_folio() and raise page's mlock_count:
* double counting, leaving the page unevictable indefinitely.
- * Communicate this danger to mlock_vma_folio() with VM_IO,
- * which is a VM_SPECIAL flag not allowed on VM_LOCKED vmas.
+ * Communicate this danger to mlock_vma_folio() with VMA_IO_BIT,
+ * which is a VMA_SPECIAL_FLAGS flag not allowed on VMA_LOCKED_BIT vmas.
* mmap_lock is held in write mode here, so this weird
* combination should not be visible to other mmap_lock users;
- * but WRITE_ONCE so rmap walkers must see VM_IO if VM_LOCKED.
+ * but WRITE_ONCE so rmap walkers must see VMA_IO_BIT if VMA_LOCKED_BIT.
*/
if (vma_flags_test(new_vma_flags, VMA_LOCKED_BIT))
vma_flags_set(new_vma_flags, VMA_IO_BIT);
@@ -458,7 +458,7 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,
/*
* mlock_fixup - handle mlock[all]/munlock[all] requests.
*
- * Filters out "special" vmas -- VM_LOCKED never gets set for these, and
+ * Filters out "special" vmas -- VMA_LOCKED_BIT never gets set for these, and
* munlock is a no-op. However, for some special vmas, we go ahead and
* populate the ptes.
*
@@ -466,24 +466,23 @@ static void mlock_vma_pages_range(struct vm_area_struct *vma,
*/
static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,
struct vm_area_struct **prev, unsigned long start,
- unsigned long end, vm_flags_t newflags)
+ unsigned long end, vma_flags_t *new_vma_flags)
{
- vma_flags_t new_vma_flags = legacy_to_vma_flags(newflags);
const vma_flags_t old_vma_flags = vma->flags;
struct mm_struct *mm = vma->vm_mm;
int nr_pages;
int ret = 0;
- if (vma_flags_same_pair(&old_vma_flags, &new_vma_flags) ||
+ if (vma_flags_same_pair(&old_vma_flags, new_vma_flags) ||
vma_is_secretmem(vma) || !vma_supports_mlock(vma)) {
/*
- * Don't set VM_LOCKED or VM_LOCKONFAULT and don't count.
+ * Don't set VMA_LOCKED_BIT or VM_LOCKONFAULT and don't count.
* For secretmem, don't allow the memory to be unlocked.
*/
goto out;
}
- vma = vma_modify_flags(vmi, *prev, vma, start, end, &new_vma_flags);
+ vma = vma_modify_flags(vmi, *prev, vma, start, end, new_vma_flags);
if (IS_ERR(vma)) {
ret = PTR_ERR(vma);
goto out;
@@ -493,7 +492,7 @@ static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,
* Keep track of amount of locked VM.
*/
nr_pages = (end - start) >> PAGE_SHIFT;
- if (!vma_flags_test(&new_vma_flags, VMA_LOCKED_BIT))
+ if (!vma_flags_test(new_vma_flags, VMA_LOCKED_BIT))
nr_pages = -nr_pages;
else if (vma_flags_test(&old_vma_flags, VMA_LOCKED_BIT))
nr_pages = 0;
@@ -502,15 +501,15 @@ static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,
/*
* vm_flags is protected by the mmap_lock held in write mode.
* It's okay if try_to_unmap_one unmaps a page just after we
- * set VM_LOCKED, populate_vma_page_range will bring it back.
+ * set VMA_LOCKED_BIT, populate_vma_page_range will bring it back.
*/
- if (vma_flags_test(&new_vma_flags, VMA_LOCKED_BIT) &&
+ if (vma_flags_test(new_vma_flags, VMA_LOCKED_BIT) &&
vma_flags_test(&old_vma_flags, VMA_LOCKED_BIT)) {
/* No work to do, and mlocking twice would be wrong */
vma_start_write(vma);
- vma->flags = new_vma_flags;
+ vma->flags = *new_vma_flags;
} else {
- mlock_vma_pages_range(vma, start, end, &new_vma_flags);
+ mlock_vma_pages_range(vma, start, end, new_vma_flags);
}
out:
*prev = vma;
@@ -518,7 +517,7 @@ static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,
}
static int apply_vma_lock_flags(unsigned long start, size_t len,
- vm_flags_t flags)
+ const vma_flags_t *flags)
{
unsigned long nstart, end, tmp;
struct vm_area_struct *vma, *prev;
@@ -543,18 +542,20 @@ static int apply_vma_lock_flags(unsigned long start, size_t len,
tmp = vma->vm_start;
for_each_vma_range(vmi, vma, end) {
int error;
- vm_flags_t newflags;
+ vma_flags_t newflags;
if (vma->vm_start != tmp)
return -ENOMEM;
- newflags = vma->vm_flags & ~VM_LOCKED_MASK;
- newflags |= flags;
+ newflags = vma->flags;
+ vma_flags_clear_mask(&newflags, VMA_LOCKED_MASK);
+ vma_flags_set_mask(&newflags, *flags);
+
/* Here we know that vma->vm_start <= nstart < vma->vm_end. */
tmp = vma->vm_end;
if (tmp > end)
tmp = end;
- error = mlock_fixup(&vmi, vma, &prev, nstart, tmp, newflags);
+ error = mlock_fixup(&vmi, vma, &prev, nstart, tmp, &newflags);
if (error)
return error;
tmp = vma_iter_end(&vmi);
@@ -589,7 +590,7 @@ static unsigned long count_mm_mlocked_page_nr(struct mm_struct *mm,
end = start + len;
for_each_vma_range(vmi, vma, end) {
- if (vma->vm_flags & VM_LOCKED) {
+ if (vma_test(vma, VMA_LOCKED_BIT)) {
if (start > vma->vm_start)
count -= (start - vma->vm_start);
if (end < vma->vm_end) {
@@ -615,7 +616,8 @@ static int __mlock_posix_error_return(long retval)
return retval;
}
-static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t flags)
+static __must_check int do_mlock(unsigned long start, size_t len,
+ vma_flags_t *flags)
{
unsigned long locked;
unsigned long lock_limit;
@@ -664,24 +666,27 @@ static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t fla
SYSCALL_DEFINE2(mlock, unsigned long, start, size_t, len)
{
- return do_mlock(start, len, VM_LOCKED);
+ vma_flags_t flags = mk_vma_flags(VMA_LOCKED_BIT);
+
+ return do_mlock(start, len, &flags);
}
SYSCALL_DEFINE3(mlock2, unsigned long, start, size_t, len, int, flags)
{
- vm_flags_t vm_flags = VM_LOCKED;
+ vma_flags_t vma_flags = mk_vma_flags(VMA_LOCKED_BIT);
if (flags & ~MLOCK_ONFAULT)
return -EINVAL;
if (flags & MLOCK_ONFAULT)
- vm_flags |= VM_LOCKONFAULT;
+ vma_flags_set(&vma_flags, VMA_LOCKONFAULT_BIT);
- return do_mlock(start, len, vm_flags);
+ return do_mlock(start, len, &vma_flags);
}
SYSCALL_DEFINE2(munlock, unsigned long, start, size_t, len)
{
+ vma_flags_t flags = EMPTY_VMA_FLAGS;
int ret;
start = untagged_addr(start);
@@ -691,7 +696,7 @@ SYSCALL_DEFINE2(munlock, unsigned long, start, size_t, len)
if (mmap_write_lock_killable(current->mm))
return -EINTR;
- ret = apply_vma_lock_flags(start, len, 0);
+ ret = apply_vma_lock_flags(start, len, &flags);
mmap_write_unlock(current->mm);
return ret;
@@ -705,14 +710,15 @@ SYSCALL_DEFINE2(munlock, unsigned long, start, size_t, len)
* There are a couple of subtleties with this. If mlockall() is called multiple
* times with different flags, the values do not necessarily stack. If mlockall
* is called once including the MCL_FUTURE flag and then a second time without
- * it, VM_LOCKED and VM_LOCKONFAULT will be cleared from mm->def_vma_flags.
+ * it, VMA_LOCKED_BIT and VMA_LOCKONFAULT_BIT will be cleared from
+ * mm->def_vma_flags.
*/
static int apply_mlockall_flags(int flags)
{
VMA_ITERATOR(vmi, current->mm, 0);
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma, *prev = NULL;
- vm_flags_t to_add = 0;
+ vma_flags_t to_add = EMPTY_VMA_FLAGS;
vma_flags_clear_mask(&mm->def_vma_flags, VMA_LOCKED_MASK);
if (flags & MCL_FUTURE) {
@@ -726,20 +732,20 @@ static int apply_mlockall_flags(int flags)
}
if (flags & MCL_CURRENT) {
- to_add |= VM_LOCKED;
+ vma_flags_set(&to_add, VMA_LOCKED_BIT);
if (flags & MCL_ONFAULT)
- to_add |= VM_LOCKONFAULT;
+ vma_flags_set(&to_add, VMA_LOCKONFAULT_BIT);
}
for_each_vma(vmi, vma) {
int error;
- vm_flags_t newflags;
+ vma_flags_t newflags = vma->flags;
- newflags = vma->vm_flags & ~VM_LOCKED_MASK;
- newflags |= to_add;
+ vma_flags_clear_mask(&newflags, VMA_LOCKED_MASK);
+ vma_flags_set_mask(&newflags, to_add);
error = mlock_fixup(&vmi, vma, &prev, vma->vm_start, vma->vm_end,
- newflags);
+ &newflags);
/* Ignore errors, but prev needs fixing up. */
if (error)
prev = vma;
--
2.54.0
^ permalink raw reply related
* [PATCH 12/13] mm/mprotect: convert mprotect code to use vma_flags_t
From: Lorenzo Stoakes @ 2026-06-29 19:25 UTC (permalink / raw)
To: Andrew Morton
Cc: Thomas Bogendoerfer, Madhavan Srinivasan, Michael Ellerman,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Lucas Stach, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Rob Clark,
Dmitry Baryshkov, Lyude Paul, Danilo Krummrich, Tomi Valkeinen,
Sandy Huang, Heiko Stübner, Andy Yan, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann, Dmitry Osipenko,
Zack Rusin, Matthew Brost, Thomas Hellstrom,
Oleksandr Andrushchenko, Helge Deller, Benjamin LaHaise,
Alexander Viro, Christian Brauner, Muchun Song, Oscar Salvador,
David Hildenbrand, Zi Yan, Baolin Wang, Liam R . Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Hugh Dickins, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Michal Hocko, Jann Horn, Pedro Falcato, Kees Cook,
Jaroslav Kysela, Takashi Iwai, linux-mips, linux-kernel,
linuxppc-dev, dri-devel, etnaviv, linux-arm-kernel,
linux-samsung-soc, intel-gfx, linux-arm-msm, freedreno, nouveau,
linux-rockchip, linux-tegra, virtualization, intel-xe, xen-devel,
linux-fbdev, linux-aio, linux-fsdevel, linux-mm, linux-sound
In-Reply-To: <cover.1782760670.git.ljs@kernel.org>
Replace use of the legacy vm_flags_t flags with vma_flags_t values
throughout the mprotect logic.
Note that we retain the legacy vm_flags_t bit shifting code in
do_mprotect_key(), deferring a vma_flags_t approach to this for the time
being.
Additionally update comments to reflect the changes to be consistent.
No functional change intended.
Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
mm/mprotect.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 9cbf932b028c..c9504b2a2525 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -40,7 +40,7 @@
static bool maybe_change_pte_writable(struct vm_area_struct *vma, pte_t pte)
{
- if (WARN_ON_ONCE(!(vma->vm_flags & VM_WRITE)))
+ if (WARN_ON_ONCE(!vma_test(vma, VMA_WRITE_BIT)))
return false;
/* Don't touch entries that are not even readable. */
@@ -97,7 +97,7 @@ static bool can_change_shared_pte_writable(struct vm_area_struct *vma,
bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr,
pte_t pte)
{
- if (!(vma->vm_flags & VM_SHARED))
+ if (!vma_test(vma, VMA_SHARED_BIT))
return can_change_private_pte_writable(vma, addr, pte);
return can_change_shared_pte_writable(vma, pte);
@@ -194,7 +194,7 @@ static __always_inline void set_write_prot_commit_flush_ptes(struct vm_area_stru
{
bool set_write;
- if (vma->vm_flags & VM_SHARED) {
+ if (vma_test(vma, VMA_SHARED_BIT)) {
set_write = can_change_shared_pte_writable(vma, ptent);
prot_commit_flush_ptes(vma, addr, ptep, oldpte, ptent, nr_ptes,
/* idx = */ 0, set_write, tlb);
@@ -811,8 +811,8 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
vm_unacct_memory(nrpages);
/*
- * Private VM_LOCKED VMA becoming writable: trigger COW to avoid major
- * fault on access.
+ * Private VMA_LOCKED_BIT VMA becoming writable: trigger COW to avoid
+ * major fault on access.
*/
if (vma_flags_test(&new_vma_flags, VMA_WRITE_BIT) &&
vma_flags_test(&old_vma_flags, VMA_LOCKED_BIT) &&
@@ -886,7 +886,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
goto out;
start = vma->vm_start;
error = -EINVAL;
- if (!(vma->vm_flags & VM_GROWSDOWN))
+ if (!vma_test(vma, VMA_GROWSDOWN_BIT))
goto out;
} else {
if (vma->vm_start > start)
@@ -894,7 +894,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
if (unlikely(grows & PROT_GROWSUP)) {
end = vma->vm_end;
error = -EINVAL;
- if (!(vma->vm_flags & VM_GROWSUP))
+ if (!vma_test(vma, VMA_GROWSUP_BIT))
goto out;
}
}
@@ -918,7 +918,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
}
/* Does the application expect PROT_READ to imply PROT_EXEC */
- if (rier && (vma->vm_flags & VM_MAYEXEC))
+ if (rier && vma_test(vma, VMA_MAYEXEC_BIT))
prot |= PROT_EXEC;
/*
--
2.54.0
^ permalink raw reply related
* [PATCH 13/13] mm/mremap: convert mremap code to use vma_flags_t
From: Lorenzo Stoakes @ 2026-06-29 19:25 UTC (permalink / raw)
To: Andrew Morton
Cc: Thomas Bogendoerfer, Madhavan Srinivasan, Michael Ellerman,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Lucas Stach, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Rob Clark,
Dmitry Baryshkov, Lyude Paul, Danilo Krummrich, Tomi Valkeinen,
Sandy Huang, Heiko Stübner, Andy Yan, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann, Dmitry Osipenko,
Zack Rusin, Matthew Brost, Thomas Hellstrom,
Oleksandr Andrushchenko, Helge Deller, Benjamin LaHaise,
Alexander Viro, Christian Brauner, Muchun Song, Oscar Salvador,
David Hildenbrand, Zi Yan, Baolin Wang, Liam R . Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Hugh Dickins, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Michal Hocko, Jann Horn, Pedro Falcato, Kees Cook,
Jaroslav Kysela, Takashi Iwai, linux-mips, linux-kernel,
linuxppc-dev, dri-devel, etnaviv, linux-arm-kernel,
linux-samsung-soc, intel-gfx, linux-arm-msm, freedreno, nouveau,
linux-rockchip, linux-tegra, virtualization, intel-xe, xen-devel,
linux-fbdev, linux-aio, linux-fsdevel, linux-mm, linux-sound
In-Reply-To: <cover.1782760670.git.ljs@kernel.org>
Replace use of the legacy vm_flags_t flags with vma_flags_t values
throughout the mremap logic.
Additionally update comments to reflect the changes to be consistent.
No functional change intended.
Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
---
mm/mremap.c | 38 ++++++++++++++++++++------------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/mm/mremap.c b/mm/mremap.c
index 079a0ba0c4a7..0ea43302b7ed 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -68,7 +68,7 @@ struct vma_remap_struct {
bool populate_expand; /* mlock()'d expanded, must populate. */
enum mremap_type remap_type; /* expand, shrink, etc. */
bool mmap_locked; /* Is mm currently write-locked? */
- unsigned long charged; /* If VM_ACCOUNT, # pages to account. */
+ unsigned long charged; /* If VMA_ACCOUNT_BIT, # pgs to account */
bool vmi_needs_invalidate; /* Is the VMA iterator invalidated? */
};
@@ -954,7 +954,7 @@ static unsigned long vrm_set_new_addr(struct vma_remap_struct *vrm)
if (vrm->flags & MREMAP_FIXED)
map_flags |= MAP_FIXED;
- if (vma->vm_flags & VM_MAYSHARE)
+ if (vma_test(vma, VMA_MAYSHARE_BIT))
map_flags |= MAP_SHARED;
res = get_unmapped_area(vma->vm_file, new_addr, vrm->new_len, pgoff,
@@ -976,7 +976,7 @@ static bool vrm_calc_charge(struct vma_remap_struct *vrm)
{
unsigned long charged;
- if (!(vrm->vma->vm_flags & VM_ACCOUNT))
+ if (!vma_test(vrm->vma, VMA_ACCOUNT_BIT))
return true;
/*
@@ -1003,7 +1003,7 @@ static bool vrm_calc_charge(struct vma_remap_struct *vrm)
*/
static void vrm_uncharge(struct vma_remap_struct *vrm)
{
- if (!(vrm->vma->vm_flags & VM_ACCOUNT))
+ if (!vma_test(vrm->vma, VMA_ACCOUNT_BIT))
return;
vm_unacct_memory(vrm->charged);
@@ -1023,7 +1023,7 @@ static void vrm_stat_account(struct vma_remap_struct *vrm,
struct vm_area_struct *vma = vrm->vma;
vm_stat_account(mm, vma->vm_flags, pages);
- if (vma->vm_flags & VM_LOCKED)
+ if (vma_test(vma, VMA_LOCKED_BIT))
mm->locked_vm += pages;
}
@@ -1167,7 +1167,7 @@ static void unmap_source_vma(struct vma_remap_struct *vrm)
* arose, in which case we _do_ wish to unmap the _new_ VMA, which means
* we actually _do_ want it be unaccounted.
*/
- bool accountable_move = (vma->vm_flags & VM_ACCOUNT) &&
+ bool accountable_move = vma_test(vma, VMA_ACCOUNT_BIT) &&
!(vrm->flags & MREMAP_DONTUNMAP);
/*
@@ -1186,7 +1186,7 @@ static void unmap_source_vma(struct vma_remap_struct *vrm)
* portions of the original VMA that remain.
*/
if (accountable_move) {
- vm_flags_clear(vma, VM_ACCOUNT);
+ vma_clear_flags(vma, VMA_ACCOUNT_BIT);
/* We are about to split vma, so store the start/end. */
vm_start = vma->vm_start;
vm_end = vma->vm_end;
@@ -1211,8 +1211,8 @@ static void unmap_source_vma(struct vma_remap_struct *vrm)
* | |
* |-------------|
*
- * Having cleared VM_ACCOUNT from the whole VMA, after we unmap above
- * we'll end up with:
+ * Having cleared VMA_ACCOUNT_BIT from the whole VMA, after we unmap
+ * above we'll end up with:
*
* addr end
* | |
@@ -1232,13 +1232,15 @@ static void unmap_source_vma(struct vma_remap_struct *vrm)
if (vm_start < addr) {
struct vm_area_struct *prev = vma_prev(&vmi);
- vm_flags_set(prev, VM_ACCOUNT); /* Acquires VMA lock. */
+ vma_start_write(prev);
+ vma_set_flags(prev, VMA_ACCOUNT_BIT);
}
if (vm_end > end) {
struct vm_area_struct *next = vma_next(&vmi);
- vm_flags_set(next, VM_ACCOUNT); /* Acquires VMA lock. */
+ vma_start_write(next);
+ vma_set_flags(next, VMA_ACCOUNT_BIT);
}
}
}
@@ -1321,8 +1323,8 @@ static void dontunmap_complete(struct vma_remap_struct *vrm,
unsigned long old_start = vrm->vma->vm_start;
unsigned long old_end = vrm->vma->vm_end;
- /* We always clear VM_LOCKED[ONFAULT] on the old VMA. */
- vm_flags_clear(vrm->vma, VM_LOCKED_MASK);
+ /* We always clear VMA_LOCKED[ONFAULT]_BIT on the old VMA. */
+ vma_clear_flags_mask(vrm->vma, VMA_LOCKED_MASK);
/*
* anon_vma links of the old vma is no longer needed after its page
@@ -1758,14 +1760,14 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
* based on the original. There are no known use cases for this
* behavior. As a result, fail such attempts.
*/
- if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
+ if (!old_len && !vma_test_any(vma, VMA_SHARED_BIT, VMA_MAYSHARE_BIT)) {
pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap. This is not supported.\n",
current->comm, current->pid);
return -EINVAL;
}
if ((vrm->flags & MREMAP_DONTUNMAP) &&
- (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)))
+ vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))
return -EINVAL;
/*
@@ -1795,7 +1797,7 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
return 0;
/* We are expanding and the VMA is mlock()'d so we need to populate. */
- if (vma->vm_flags & VM_LOCKED)
+ if (vma_test(vma, VMA_LOCKED_BIT))
vrm->populate_expand = true;
/* Need to be careful about a growing mapping */
@@ -1803,10 +1805,10 @@ static int check_prep_vma(struct vma_remap_struct *vrm)
if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
return -EINVAL;
- if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
+ if (vma_test_any(vma, VMA_DONTEXPAND_BIT, VMA_PFNMAP_BIT))
return -EFAULT;
- if (!mlock_future_ok(mm, vma->vm_flags & VM_LOCKED, vrm->delta))
+ if (!mlock_future_ok(mm, vma_test(vma, VMA_LOCKED_BIT), vrm->delta))
return -EAGAIN;
if (!may_expand_vm(mm, &vma->flags, vrm->delta >> PAGE_SHIFT))
--
2.54.0
^ permalink raw reply related
* Re: [PATCH 01/13] mm: introduce vma_flags_can_grow() and vma_can_grow()
From: Zi Yan @ 2026-06-29 20:26 UTC (permalink / raw)
To: Lorenzo Stoakes, Andrew Morton
Cc: Thomas Bogendoerfer, Madhavan Srinivasan, Michael Ellerman,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Lucas Stach, Inki Dae, Seung-Woo Kim,
Kyungmin Park, Krzysztof Kozlowski, Peter Griffin, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Rob Clark,
Dmitry Baryshkov, Lyude Paul, Danilo Krummrich, Tomi Valkeinen,
Sandy Huang, Heiko Stübner, Andy Yan, Thierry Reding,
Mikko Perttunen, Jonathan Hunter, Gerd Hoffmann, Dmitry Osipenko,
Zack Rusin, Matthew Brost, Thomas Hellstrom,
Oleksandr Andrushchenko, Helge Deller, Benjamin LaHaise,
Alexander Viro, Christian Brauner, Muchun Song, Oscar Salvador,
David Hildenbrand, Baolin Wang, Liam R . Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Hugh Dickins,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jann Horn, Pedro Falcato, Kees Cook, Jaroslav Kysela,
Takashi Iwai, linux-mips, linux-kernel, linuxppc-dev, dri-devel,
etnaviv, linux-arm-kernel, linux-samsung-soc, intel-gfx,
linux-arm-msm, freedreno, nouveau, linux-rockchip, linux-tegra,
virtualization, intel-xe, xen-devel, linux-fbdev, linux-aio,
linux-fsdevel, linux-mm, linux-sound
In-Reply-To: <f2e8c32515d328db62279cc8bab8398ea278d74f.1782760670.git.ljs@kernel.org>
On Mon Jun 29, 2026 at 3:25 PM EDT, Lorenzo Stoakes wrote:
> These test whether the VMA has stack sematics, i.e. is able to grow upwards
> or downwards depending on the architecture.
>
> In order to account for arches which do not support upward-growing stacks,
> introduce VMA_GROWSUP whose definition depends on the architecture
> supporting it, and use vma_flags_test_single_mask() in vma_flags_can_grow()
> to account for this.
>
> Update the VMA userland tests to reflect the changes
>
> No functional change intended.
>
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> include/linux/mm.h | 21 ++++++++++++++++++---
> tools/testing/vma/include/dup.h | 4 ++++
> 2 files changed, 22 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 868b2334bff3..cf7df1569052 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -472,6 +472,7 @@ enum {
> #define VM_SAO INIT_VM_FLAG(SAO)
> #elif defined(CONFIG_PARISC)
> #define VM_GROWSUP INIT_VM_FLAG(GROWSUP)
> +#define VMA_GROWSUP mk_vma_flags(VMA_GROWSUP_BIT)
> #elif defined(CONFIG_SPARC64)
> #define VM_SPARC_ADI INIT_VM_FLAG(SPARC_ADI)
> #define VM_ARCH_CLEAR INIT_VM_FLAG(ARCH_CLEAR)
> @@ -483,6 +484,7 @@ enum {
> #endif
> #ifndef VM_GROWSUP
> #define VM_GROWSUP VM_NONE
> +#define VMA_GROWSUP EMPTY_VMA_FLAGS
> #endif
> #ifdef CONFIG_ARM64_MTE
> #define VM_MTE INIT_VM_FLAG(MTE)
> @@ -1563,11 +1565,24 @@ static inline bool vma_is_initial_stack(const struct vm_area_struct *vma)
> vma->vm_end >= vma->vm_mm->start_stack;
> }
>
> -static inline bool vma_is_temporary_stack(const struct vm_area_struct *vma)
> +static inline bool vma_flags_can_grow(const vma_flags_t *flags)
> {
> - int maybe_stack = vma->vm_flags & (VM_GROWSDOWN | VM_GROWSUP);
> + if (vma_flags_test_single_mask(flags, VMA_GROWSUP))
> + return true;
> + if (vma_flags_test(flags, VMA_GROWSDOWN_BIT))
> + return true;
> +
> + return false;
> +}
>
> - if (!maybe_stack)
> +static inline bool vma_can_grow(const struct vm_area_struct *vma)
> +{
> + return vma_flags_can_grow(&vma->flags);
Would it save vma_flags_can_grow() if we do below?
return vma_test(vma, VMA_GROWSDOWN_BIT) || vma_test_single_mask(vma, VMA_GROWSUP);
I find these two functions when I am reading mm.h.
> +}
> +
> +static inline bool vma_is_temporary_stack(const struct vm_area_struct *vma)
> +{
> + if (!vma_can_grow(vma))
> return false;
>
> if ((vma->vm_flags & VM_STACK_INCOMPLETE_SETUP) ==
> diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
> index 5d7d0afd7765..6f5bcd7fbcd8 100644
> --- a/tools/testing/vma/include/dup.h
> +++ b/tools/testing/vma/include/dup.h
> @@ -245,8 +245,10 @@ enum {
> #define VM_STACK INIT_VM_FLAG(STACK)
> #ifdef CONFIG_STACK_GROWS_UP
> #define VM_STACK_EARLY INIT_VM_FLAG(STACK_EARLY)
> +#define VMA_STACK_EARLY mk_vma_flags(VMA_STACK_EARLY_BIT)
> #else
> #define VM_STACK_EARLY VM_NONE
> +#define VMA_STACK_EARLY EMPTY_VMA_FLAGS
> #endif
> #ifdef CONFIG_ARCH_HAS_PKEYS
> #define VM_PKEY_SHIFT ((__force int)VMA_HIGH_ARCH_0_BIT)
> @@ -315,6 +317,8 @@ enum {
>
> /* Bits set in the VMA until the stack is in its final location */
> #define VM_STACK_INCOMPLETE_SETUP (VM_RAND_READ | VM_SEQ_READ | VM_STACK_EARLY)
> +#define VMA_STACK_INCOMPLETE_SETUP append_vma_flags( \
> + VMA_STACK_EARLY, VMA_RAND_READ_BIT, VMA_SEQ_READ_BIT)
>
> #define TASK_EXEC_BIT ((current->personality & READ_IMPLIES_EXEC) ? \
> VM_EXEC_BIT : VM_READ_BIT)
Why are VMA_STACK_EARLY and VMA_STACK_INCOMPLETE_SETUP added here but
not in mm.h?
--
Best Regards,
Yan, Zi
^ permalink raw reply
* Re: [PATCH] drm/virtio: Don't detach GEM from a non-created context
From: Dmitry Osipenko @ 2026-06-29 21:38 UTC (permalink / raw)
To: Jason Macnak, David Airlie, Gerd Hoffmann, Gurchetan Singh
Cc: dri-devel, virtualization, linux-kernel, stable
In-Reply-To: <20260625170828.3335431-1-natsu@google.com>
Hi,
On 6/25/26 20:08, Jason Macnak wrote:
> Applies the same treatment as commit 7cf6dd467e87 ("drm/virtio:
> Don't attach GEM to a non-created context in gem_object_open()")
> to virtio_gpu_gem_object_close() to avoid trying to detach
> a resource that was never attached due to a context
> never being created when context_init is supported.
>
> Fixes: 086b9f27f0ab ("drm/virtio: Don't create a context with default param if context_init is supported")
> Cc: <stable@vger.kernel.org> # v6.14+
> Signed-off-by: Jason Macnak <natsu@google.com>
> ---
> drivers/gpu/drm/virtio/virtgpu_gem.c | 14 ++++++++------
> 1 file changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/virtio/virtgpu_gem.c b/drivers/gpu/drm/virtio/virtgpu_gem.c
> index 435d37d36034..66c3f6f74e9c 100644
> --- a/drivers/gpu/drm/virtio/virtgpu_gem.c
> +++ b/drivers/gpu/drm/virtio/virtgpu_gem.c
> @@ -139,13 +139,15 @@ void virtio_gpu_gem_object_close(struct drm_gem_object *obj,
> if (!vgdev->has_virgl_3d)
> return;
>
> - objs = virtio_gpu_array_alloc(1);
> - if (!objs)
> - return;
> - virtio_gpu_array_add_obj(objs, obj);
> + if (vfpriv->context_created) {
> + objs = virtio_gpu_array_alloc(1);
> + if (!objs)
> + return;
> + virtio_gpu_array_add_obj(objs, obj);
>
> - virtio_gpu_cmd_context_detach_resource(vgdev, vfpriv->ctx_id,
> - objs);
> + virtio_gpu_cmd_context_detach_resource(vgdev, vfpriv->ctx_id,
> + objs);
> + }
> virtio_gpu_notify(vgdev);
> }
The following scenario still will be troubling:
1. vgdev->has_context_init = true
2. virtio_gpu_gem_object_open() invoked, GEM created and not attached to ctx
3. virtio_gpu_context_init_ioctl() invoked, now vfpriv->context_created
= true
4. virtio_gpu_gem_object_close() will detach resource that wasn't attached
Add obj->ctx_attached member to struct virtio_gpu_object. See
virtio_gpu_object_attach() that uses obj->attached, do the same for
virtio_gpu_cmd_context_attach_resource().
--
Best regards,
Dmitry
^ permalink raw reply
* Re: [PATCH] drm/virtio: warn when virtqueue has no free space for too long
From: Ryosuke Yasuoka @ 2026-06-30 2:19 UTC (permalink / raw)
To: Dmitry Osipenko, David Airlie, Gerd Hoffmann, Gurchetan Singh,
Chia-I Wu, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Simona Vetter
Cc: dri-devel, virtualization, linux-kernel
In-Reply-To: <111d0a7c-60d3-4451-b214-510c98229883@collabora.com>
On 29/06/2026 15:11, Dmitry Osipenko wrote:
> Hi,
>
> On 6/18/26 10:18, Ryosuke Yasuoka wrote:
>> virtio_gpu_queue_ctrl_sgs() and virtio_gpu_queue_cursor() wait for
>> virtqueue space using wait_event() with vqs_released as the only abort
>> condition. This covers the device removal path, where
>> virtio_gpu_release_vqs() sets the flag, but does not help when the host
>> simply stops processing the virtqueue while the device remains present.
>>
>> In that case, the virtqueue fills up and subsequent command submissions
>> block indefinitely in D state with no diagnostic output, making the root
>> cause difficult to identify.
>>
>> Replace the bare wait_event() with a wait_event_timeout() loop that
>> prints a warning every 5 seconds while the virtqueue remains full. The
>> wait still blocks indefinitely so driver behavior is unchanged. The
>> warnings help identify an unresponsive host device during
>> troubleshooting.
>>
>> Signed-off-by: Ryosuke Yasuoka <ryasuoka@redhat.com>
>> ---
>> When the host stops processing the virtio-gpu virtqueue without
>> triggering device removal, the bare wait_event() in
>> virtio_gpu_queue_ctrl_sgs() and virtio_gpu_queue_cursor() blocks
>> indefinitely with no diagnostic output. A DRM atomic commit worker
>> blocks in virtio_gpu_queue_fenced_ctrl_buffer() while holding the
>> modeset lock. During graceful shutdown, systemd (PID 1) needs the same
>> lock — either by writing to the console via fbcon, or by closing a DRM
>> file descriptor that triggers framebuffer cleanup — and blocks as well,
>> making the VM unrecoverable without a forced power-off.
>>
>> PID: 553 COMMAND: "kworker/u4:3"
>> #0 __schedule
>> #1 schedule
>> #2 virtio_gpu_queue_fenced_ctrl_buffer [virtio_gpu]
>> #3 virtio_gpu_primary_plane_update [virtio_gpu]
>> ...
>>
>> PID: 1 COMMAND: "systemd" (console write path)
>> #0 __schedule
>> #1 schedule
>> #2 schedule_preempt_disabled
>> #3 __ww_mutex_lock
>> #4 drm_modeset_lock [drm]
>> #5 drm_atomic_get_plane_state [drm]
>> #6 drm_client_modeset_commit_atomic [drm]
>> #7 drm_client_modeset_commit_locked [drm]
>> #8 drm_fb_helper_pan_display [drm_kms_helper]
>> #9 fb_pan_display
>> #10 bit_update_start
>> #11 fbcon_switch
>> #12 redraw_screen
>> ...
>>
>> Reproduction steps:
>> 1. Build QEMU with the fault injection patch [1] that adds an
>> x-ctrl-queue-broken property to virtio-gpu.
>> 2. Boot the VM and trigger the fault injection from the host.
>> 3. Fill the ctrlq (e.g., move the mouse on the guest's display).
>> The process gets stuck in virtio_gpu_queue_fenced_ctrl_buffer()
>> in D state.
>> 4. Run a graceful shutdown command (shutdown now or reboot).
>> 5. The shutdown process hangs.
>>
>> My earlier patch a46991b334f6 ("drm/virtio: abort virtqueue wait on
>> device removal to avoid hung task") covers the case where the shutdown
>> process reaches the device_shutdown() call path, which sets vqs_released
>> to unblock the wait. However, during graceful shutdown, systemd (PID 1)
>> gets stuck on the modeset lock before ever reaching device_shutdown(),
>> so vqs_released is never set and the wait is never unblocked.
>>
>> I initially considered adding a module parameter to abort the wait with
>> -ENODEV on timeout:
>>
>> +static unsigned int virtio_gpu_vq_timeout;
>> +MODULE_PARM_DESC(vq_timeout,
>> + "Timeout in seconds for virtqueue wait (0 = no timeout, default)");
>> +module_param_named(vq_timeout, virtio_gpu_vq_timeout, uint, 0444);
>> ...
>> + if (virtio_gpu_vq_timeout) {
>> + if (!wait_event_timeout(vgdev->ctrlq.ack_queue,
>> + vq->num_free >= elemcnt ||
>> + vgdev->vqs_released,
>> + secs_to_jiffies(virtio_gpu_vq_timeout))) {
>> + if (fence && vbuf->objs)
>> + virtio_gpu_array_unlock_resv(vbuf->objs);
>> + free_vbuf(vgdev, vbuf);
>> + drm_dev_exit(idx);
>> + return -ENODEV;
>> + }
>> + } else {
>> + wait_event(vgdev->ctrlq.ack_queue,
>> + vq->num_free >= elemcnt ||
>> + vgdev->vqs_released);
>> + }
>>
>> This approach aborts the wait and allows the graceful shutdown process
>> to eventually proceed, albeit with a delay.
>>
>> But that approach has drawbacks: it allows users to set arbitrarily
>> short timeouts that could destabilize the driver, and aborting commands
>> mid-flight is a rough recovery path. An unconditional timeout was also
>> discussed previously [2] but is not appropriate without virtio
>> specification support.
>>
>> This patch takes a safer approach: replace the bare wait_event() with
>> wait_event_timeout() and print a warning every 5 seconds while the
>> virtqueue remains full. The wait still blocks indefinitely and no
>> commands are aborted, so driver behavior is unchanged. The warnings
>> help identify an unresponsive host device during troubleshooting.
>> Once the user notices the warning, they can work around the hang by
>> unbinding the VT from fbcon, removing the device, or forcing a shutdown
>> via SysRq.
>>
>> [1] https://gist.github.com/YsuOS/fbcd181752594af35f954953a1d260b8
>> [2] https://lore.kernel.org/all/8a986c52-964f-42a5-b063-fbe2b242ca36@collabora.com/
>> ---
>> drivers/gpu/drm/virtio/virtgpu_vq.c | 18 ++++++++++++++----
>> 1 file changed, 14 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
>> index 68d097ad9d1d..a546130d3b6a 100644
>> --- a/drivers/gpu/drm/virtio/virtgpu_vq.c
>> +++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
>> @@ -410,8 +410,13 @@ static int virtio_gpu_queue_ctrl_sgs(struct virtio_gpu_device *vgdev,
>> if (vq->num_free < elemcnt) {
>> spin_unlock(&vgdev->ctrlq.qlock);
>> virtio_gpu_notify(vgdev);
>> - wait_event(vgdev->ctrlq.ack_queue,
>> - vq->num_free >= elemcnt || vgdev->vqs_released);
>> + while (!wait_event_timeout(vgdev->ctrlq.ack_queue,
>> + vq->num_free >= elemcnt ||
>> + vgdev->vqs_released,
>> + 5 * HZ) && !vgdev->vqs_released)
>> + DRM_WARN("ctrlq waiting for host: no free space for %d secs\n",
>> + 5);
>> +
>> /*
>> * Set by virtio_gpu_release_vqs() to unblock
>> * synchronize_srcu() wait in drm_dev_unplug().
>> @@ -592,8 +597,13 @@ static void virtio_gpu_queue_cursor(struct virtio_gpu_device *vgdev,
>> ret = virtqueue_add_sgs(vq, sgs, outcnt, 0, vbuf, GFP_ATOMIC);
>> if (ret == -ENOSPC) {
>> spin_unlock(&vgdev->cursorq.qlock);
>> - wait_event(vgdev->cursorq.ack_queue,
>> - vq->num_free >= outcnt || vgdev->vqs_released);
>> + while (!wait_event_timeout(vgdev->cursorq.ack_queue,
>> + vq->num_free >= outcnt ||
>> + vgdev->vqs_released,
>> + 5 * HZ) && !vgdev->vqs_released)
>> + DRM_WARN("cursorq waiting for host: no free space for %d secs\n",
>> + 5);
>> +
>> /* See comment in virtio_gpu_queue_ctrl_sgs(). */
>> if (vgdev->vqs_released) {
>> free_vbuf(vgdev, vbuf);
>
> The [1] has a valid point about the hangcheck warning. Is
> wait_event_timeout() worth the change then?
>
> [1]
> https://sashiko.dev/#/patchset/20260618-virtiogpu_add_timeout-v1-1-dc36cef609d9%40redhat.com
Thanks for pointing this out.
You're right — the wait_event_timeout() loop wakes the task every
5 seconds, resetting the context switch counter each time, so
khungtaskd will never detect these tasks as hung.
I originally thought the earlier, virtio-specific warning would be
more useful for troubleshooting than the generic hung task output.
But reconsidering, the practical workflow for the user doesn't
really change either way: the device gets stuck, the user sees a
diagnostic message — whether that's the hung task warning or my
proposed DRM_WARN — and then manually intervenes with SysRq or a
forced shutdown.
The difference is what diagnostic information is available. The
hung task warning includes a full stack trace pointing directly at
the virtqueue wait, which is more useful for identifying the root
cause than a short warning line. Suppressing that by resetting the
context switch counter every 5 seconds makes the diagnostics
strictly worse.
More fundamentally, the guest can't recover from this situation
regardless — the root cause is an unresponsive host, and the fix
has to happen on the host side. The existing wait_event() with the
kernel's built-in hung task detection is the better behavior here.
I'll drop this patch.
Best regards,
Ryosuke
^ permalink raw reply
* [PATCH v2] virtio_mem: fix hardcoded 'vm' variable in bbm iteration macros
From: lirongqing @ 2026-06-30 4:59 UTC (permalink / raw)
To: David Hildenbrand, Michael S . Tsirkin, Jason Wang, Xuan Zhuo,
Eugenio Pérez, virtualization, linux-kernel
Cc: Li RongQing
From: Li RongQing <lirongqing@baidu.com>
virtio_mem_bbm_for_each_bb() and virtio_mem_bbm_for_each_bb_rev()
accept a '_vm' parameter to allow callers to pass any variable name
referring to the virtio_mem instance. However, the 'for' loop
initializer and part of the loop condition use the bare name 'vm'
instead of the macro parameter '_vm'.
Fix by replacing all bare 'vm->' references inside the macros with
the '_vm' parameter, and wrap in parentheses following kernel macro
conventions.
Signed-off-by: Li RongQing <lirongqing@baidu.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
diff with v1: drop the Fixes tag, since there is no user-visible bug
drivers/virtio/virtio_mem.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/virtio/virtio_mem.c b/drivers/virtio/virtio_mem.c
index 11c4415..82a285c 100644
--- a/drivers/virtio/virtio_mem.c
+++ b/drivers/virtio/virtio_mem.c
@@ -423,14 +423,14 @@ static int virtio_mem_bbm_bb_states_prepare_next_bb(struct virtio_mem *vm)
}
#define virtio_mem_bbm_for_each_bb(_vm, _bb_id, _state) \
- for (_bb_id = vm->bbm.first_bb_id; \
- _bb_id < vm->bbm.next_bb_id && _vm->bbm.bb_count[_state]; \
+ for (_bb_id = (_vm)->bbm.first_bb_id; \
+ _bb_id < (_vm)->bbm.next_bb_id && (_vm)->bbm.bb_count[_state]; \
_bb_id++) \
if (virtio_mem_bbm_get_bb_state(_vm, _bb_id) == _state)
#define virtio_mem_bbm_for_each_bb_rev(_vm, _bb_id, _state) \
- for (_bb_id = vm->bbm.next_bb_id - 1; \
- _bb_id >= vm->bbm.first_bb_id && _vm->bbm.bb_count[_state]; \
+ for (_bb_id = (_vm)->bbm.next_bb_id - 1; \
+ _bb_id >= (_vm)->bbm.first_bb_id && (_vm)->bbm.bb_count[_state]; \
_bb_id--) \
if (virtio_mem_bbm_get_bb_state(_vm, _bb_id) == _state)
--
2.9.4
^ permalink raw reply related
* Re: [PATCH 01/13] mm: introduce vma_flags_can_grow() and vma_can_grow()
From: Lorenzo Stoakes @ 2026-06-30 7:38 UTC (permalink / raw)
To: Zi Yan
Cc: Andrew Morton, Thomas Bogendoerfer, Madhavan Srinivasan,
Michael Ellerman, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Lucas Stach,
Inki Dae, Seung-Woo Kim, Kyungmin Park, Krzysztof Kozlowski,
Peter Griffin, Jani Nikula, Joonas Lahtinen, Rodrigo Vivi,
Tvrtko Ursulin, Rob Clark, Dmitry Baryshkov, Lyude Paul,
Danilo Krummrich, Tomi Valkeinen, Sandy Huang, Heiko Stübner,
Andy Yan, Thierry Reding, Mikko Perttunen, Jonathan Hunter,
Gerd Hoffmann, Dmitry Osipenko, Zack Rusin, Matthew Brost,
Thomas Hellstrom, Oleksandr Andrushchenko, Helge Deller,
Benjamin LaHaise, Alexander Viro, Christian Brauner, Muchun Song,
Oscar Salvador, David Hildenbrand, Baolin Wang, Liam R . Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Hugh Dickins, Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Michal Hocko, Jann Horn, Pedro Falcato, Kees Cook,
Jaroslav Kysela, Takashi Iwai, linux-mips, linux-kernel,
linuxppc-dev, dri-devel, etnaviv, linux-arm-kernel,
linux-samsung-soc, intel-gfx, linux-arm-msm, freedreno, nouveau,
linux-rockchip, linux-tegra, virtualization, intel-xe, xen-devel,
linux-fbdev, linux-aio, linux-fsdevel, linux-mm, linux-sound
In-Reply-To: <DJLTDCPWMXDL.4ICZ77A1LPZZ@nvidia.com>
On Mon, Jun 29, 2026 at 04:26:18PM -0400, Zi Yan wrote:
> On Mon Jun 29, 2026 at 3:25 PM EDT, Lorenzo Stoakes wrote:
> > These test whether the VMA has stack sematics, i.e. is able to grow upwards
> > or downwards depending on the architecture.
> >
> > In order to account for arches which do not support upward-growing stacks,
> > introduce VMA_GROWSUP whose definition depends on the architecture
> > supporting it, and use vma_flags_test_single_mask() in vma_flags_can_grow()
> > to account for this.
> >
> > Update the VMA userland tests to reflect the changes
> >
> > No functional change intended.
> >
> > Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
> > ---
> > include/linux/mm.h | 21 ++++++++++++++++++---
> > tools/testing/vma/include/dup.h | 4 ++++
> > 2 files changed, 22 insertions(+), 3 deletions(-)
> >
> > diff --git a/include/linux/mm.h b/include/linux/mm.h
> > index 868b2334bff3..cf7df1569052 100644
> > --- a/include/linux/mm.h
> > +++ b/include/linux/mm.h
> > @@ -472,6 +472,7 @@ enum {
> > #define VM_SAO INIT_VM_FLAG(SAO)
> > #elif defined(CONFIG_PARISC)
> > #define VM_GROWSUP INIT_VM_FLAG(GROWSUP)
> > +#define VMA_GROWSUP mk_vma_flags(VMA_GROWSUP_BIT)
> > #elif defined(CONFIG_SPARC64)
> > #define VM_SPARC_ADI INIT_VM_FLAG(SPARC_ADI)
> > #define VM_ARCH_CLEAR INIT_VM_FLAG(ARCH_CLEAR)
> > @@ -483,6 +484,7 @@ enum {
> > #endif
> > #ifndef VM_GROWSUP
> > #define VM_GROWSUP VM_NONE
> > +#define VMA_GROWSUP EMPTY_VMA_FLAGS
> > #endif
> > #ifdef CONFIG_ARM64_MTE
> > #define VM_MTE INIT_VM_FLAG(MTE)
> > @@ -1563,11 +1565,24 @@ static inline bool vma_is_initial_stack(const struct vm_area_struct *vma)
> > vma->vm_end >= vma->vm_mm->start_stack;
> > }
> >
> > -static inline bool vma_is_temporary_stack(const struct vm_area_struct *vma)
> > +static inline bool vma_flags_can_grow(const vma_flags_t *flags)
> > {
> > - int maybe_stack = vma->vm_flags & (VM_GROWSDOWN | VM_GROWSUP);
> > + if (vma_flags_test_single_mask(flags, VMA_GROWSUP))
> > + return true;
> > + if (vma_flags_test(flags, VMA_GROWSDOWN_BIT))
> > + return true;
> > +
> > + return false;
> > +}
> >
> > - if (!maybe_stack)
> > +static inline bool vma_can_grow(const struct vm_area_struct *vma)
> > +{
> > + return vma_flags_can_grow(&vma->flags);
>
> Would it save vma_flags_can_grow() if we do below?
>
> return vma_test(vma, VMA_GROWSDOWN_BIT) || vma_test_single_mask(vma, VMA_GROWSUP);
>
> I find these two functions when I am reading mm.h.
Yeah but we require vma_flags_can_grow() for code in mmap.c, the majority of
checks of this have only vma_flags_t to work with not a VMA :)
>
> > +}
> > +
> > +static inline bool vma_is_temporary_stack(const struct vm_area_struct *vma)
> > +{
> > + if (!vma_can_grow(vma))
> > return false;
> >
> > if ((vma->vm_flags & VM_STACK_INCOMPLETE_SETUP) ==
> > diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
> > index 5d7d0afd7765..6f5bcd7fbcd8 100644
> > --- a/tools/testing/vma/include/dup.h
> > +++ b/tools/testing/vma/include/dup.h
> > @@ -245,8 +245,10 @@ enum {
> > #define VM_STACK INIT_VM_FLAG(STACK)
> > #ifdef CONFIG_STACK_GROWS_UP
> > #define VM_STACK_EARLY INIT_VM_FLAG(STACK_EARLY)
> > +#define VMA_STACK_EARLY mk_vma_flags(VMA_STACK_EARLY_BIT)
> > #else
> > #define VM_STACK_EARLY VM_NONE
> > +#define VMA_STACK_EARLY EMPTY_VMA_FLAGS
> > #endif
> > #ifdef CONFIG_ARCH_HAS_PKEYS
> > #define VM_PKEY_SHIFT ((__force int)VMA_HIGH_ARCH_0_BIT)
> > @@ -315,6 +317,8 @@ enum {
> >
> > /* Bits set in the VMA until the stack is in its final location */
> > #define VM_STACK_INCOMPLETE_SETUP (VM_RAND_READ | VM_SEQ_READ | VM_STACK_EARLY)
> > +#define VMA_STACK_INCOMPLETE_SETUP append_vma_flags( \
> > + VMA_STACK_EARLY, VMA_RAND_READ_BIT, VMA_SEQ_READ_BIT)
> >
> > #define TASK_EXEC_BIT ((current->personality & READ_IMPLIES_EXEC) ? \
> > VM_EXEC_BIT : VM_READ_BIT)
>
> Why are VMA_STACK_EARLY and VMA_STACK_INCOMPLETE_SETUP added here but
> not in mm.h?
Yeah urgh oops my bad. It doesn't really break anything but I'll fix it if a
respin is needed...
>
>
> --
> Best Regards,
> Yan, Zi
>
Thanks, Lorenzo
^ permalink raw reply
* Re: [PATCH 08/13] mm: introduce vma_get_page_prot() and use it
From: Thomas Zimmermann @ 2026-06-30 7:57 UTC (permalink / raw)
To: Lorenzo Stoakes, Andrew Morton
Cc: Thomas Bogendoerfer, Madhavan Srinivasan, Michael Ellerman,
Maarten Lankhorst, Maxime Ripard, David Airlie, Simona Vetter,
Lucas Stach, Inki Dae, Seung-Woo Kim, Kyungmin Park,
Krzysztof Kozlowski, Peter Griffin, Jani Nikula, Joonas Lahtinen,
Rodrigo Vivi, Tvrtko Ursulin, Rob Clark, Dmitry Baryshkov,
Lyude Paul, Danilo Krummrich, Tomi Valkeinen, Sandy Huang,
Heiko Stübner, Andy Yan, Thierry Reding, Mikko Perttunen,
Jonathan Hunter, Gerd Hoffmann, Dmitry Osipenko, Zack Rusin,
Matthew Brost, Thomas Hellstrom, Oleksandr Andrushchenko,
Helge Deller, Benjamin LaHaise, Alexander Viro, Christian Brauner,
Muchun Song, Oscar Salvador, David Hildenbrand, Zi Yan,
Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts, Dev Jain,
Barry Song, Lance Yang, Hugh Dickins, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Jann Horn,
Pedro Falcato, Kees Cook, Jaroslav Kysela, Takashi Iwai,
linux-mips, linux-kernel, linuxppc-dev, dri-devel, etnaviv,
linux-arm-kernel, linux-samsung-soc, intel-gfx, linux-arm-msm,
freedreno, nouveau, linux-rockchip, linux-tegra, virtualization,
intel-xe, xen-devel, linux-fbdev, linux-aio, linux-fsdevel,
linux-mm, linux-sound
In-Reply-To: <3bb8bdc4788230c33102166d56cbc5abfad9d4cb.1782760670.git.ljs@kernel.org>
Am 29.06.26 um 21:25 schrieb Lorenzo Stoakes:
> There's a large number of vm_get_page_prot(vma->vm_flags) invocations. Make
> life easier by introducing vma_get_page_prot() parameterised by the VMA.
>
> This also makes converting vm_get_page_prot() to vma_flags_t easier.
>
> Also update the userland VMA tests to reflect the change.
>
> No functional change intended.
>
> Signed-off-by: Lorenzo Stoakes <ljs@kernel.org>
For the DRM changes:
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
> ---
> drivers/gpu/drm/drm_gem.c | 2 +-
> drivers/gpu/drm/drm_gem_dma_helper.c | 2 +-
> drivers/gpu/drm/drm_gem_shmem_helper.c | 2 +-
> drivers/gpu/drm/etnaviv/etnaviv_gem.c | 2 +-
> drivers/gpu/drm/exynos/exynos_drm_gem.c | 6 +++---
> drivers/gpu/drm/i915/gem/i915_gem_mman.c | 12 ++++++------
> drivers/gpu/drm/msm/msm_gem.c | 2 +-
> drivers/gpu/drm/nouveau/nouveau_gem.c | 2 +-
> drivers/gpu/drm/omapdrm/omap_fbdev.c | 2 +-
> drivers/gpu/drm/omapdrm/omap_gem.c | 6 +++---
> drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 2 +-
> drivers/gpu/drm/tegra/gem.c | 2 +-
> drivers/gpu/drm/virtio/virtgpu_vram.c | 2 +-
> drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c | 2 +-
> drivers/gpu/drm/xe/xe_device.c | 2 +-
> drivers/gpu/drm/xe/xe_mmio_gem.c | 2 +-
> drivers/gpu/drm/xen/xen_drm_front_gem.c | 2 +-
> drivers/video/fbdev/core/fb_io_fops.c | 2 +-
> include/linux/mm.h | 10 +++++++++-
> mm/vma.c | 2 +-
> mm/vma_exec.c | 2 +-
> sound/core/memalloc.c | 2 +-
> tools/testing/vma/include/dup.h | 4 ++++
> 23 files changed, 43 insertions(+), 31 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
> index e3ed684ddcf2..32a05d889b9a 100644
> --- a/drivers/gpu/drm/drm_gem.c
> +++ b/drivers/gpu/drm/drm_gem.c
> @@ -1252,7 +1252,7 @@ int drm_gem_mmap_obj(struct drm_gem_object *obj, unsigned long obj_size,
> }
>
> vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> - vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_writecombine(vma_get_page_prot(vma));
> vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
> }
>
> diff --git a/drivers/gpu/drm/drm_gem_dma_helper.c b/drivers/gpu/drm/drm_gem_dma_helper.c
> index 1c00a71ab3c9..7d9612075d31 100644
> --- a/drivers/gpu/drm/drm_gem_dma_helper.c
> +++ b/drivers/gpu/drm/drm_gem_dma_helper.c
> @@ -540,7 +540,7 @@ int drm_gem_dma_mmap(struct drm_gem_dma_object *dma_obj, struct vm_area_struct *
> vm_flags_mod(vma, VM_DONTDUMP | VM_DONTEXPAND, VM_PFNMAP);
>
> if (dma_obj->map_noncoherent) {
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
>
> ret = dma_mmap_pages(drm_dev_dma_dev(dma_obj->base.dev),
> vma, vma->vm_end - vma->vm_start,
> diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
> index c989459eb215..06d019d51d3e 100644
> --- a/drivers/gpu/drm/drm_gem_shmem_helper.c
> +++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
> @@ -764,7 +764,7 @@ int drm_gem_shmem_mmap(struct drm_gem_shmem_object *shmem, struct vm_area_struct
> return ret;
>
> vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> if (shmem->map_wc)
> vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
>
> diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gem.c b/drivers/gpu/drm/etnaviv/etnaviv_gem.c
> index 2e4d6d117ee2..f9c8b7b2bfc7 100644
> --- a/drivers/gpu/drm/etnaviv/etnaviv_gem.c
> +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem.c
> @@ -133,7 +133,7 @@ static int etnaviv_gem_mmap_obj(struct etnaviv_gem_object *etnaviv_obj,
>
> vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
>
> - vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vm_page_prot = vma_get_page_prot(vma);
>
> if (etnaviv_obj->flags & ETNA_BO_WC) {
> vma->vm_page_prot = pgprot_writecombine(vm_page_prot);
> diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c
> index 9a6270f3dca6..0208c9259572 100644
> --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c
> +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c
> @@ -377,13 +377,13 @@ static int exynos_drm_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct
>
> /* non-cachable as default. */
> if (exynos_gem->flags & EXYNOS_BO_CACHABLE)
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> else if (exynos_gem->flags & EXYNOS_BO_WC)
> vma->vm_page_prot =
> - pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + pgprot_writecombine(vma_get_page_prot(vma));
> else
> vma->vm_page_prot =
> - pgprot_noncached(vm_get_page_prot(vma->vm_flags));
> + pgprot_noncached(vma_get_page_prot(vma));
>
> ret = exynos_drm_gem_mmap_buffer(exynos_gem, vma);
> if (ret)
> diff --git a/drivers/gpu/drm/i915/gem/i915_gem_mman.c b/drivers/gpu/drm/i915/gem/i915_gem_mman.c
> index 0644f85c6c8e..9ca90c1bb5b4 100644
> --- a/drivers/gpu/drm/i915/gem/i915_gem_mman.c
> +++ b/drivers/gpu/drm/i915/gem/i915_gem_mman.c
> @@ -112,7 +112,7 @@ i915_gem_mmap_ioctl(struct drm_device *dev, void *data,
> vma = find_vma(mm, addr);
> if (vma && __vma_matches(vma, obj->base.filp, addr, args->size))
> vma->vm_page_prot =
> - pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + pgprot_writecombine(vma_get_page_prot(vma));
> else
> addr = -ENOMEM;
> mmap_write_unlock(mm);
> @@ -1024,7 +1024,7 @@ i915_gem_object_mmap(struct drm_i915_gem_object *obj,
> fput(anon);
>
> if (obj->ops->mmap_ops) {
> - vma->vm_page_prot = pgprot_decrypted(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_decrypted(vma_get_page_prot(vma));
> vma->vm_ops = obj->ops->mmap_ops;
> vma->vm_private_data = obj->base.vma_node.driver_private;
> return 0;
> @@ -1035,7 +1035,7 @@ i915_gem_object_mmap(struct drm_i915_gem_object *obj,
> switch (mmo->mmap_type) {
> case I915_MMAP_TYPE_WC:
> vma->vm_page_prot =
> - pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + pgprot_writecombine(vma_get_page_prot(vma));
> vma->vm_ops = &vm_ops_cpu;
> break;
>
> @@ -1043,19 +1043,19 @@ i915_gem_object_mmap(struct drm_i915_gem_object *obj,
> GEM_WARN_ON(1);
> fallthrough;
> case I915_MMAP_TYPE_WB:
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> vma->vm_ops = &vm_ops_cpu;
> break;
>
> case I915_MMAP_TYPE_UC:
> vma->vm_page_prot =
> - pgprot_noncached(vm_get_page_prot(vma->vm_flags));
> + pgprot_noncached(vma_get_page_prot(vma));
> vma->vm_ops = &vm_ops_cpu;
> break;
>
> case I915_MMAP_TYPE_GTT:
> vma->vm_page_prot =
> - pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + pgprot_writecombine(vma_get_page_prot(vma));
> vma->vm_ops = &vm_ops_gtt;
> break;
> }
> diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c
> index cbf723a5d86f..6a78e242de7c 100644
> --- a/drivers/gpu/drm/msm/msm_gem.c
> +++ b/drivers/gpu/drm/msm/msm_gem.c
> @@ -1125,7 +1125,7 @@ static int msm_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct
> struct msm_gem_object *msm_obj = to_msm_bo(obj);
>
> vm_flags_set(vma, VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP);
> - vma->vm_page_prot = msm_gem_pgprot(msm_obj, vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = msm_gem_pgprot(msm_obj, vma_get_page_prot(vma));
>
> return 0;
> }
> diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
> index 20dba02d6175..9a6ee2e880c0 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_gem.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
> @@ -55,7 +55,7 @@ static vm_fault_t nouveau_ttm_fault(struct vm_fault *vmf)
> goto error_unlock;
>
> nouveau_bo_del_io_reserve_lru(bo);
> - prot = vm_get_page_prot(vma->vm_flags);
> + prot = vma_get_page_prot(vma);
> ret = ttm_bo_vm_fault_reserved(vmf, prot, TTM_BO_VM_NUM_PREFAULT);
> nouveau_bo_add_io_reserve_lru(bo);
> if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
> diff --git a/drivers/gpu/drm/omapdrm/omap_fbdev.c b/drivers/gpu/drm/omapdrm/omap_fbdev.c
> index ca3fb186bf19..4881777642d2 100644
> --- a/drivers/gpu/drm/omapdrm/omap_fbdev.c
> +++ b/drivers/gpu/drm/omapdrm/omap_fbdev.c
> @@ -84,7 +84,7 @@ static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, struct fb_info
>
> static int omap_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
> {
> - vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_writecombine(vma_get_page_prot(vma));
>
> return fb_deferred_io_mmap(info, vma);
> }
> diff --git a/drivers/gpu/drm/omapdrm/omap_gem.c b/drivers/gpu/drm/omapdrm/omap_gem.c
> index 00404fb6c29a..fb0e6f556b31 100644
> --- a/drivers/gpu/drm/omapdrm/omap_gem.c
> +++ b/drivers/gpu/drm/omapdrm/omap_gem.c
> @@ -538,9 +538,9 @@ static int omap_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struc
> vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP | VM_IO | VM_MIXEDMAP);
>
> if (omap_obj->flags & OMAP_BO_WC) {
> - vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_writecombine(vma_get_page_prot(vma));
> } else if (omap_obj->flags & OMAP_BO_UNCACHED) {
> - vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_noncached(vma_get_page_prot(vma));
> } else {
> /*
> * We do have some private objects, at least for scanout buffers
> @@ -558,7 +558,7 @@ static int omap_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struc
> vma->vm_pgoff -= drm_vma_node_start(&obj->vma_node);
> vma_set_file(vma, obj->filp);
>
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> }
>
> vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
> index b188539dca0b..9a1dc9f12072 100644
> --- a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c
> @@ -255,7 +255,7 @@ static int rockchip_drm_gem_object_mmap(struct drm_gem_object *obj,
> */
> vm_flags_mod(vma, VM_IO | VM_DONTEXPAND | VM_DONTDUMP, VM_PFNMAP);
>
> - vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_writecombine(vma_get_page_prot(vma));
> vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
>
> if (rk_obj->pages)
> diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c
> index 1d8d27a5ea89..f76af733ea79 100644
> --- a/drivers/gpu/drm/tegra/gem.c
> +++ b/drivers/gpu/drm/tegra/gem.c
> @@ -602,7 +602,7 @@ int __tegra_gem_mmap(struct drm_gem_object *gem, struct vm_area_struct *vma)
>
> vma->vm_pgoff = vm_pgoff;
> } else {
> - pgprot_t prot = vm_get_page_prot(vma->vm_flags);
> + pgprot_t prot = vma_get_page_prot(vma);
>
> vm_flags_mod(vma, VM_MIXEDMAP, VM_PFNMAP);
>
> diff --git a/drivers/gpu/drm/virtio/virtgpu_vram.c b/drivers/gpu/drm/virtio/virtgpu_vram.c
> index 4ae3cbc35dd3..544a6abddbc8 100644
> --- a/drivers/gpu/drm/virtio/virtgpu_vram.c
> +++ b/drivers/gpu/drm/virtio/virtgpu_vram.c
> @@ -55,7 +55,7 @@ static int virtio_gpu_vram_mmap(struct drm_gem_object *obj,
>
> vma->vm_pgoff -= drm_vma_node_start(&obj->vma_node);
> vm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND);
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
> vma->vm_ops = &virtio_gpu_vram_vm_ops;
>
> diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> index 45561bc1c9ef..a9fd4015a0ca 100644
> --- a/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_page_dirty.c
> @@ -481,7 +481,7 @@ vm_fault_t vmw_bo_vm_fault(struct vm_fault *vmf)
> if (vbo->dirty && vbo->dirty->method == VMW_BO_DIRTY_MKWRITE)
> prot = vm_get_page_prot(vma->vm_flags & ~VM_SHARED);
> else
> - prot = vm_get_page_prot(vma->vm_flags);
> + prot = vma_get_page_prot(vma);
>
> ret = ttm_bo_vm_fault_reserved(vmf, prot, num_prefault);
> if (ret == VM_FAULT_RETRY && !(vmf->flags & FAULT_FLAG_RETRY_NOWAIT))
> diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
> index d224861b6f6f..758acaae85d3 100644
> --- a/drivers/gpu/drm/xe/xe_device.c
> +++ b/drivers/gpu/drm/xe/xe_device.c
> @@ -281,7 +281,7 @@ static vm_fault_t barrier_fault(struct vm_fault *vmf)
> pgprot_t prot;
> int idx;
>
> - prot = vm_get_page_prot(vma->vm_flags);
> + prot = vma_get_page_prot(vma);
>
> if (drm_dev_enter(dev, &idx)) {
> unsigned long pfn;
> diff --git a/drivers/gpu/drm/xe/xe_mmio_gem.c b/drivers/gpu/drm/xe/xe_mmio_gem.c
> index 8c803ef233cc..3741ae60f532 100644
> --- a/drivers/gpu/drm/xe/xe_mmio_gem.c
> +++ b/drivers/gpu/drm/xe/xe_mmio_gem.c
> @@ -149,7 +149,7 @@ static int xe_mmio_gem_mmap(struct drm_gem_object *base, struct vm_area_struct *
>
> /* Set vm_pgoff (used as a fake buffer offset by DRM) to 0 */
> vma->vm_pgoff = 0;
> - vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags));
> + vma->vm_page_prot = pgprot_noncached(vma_get_page_prot(vma));
> vm_flags_set(vma, VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP |
> VM_DONTCOPY | VM_NORESERVE);
>
> diff --git a/drivers/gpu/drm/xen/xen_drm_front_gem.c b/drivers/gpu/drm/xen/xen_drm_front_gem.c
> index eec4c1da3f9e..dd158443f55f 100644
> --- a/drivers/gpu/drm/xen/xen_drm_front_gem.c
> +++ b/drivers/gpu/drm/xen/xen_drm_front_gem.c
> @@ -80,7 +80,7 @@ static int xen_drm_front_gem_object_mmap(struct drm_gem_object *gem_obj,
> * which is mapped as Normal Inner Write-Back Outer Write-Back
> * Inner-Shareable.
> */
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
>
> /*
> * vm_operations_struct.fault handler will be called if CPU access
> diff --git a/drivers/video/fbdev/core/fb_io_fops.c b/drivers/video/fbdev/core/fb_io_fops.c
> index 6ab60fcd0050..6d0a8c8e141a 100644
> --- a/drivers/video/fbdev/core/fb_io_fops.c
> +++ b/drivers/video/fbdev/core/fb_io_fops.c
> @@ -161,7 +161,7 @@ int fb_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
> len = info->fix.mmio_len;
> }
>
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> vma->vm_page_prot = pgprot_framebuffer(vma->vm_page_prot, vma->vm_start,
> vma->vm_end, start);
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index c8336f68d7bb..b55790c75038 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -4575,6 +4575,10 @@ static inline pgprot_t vma_flags_to_page_prot(vma_flags_t vma_flags)
> return vm_get_page_prot(vm_flags);
> }
>
> +static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma)
> +{
> + return vma_flags_to_page_prot(vma->flags);
> +}
> void vma_set_page_prot(struct vm_area_struct *vma);
> #else
> static inline pgprot_t vm_get_page_prot(vm_flags_t vm_flags)
> @@ -4585,9 +4589,13 @@ static inline pgprot_t vma_flags_to_page_prot(vma_flags_t vma_flags)
> {
> return __pgprot(0);
> }
> +static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma)
> +{
> + return __pgprot(0);
> +}
> static inline void vma_set_page_prot(struct vm_area_struct *vma)
> {
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
> }
> #endif
>
> diff --git a/mm/vma.c b/mm/vma.c
> index fcdd2ac3ac68..b81c05e67a61 100644
> --- a/mm/vma.c
> +++ b/mm/vma.c
> @@ -3429,7 +3429,7 @@ struct vm_area_struct *__install_special_mapping(
> if (pgtable_supports_soft_dirty())
> vm_flags |= VM_SOFTDIRTY;
> vm_flags_init(vma, vm_flags & ~VM_LOCKED_MASK);
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
>
> vma->vm_ops = ops;
> vma->vm_private_data = priv;
> diff --git a/mm/vma_exec.c b/mm/vma_exec.c
> index c0f7ba2cfb27..b01c4964f2c9 100644
> --- a/mm/vma_exec.c
> +++ b/mm/vma_exec.c
> @@ -146,7 +146,7 @@ int create_init_stack_vma(struct mm_struct *mm, struct vm_area_struct **vmap,
> if (pgtable_supports_soft_dirty())
> flags |= VM_SOFTDIRTY;
> vm_flags_init(vma, flags);
> - vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
> + vma->vm_page_prot = vma_get_page_prot(vma);
>
> err = insert_vm_struct(mm, vma);
> if (err)
> diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c
> index 9320671dfcc8..5bc7e586b430 100644
> --- a/sound/core/memalloc.c
> +++ b/sound/core/memalloc.c
> @@ -851,7 +851,7 @@ static void snd_dma_noncoherent_free(struct snd_dma_buffer *dmab)
> static int snd_dma_noncoherent_mmap(struct snd_dma_buffer *dmab,
> struct vm_area_struct *area)
> {
> - area->vm_page_prot = vm_get_page_prot(area->vm_flags);
> + area->vm_page_prot = vma_get_page_prot(area);
> return dma_mmap_pages(dmab->dev.dev, area,
> area->vm_end - area->vm_start,
> virt_to_page(dmab->area));
> diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
> index 1ffac38a5377..d86bef8cb37f 100644
> --- a/tools/testing/vma/include/dup.h
> +++ b/tools/testing/vma/include/dup.h
> @@ -1577,3 +1577,7 @@ static inline pgoff_t linear_page_index(const struct vm_area_struct *vma,
> pgoff += vma_start_pgoff(vma);
> return pgoff;
> }
> +static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma)
> +{
> + return vma_flags_to_page_prot(vma->flags);
> +}
--
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)
^ permalink raw reply
* [PATCH] drm/virtio: defer hotplug event from dequeue worker to avoid deadlock
From: Ryosuke Yasuoka @ 2026-06-30 9:16 UTC (permalink / raw)
To: David Airlie, Gerd Hoffmann, Dmitry Osipenko, Gurchetan Singh,
Chia-I Wu, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
Simona Vetter, Dmitry Baryshkov, Javier Martinez Canillas
Cc: dri-devel, virtualization, linux-kernel, Ryosuke Yasuoka
A probe-time deadlock can occur between the dequeue worker and
drm_client_register(). During probe, drm_client_register() holds
clientlist_mutex and calls the fbdev hotplug callback, which triggers an
atomic commit that ends up sleeping in virtio_gpu_queue_ctrl_sgs()
waiting for virtqueue space. The dequeue worker that would free that
space calls virtio_gpu_cmd_get_display_info_cb(), which invokes
drm_kms_helper_hotplug_event() -> drm_client_dev_hotplug(), attempting
to acquire the same clientlist_mutex. Since wake_up() is only called
after the resp_cb loop, the probe thread is never woken and both threads
deadlock.
Fix this by deferring the hotplug notification from
virtio_gpu_cmd_get_display_info_cb() to a separate work item. The
display data (outputs[i].info) is still updated synchronously in the
callback, and the deferred work only triggers a re-probe notification to
DRM clients.
Fixes: 27655b9bb9f0 ("drm/client: Send hotplug event after registering a client")
Closes: https://syzkaller.appspot.com/bug?id=d6dd6f86d3aaf7eebe7406e45c1c6e549453f224
Closes: https://syzkaller.appspot.com/bug?id=908bd910da5dd79b88de4cf7baf376cc873a922e
Signed-off-by: Ryosuke Yasuoka <ryasuoka@redhat.com>
---
drivers/gpu/drm/virtio/virtgpu_drv.h | 3 +++
drivers/gpu/drm/virtio/virtgpu_kms.c | 3 +++
drivers/gpu/drm/virtio/virtgpu_vq.c | 12 ++++++++++--
3 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h b/drivers/gpu/drm/virtio/virtgpu_drv.h
index 7449907754a4..27ffa4697ae9 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -264,6 +264,8 @@ struct virtio_gpu_device {
struct work_struct config_changed_work;
+ struct work_struct hotplug_work;
+
struct work_struct obj_free_work;
spinlock_t obj_free_lock;
struct list_head obj_free_list;
@@ -350,6 +352,7 @@ void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
uint32_t x, uint32_t y,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
+void virtio_gpu_hotplug_work_func(struct work_struct *work);
void virtio_gpu_panic_cmd_resource_flush(struct virtio_gpu_device *vgdev,
uint32_t resource_id,
uint32_t x, uint32_t y,
diff --git a/drivers/gpu/drm/virtio/virtgpu_kms.c b/drivers/gpu/drm/virtio/virtgpu_kms.c
index cfde9f573df6..cfb532ba43a4 100644
--- a/drivers/gpu/drm/virtio/virtgpu_kms.c
+++ b/drivers/gpu/drm/virtio/virtgpu_kms.c
@@ -154,6 +154,8 @@ int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev)
INIT_WORK(&vgdev->config_changed_work,
virtio_gpu_config_changed_work_func);
+ INIT_WORK(&vgdev->hotplug_work, virtio_gpu_hotplug_work_func);
+
INIT_WORK(&vgdev->obj_free_work,
virtio_gpu_array_put_free_work);
INIT_LIST_HEAD(&vgdev->obj_free_list);
@@ -293,6 +295,7 @@ void virtio_gpu_deinit(struct drm_device *dev)
flush_work(&vgdev->obj_free_work);
flush_work(&vgdev->ctrlq.dequeue_work);
flush_work(&vgdev->cursorq.dequeue_work);
+ flush_work(&vgdev->hotplug_work);
flush_work(&vgdev->config_changed_work);
virtio_reset_device(vgdev->vdev);
vgdev->vdev->config->del_vqs(vgdev->vdev);
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c b/drivers/gpu/drm/virtio/virtgpu_vq.c
index 67865810a2e7..084d98f5dc7b 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -816,6 +816,15 @@ virtio_gpu_cmd_resource_detach_backing(struct virtio_gpu_device *vgdev,
virtio_gpu_queue_fenced_ctrl_buffer(vgdev, vbuf, fence);
}
+void virtio_gpu_hotplug_work_func(struct work_struct *work)
+{
+ struct virtio_gpu_device *vgdev =
+ container_of(work, struct virtio_gpu_device, hotplug_work);
+
+ if (!drm_helper_hpd_irq_event(vgdev->ddev))
+ drm_kms_helper_hotplug_event(vgdev->ddev);
+}
+
static void virtio_gpu_cmd_get_display_info_cb(struct virtio_gpu_device *vgdev,
struct virtio_gpu_vbuffer *vbuf)
{
@@ -841,8 +850,7 @@ static void virtio_gpu_cmd_get_display_info_cb(struct virtio_gpu_device *vgdev,
spin_unlock(&vgdev->display_info_lock);
wake_up(&vgdev->resp_wq);
- if (!drm_helper_hpd_irq_event(vgdev->ddev))
- drm_kms_helper_hotplug_event(vgdev->ddev);
+ schedule_work(&vgdev->hotplug_work);
}
static void virtio_gpu_cmd_get_capset_info_cb(struct virtio_gpu_device *vgdev,
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260619-virtiogpu_syzbot-bdab508ffcd5
Best regards,
--
Ryosuke Yasuoka <ryasuoka@redhat.com>
^ permalink raw reply related
* [PATCH v7 00/12] nvdimm: virtio_pmem: fix flush/request failure paths
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel
Hi,
This series started as a virtio-pmem request lifetime and broken virtqueue
fix, but the rerolls have picked up several related flush-path fixes found
during local testing and review. Since the series is now broader than the
original lifetime bug, this cover letter calls out where the patches came
from.
The nvdimm flush helper maps provider flush failures to -EIO. That should
remain the default for provider/backend failures because host-side errors are
still best reported as generic I/O errors to the guest. However, virtio-pmem
may also fail a guest-local flush request allocation with -ENOMEM before any
request is submitted to the host. Reporting that resource failure as -EIO
makes memory pressure look like media failure.
The raw failure seen in the local mkfs sanity test was:
wipefs: /dev/pmem0: cannot flush modified buffers: Input/output error
mkfs.ext4: Input/output error while writing out and closing file system
nd_region region0: dbg: nvdimm_flush rc=-5
Patch 1 comes from that local failure, with the error policy narrowed after
Pankaj pointed out that host/backend provider errors should not all be exposed
directly to the guest. It now preserves only -ENOMEM and keeps other provider
flush failures mapped to -EIO.
Patches 2 and 3 come from review of the pmem flush path. Patch 2 keeps a
failed REQ_PREFLUSH from being overwritten after data copy, and patch 3 is the
dataless-bio guard added after the Sashiko review. Patch 4 comes from the
local child flush bio allocation failure, but v7 reworks the v6 synchronous
FUA approach after Pankaj noted that the old child flush bio path completed
asynchronously. This version removes the child bio while keeping parent bio
completion asynchronous: the provider returns NVDIMM_FLUSH_ASYNC, queues
ordered WQ_MEM_RECLAIM work, and completes the parent bio after
virtio_pmem_flush() finishes. Patch 5 is the remaining allocation-policy
follow-up for the actual virtio-pmem flush request object, not for a child
bio.
Patches 6 and 7 are the older waiter fixes. Patch 6 wakes one -ENOSPC waiter
for each reclaimed used buffer, and patch 7 makes the wait flags explicit
READ_ONCE()/WRITE_ONCE() accesses. Pankaj asked for those changes to be split
across patches, and patch 7 carries his Acked-by.
Patch 8 is the original KASAN use-after-free fix for the request token
lifetime. Patches 9 and 10 are follow-up hardening in the same completion
path: order response publication before the submitter reads resp.ret, and keep
the DMA_FROM_DEVICE response buffer away from CPU-owned request fields. Patch
11 addresses the broken virtqueue / notify failure path reported by LKP and
reproduced locally with fault injection. It also serializes async parent-bio
flush work against broken-state publication, so remove/freeze cannot drain the
workqueue before a racing FUA bio queues new completion work. Patch 12 handles
teardown: it drains requests across freeze/remove and also addresses the
Sashiko-reported req_vq-after-free/NULL-deref class by clearing req_vq after
del_vqs() and making the drain helper tolerate a NULL queue. It also stops the
submit path from checking req_vq after the broken state is visible.
The original repros were on QEMU x86_64 with a virtio-pmem device exported
as /dev/pmem0. For this v7 reroll, the series applies to v7.1-rc7.
Thanks,
Li Chen
Changelog:
v6->v7:
- Address Pankaj's feedback on nvdimm_flush() error policy.
- Preserve only -ENOMEM from provider flush callbacks and continue to map
other provider/backend failures to -EIO.
- Address Pankaj's feedback on the FUA flush behavior: replace the v6
synchronous FUA path with provider-owned asynchronous parent bio completion.
- Add NVDIMM_FLUSH_ASYNC and use ordered WQ_MEM_RECLAIM work to run
virtio_pmem_flush() and complete the parent bio after the host flush.
- Keep GFP_NOIO for the virtio-pmem request allocation, but no longer describe
it as a child bio allocation fix.
- Add Pankaj's Acked-by on the READ_ONCE()/WRITE_ONCE() patch.
- Serialize async parent-bio flush work against broken-state publication in
the broken-virtqueue patch, so remove/freeze cannot drain the workqueue
before a racing FUA bio queues new completion work.
- Fold the Sashiko-reported req_vq NULL-deref fix into the freeze/remove
drain patch.
- Update commit messages and this cover letter to describe patch origins.
v5->v6:
- Address Sashiko review feedback:
- Add a data-loop guard for dataless bios in pmem_submit_bio().
- Replace the child flush bio allocation with synchronous FUA flushing.
- Keep GFP_NOIO only for the virtio-pmem request allocation.
- Publish request completion with release/acquire ordering.
- Isolate the DMA_FROM_DEVICE response buffer from CPU-owned fields.
- Wake the in-flight host-completion waiter when marking the queue broken.
- Clear req_vq after del_vqs() and make drain tolerate a NULL queue.
v4->v5:
- Address review feedback about REQ_PREFLUSH ordering and active virtqueue
detach.
- Add 2/8 so a failed REQ_PREFLUSH fails the bio before any data copy, and
make REQ_PREFLUSH use a synchronous provider flush instead of a deferred
child bio.
- Rework broken-queue handling so runtime failure marking only stops new
submissions and wakes local -ENOSPC waiters; used/unused token draining is
done after device reset in remove() and freeze().
- Remove the broken-state shortcut from the host-completion wait so the
submitter never reads an uninitialized response field.
- Keep the raw broken-virtqueue dmesg in 7/8 while updating the teardown
rationale.
- Renumber the old virtio-pmem fixes after the new pmem PREFLUSH patch.
v3->v4:
- Rebased the series onto v7.1-rc7 so it applies cleanly to Linux 7.1-rc7.
- Update the allocation site in 6/7 from kmalloc(sizeof(*req_data),
GFP_KERNEL) to kmalloc_obj(*req_data) to match current nvdimm code.
- Add 1/7 to preserve provider flush callback errors in nvdimm_flush().
- Include the GFP_NOIO child flush bio allocation fix as 2/7.
- Renumber the old request lifetime and broken virtqueue fixes after the two
new flush error patches.
v2->v3:
- Split patch 1 as suggested by Pankaj Gupta: keep the waiter wakeup
ordering change in 1/5 and move READ_ONCE()/WRITE_ONCE() updates to
2/5 (no functional change intended).
- Add log report to commit msg.
- Fold the export fix into 4/5 to keep the series bisectable when
CONFIG_VIRTIO_PMEM=m.
v1->v2:
- Add the export patch to fix compile issue.
Links:
v6: https://lore.kernel.org/all/20260621130246.2973254-1-me@linux.beauty/
v5: https://lore.kernel.org/all/20260617122442.2118957-1-me@linux.beauty/
v4: https://lore.kernel.org/all/20260609120726.1714780-1-me@linux.beauty/
v3: https://lore.kernel.org/all/20260226025712.2236279-1-me@linux.beauty/#t
v2: https://lore.kernel.org/all/20251225042915.334117-1-me@linux.beauty/
v1: https://www.spinics.net/lists/kernel/msg5974818.html
Li Chen (12):
nvdimm: preserve flush callback -ENOMEM
nvdimm: pmem: keep PREFLUSH before data writes
nvdimm: pmem: guard data loop for dataless bios
nvdimm: virtio_pmem: stop allocating child flush bio
nvdimm: virtio_pmem: use GFP_NOIO for flush requests
nvdimm: virtio_pmem: always wake -ENOSPC waiters
nvdimm: virtio_pmem: use READ_ONCE()/WRITE_ONCE() for wait flags
nvdimm: virtio_pmem: refcount requests for token lifetime
nvdimm: virtio_pmem: publish done with release/acquire
nvdimm: virtio_pmem: isolate DMA request buffers
nvdimm: virtio_pmem: converge broken virtqueue to -EIO
nvdimm: virtio_pmem: drain requests in freeze
drivers/nvdimm/nd_virtio.c | 265 +++++++++++++++++++++++++++++------
drivers/nvdimm/pmem.c | 51 ++++---
drivers/nvdimm/region_devs.c | 5 +-
drivers/nvdimm/virtio_pmem.c | 65 ++++++++-
drivers/nvdimm/virtio_pmem.h | 22 ++-
include/linux/libnvdimm.h | 9 ++
6 files changed, 343 insertions(+), 74 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH v7 01/12] nvdimm: preserve flush callback -ENOMEM
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
nvdimm_flush() maps provider flush failures to -EIO. Keep that default
because provider callbacks can report host-side or backend failures that
should remain generic I/O errors to the guest.
Guest-side allocation failures should not be reported as I/O errors. In the
virtio-pmem path, the flush request allocation can fail with -ENOMEM before
any request is submitted to the host. Mapping that to -EIO makes resource
pressure look like media failure.
Preserve -ENOMEM from provider callbacks and continue to map other non-zero
provider failures to -EIO. The generic flush path still returns 0, and
pmem_submit_bio() already converts errno values to block status for bio
completion.
Suggested-by: Pankaj Gupta <pankaj.gupta.linux@gmail.com>
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v7:
- Preserve only -ENOMEM and keep other provider/backend failures mapped to
-EIO, per Pankaj's feedback.
v3->v4:
- New patch.
drivers/nvdimm/region_devs.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c
index e35c2e18518f0..7cd2c2f0d3121 100644
--- a/drivers/nvdimm/region_devs.c
+++ b/drivers/nvdimm/region_devs.c
@@ -1115,7 +1115,8 @@ int nvdimm_flush(struct nd_region *nd_region, struct bio *bio)
if (!nd_region->flush)
rc = generic_nvdimm_flush(nd_region);
else {
- if (nd_region->flush(nd_region, bio))
+ rc = nd_region->flush(nd_region, bio);
+ if (rc && rc != -ENOMEM)
rc = -EIO;
}
--
2.52.0
^ permalink raw reply related
* [PATCH v7 02/12] nvdimm: pmem: keep PREFLUSH before data writes
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
pmem_submit_bio() records a REQ_PREFLUSH error, but continues to copy the
bio data and can later overwrite the error with a successful REQ_FUA flush.
That lets data writes run after a failed preflush and can complete the bio
successfully despite the failed ordering barrier.
Run the REQ_PREFLUSH flush synchronously before touching the bio data and
complete the bio with the flush error if it fails. Keep asynchronous flush
chaining for REQ_FUA. At that point, data copy has completed and the parent
bio can wait for the chained flush bio.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v5:
- New patch.
drivers/nvdimm/pmem.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
index 92c67fbbc1c85..05d3de33e2706 100644
--- a/drivers/nvdimm/pmem.c
+++ b/drivers/nvdimm/pmem.c
@@ -208,8 +208,14 @@ static void pmem_submit_bio(struct bio *bio)
struct pmem_device *pmem = bio->bi_bdev->bd_disk->private_data;
struct nd_region *nd_region = to_region(pmem);
- if (bio->bi_opf & REQ_PREFLUSH)
- ret = nvdimm_flush(nd_region, bio);
+ if (bio->bi_opf & REQ_PREFLUSH) {
+ ret = nvdimm_flush(nd_region, NULL);
+ if (ret) {
+ bio->bi_status = errno_to_blk_status(ret);
+ bio_endio(bio);
+ return;
+ }
+ }
do_acct = blk_queue_io_stat(bio->bi_bdev->bd_disk->queue);
if (do_acct)
@@ -229,7 +235,7 @@ static void pmem_submit_bio(struct bio *bio)
if (do_acct)
bio_end_io_acct(bio, start);
- if (bio->bi_opf & REQ_FUA)
+ if ((bio->bi_opf & REQ_FUA) && !bio->bi_status)
ret = nvdimm_flush(nd_region, bio);
if (ret)
--
2.52.0
^ permalink raw reply related
* [PATCH v7 03/12] nvdimm: pmem: guard data loop for dataless bios
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
pmem_submit_bio() handles flush-only bios before and after the data
loop. Keep dataless bios out of bio_for_each_segment() so the data path
only walks bios that actually carry bvec data.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v6:
- New patch.
drivers/nvdimm/pmem.c | 36 +++++++++++++++++++++---------------
1 file changed, 21 insertions(+), 15 deletions(-)
diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
index 05d3de33e2706..82ee1ddb3a445 100644
--- a/drivers/nvdimm/pmem.c
+++ b/drivers/nvdimm/pmem.c
@@ -217,23 +217,29 @@ static void pmem_submit_bio(struct bio *bio)
}
}
- do_acct = blk_queue_io_stat(bio->bi_bdev->bd_disk->queue);
- if (do_acct)
- start = bio_start_io_acct(bio);
- bio_for_each_segment(bvec, bio, iter) {
- if (op_is_write(bio_op(bio)))
- rc = pmem_do_write(pmem, bvec.bv_page, bvec.bv_offset,
- iter.bi_sector, bvec.bv_len);
- else
- rc = pmem_do_read(pmem, bvec.bv_page, bvec.bv_offset,
- iter.bi_sector, bvec.bv_len);
- if (rc) {
- bio->bi_status = rc;
- break;
+ if (bio_has_data(bio)) {
+ do_acct = blk_queue_io_stat(bio->bi_bdev->bd_disk->queue);
+ if (do_acct)
+ start = bio_start_io_acct(bio);
+ bio_for_each_segment(bvec, bio, iter) {
+ if (op_is_write(bio_op(bio)))
+ rc = pmem_do_write(pmem, bvec.bv_page,
+ bvec.bv_offset,
+ iter.bi_sector,
+ bvec.bv_len);
+ else
+ rc = pmem_do_read(pmem, bvec.bv_page,
+ bvec.bv_offset,
+ iter.bi_sector,
+ bvec.bv_len);
+ if (rc) {
+ bio->bi_status = rc;
+ break;
+ }
}
+ if (do_acct)
+ bio_end_io_acct(bio, start);
}
- if (do_acct)
- bio_end_io_acct(bio, start);
if ((bio->bi_opf & REQ_FUA) && !bio->bi_status)
ret = nvdimm_flush(nd_region, bio);
--
2.52.0
^ permalink raw reply related
* [PATCH v7 04/12] nvdimm: virtio_pmem: stop allocating child flush bio
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
pmem_submit_bio() passes the parent bio to nvdimm_flush() for
REQ_FUA. For virtio-pmem this makes async_pmem_flush() allocate
and submit a child PREFLUSH bio chained to the parent.
That child allocation is in the block submit path. Making it
blocking with GFP_NOIO can consume the same global bio mempool that
submit_bio() uses, while making it GFP_ATOMIC can fail under
pressure. A forced failure of the child allocation produced:
virtio_pmem: forcing child bio allocation failure for test
Buffer I/O error on dev pmem0, logical block 0, lost sync page write
EXT4-fs (pmem0): I/O error while writing superblock
EXT4-fs (pmem0): mount failed
Avoid the child bio without turning REQ_FUA into a synchronous
submit-path wait. Let provider flush callbacks return
NVDIMM_FLUSH_ASYNC after taking ownership of parent bio completion.
pmem_submit_bio() returns in that case, and virtio-pmem queues an
ordered WQ_MEM_RECLAIM work item that runs the existing host flush
path and completes the parent bio.
This keeps the asynchronous completion model of the child-bio path
while removing the child bio allocation from the submit path.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v7:
- Replace synchronous FUA flushing with provider-owned asynchronous parent bio
completion.
- Add NVDIMM_FLUSH_ASYNC and ordered WQ_MEM_RECLAIM flush work.
Changes in v6:
- Replace the child bio allocation fix with synchronous FUA flushing.
drivers/nvdimm/nd_virtio.c | 54 +++++++++++++++++++++++++-----------
drivers/nvdimm/pmem.c | 5 +++-
drivers/nvdimm/region_devs.c | 2 ++
drivers/nvdimm/virtio_pmem.c | 17 +++++++++++-
drivers/nvdimm/virtio_pmem.h | 4 +++
include/linux/libnvdimm.h | 9 ++++++
6 files changed, 73 insertions(+), 18 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index 4176046627beb..8e16b7780be1a 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -9,6 +9,12 @@
#include "virtio_pmem.h"
#include "nd.h"
+struct virtio_pmem_flush_work {
+ struct work_struct work;
+ struct nd_region *nd_region;
+ struct bio *bio;
+};
+
/* The interrupt handler */
void virtio_pmem_host_ack(struct virtqueue *vq)
{
@@ -107,30 +113,46 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
return err;
};
+static void virtio_pmem_flush_work(struct work_struct *work)
+{
+ struct virtio_pmem_flush_work *flush;
+ int err;
+
+ flush = container_of(work, struct virtio_pmem_flush_work, work);
+ err = virtio_pmem_flush(flush->nd_region);
+ if (err > 0)
+ err = -EIO;
+ if (err)
+ flush->bio->bi_status = errno_to_blk_status(err);
+ bio_endio(flush->bio);
+ kfree(flush);
+}
+
/* The asynchronous flush callback function */
int async_pmem_flush(struct nd_region *nd_region, struct bio *bio)
{
- /*
- * Create child bio for asynchronous flush and chain with
- * parent bio. Otherwise directly call nd_region flush.
- */
- if (bio && bio->bi_iter.bi_sector != -1) {
- struct bio *child = bio_alloc(bio->bi_bdev, 0,
- REQ_OP_WRITE | REQ_PREFLUSH,
- GFP_ATOMIC);
+ struct virtio_device *vdev = nd_region->provider_data;
+ struct virtio_pmem *vpmem = vdev->priv;
+ struct virtio_pmem_flush_work *flush;
+ int err;
- if (!child)
+ if (bio && bio->bi_iter.bi_sector != -1) {
+ flush = kmalloc_obj(*flush, GFP_NOIO);
+ if (!flush)
return -ENOMEM;
- bio_clone_blkg_association(child, bio);
- child->bi_iter.bi_sector = -1;
- bio_chain(child, bio);
- submit_bio(child);
- return 0;
+
+ INIT_WORK(&flush->work, virtio_pmem_flush_work);
+ flush->nd_region = nd_region;
+ flush->bio = bio;
+ queue_work(vpmem->flush_wq, &flush->work);
+ return NVDIMM_FLUSH_ASYNC;
}
- if (virtio_pmem_flush(nd_region))
+
+ err = virtio_pmem_flush(nd_region);
+ if (err > 0)
return -EIO;
- return 0;
+ return err;
};
EXPORT_SYMBOL_GPL(async_pmem_flush);
MODULE_DESCRIPTION("Virtio Persistent Memory Driver");
diff --git a/drivers/nvdimm/pmem.c b/drivers/nvdimm/pmem.c
index 82ee1ddb3a445..30a51c365ce8b 100644
--- a/drivers/nvdimm/pmem.c
+++ b/drivers/nvdimm/pmem.c
@@ -241,8 +241,11 @@ static void pmem_submit_bio(struct bio *bio)
bio_end_io_acct(bio, start);
}
- if ((bio->bi_opf & REQ_FUA) && !bio->bi_status)
+ if ((bio->bi_opf & REQ_FUA) && !bio->bi_status) {
ret = nvdimm_flush(nd_region, bio);
+ if (ret == NVDIMM_FLUSH_ASYNC)
+ return;
+ }
if (ret)
bio->bi_status = errno_to_blk_status(ret);
diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c
index 7cd2c2f0d3121..c540f1cff9250 100644
--- a/drivers/nvdimm/region_devs.c
+++ b/drivers/nvdimm/region_devs.c
@@ -1116,6 +1116,8 @@ int nvdimm_flush(struct nd_region *nd_region, struct bio *bio)
rc = generic_nvdimm_flush(nd_region);
else {
rc = nd_region->flush(nd_region, bio);
+ if (rc > 0)
+ return rc;
if (rc && rc != -ENOMEM)
rc = -EIO;
}
diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c
index 77b1966619059..9cf822a6c0c38 100644
--- a/drivers/nvdimm/virtio_pmem.c
+++ b/drivers/nvdimm/virtio_pmem.c
@@ -67,10 +67,17 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
mutex_init(&vpmem->flush_lock);
vpmem->vdev = vdev;
vdev->priv = vpmem;
+ vpmem->flush_wq = alloc_ordered_workqueue("virtio-pmem-flush",
+ WQ_MEM_RECLAIM);
+ if (!vpmem->flush_wq) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+
err = init_vq(vpmem);
if (err) {
dev_err(&vdev->dev, "failed to initialize virtio pmem vq's\n");
- goto out_err;
+ goto out_wq;
}
if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION)) {
@@ -131,6 +138,8 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
nvdimm_bus_unregister(vpmem->nvdimm_bus);
out_vq:
vdev->config->del_vqs(vdev);
+out_wq:
+ destroy_workqueue(vpmem->flush_wq);
out_err:
return err;
}
@@ -138,14 +147,20 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
static void virtio_pmem_remove(struct virtio_device *vdev)
{
struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(&vdev->dev);
+ struct virtio_pmem *vpmem = vdev->priv;
nvdimm_bus_unregister(nvdimm_bus);
+ drain_workqueue(vpmem->flush_wq);
vdev->config->del_vqs(vdev);
virtio_reset_device(vdev);
+ destroy_workqueue(vpmem->flush_wq);
}
static int virtio_pmem_freeze(struct virtio_device *vdev)
{
+ struct virtio_pmem *vpmem = vdev->priv;
+
+ drain_workqueue(vpmem->flush_wq);
vdev->config->del_vqs(vdev);
virtio_reset_device(vdev);
diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h
index f72cf17f9518f..e6dfc10ce0762 100644
--- a/drivers/nvdimm/virtio_pmem.h
+++ b/drivers/nvdimm/virtio_pmem.h
@@ -15,6 +15,7 @@
#include <linux/libnvdimm.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
+#include <linux/workqueue.h>
struct virtio_pmem_request {
struct virtio_pmem_req req;
@@ -39,6 +40,9 @@ struct virtio_pmem {
/* Serialize flush requests to the device. */
struct mutex flush_lock;
+ /* Complete asynchronous FUA flushes outside the submit path. */
+ struct workqueue_struct *flush_wq;
+
/* nvdimm bus registers virtio pmem device */
struct nvdimm_bus *nvdimm_bus;
struct nvdimm_bus_descriptor nd_desc;
diff --git a/include/linux/libnvdimm.h b/include/linux/libnvdimm.h
index 28f086c4a1873..d929d83abf3be 100644
--- a/include/linux/libnvdimm.h
+++ b/include/linux/libnvdimm.h
@@ -126,6 +126,15 @@ struct nd_mapping_desc {
struct bio;
struct resource;
struct nd_region;
+
+/*
+ * Provider flush callback return values:
+ * 0: flush completed synchronously
+ * <0: flush failed
+ * >0: flush completion was queued and @bio will be completed later
+ */
+#define NVDIMM_FLUSH_ASYNC 1
+
struct nd_region_desc {
struct resource *res;
struct nd_mapping_desc *mapping;
--
2.52.0
^ permalink raw reply related
* [PATCH v7 05/12] nvdimm: virtio_pmem: use GFP_NOIO for flush requests
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
virtio_pmem_flush() can run from pmem_submit_bio() while filesystem IO
is waiting on the flush completion. The request object allocation can
sleep, but it should not enter filesystem or block IO reclaim from this
flush path.
Use GFP_NOIO for the request allocation. The virtqueue descriptor
allocation still uses GFP_ATOMIC because it runs under pmem_lock.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v7:
- Keep GFP_NOIO for the virtio-pmem request allocation after removing the
child flush bio path.
Changes in v6:
- New patch; keep GFP_NOIO only for the virtio-pmem request allocation.
drivers/nvdimm/nd_virtio.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index 8e16b7780be1a..a35044afddf34 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -61,7 +61,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
return -EIO;
}
- req_data = kmalloc_obj(*req_data);
+ req_data = kmalloc_obj(*req_data, GFP_NOIO);
if (!req_data)
return -ENOMEM;
--
2.52.0
^ permalink raw reply related
* [PATCH v7 06/12] nvdimm: virtio_pmem: always wake -ENOSPC waiters
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
virtio_pmem_host_ack() reclaims virtqueue descriptors with
virtqueue_get_buf(). The -ENOSPC waiter wakeup is tied to completing the
returned token. If token completion is skipped for any reason, reclaimed
descriptors may not wake a waiter and the submitter may sleep forever
waiting for a free slot. Always wake one -ENOSPC waiter for each virtqueue
completion before touching the returned token.
Signed-off-by: Li Chen <me@linux.beauty>
---
v2->v3:
- Split out the waiter wakeup ordering change from READ_ONCE()/WRITE_ONCE()
updates (now patch 4/7), per Pankaj's suggestion.
v3->v4:
- Rebased onto v7.1-rc7 and renumbered after the flush error patches.
drivers/nvdimm/nd_virtio.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index a35044afddf34..fcb26a595d7c6 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -15,26 +15,33 @@ struct virtio_pmem_flush_work {
struct bio *bio;
};
+static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
+{
+ struct virtio_pmem_request *req_buf;
+
+ if (list_empty(&vpmem->req_list))
+ return;
+
+ req_buf = list_first_entry(&vpmem->req_list,
+ struct virtio_pmem_request, list);
+ req_buf->wq_buf_avail = true;
+ wake_up(&req_buf->wq_buf);
+ list_del(&req_buf->list);
+}
+
/* The interrupt handler */
void virtio_pmem_host_ack(struct virtqueue *vq)
{
struct virtio_pmem *vpmem = vq->vdev->priv;
- struct virtio_pmem_request *req_data, *req_buf;
+ struct virtio_pmem_request *req_data;
unsigned long flags;
unsigned int len;
spin_lock_irqsave(&vpmem->pmem_lock, flags);
while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) {
+ virtio_pmem_wake_one_waiter(vpmem);
req_data->done = true;
wake_up(&req_data->host_acked);
-
- if (!list_empty(&vpmem->req_list)) {
- req_buf = list_first_entry(&vpmem->req_list,
- struct virtio_pmem_request, list);
- req_buf->wq_buf_avail = true;
- wake_up(&req_buf->wq_buf);
- list_del(&req_buf->list);
- }
}
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
}
--
2.52.0
^ permalink raw reply related
* [PATCH v7 07/12] nvdimm: virtio_pmem: use READ_ONCE()/WRITE_ONCE() for wait flags
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
Use READ_ONCE()/WRITE_ONCE() for the wait_event() flags (done and
wq_buf_avail). They are observed by waiters without pmem_lock, so make
the accesses explicit single loads/stores and avoid compiler
reordering/caching across the wait/wake paths.
Acked-by: Pankaj Gupta <pankaj.gupta.linux@gmail.com>
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v7:
- Add Pankaj's Acked-by.
v2->v3:
- Split out READ_ONCE()/WRITE_ONCE() updates from patch 3/7 (no functional
change intended).
v3->v4:
- Rebased onto v7.1-rc7 and renumbered after the flush error patches.
drivers/nvdimm/nd_virtio.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index fcb26a595d7c6..8c0d4347938a1 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -24,9 +24,9 @@ static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
req_buf = list_first_entry(&vpmem->req_list,
struct virtio_pmem_request, list);
- req_buf->wq_buf_avail = true;
+ list_del_init(&req_buf->list);
+ WRITE_ONCE(req_buf->wq_buf_avail, true);
wake_up(&req_buf->wq_buf);
- list_del(&req_buf->list);
}
/* The interrupt handler */
@@ -40,7 +40,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq)
spin_lock_irqsave(&vpmem->pmem_lock, flags);
while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) {
virtio_pmem_wake_one_waiter(vpmem);
- req_data->done = true;
+ WRITE_ONCE(req_data->done, true);
wake_up(&req_data->host_acked);
}
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
@@ -72,7 +72,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
if (!req_data)
return -ENOMEM;
- req_data->done = false;
+ WRITE_ONCE(req_data->done, false);
init_waitqueue_head(&req_data->host_acked);
init_waitqueue_head(&req_data->wq_buf);
INIT_LIST_HEAD(&req_data->list);
@@ -93,12 +93,12 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
GFP_ATOMIC)) == -ENOSPC) {
dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n");
- req_data->wq_buf_avail = false;
+ WRITE_ONCE(req_data->wq_buf_avail, false);
list_add_tail(&req_data->list, &vpmem->req_list);
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
/* A host response results in "host_ack" getting called */
- wait_event(req_data->wq_buf, req_data->wq_buf_avail);
+ wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail));
spin_lock_irqsave(&vpmem->pmem_lock, flags);
}
err1 = virtqueue_kick(vpmem->req_vq);
@@ -112,7 +112,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
err = -EIO;
} else {
/* A host response results in "host_ack" getting called */
- wait_event(req_data->host_acked, req_data->done);
+ wait_event(req_data->host_acked, READ_ONCE(req_data->done));
err = le32_to_cpu(req_data->resp.ret);
}
--
2.52.0
^ permalink raw reply related
* [PATCH v7 08/12] nvdimm: virtio_pmem: refcount requests for token lifetime
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, stable, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
KASAN reports slab-use-after-free in __wake_up_common():
BUG: KASAN: slab-use-after-free in __wake_up_common+0x114/0x160
Read of size 8 at addr ffff88810fdcb710 by task swapper/0/0
CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted
6.19.0-next-20260220-00006-g1eae5f204ec3 #4 PREEMPT(full)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux
1.17.0-2-2 04/01/2014
Call Trace:
<IRQ>
dump_stack_lvl+0x6d/0xb0
print_report+0x170/0x4e2
? __pfx__raw_spin_lock_irqsave+0x10/0x10
? __virt_addr_valid+0x1dc/0x380
kasan_report+0xbc/0xf0
? __wake_up_common+0x114/0x160
? __wake_up_common+0x114/0x160
__wake_up_common+0x114/0x160
? __pfx__raw_spin_lock_irqsave+0x10/0x10
__wake_up+0x36/0x60
virtio_pmem_host_ack+0x11d/0x3b0
? sched_balance_domains+0x29f/0xb00
? __pfx_virtio_pmem_host_ack+0x10/0x10
? _raw_spin_lock_irqsave+0x98/0x100
? __pfx__raw_spin_lock_irqsave+0x10/0x10
vring_interrupt+0x1c9/0x5e0
? __pfx_vp_interrupt+0x10/0x10
vp_vring_interrupt+0x87/0x100
? __pfx_vp_interrupt+0x10/0x10
__handle_irq_event_percpu+0x17f/0x550
? __pfx__raw_spin_lock+0x10/0x10
handle_irq_event+0xab/0x1c0
handle_fasteoi_irq+0x276/0xae0
__common_interrupt+0x65/0x130
common_interrupt+0x78/0xa0
</IRQ>
virtio_pmem_host_ack() wakes a request that has already been freed by the
submitter.
This happens when the request token is still reachable via the virtqueue,
but virtio_pmem_flush() returns and frees it.
Fix the token lifetime by refcounting struct virtio_pmem_request.
virtio_pmem_flush() holds a submitter reference, and the virtqueue holds an
extra reference once the request is queued. The completion path drops the
virtqueue reference, and the submitter drops its reference before
returning.
Fixes: 6e84200c0a29 ("virtio-pmem: Add virtio pmem driver")
Cc: stable@vger.kernel.org
Signed-off-by: Li Chen <me@linux.beauty>
---
v2->v3:
- Add raw KASAN report to the patch description.
- Drop timestamps from the embedded report.
v3->v4:
- Rebased onto v7.1-rc7 and renumbered after the flush error patches.
drivers/nvdimm/nd_virtio.c | 34 +++++++++++++++++++++++++++++-----
drivers/nvdimm/virtio_pmem.h | 2 ++
2 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index 8c0d4347938a1..1cf53f75b1281 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -15,6 +15,14 @@ struct virtio_pmem_flush_work {
struct bio *bio;
};
+static void virtio_pmem_req_release(struct kref *kref)
+{
+ struct virtio_pmem_request *req;
+
+ req = container_of(kref, struct virtio_pmem_request, kref);
+ kfree(req);
+}
+
static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
{
struct virtio_pmem_request *req_buf;
@@ -42,6 +50,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq)
virtio_pmem_wake_one_waiter(vpmem);
WRITE_ONCE(req_data->done, true);
wake_up(&req_data->host_acked);
+ kref_put(&req_data->kref, virtio_pmem_req_release);
}
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
}
@@ -72,6 +81,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
if (!req_data)
return -ENOMEM;
+ kref_init(&req_data->kref);
WRITE_ONCE(req_data->done, false);
init_waitqueue_head(&req_data->host_acked);
init_waitqueue_head(&req_data->wq_buf);
@@ -89,10 +99,23 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
* to req_list and wait for host_ack to wake us up when free
* slots are available.
*/
- while ((err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
- GFP_ATOMIC)) == -ENOSPC) {
-
- dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n");
+ for (;;) {
+ err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
+ GFP_ATOMIC);
+ if (!err) {
+ /*
+ * Take the virtqueue reference while @pmem_lock is
+ * held so completion cannot run concurrently.
+ */
+ kref_get(&req_data->kref);
+ break;
+ }
+
+ if (err != -ENOSPC)
+ break;
+
+ dev_info_ratelimited(&vdev->dev,
+ "failed to send command to virtio pmem device, no free slots in the virtqueue\n");
WRITE_ONCE(req_data->wq_buf_avail, false);
list_add_tail(&req_data->list, &vpmem->req_list);
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
@@ -101,6 +124,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail));
spin_lock_irqsave(&vpmem->pmem_lock, flags);
}
+
err1 = virtqueue_kick(vpmem->req_vq);
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
/*
@@ -116,7 +140,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
err = le32_to_cpu(req_data->resp.ret);
}
- kfree(req_data);
+ kref_put(&req_data->kref, virtio_pmem_req_release);
return err;
};
diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h
index e6dfc10ce0762..3af92588bd9d1 100644
--- a/drivers/nvdimm/virtio_pmem.h
+++ b/drivers/nvdimm/virtio_pmem.h
@@ -12,12 +12,14 @@
#include <linux/module.h>
#include <uapi/linux/virtio_pmem.h>
+#include <linux/kref.h>
#include <linux/libnvdimm.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
struct virtio_pmem_request {
+ struct kref kref;
struct virtio_pmem_req req;
struct virtio_pmem_resp resp;
--
2.52.0
^ permalink raw reply related
* [PATCH v7 09/12] nvdimm: virtio_pmem: publish done with release/acquire
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
virtio_pmem_host_ack() publishes the device response by setting done and
waking the submitter. The submitter reads resp.ret after wait_event()
observes done.
Use smp_store_release() on done and smp_load_acquire() in the wait
condition so the response read is ordered after completion.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v6:
- New patch.
drivers/nvdimm/nd_virtio.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index 1cf53f75b1281..e4e4284ae19e5 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -23,6 +23,19 @@ static void virtio_pmem_req_release(struct kref *kref)
kfree(req);
}
+static void virtio_pmem_signal_done(struct virtio_pmem_request *req)
+{
+ /* Pairs with smp_load_acquire() in virtio_pmem_req_done(). */
+ smp_store_release(&req->done, true);
+ wake_up(&req->host_acked);
+}
+
+static bool virtio_pmem_req_done(struct virtio_pmem_request *req)
+{
+ /* Pairs with smp_store_release() in virtio_pmem_signal_done(). */
+ return smp_load_acquire(&req->done);
+}
+
static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
{
struct virtio_pmem_request *req_buf;
@@ -48,8 +61,7 @@ void virtio_pmem_host_ack(struct virtqueue *vq)
spin_lock_irqsave(&vpmem->pmem_lock, flags);
while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) {
virtio_pmem_wake_one_waiter(vpmem);
- WRITE_ONCE(req_data->done, true);
- wake_up(&req_data->host_acked);
+ virtio_pmem_signal_done(req_data);
kref_put(&req_data->kref, virtio_pmem_req_release);
}
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
@@ -136,7 +148,8 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
err = -EIO;
} else {
/* A host response results in "host_ack" getting called */
- wait_event(req_data->host_acked, READ_ONCE(req_data->done));
+ wait_event(req_data->host_acked,
+ virtio_pmem_req_done(req_data));
err = le32_to_cpu(req_data->resp.ret);
}
--
2.52.0
^ permalink raw reply related
* [PATCH v7 10/12] nvdimm: virtio_pmem: isolate DMA request buffers
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
The virtio-pmem request object stores wait queues, flags, and list
pointers next to buffers mapped for virtqueue DMA. The response buffer is
mapped DMA_FROM_DEVICE, so non-coherent DMA invalidation must not share a
cache line with CPU-owned fields.
Keep the request buffer outside the DMA-from-device group and wrap only
the response buffer with __dma_from_device_group_begin/end.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v6:
- New patch.
drivers/nvdimm/virtio_pmem.h | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h
index 3af92588bd9d1..8843a8b965874 100644
--- a/drivers/nvdimm/virtio_pmem.h
+++ b/drivers/nvdimm/virtio_pmem.h
@@ -10,6 +10,7 @@
#ifndef _LINUX_VIRTIO_PMEM_H
#define _LINUX_VIRTIO_PMEM_H
+#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <uapi/linux/virtio_pmem.h>
#include <linux/kref.h>
@@ -20,8 +21,6 @@
struct virtio_pmem_request {
struct kref kref;
- struct virtio_pmem_req req;
- struct virtio_pmem_resp resp;
/* Wait queue to process deferred work after ack from host */
wait_queue_head_t host_acked;
@@ -31,6 +30,11 @@ struct virtio_pmem_request {
wait_queue_head_t wq_buf;
bool wq_buf_avail;
struct list_head list;
+
+ struct virtio_pmem_req req;
+ __dma_from_device_group_begin(resp);
+ struct virtio_pmem_resp resp;
+ __dma_from_device_group_end(resp);
};
struct virtio_pmem {
--
2.52.0
^ permalink raw reply related
* [PATCH v7 11/12] nvdimm: virtio_pmem: converge broken virtqueue to -EIO
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
dmesg reports virtqueue failure and device reset:
virtio_pmem virtio2: failed to send command to
virtio pmem device, no free slots in the virtqueue
virtio_pmem virtio2: virtio pmem device
needs a reset
virtio_pmem_flush() can wait for a free virtqueue descriptor (-ENOSPC).
It can also wait for host completion. If the request virtqueue breaks,
those waiters may never make progress. One example is notify failure from
virtqueue_kick().
Track a device-level broken state and converge the failure to -EIO. New
requests fail fast, -ENOSPC waiters are unlinked and woken, and the
currently submitted request is woken so its host_acked waiter can return
without waiting forever for host completion. Completed requests are forced
to report an error after the queue is marked broken.
Also serialize async parent-bio flush work against the broken state with
pmem_lock. That way remove and freeze either drain work queued before
virtio_pmem_mark_broken(), or later callers see nvdimm_flush() complete
the parent bio synchronously with -EIO instead of queuing work after the
drain point.
Do not detach unused buffers from an active virtqueue. Runtime
broken-queue handling only stops new submissions and wakes local waiters.
Removal resets the device first. It then drains request tokens. After
that, the device no longer owns the buffers when the virtqueue reference
is dropped.
Closes: https://lore.kernel.org/r/202512250116.ewtzlD0g-lkp@intel.com/
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v7:
- Serialize async parent-bio flush work against broken-state publication so
remove/freeze cannot drain the workqueue before a racing FUA bio queues new
completion work.
Changes in v6:
- Wake the in-flight host-completion waiter when marking the queue broken.
- Track req_inflight and clear it on completion/drain paths.
- Return -EIO if the queue breaks before a host response is observed.
Changes in v5:
- Split broken marking from token draining.
- Do not call virtqueue_detach_unused_buf() on an active queue.
- Reset the device before draining tokens in remove().
- Do not let the host-completion wait return only because the device is
marked broken.
v2->v3:
- Add raw dmesg excerpt to the patch description.
- Drop timestamps from the embedded dmesg.
- Fold the CONFIG_VIRTIO_PMEM=m export fix into this patch.
v3->v4:
- Rebased onto v7.1-rc7 and renumbered after the flush error patches.
- Use kmalloc_obj(*req_data) at the allocation site to match current nvdimm
code.
drivers/nvdimm/nd_virtio.c | 126 +++++++++++++++++++++++++++++++----
drivers/nvdimm/virtio_pmem.c | 16 ++++-
drivers/nvdimm/virtio_pmem.h | 8 +++
3 files changed, 136 insertions(+), 14 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index e4e4284ae19e5..a6820300cbe8f 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -36,6 +36,12 @@ static bool virtio_pmem_req_done(struct virtio_pmem_request *req)
return smp_load_acquire(&req->done);
}
+static void virtio_pmem_complete_err(struct virtio_pmem_request *req)
+{
+ req->resp.ret = cpu_to_le32(1);
+ virtio_pmem_signal_done(req);
+}
+
static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
{
struct virtio_pmem_request *req_buf;
@@ -50,6 +56,63 @@ static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem)
wake_up(&req_buf->wq_buf);
}
+static void virtio_pmem_wake_all_waiters(struct virtio_pmem *vpmem)
+{
+ struct virtio_pmem_request *req, *tmp;
+
+ list_for_each_entry_safe(req, tmp, &vpmem->req_list, list) {
+ list_del_init(&req->list);
+ WRITE_ONCE(req->wq_buf_avail, true);
+ wake_up(&req->wq_buf);
+ }
+}
+
+static void virtio_pmem_clear_inflight(struct virtio_pmem *vpmem,
+ struct virtio_pmem_request *req)
+{
+ if (vpmem->req_inflight == req)
+ vpmem->req_inflight = NULL;
+}
+
+static void virtio_pmem_wake_inflight(struct virtio_pmem *vpmem)
+{
+ struct virtio_pmem_request *req = vpmem->req_inflight;
+
+ if (req)
+ wake_up(&req->host_acked);
+}
+
+void virtio_pmem_mark_broken(struct virtio_pmem *vpmem)
+{
+ if (!READ_ONCE(vpmem->broken)) {
+ WRITE_ONCE(vpmem->broken, true);
+ dev_err_once(&vpmem->vdev->dev, "virtqueue is broken\n");
+ }
+
+ virtio_pmem_wake_inflight(vpmem);
+ virtio_pmem_wake_all_waiters(vpmem);
+}
+EXPORT_SYMBOL_GPL(virtio_pmem_mark_broken);
+
+void virtio_pmem_drain(struct virtio_pmem *vpmem)
+{
+ struct virtio_pmem_request *req;
+ unsigned int len;
+
+ while ((req = virtqueue_get_buf(vpmem->req_vq, &len)) != NULL) {
+ virtio_pmem_clear_inflight(vpmem, req);
+ virtio_pmem_complete_err(req);
+ kref_put(&req->kref, virtio_pmem_req_release);
+ }
+
+ while ((req = virtqueue_detach_unused_buf(vpmem->req_vq)) != NULL) {
+ virtio_pmem_clear_inflight(vpmem, req);
+ virtio_pmem_complete_err(req);
+ kref_put(&req->kref, virtio_pmem_req_release);
+ }
+}
+EXPORT_SYMBOL_GPL(virtio_pmem_drain);
+
/* The interrupt handler */
void virtio_pmem_host_ack(struct virtqueue *vq)
{
@@ -60,8 +123,12 @@ void virtio_pmem_host_ack(struct virtqueue *vq)
spin_lock_irqsave(&vpmem->pmem_lock, flags);
while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) {
+ virtio_pmem_clear_inflight(vpmem, req_data);
virtio_pmem_wake_one_waiter(vpmem);
- virtio_pmem_signal_done(req_data);
+ if (READ_ONCE(vpmem->broken))
+ virtio_pmem_complete_err(req_data);
+ else
+ virtio_pmem_signal_done(req_data);
kref_put(&req_data->kref, virtio_pmem_req_release);
}
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
@@ -89,6 +156,9 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
return -EIO;
}
+ if (READ_ONCE(vpmem->broken))
+ return -EIO;
+
req_data = kmalloc_obj(*req_data, GFP_NOIO);
if (!req_data)
return -ENOMEM;
@@ -105,13 +175,18 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
sgs[1] = &ret;
spin_lock_irqsave(&vpmem->pmem_lock, flags);
- /*
- * If virtqueue_add_sgs returns -ENOSPC then req_vq virtual
- * queue does not have free descriptor. We add the request
- * to req_list and wait for host_ack to wake us up when free
- * slots are available.
- */
+ /*
+ * If virtqueue_add_sgs returns -ENOSPC then req_vq virtual
+ * queue does not have free descriptor. We add the request
+ * to req_list and wait for host_ack to wake us up when free
+ * slots are available.
+ */
for (;;) {
+ if (READ_ONCE(vpmem->broken)) {
+ err = -EIO;
+ break;
+ }
+
err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
GFP_ATOMIC);
if (!err) {
@@ -120,6 +195,7 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
* held so completion cannot run concurrently.
*/
kref_get(&req_data->kref);
+ vpmem->req_inflight = req_data;
break;
}
@@ -133,24 +209,41 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
/* A host response results in "host_ack" getting called */
- wait_event(req_data->wq_buf, READ_ONCE(req_data->wq_buf_avail));
+ wait_event(req_data->wq_buf,
+ READ_ONCE(req_data->wq_buf_avail) ||
+ READ_ONCE(vpmem->broken));
spin_lock_irqsave(&vpmem->pmem_lock, flags);
+
+ if (READ_ONCE(vpmem->broken))
+ break;
}
- err1 = virtqueue_kick(vpmem->req_vq);
+ if (err == -EIO || virtqueue_is_broken(vpmem->req_vq))
+ virtio_pmem_mark_broken(vpmem);
+
+ err1 = true;
+ if (!err && !READ_ONCE(vpmem->broken)) {
+ err1 = virtqueue_kick(vpmem->req_vq);
+ if (!err1)
+ virtio_pmem_mark_broken(vpmem);
+ }
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
/*
* virtqueue_add_sgs failed with error different than -ENOSPC, we can't
* do anything about that.
*/
- if (err || !err1) {
+ if (READ_ONCE(vpmem->broken) || err || !err1) {
dev_info(&vdev->dev, "failed to send command to virtio pmem device\n");
err = -EIO;
} else {
/* A host response results in "host_ack" getting called */
wait_event(req_data->host_acked,
- virtio_pmem_req_done(req_data));
- err = le32_to_cpu(req_data->resp.ret);
+ virtio_pmem_req_done(req_data) ||
+ READ_ONCE(vpmem->broken));
+ if (virtio_pmem_req_done(req_data))
+ err = le32_to_cpu(req_data->resp.ret);
+ else
+ err = -EIO;
}
kref_put(&req_data->kref, virtio_pmem_req_release);
@@ -178,6 +271,7 @@ int async_pmem_flush(struct nd_region *nd_region, struct bio *bio)
struct virtio_device *vdev = nd_region->provider_data;
struct virtio_pmem *vpmem = vdev->priv;
struct virtio_pmem_flush_work *flush;
+ unsigned long flags;
int err;
if (bio && bio->bi_iter.bi_sector != -1) {
@@ -188,7 +282,15 @@ int async_pmem_flush(struct nd_region *nd_region, struct bio *bio)
INIT_WORK(&flush->work, virtio_pmem_flush_work);
flush->nd_region = nd_region;
flush->bio = bio;
+
+ spin_lock_irqsave(&vpmem->pmem_lock, flags);
+ if (READ_ONCE(vpmem->broken)) {
+ spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
+ kfree(flush);
+ return -EIO;
+ }
queue_work(vpmem->flush_wq, &flush->work);
+ spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
return NVDIMM_FLUSH_ASYNC;
}
diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c
index 9cf822a6c0c38..36664a5ea25e3 100644
--- a/drivers/nvdimm/virtio_pmem.c
+++ b/drivers/nvdimm/virtio_pmem.c
@@ -25,6 +25,8 @@ static int init_vq(struct virtio_pmem *vpmem)
spin_lock_init(&vpmem->pmem_lock);
INIT_LIST_HEAD(&vpmem->req_list);
+ vpmem->req_inflight = NULL;
+ WRITE_ONCE(vpmem->broken, false);
return 0;
};
@@ -148,11 +150,21 @@ static void virtio_pmem_remove(struct virtio_device *vdev)
{
struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(&vdev->dev);
struct virtio_pmem *vpmem = vdev->priv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vpmem->pmem_lock, flags);
+ virtio_pmem_mark_broken(vpmem);
+ spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
- nvdimm_bus_unregister(nvdimm_bus);
drain_workqueue(vpmem->flush_wq);
- vdev->config->del_vqs(vdev);
virtio_reset_device(vdev);
+
+ spin_lock_irqsave(&vpmem->pmem_lock, flags);
+ virtio_pmem_drain(vpmem);
+ spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
+
+ nvdimm_bus_unregister(nvdimm_bus);
+ vdev->config->del_vqs(vdev);
destroy_workqueue(vpmem->flush_wq);
}
diff --git a/drivers/nvdimm/virtio_pmem.h b/drivers/nvdimm/virtio_pmem.h
index 8843a8b965874..0b90777d7658b 100644
--- a/drivers/nvdimm/virtio_pmem.h
+++ b/drivers/nvdimm/virtio_pmem.h
@@ -56,6 +56,12 @@ struct virtio_pmem {
/* List to store deferred work if virtqueue is full */
struct list_head req_list;
+ /* Request currently owned by the virtqueue. */
+ struct virtio_pmem_request *req_inflight;
+
+ /* Fail fast and wake waiters if the request virtqueue is broken. */
+ bool broken;
+
/* Synchronize virtqueue data */
spinlock_t pmem_lock;
@@ -65,5 +71,7 @@ struct virtio_pmem {
};
void virtio_pmem_host_ack(struct virtqueue *vq);
+void virtio_pmem_mark_broken(struct virtio_pmem *vpmem);
+void virtio_pmem_drain(struct virtio_pmem *vpmem);
int async_pmem_flush(struct nd_region *nd_region, struct bio *bio);
#endif
--
2.52.0
^ permalink raw reply related
* [PATCH v7 12/12] nvdimm: virtio_pmem: drain requests in freeze
From: Li Chen @ 2026-06-30 9:23 UTC (permalink / raw)
To: Pankaj Gupta, Vishal Verma, Dave Jiang, Alison Schofield,
virtualization, nvdimm
Cc: linux-kernel, Li Chen
In-Reply-To: <20260630092338.2094628-1-me@linux.beauty>
virtio_pmem_freeze() currently deletes virtqueues and resets the device
without waking threads waiting for a virtqueue descriptor or a host
completion.
Mark the request virtqueue broken before reset. This makes new submissions
fail fast and lets -ENOSPC waiters leave the wait list. Reset the device
before draining used and unused request tokens, then delete the virtqueues.
This wakes waiters with -EIO. It also keeps the detach call on a quiesced
device.
Clear req_vq after del_vqs(). Make drain tolerate a NULL queue so remove
after freeze does not dereference a stale virtqueue pointer. Also make
virtio_pmem_flush() stop checking req_vq once the broken state is visible.
A waiter woken by freeze/remove can resume after del_vqs() has cleared
req_vq.
Signed-off-by: Li Chen <me@linux.beauty>
---
Changes in v7:
- Stop checking req_vq once the broken state is visible, so a waiter woken
by freeze/remove does not dereference req_vq after del_vqs() clears it.
Changes in v6:
- Clear req_vq after del_vqs() and make drain tolerate a NULL queue.
Changes in v5:
- Reset the device before draining used and unused request tokens.
- Use the split broken-marking and post-reset drain helpers.
v2->v3:
- No change.
v3->v4:
- Rebased onto v7.1-rc7 and renumbered after the flush error patches.
drivers/nvdimm/nd_virtio.c | 5 +++++
drivers/nvdimm/virtio_pmem.c | 34 +++++++++++++++++++++++++++++-----
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/drivers/nvdimm/nd_virtio.c b/drivers/nvdimm/nd_virtio.c
index a6820300cbe8f..3b8be79a20a0f 100644
--- a/drivers/nvdimm/nd_virtio.c
+++ b/drivers/nvdimm/nd_virtio.c
@@ -99,6 +99,9 @@ void virtio_pmem_drain(struct virtio_pmem *vpmem)
struct virtio_pmem_request *req;
unsigned int len;
+ if (!vpmem->req_vq)
+ return;
+
while ((req = virtqueue_get_buf(vpmem->req_vq, &len)) != NULL) {
virtio_pmem_clear_inflight(vpmem, req);
virtio_pmem_complete_err(req);
@@ -218,6 +221,8 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
break;
}
+ if (READ_ONCE(vpmem->broken))
+ err = -EIO;
if (err == -EIO || virtqueue_is_broken(vpmem->req_vq))
virtio_pmem_mark_broken(vpmem);
diff --git a/drivers/nvdimm/virtio_pmem.c b/drivers/nvdimm/virtio_pmem.c
index 36664a5ea25e3..7ee3fb1779f73 100644
--- a/drivers/nvdimm/virtio_pmem.c
+++ b/drivers/nvdimm/virtio_pmem.c
@@ -17,11 +17,16 @@ static struct virtio_device_id id_table[] = {
/* Initialize virt queue */
static int init_vq(struct virtio_pmem *vpmem)
{
+ int err;
+
/* single vq */
vpmem->req_vq = virtio_find_single_vq(vpmem->vdev,
virtio_pmem_host_ack, "flush_queue");
- if (IS_ERR(vpmem->req_vq))
- return PTR_ERR(vpmem->req_vq);
+ if (IS_ERR(vpmem->req_vq)) {
+ err = PTR_ERR(vpmem->req_vq);
+ vpmem->req_vq = NULL;
+ return err;
+ }
spin_lock_init(&vpmem->pmem_lock);
INIT_LIST_HEAD(&vpmem->req_list);
@@ -31,6 +36,15 @@ static int init_vq(struct virtio_pmem *vpmem)
return 0;
};
+static void virtio_pmem_del_vqs(struct virtio_pmem *vpmem)
+{
+ if (!vpmem->req_vq)
+ return;
+
+ vpmem->vdev->config->del_vqs(vpmem->vdev);
+ vpmem->req_vq = NULL;
+}
+
static int virtio_pmem_validate(struct virtio_device *vdev)
{
struct virtio_shm_region shm_reg;
@@ -139,7 +153,7 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
virtio_reset_device(vdev);
nvdimm_bus_unregister(vpmem->nvdimm_bus);
out_vq:
- vdev->config->del_vqs(vdev);
+ virtio_pmem_del_vqs(vpmem);
out_wq:
destroy_workqueue(vpmem->flush_wq);
out_err:
@@ -164,18 +178,28 @@ static void virtio_pmem_remove(struct virtio_device *vdev)
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
nvdimm_bus_unregister(nvdimm_bus);
- vdev->config->del_vqs(vdev);
+ virtio_pmem_del_vqs(vpmem);
destroy_workqueue(vpmem->flush_wq);
}
static int virtio_pmem_freeze(struct virtio_device *vdev)
{
struct virtio_pmem *vpmem = vdev->priv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vpmem->pmem_lock, flags);
+ virtio_pmem_mark_broken(vpmem);
+ spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
drain_workqueue(vpmem->flush_wq);
- vdev->config->del_vqs(vdev);
virtio_reset_device(vdev);
+ spin_lock_irqsave(&vpmem->pmem_lock, flags);
+ virtio_pmem_drain(vpmem);
+ spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
+
+ virtio_pmem_del_vqs(vpmem);
+
return 0;
}
--
2.52.0
^ permalink raw reply related
* Re: [PATCH net-next v4] vsock/virtio: rewrite MSG_ZEROCOPY flag handling
From: Stefano Garzarella @ 2026-06-30 9:41 UTC (permalink / raw)
To: Arseniy Krasnov
Cc: Stefan Hajnoczi, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Michael S. Tsirkin, Jason Wang, Bobby Eshleman,
Xuan Zhuo, Eugenio Pérez, Simon Horman, kvm, virtualization,
netdev, linux-kernel, oxffffaa, rulkc
In-Reply-To: <20260628182052.951760-1-avkrasnov@rulkc.org>
On Sun, Jun 28, 2026 at 09:20:52PM +0300, Arseniy Krasnov wrote:
>Logically it was based on TCP implementation, so to make further support
>easier, rewrite it in the TCP way (like in 'tcp_sendmsg_locked()'). By
>this way, patch also adds handling case when 'msg_ubuf' is already set.
Thanks for this!
IIUC the result will be similar of commit eb315a7d1396 ("tcp: support
externally provided ubufs") for tcp. Maybe I would have added it to the
commit description, anyway the patch LGTM:
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
>
>Signed-off-by: Arseniy Krasnov <avkrasnov@rulkc.org>
>---
> Changelog v1->v2:
> * Rebase on last 'net-next'. Don't need 'skb_zcopy_set()' now - it was
> already added.
> Changelog v2->v3:
> * Update commit message.
> * Remove one empty line.
> Changelog v3->v4:
> * Update commit message.
>
> net/vmw_vsock/virtio_transport_common.c | 47 ++++++++++++-------------
> 1 file changed, 22 insertions(+), 25 deletions(-)
>
>diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
>index 09475007165b..41c2a0b82a8e 100644
>--- a/net/vmw_vsock/virtio_transport_common.c
>+++ b/net/vmw_vsock/virtio_transport_common.c
>@@ -328,38 +328,35 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
> if (pkt_len == 0 && info->op == VIRTIO_VSOCK_OP_RW)
> return pkt_len;
>
>- if (info->msg) {
>- /* If zerocopy is not enabled by 'setsockopt()', we behave as
>- * there is no MSG_ZEROCOPY flag set.
>+ if (info->msg && (info->msg->msg_flags & MSG_ZEROCOPY)) {
>+ /* If 'info->msg' is not NULL, this is only VIRTIO_VSOCK_OP_RW.
>+ * 'MSG_ZEROCOPY' flag handling here is based on the same flag
>+ * handling from 'tcp_sendmsg_locked()'.
> */
>- if (!sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY))
>- info->msg->msg_flags &= ~MSG_ZEROCOPY;
>+ if (info->msg->msg_ubuf) {
>+ uarg = info->msg->msg_ubuf;
>+ can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len);
>+ } else if (sock_flag(sk_vsock(vsk), SOCK_ZEROCOPY)) {
>+ uarg = msg_zerocopy_realloc(sk_vsock(vsk), pkt_len,
>+ NULL, false);
>+ if (!uarg) {
>+ virtio_transport_put_credit(vvs, pkt_len);
>+ return -ENOMEM;
>+ }
>
>- if (info->msg->msg_flags & MSG_ZEROCOPY)
> can_zcopy = virtio_transport_can_zcopy(t_ops, info, pkt_len);
>+ if (!can_zcopy)
>+ uarg_to_msgzc(uarg)->zerocopy = 0;
>
>+ have_uref = true;
>+ }
>+
>+ /* 'can_zcopy' means that this transmission will be
>+ * in zerocopy way (e.g. using 'frags' array).
>+ */
> if (can_zcopy)
> max_skb_len = min_t(u32, VIRTIO_VSOCK_MAX_PKT_BUF_SIZE,
> (MAX_SKB_FRAGS * PAGE_SIZE));
>-
>- if (info->msg->msg_flags & MSG_ZEROCOPY &&
>- info->op == VIRTIO_VSOCK_OP_RW) {
>- uarg = info->msg->msg_ubuf;
>-
>- if (!uarg) {
>- uarg = msg_zerocopy_realloc(sk_vsock(vsk),
>- pkt_len, NULL, false);
>- if (!uarg) {
>- virtio_transport_put_credit(vvs, pkt_len);
>- return -ENOMEM;
>- }
>-
>- if (!can_zcopy)
>- uarg_to_msgzc(uarg)->zerocopy = 0;
>-
>- have_uref = true;
>- }
>- }
> }
>
> rest_len = pkt_len;
>--
>2.25.1
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox