Commit | Line | Data |
---|---|---|
60b59bea JK |
1 | /* |
2 | * linux/drivers/video/fb_defio.c | |
3 | * | |
4 | * Copyright (C) 2006 Jaya Kumar | |
5 | * | |
6 | * This file is subject to the terms and conditions of the GNU General Public | |
de7c6d15 | 7 | * License. See the file COPYING in the main directory of this archive |
60b59bea JK |
8 | * for more details. |
9 | */ | |
10 | ||
11 | #include <linux/module.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/errno.h> | |
14 | #include <linux/string.h> | |
15 | #include <linux/mm.h> | |
60b59bea JK |
16 | #include <linux/vmalloc.h> |
17 | #include <linux/delay.h> | |
18 | #include <linux/interrupt.h> | |
19 | #include <linux/fb.h> | |
20 | #include <linux/list.h> | |
60b59bea JK |
21 | |
22 | /* to support deferred IO */ | |
23 | #include <linux/rmap.h> | |
24 | #include <linux/pagemap.h> | |
25 | ||
1ecbc7dd | 26 | static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs) |
37b48379 | 27 | { |
747bda7b | 28 | struct fb_deferred_io *fbdefio = info->fbdefio; |
abc79a0e | 29 | const void *screen_buffer = info->screen_buffer; |
dcaefc82 | 30 | struct page *page = NULL; |
37b48379 | 31 | |
747bda7b TZ |
32 | if (fbdefio->get_page) |
33 | return fbdefio->get_page(info, offs); | |
34 | ||
abc79a0e TZ |
35 | if (is_vmalloc_addr(screen_buffer + offs)) |
36 | page = vmalloc_to_page(screen_buffer + offs); | |
dcaefc82 | 37 | else if (info->fix.smem_start) |
37b48379 MD |
38 | page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT); |
39 | ||
1ecbc7dd TZ |
40 | if (page) |
41 | get_page(page); | |
42 | ||
37b48379 MD |
43 | return page; |
44 | } | |
45 | ||
757a2713 TZ |
46 | static struct fb_deferred_io_pageref *fb_deferred_io_pageref_lookup(struct fb_info *info, |
47 | unsigned long offset, | |
48 | struct page *page) | |
49 | { | |
50 | unsigned long pgoff = offset >> PAGE_SHIFT; | |
51 | struct fb_deferred_io_pageref *pageref; | |
52 | ||
53 | if (fb_WARN_ON_ONCE(info, pgoff >= info->npagerefs)) | |
54 | return NULL; /* incorrect allocation size */ | |
55 | ||
56 | /* 1:1 mapping between pageref and page offset */ | |
57 | pageref = &info->pagerefs[pgoff]; | |
58 | ||
59 | if (pageref->page) | |
60 | goto out; | |
61 | ||
62 | pageref->page = page; | |
63 | pageref->offset = pgoff << PAGE_SHIFT; | |
64 | INIT_LIST_HEAD(&pageref->list); | |
65 | ||
66 | out: | |
67 | if (fb_WARN_ON_ONCE(info, pageref->page != page)) | |
68 | return NULL; /* inconsistent state */ | |
69 | return pageref; | |
70 | } | |
71 | ||
28aea43c TZ |
72 | static void fb_deferred_io_pageref_clear(struct fb_deferred_io_pageref *pageref) |
73 | { | |
74 | struct page *page = pageref->page; | |
75 | ||
76 | if (page) | |
77 | page->mapping = NULL; | |
78 | } | |
79 | ||
56c134f7 TZ |
80 | static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info *info, |
81 | unsigned long offset, | |
82 | struct page *page) | |
83 | { | |
84 | struct fb_deferred_io *fbdefio = info->fbdefio; | |
e80eec1b | 85 | struct list_head *pos = &fbdefio->pagereflist; |
56c134f7 TZ |
86 | struct fb_deferred_io_pageref *pageref, *cur; |
87 | ||
757a2713 TZ |
88 | pageref = fb_deferred_io_pageref_lookup(info, offset, page); |
89 | if (!pageref) | |
90 | return NULL; | |
56c134f7 TZ |
91 | |
92 | /* | |
93 | * This check is to catch the case where a new process could start | |
94 | * writing to the same page through a new PTE. This new access | |
95 | * can cause a call to .page_mkwrite even if the original process' | |
96 | * PTE is marked writable. | |
97 | */ | |
98 | if (!list_empty(&pageref->list)) | |
99 | goto pageref_already_added; | |
100 | ||
e80eec1b | 101 | if (unlikely(fbdefio->sort_pagereflist)) { |
56c134f7 TZ |
102 | /* |
103 | * We loop through the list of pagerefs before adding in | |
104 | * order to keep the pagerefs sorted. This has significant | |
105 | * overhead of O(n^2) with n being the number of written | |
106 | * pages. If possible, drivers should try to work with | |
107 | * unsorted page lists instead. | |
108 | */ | |
e80eec1b | 109 | list_for_each_entry(cur, &fbdefio->pagereflist, list) { |
56c134f7 TZ |
110 | if (cur->offset > pageref->offset) |
111 | break; | |
112 | } | |
113 | pos = &cur->list; | |
114 | } | |
115 | ||
116 | list_add_tail(&pageref->list, pos); | |
117 | ||
118 | pageref_already_added: | |
119 | return pageref; | |
120 | } | |
121 | ||
122 | static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref, | |
123 | struct fb_info *info) | |
124 | { | |
125 | list_del_init(&pageref->list); | |
126 | } | |
127 | ||
60b59bea | 128 | /* this is to find and return the vmalloc-ed fb pages */ |
c8b54776 | 129 | static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf) |
60b59bea JK |
130 | { |
131 | unsigned long offset; | |
132 | struct page *page; | |
11bac800 | 133 | struct fb_info *info = vmf->vma->vm_private_data; |
60b59bea | 134 | |
529e55b6 | 135 | offset = vmf->pgoff << PAGE_SHIFT; |
60b59bea | 136 | if (offset >= info->fix.smem_len) |
529e55b6 | 137 | return VM_FAULT_SIGBUS; |
60b59bea | 138 | |
1ecbc7dd | 139 | page = fb_deferred_io_get_page(info, offset); |
60b59bea | 140 | if (!page) |
529e55b6 | 141 | return VM_FAULT_SIGBUS; |
60b59bea | 142 | |
0b78f8bc MW |
143 | if (vmf->vma->vm_file) |
144 | page->mapping = vmf->vma->vm_file->f_mapping; | |
145 | else | |
146 | printk(KERN_ERR "no mapping available\n"); | |
147 | ||
148 | BUG_ON(!page->mapping); | |
a929e0d1 | 149 | page->index = vmf->pgoff; /* for folio_mkclean() */ |
de7c6d15 | 150 | |
529e55b6 NP |
151 | vmf->page = page; |
152 | return 0; | |
60b59bea JK |
153 | } |
154 | ||
02c24a82 | 155 | int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync) |
5e841b88 PM |
156 | { |
157 | struct fb_info *info = file->private_data; | |
496ad9aa | 158 | struct inode *inode = file_inode(file); |
3b49c9a1 | 159 | int err = file_write_and_wait_range(file, start, end); |
02c24a82 JB |
160 | if (err) |
161 | return err; | |
5e841b88 | 162 | |
94e2bd68 | 163 | /* Skip if deferred io is compiled-in but disabled on this fbdev */ |
87884bd8 MD |
164 | if (!info->fbdefio) |
165 | return 0; | |
166 | ||
5955102c | 167 | inode_lock(inode); |
15e4c1f4 | 168 | flush_delayed_work(&info->deferred_work); |
5955102c | 169 | inode_unlock(inode); |
30ea9c52 TV |
170 | |
171 | return 0; | |
5e841b88 PM |
172 | } |
173 | EXPORT_SYMBOL_GPL(fb_deferred_io_fsync); | |
174 | ||
3ed38112 TZ |
175 | /* |
176 | * Adds a page to the dirty list. Call this from struct | |
177 | * vm_operations_struct.page_mkwrite. | |
178 | */ | |
179 | static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset, | |
180 | struct page *page) | |
60b59bea | 181 | { |
60b59bea | 182 | struct fb_deferred_io *fbdefio = info->fbdefio; |
56c134f7 | 183 | struct fb_deferred_io_pageref *pageref; |
56c134f7 TZ |
184 | vm_fault_t ret; |
185 | ||
60b59bea JK |
186 | /* protect against the workqueue changing the page list */ |
187 | mutex_lock(&fbdefio->lock); | |
f31ad92f | 188 | |
56c134f7 TZ |
189 | pageref = fb_deferred_io_pageref_get(info, offset, page); |
190 | if (WARN_ON_ONCE(!pageref)) { | |
191 | ret = VM_FAULT_OOM; | |
192 | goto err_mutex_unlock; | |
193 | } | |
194 | ||
d6d03f91 AH |
195 | /* |
196 | * We want the page to remain locked from ->page_mkwrite until | |
a929e0d1 | 197 | * the PTE is marked dirty to avoid folio_mkclean() being called |
d6d03f91 AH |
198 | * before the PTE is updated, which would leave the page ignored |
199 | * by defio. | |
200 | * Do this by locking the page here and informing the caller | |
201 | * about it with VM_FAULT_LOCKED. | |
202 | */ | |
56c134f7 | 203 | lock_page(pageref->page); |
f31ad92f | 204 | |
60b59bea JK |
205 | mutex_unlock(&fbdefio->lock); |
206 | ||
207 | /* come back after delay to process the deferred IO */ | |
208 | schedule_delayed_work(&info->deferred_work, fbdefio->delay); | |
d6d03f91 | 209 | return VM_FAULT_LOCKED; |
56c134f7 TZ |
210 | |
211 | err_mutex_unlock: | |
212 | mutex_unlock(&fbdefio->lock); | |
213 | return ret; | |
60b59bea JK |
214 | } |
215 | ||
3ed38112 TZ |
216 | /* |
217 | * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O | |
218 | * @fb_info: The fbdev info structure | |
219 | * @vmf: The VM fault | |
220 | * | |
221 | * This is a callback we get when userspace first tries to | |
222 | * write to the page. We schedule a workqueue. That workqueue | |
223 | * will eventually mkclean the touched pages and execute the | |
224 | * deferred framebuffer IO. Then if userspace touches a page | |
225 | * again, we repeat the same scheme. | |
226 | * | |
227 | * Returns: | |
228 | * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise. | |
229 | */ | |
230 | static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf) | |
231 | { | |
78d9161d | 232 | unsigned long offset = vmf->pgoff << PAGE_SHIFT; |
3ed38112 TZ |
233 | struct page *page = vmf->page; |
234 | ||
235 | file_update_time(vmf->vma->vm_file); | |
236 | ||
237 | return fb_deferred_io_track_page(info, offset, page); | |
238 | } | |
239 | ||
240 | /* vm_ops->page_mkwrite handler */ | |
241 | static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) | |
242 | { | |
243 | struct fb_info *info = vmf->vma->vm_private_data; | |
244 | ||
245 | return fb_deferred_io_page_mkwrite(info, vmf); | |
246 | } | |
247 | ||
f0f37e2f | 248 | static const struct vm_operations_struct fb_deferred_io_vm_ops = { |
529e55b6 | 249 | .fault = fb_deferred_io_fault, |
60b59bea JK |
250 | .page_mkwrite = fb_deferred_io_mkwrite, |
251 | }; | |
252 | ||
0b78f8bc | 253 | static const struct address_space_operations fb_deferred_io_aops = { |
51cdea7a | 254 | .dirty_folio = noop_dirty_folio, |
0b78f8bc MW |
255 | }; |
256 | ||
ba026334 | 257 | int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma) |
60b59bea | 258 | { |
76f92201 TZ |
259 | vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); |
260 | ||
60b59bea | 261 | vma->vm_ops = &fb_deferred_io_vm_ops; |
1c71222e | 262 | vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP); |
7164bb43 | 263 | if (!(info->flags & FBINFO_VIRTFB)) |
1c71222e | 264 | vm_flags_set(vma, VM_IO); |
60b59bea JK |
265 | vma->vm_private_data = info; |
266 | return 0; | |
267 | } | |
59055851 | 268 | EXPORT_SYMBOL_GPL(fb_deferred_io_mmap); |
60b59bea JK |
269 | |
270 | /* workqueue callback */ | |
271 | static void fb_deferred_io_work(struct work_struct *work) | |
272 | { | |
56c134f7 TZ |
273 | struct fb_info *info = container_of(work, struct fb_info, deferred_work.work); |
274 | struct fb_deferred_io_pageref *pageref, *next; | |
60b59bea JK |
275 | struct fb_deferred_io *fbdefio = info->fbdefio; |
276 | ||
277 | /* here we mkclean the pages, then do all deferred IO */ | |
278 | mutex_lock(&fbdefio->lock); | |
e80eec1b | 279 | list_for_each_entry(pageref, &fbdefio->pagereflist, list) { |
645b1399 KW |
280 | struct folio *folio = page_folio(pageref->page); |
281 | ||
282 | folio_lock(folio); | |
283 | folio_mkclean(folio); | |
284 | folio_unlock(folio); | |
60b59bea JK |
285 | } |
286 | ||
e80eec1b TZ |
287 | /* driver's callback with pagereflist */ |
288 | fbdefio->deferred_io(info, &fbdefio->pagereflist); | |
60b59bea | 289 | |
3f505ca4 | 290 | /* clear the list */ |
e80eec1b | 291 | list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list) |
56c134f7 TZ |
292 | fb_deferred_io_pageref_put(pageref, info); |
293 | ||
60b59bea JK |
294 | mutex_unlock(&fbdefio->lock); |
295 | } | |
296 | ||
56c134f7 | 297 | int fb_deferred_io_init(struct fb_info *info) |
60b59bea JK |
298 | { |
299 | struct fb_deferred_io *fbdefio = info->fbdefio; | |
56c134f7 | 300 | struct fb_deferred_io_pageref *pagerefs; |
757a2713 | 301 | unsigned long npagerefs; |
56c134f7 | 302 | int ret; |
60b59bea JK |
303 | |
304 | BUG_ON(!fbdefio); | |
56c134f7 TZ |
305 | |
306 | if (WARN_ON(!info->fix.smem_len)) | |
307 | return -EINVAL; | |
308 | ||
60b59bea | 309 | mutex_init(&fbdefio->lock); |
60b59bea | 310 | INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); |
e80eec1b | 311 | INIT_LIST_HEAD(&fbdefio->pagereflist); |
60b59bea JK |
312 | if (fbdefio->delay == 0) /* set a default of 1 s */ |
313 | fbdefio->delay = HZ; | |
856082f0 | 314 | |
56c134f7 TZ |
315 | npagerefs = DIV_ROUND_UP(info->fix.smem_len, PAGE_SIZE); |
316 | ||
317 | /* alloc a page ref for each page of the display memory */ | |
318 | pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL); | |
319 | if (!pagerefs) { | |
320 | ret = -ENOMEM; | |
321 | goto err; | |
856082f0 | 322 | } |
56c134f7 TZ |
323 | info->npagerefs = npagerefs; |
324 | info->pagerefs = pagerefs; | |
325 | ||
326 | return 0; | |
327 | ||
328 | err: | |
329 | mutex_destroy(&fbdefio->lock); | |
330 | return ret; | |
60b59bea JK |
331 | } |
332 | EXPORT_SYMBOL_GPL(fb_deferred_io_init); | |
333 | ||
0b78f8bc MW |
334 | void fb_deferred_io_open(struct fb_info *info, |
335 | struct inode *inode, | |
336 | struct file *file) | |
337 | { | |
fe9ae05c TI |
338 | struct fb_deferred_io *fbdefio = info->fbdefio; |
339 | ||
0b78f8bc | 340 | file->f_mapping->a_ops = &fb_deferred_io_aops; |
fe9ae05c | 341 | fbdefio->open_count++; |
0b78f8bc MW |
342 | } |
343 | EXPORT_SYMBOL_GPL(fb_deferred_io_open); | |
344 | ||
fe9ae05c | 345 | static void fb_deferred_io_lastclose(struct fb_info *info) |
60b59bea | 346 | { |
28aea43c | 347 | unsigned long i; |
60b59bea | 348 | |
33cd6ea9 | 349 | flush_delayed_work(&info->deferred_work); |
0b78f8bc MW |
350 | |
351 | /* clear out the mapping that we setup */ | |
28aea43c TZ |
352 | for (i = 0; i < info->npagerefs; ++i) |
353 | fb_deferred_io_pageref_clear(&info->pagerefs[i]); | |
3efc61d9 | 354 | } |
fe9ae05c TI |
355 | |
356 | void fb_deferred_io_release(struct fb_info *info) | |
357 | { | |
358 | struct fb_deferred_io *fbdefio = info->fbdefio; | |
359 | ||
360 | if (!--fbdefio->open_count) | |
361 | fb_deferred_io_lastclose(info); | |
362 | } | |
3efc61d9 TI |
363 | EXPORT_SYMBOL_GPL(fb_deferred_io_release); |
364 | ||
365 | void fb_deferred_io_cleanup(struct fb_info *info) | |
366 | { | |
367 | struct fb_deferred_io *fbdefio = info->fbdefio; | |
368 | ||
fe9ae05c | 369 | fb_deferred_io_lastclose(info); |
0b78f8bc | 370 | |
56c134f7 | 371 | kvfree(info->pagerefs); |
6e1038a9 | 372 | mutex_destroy(&fbdefio->lock); |
60b59bea JK |
373 | } |
374 | EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); |