VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46065

CVE-2026-46065

Description

In the Linux kernel, the following vulnerability has been resolved:

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

Hold state of deferred I/O in struct fb_deferred_io_state. Allocate an instance as part of initializing deferred I/O and remove it only after the final mapping has been closed. If the fb_info and the contained deferred I/O meanwhile goes away, clear struct fb_deferred_io_state.info to invalidate the mapping. Any access will then result in a SIGBUS signal.

Fixes a long-standing problem, where a device hot-unplug happens while user space still has an active mapping of the graphics memory. The hot- unplug frees the instance of struct fb_info. Accessing the memory will operate on undefined state.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Linux kernel fbdev deferred I/O fix prevents use-after-free on hot-unplug by separating state lifetime from fb_info.

Vulnerability

In the Linux kernel's framebuffer device (fbdev) subsystem, the deferred I/O mechanism (defio) tied its state to the lifetime of struct fb_info. When a device is hot-unplugged, struct fb_info is freed, but user-space mappings of the graphics memory may remain active. Accessing such memory after the fb_info is freed results in undefined behavior, a use-after-free condition. The vulnerability affects all kernel versions using fbdev with deferred I/O. The fix is included in stable kernel updates as of commit a0aafb421dd15e935d81543152617f2742cefa70 [1].

Exploitation

An attacker with physical access or the ability to trigger a device hot-unplug (e.g., via USB removal) while a user-space process has an active mmap of the framebuffer memory can cause the kernel to access freed fb_info structures. No special privileges are required beyond the ability to open the framebuffer device and map it. The race window is between the device removal and the user-space process closing the mapping.

Impact

Successful exploitation leads to a use-after-free condition, potentially allowing an attacker to corrupt kernel memory or cause a denial of service (system crash). The fix ensures that any access to the freed memory results in a SIGBUS signal, terminating the offending process and preventing further damage.

Mitigation

The fix is merged into the Linux kernel stable tree as commit a0aafb421dd15e935d81543152617f2742cefa70 [1]. Users should apply the latest stable kernel update that includes this commit. No workaround is available; the issue is resolved by updating the kernel.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

10
9ded47ad003f

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitThomas ZimmermannFeb 24, 2026Fixed in 7.1-rc1via kernel-cna
4 files changed · +290 76
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
a0aafb421dd1

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitThomas ZimmermannFeb 24, 2026Fixed in 7.0.4via kernel-cna
4 files changed · +290 76
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
2b53d3a52e8e

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitThomas ZimmermannFixed in 6.12.88via kernel-cna
4 files changed · +290 78
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 65363df8e81b5a..e0a44298c248d1 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -128,17 +197,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (vmf->vma->vm_file)
     		page->mapping = vmf->vma->vm_file->f_mapping;
    @@ -148,8 +231,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for folio_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -176,15 +266,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -202,50 +301,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -262,7 +349,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -273,9 +363,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct folio *folio = page_folio(pageref->page);
     
    @@ -291,12 +382,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -306,7 +398,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -323,10 +419,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -364,11 +462,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 65363df8e81b5a..e0a44298c248d1 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -128,17 +197,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (vmf->vma->vm_file)
     		page->mapping = vmf->vma->vm_file->f_mapping;
    @@ -148,8 +231,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for folio_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -176,15 +266,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -202,50 +301,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -262,7 +349,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -273,9 +363,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct folio *folio = page_folio(pageref->page);
     
    @@ -291,12 +382,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -306,7 +398,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -323,10 +419,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -364,11 +462,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 267b59ead43212..d87d6547c6e41d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -222,12 +222,13 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -485,6 +486,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 267b59ead43212..d87d6547c6e41d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -222,12 +222,13 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -485,6 +486,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
2a40f8bc9bb7

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitThomas ZimmermannFixed in 6.6.140via kernel-cna
4 files changed · +290 78
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index b9607d5a370d4e..f4812a76c3cc0e 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs)
     {
     	void *screen_base = (void __force *) info->screen_base;
    @@ -93,17 +162,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	get_page(page);
     
    @@ -115,8 +198,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for page_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -143,15 +233,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -169,50 +268,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -227,7 +314,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -238,9 +328,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *cur = pageref->page;
     		lock_page(cur);
    @@ -255,12 +346,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs, i;
     	int ret;
    @@ -270,7 +362,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -289,10 +385,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -333,11 +431,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index b9607d5a370d4e..f4812a76c3cc0e 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs)
     {
     	void *screen_base = (void __force *) info->screen_base;
    @@ -93,17 +162,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	get_page(page);
     
    @@ -115,8 +198,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for page_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -143,15 +233,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -169,50 +268,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -227,7 +314,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -238,9 +328,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *cur = pageref->page;
     		lock_page(cur);
    @@ -255,12 +346,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs, i;
     	int ret;
    @@ -270,7 +362,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -289,10 +385,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -333,11 +431,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 322b4d20afa558..8a9d949cc7e2d8 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -214,11 +214,12 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -476,6 +477,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 322b4d20afa558..8a9d949cc7e2d8 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -214,11 +214,12 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -476,6 +477,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
25c2b77bc463

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitThomas ZimmermannFixed in 6.18.30via kernel-cna
4 files changed · +290 76
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 8df2e51e33909f..0b099a89a8234c 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 8df2e51e33909f..0b099a89a8234c 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index c3302d51354660..da2fdabd18cb3a 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -217,13 +217,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -490,6 +491,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index c3302d51354660..da2fdabd18cb3a 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -217,13 +217,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -490,6 +491,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
9ded47ad003f

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

4 files changed · +290 76
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
a0aafb421dd1

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

4 files changed · +290 76
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index ca48b89a323d35..93bd2f696fa475 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc_obj(*fbdefio_state);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 6d4a58084fd5f3..aed17567fe508d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -218,13 +218,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -487,6 +488,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
2a40f8bc9bb7

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

4 files changed · +290 78
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index b9607d5a370d4e..f4812a76c3cc0e 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs)
     {
     	void *screen_base = (void __force *) info->screen_base;
    @@ -93,17 +162,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	get_page(page);
     
    @@ -115,8 +198,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for page_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -143,15 +233,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -169,50 +268,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -227,7 +314,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -238,9 +328,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *cur = pageref->page;
     		lock_page(cur);
    @@ -255,12 +346,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs, i;
     	int ret;
    @@ -270,7 +362,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -289,10 +385,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -333,11 +431,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index b9607d5a370d4e..f4812a76c3cc0e 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs)
     {
     	void *screen_base = (void __force *) info->screen_base;
    @@ -93,17 +162,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	get_page(page);
     
    @@ -115,8 +198,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for page_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -143,15 +233,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -169,50 +268,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -227,7 +314,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -238,9 +328,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *cur = pageref->page;
     		lock_page(cur);
    @@ -255,12 +346,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs, i;
     	int ret;
    @@ -270,7 +362,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -289,10 +385,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -333,11 +431,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 322b4d20afa558..8a9d949cc7e2d8 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -214,11 +214,12 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -476,6 +477,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 322b4d20afa558..8a9d949cc7e2d8 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -214,11 +214,12 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -476,6 +477,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
2b53d3a52e8e

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

4 files changed · +290 78
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 65363df8e81b5a..e0a44298c248d1 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -128,17 +197,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (vmf->vma->vm_file)
     		page->mapping = vmf->vma->vm_file->f_mapping;
    @@ -148,8 +231,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for folio_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -176,15 +266,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -202,50 +301,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -262,7 +349,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -273,9 +363,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct folio *folio = page_folio(pageref->page);
     
    @@ -291,12 +382,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -306,7 +398,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -323,10 +419,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -364,11 +462,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 37 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 65363df8e81b5a..e0a44298c248d1 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -23,6 +23,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -128,17 +197,31 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (vmf->vma->vm_file)
     		page->mapping = vmf->vma->vm_file->f_mapping;
    @@ -148,8 +231,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     	BUG_ON(!page->mapping);
     	page->index = vmf->pgoff; /* for folio_mkclean() */
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -176,15 +266,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -202,50 +301,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -262,7 +349,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -273,9 +363,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we mkclean the pages, then do all deferred IO */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct folio *folio = page_folio(pageref->page);
     
    @@ -291,12 +382,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -306,7 +398,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -323,10 +419,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -364,11 +462,18 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 267b59ead43212..d87d6547c6e41d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -222,12 +222,13 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -485,6 +486,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index 267b59ead43212..d87d6547c6e41d 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -222,12 +222,13 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -485,6 +486,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
25c2b77bc463

fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info

4 files changed · +290 76
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 8df2e51e33909f..0b099a89a8234c 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • drivers/video/fbdev/core/fb_defio.c+142 36 modified
    diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
    index 8df2e51e33909f..0b099a89a8234c 100644
    --- a/drivers/video/fbdev/core/fb_defio.c
    +++ b/drivers/video/fbdev/core/fb_defio.c
    @@ -24,6 +24,75 @@
     #include <linux/rmap.h>
     #include <linux/pagemap.h>
     
    +/*
    + * struct fb_deferred_io_state
    + */
    +
    +struct fb_deferred_io_state {
    +	struct kref ref;
    +
    +	struct mutex lock; /* mutex that protects the pageref list */
    +	/* fields protected by lock */
    +	struct fb_info *info;
    +};
    +
    +static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
    +{
    +	struct fb_deferred_io_state *fbdefio_state;
    +
    +	fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
    +	if (!fbdefio_state)
    +		return NULL;
    +
    +	kref_init(&fbdefio_state->ref);
    +	mutex_init(&fbdefio_state->lock);
    +
    +	return fbdefio_state;
    +}
    +
    +static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	mutex_destroy(&fbdefio_state->lock);
    +
    +	kfree(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_get(&fbdefio_state->ref);
    +}
    +
    +static void __fb_deferred_io_state_release(struct kref *ref)
    +{
    +	struct fb_deferred_io_state *fbdefio_state =
    +		container_of(ref, struct fb_deferred_io_state, ref);
    +
    +	fb_deferred_io_state_release(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
    +{
    +	kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
    +}
    +
    +/*
    + * struct vm_operations_struct
    + */
    +
    +static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_get(fbdefio_state);
    +}
    +
    +static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
    +{
    +	struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +}
    +
     static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    @@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
     /* this is to find and return the vmalloc-ed fb pages */
     static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
     {
    +	struct fb_info *info;
     	unsigned long offset;
     	struct page *page;
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	vm_fault_t ret;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
     
     	offset = vmf->pgoff << PAGE_SHIFT;
    -	if (offset >= info->fix.smem_len)
    -		return VM_FAULT_SIGBUS;
    +	if (offset >= info->fix.smem_len) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	page = fb_deferred_io_get_page(info, offset);
    -	if (!page)
    -		return VM_FAULT_SIGBUS;
    +	if (!page) {
    +		ret = VM_FAULT_SIGBUS;
    +		goto err_mutex_unlock;
    +	}
     
     	if (!vmf->vma->vm_file)
     		fb_err(info, "no mapping available\n");
     
     	BUG_ON(!info->fbdefio->mapping);
     
    +	mutex_unlock(&fbdefio_state->lock);
    +
     	vmf->page = page;
    +
     	return 0;
    +
    +err_mutex_unlock:
    +	mutex_unlock(&fbdefio_state->lock);
    +	return ret;
     }
     
     int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
    @@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
      * Adds a page to the dirty list. Call this from struct
      * vm_operations_struct.page_mkwrite.
      */
    -static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
    -					    struct page *page)
    +static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
    +					    unsigned long offset, struct page *page)
     {
    -	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_info *info;
    +	struct fb_deferred_io *fbdefio;
     	struct fb_deferred_io_pageref *pageref;
     	vm_fault_t ret;
     
     	/* protect against the workqueue changing the page list */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
    +
    +	info = fbdefio_state->info;
    +	if (!info) {
    +		ret = VM_FAULT_SIGBUS; /* our device is gone */
    +		goto err_mutex_unlock;
    +	}
    +
    +	fbdefio = info->fbdefio;
     
     	pageref = fb_deferred_io_pageref_get(info, offset, page);
     	if (WARN_ON_ONCE(!pageref)) {
    @@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
     	 */
     	lock_page(pageref->page);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     
     	/* come back after delay to process the deferred IO */
     	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
     	return VM_FAULT_LOCKED;
     
     err_mutex_unlock:
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     	return ret;
     }
     
    -/*
    - * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
    - * @fb_info: The fbdev info structure
    - * @vmf: The VM fault
    - *
    - * This is a callback we get when userspace first tries to
    - * write to the page. We schedule a workqueue. That workqueue
    - * will eventually mkclean the touched pages and execute the
    - * deferred framebuffer IO. Then if userspace touches a page
    - * again, we repeat the same scheme.
    - *
    - * Returns:
    - * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
    - */
    -static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
    +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
    +					      struct vm_fault *vmf)
     {
     	unsigned long offset = vmf->pgoff << PAGE_SHIFT;
     	struct page *page = vmf->page;
     
     	file_update_time(vmf->vma->vm_file);
     
    -	return fb_deferred_io_track_page(info, offset, page);
    +	return fb_deferred_io_track_page(fbdefio_state, offset, page);
     }
     
    -/* vm_ops->page_mkwrite handler */
     static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
     {
    -	struct fb_info *info = vmf->vma->vm_private_data;
    +	struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
     
    -	return fb_deferred_io_page_mkwrite(info, vmf);
    +	return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
     }
     
     static const struct vm_operations_struct fb_deferred_io_vm_ops = {
    +	.open		= fb_deferred_io_vm_open,
    +	.close		= fb_deferred_io_vm_close,
     	.fault		= fb_deferred_io_fault,
     	.page_mkwrite	= fb_deferred_io_mkwrite,
     };
    @@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
     	vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
     	if (!(info->flags & FBINFO_VIRTFB))
     		vm_flags_set(vma, VM_IO);
    -	vma->vm_private_data = info;
    +	vma->vm_private_data = info->fbdefio_state;
    +
    +	fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
    +
     	return 0;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
    @@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
     	struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
     	struct fb_deferred_io_pageref *pageref, *next;
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	/* here we wrprotect the page's mappings, then do all deferred IO. */
    -	mutex_lock(&fbdefio->lock);
    +	mutex_lock(&fbdefio_state->lock);
     #ifdef CONFIG_MMU
     	list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
     		struct page *page = pageref->page;
    @@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
     	list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
     		fb_deferred_io_pageref_put(pageref, info);
     
    -	mutex_unlock(&fbdefio->lock);
    +	mutex_unlock(&fbdefio_state->lock);
     }
     
     int fb_deferred_io_init(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     	struct fb_deferred_io_pageref *pagerefs;
     	unsigned long npagerefs;
     	int ret;
    @@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
     	if (WARN_ON(!info->fix.smem_len))
     		return -EINVAL;
     
    -	mutex_init(&fbdefio->lock);
    +	fbdefio_state = fb_deferred_io_state_alloc();
    +	if (!fbdefio_state)
    +		return -ENOMEM;
    +	fbdefio_state->info = info;
    +
     	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
     	INIT_LIST_HEAD(&fbdefio->pagereflist);
     	if (fbdefio->delay == 0) /* set a default of 1 s */
    @@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
     	info->npagerefs = npagerefs;
     	info->pagerefs = pagerefs;
     
    +	info->fbdefio_state = fbdefio_state;
    +
     	return 0;
     
     err:
    -	mutex_destroy(&fbdefio->lock);
    +	fb_deferred_io_state_release(fbdefio_state);
     	return ret;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_init);
    @@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
     void fb_deferred_io_cleanup(struct fb_info *info)
     {
     	struct fb_deferred_io *fbdefio = info->fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
     
     	fb_deferred_io_lastclose(info);
     
    +	info->fbdefio_state = NULL;
    +
    +	mutex_lock(&fbdefio_state->lock);
    +	fbdefio_state->info = NULL;
    +	mutex_unlock(&fbdefio_state->lock);
    +
    +	fb_deferred_io_state_put(fbdefio_state);
    +
     	kvfree(info->pagerefs);
    -	mutex_destroy(&fbdefio->lock);
     	fbdefio->mapping = NULL;
     }
     EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index c3302d51354660..da2fdabd18cb3a 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -217,13 +217,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -490,6 +491,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    
  • include/linux/fb.h+3 2 modified
    diff --git a/include/linux/fb.h b/include/linux/fb.h
    index c3302d51354660..da2fdabd18cb3a 100644
    --- a/include/linux/fb.h
    +++ b/include/linux/fb.h
    @@ -217,13 +217,14 @@ struct fb_deferred_io {
     	unsigned long delay;
     	bool sort_pagereflist; /* sort pagelist by offset */
     	int open_count; /* number of opened files; protected by fb_info lock */
    -	struct mutex lock; /* mutex that protects the pageref list */
     	struct list_head pagereflist; /* list of pagerefs for touched pages */
     	struct address_space *mapping; /* page cache object for fb device */
     	/* callback */
     	struct page *(*get_page)(struct fb_info *info, unsigned long offset);
     	void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
     };
    +
    +struct fb_deferred_io_state;
     #endif
     
     /*
    @@ -490,6 +491,7 @@ struct fb_info {
     	unsigned long npagerefs;
     	struct fb_deferred_io_pageref *pagerefs;
     	struct fb_deferred_io *fbdefio;
    +	struct fb_deferred_io_state *fbdefio_state;
     #endif
     
     	const struct fb_ops *fbops;
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Use-after-free: deferred I/O state (mutex and fb_info pointer) was embedded in struct fb_info and freed on device hot-unplug while user-space mmap mappings still referenced it."

Attack vector

An attacker with access to a framebuffer device that uses deferred I/O (e.g., a USB graphics adapter) can `mmap` the framebuffer memory and then trigger a device hot-unplug (physically disconnect the device or trigger a driver unbind). While the user-space mapping remains open, any page fault (read or write access to the mapped memory) will dereference the freed `struct fb_info` pointer stored in `vma->vm_private_data`, operating on undefined kernel memory. No special privileges beyond the ability to open and mmap the fbdev device are required; the precondition is that the device supports deferred I/O and can be hot-unplugged.

Affected code

The vulnerability is in `drivers/video/fbdev/core/fb_defio.c` and `include/linux/fb.h`. The fault lies in the deferred I/O (defio) subsystem, where `vma->vm_private_data` was set to a raw pointer to `struct fb_info`, and the mutex protecting the pageref list lived inside `struct fb_deferred_io` (which is embedded in `struct fb_info`). When the device is hot-unplugged, `struct fb_info` is freed while user-space memory mappings still reference it via `vm_private_data`, leading to use-after-free on the mutex and the info structure [patch_id=2659999].

What the fix does

The patch introduces a new reference-counted structure `struct fb_deferred_io_state` that holds the mutex and a back-pointer to `struct fb_info`. This state object is allocated in `fb_deferred_io_init()` and freed only when the last `vma` mapping is closed (via `kref` in `vm_open`/`vm_close`). In `fb_deferred_io_cleanup()`, the back-pointer `fbdefio_state->info` is set to NULL under the lock, and `info->fbdefio_state` is cleared. Any subsequent page fault checks `fbdefio_state->info` and returns `VM_FAULT_SIGBUS` if the device is gone, preventing use-after-free. The mutex is now in the separately allocated state object, so it remains valid as long as any mapping exists [patch_id=2659999].

Preconditions

  • configThe framebuffer device must use deferred I/O (fbdefio is initialized).
  • inputAttacker must be able to mmap the framebuffer device and keep the mapping open.
  • networkThe device must support hot-unplug (e.g., USB display adapter, driver unbind).

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.