diff options
Diffstat (limited to 'drivers/gpu/drm/qxl/qxl_fb.c')
-rw-r--r-- | drivers/gpu/drm/qxl/qxl_fb.c | 184 |
1 files changed, 163 insertions, 21 deletions
diff --git a/drivers/gpu/drm/qxl/qxl_fb.c b/drivers/gpu/drm/qxl/qxl_fb.c index 76f39d88d684..88722f233430 100644 --- a/drivers/gpu/drm/qxl/qxl_fb.c +++ b/drivers/gpu/drm/qxl/qxl_fb.c | |||
@@ -37,12 +37,29 @@ | |||
37 | 37 | ||
38 | #define QXL_DIRTY_DELAY (HZ / 30) | 38 | #define QXL_DIRTY_DELAY (HZ / 30) |
39 | 39 | ||
40 | #define QXL_FB_OP_FILLRECT 1 | ||
41 | #define QXL_FB_OP_COPYAREA 2 | ||
42 | #define QXL_FB_OP_IMAGEBLIT 3 | ||
43 | |||
44 | struct qxl_fb_op { | ||
45 | struct list_head head; | ||
46 | int op_type; | ||
47 | union { | ||
48 | struct fb_fillrect fr; | ||
49 | struct fb_copyarea ca; | ||
50 | struct fb_image ib; | ||
51 | } op; | ||
52 | void *img_data; | ||
53 | }; | ||
54 | |||
40 | struct qxl_fbdev { | 55 | struct qxl_fbdev { |
41 | struct drm_fb_helper helper; | 56 | struct drm_fb_helper helper; |
42 | struct qxl_framebuffer qfb; | 57 | struct qxl_framebuffer qfb; |
43 | struct list_head fbdev_list; | 58 | struct list_head fbdev_list; |
44 | struct qxl_device *qdev; | 59 | struct qxl_device *qdev; |
45 | 60 | ||
61 | spinlock_t delayed_ops_lock; | ||
62 | struct list_head delayed_ops; | ||
46 | void *shadow; | 63 | void *shadow; |
47 | int size; | 64 | int size; |
48 | 65 | ||
@@ -164,8 +181,69 @@ static struct fb_deferred_io qxl_defio = { | |||
164 | .deferred_io = qxl_deferred_io, | 181 | .deferred_io = qxl_deferred_io, |
165 | }; | 182 | }; |
166 | 183 | ||
167 | static void qxl_fb_fillrect(struct fb_info *info, | 184 | static void qxl_fb_delayed_fillrect(struct qxl_fbdev *qfbdev, |
168 | const struct fb_fillrect *fb_rect) | 185 | const struct fb_fillrect *fb_rect) |
186 | { | ||
187 | struct qxl_fb_op *op; | ||
188 | unsigned long flags; | ||
189 | |||
190 | op = kmalloc(sizeof(struct qxl_fb_op), GFP_ATOMIC | __GFP_NOWARN); | ||
191 | if (!op) | ||
192 | return; | ||
193 | |||
194 | op->op.fr = *fb_rect; | ||
195 | op->img_data = NULL; | ||
196 | op->op_type = QXL_FB_OP_FILLRECT; | ||
197 | |||
198 | spin_lock_irqsave(&qfbdev->delayed_ops_lock, flags); | ||
199 | list_add_tail(&op->head, &qfbdev->delayed_ops); | ||
200 | spin_unlock_irqrestore(&qfbdev->delayed_ops_lock, flags); | ||
201 | } | ||
202 | |||
203 | static void qxl_fb_delayed_copyarea(struct qxl_fbdev *qfbdev, | ||
204 | const struct fb_copyarea *fb_copy) | ||
205 | { | ||
206 | struct qxl_fb_op *op; | ||
207 | unsigned long flags; | ||
208 | |||
209 | op = kmalloc(sizeof(struct qxl_fb_op), GFP_ATOMIC | __GFP_NOWARN); | ||
210 | if (!op) | ||
211 | return; | ||
212 | |||
213 | op->op.ca = *fb_copy; | ||
214 | op->img_data = NULL; | ||
215 | op->op_type = QXL_FB_OP_COPYAREA; | ||
216 | |||
217 | spin_lock_irqsave(&qfbdev->delayed_ops_lock, flags); | ||
218 | list_add_tail(&op->head, &qfbdev->delayed_ops); | ||
219 | spin_unlock_irqrestore(&qfbdev->delayed_ops_lock, flags); | ||
220 | } | ||
221 | |||
222 | static void qxl_fb_delayed_imageblit(struct qxl_fbdev *qfbdev, | ||
223 | const struct fb_image *fb_image) | ||
224 | { | ||
225 | struct qxl_fb_op *op; | ||
226 | unsigned long flags; | ||
227 | uint32_t size = fb_image->width * fb_image->height * (fb_image->depth >= 8 ? fb_image->depth / 8 : 1); | ||
228 | |||
229 | op = kmalloc(sizeof(struct qxl_fb_op) + size, GFP_ATOMIC | __GFP_NOWARN); | ||
230 | if (!op) | ||
231 | return; | ||
232 | |||
233 | op->op.ib = *fb_image; | ||
234 | op->img_data = (void *)(op + 1); | ||
235 | op->op_type = QXL_FB_OP_IMAGEBLIT; | ||
236 | |||
237 | memcpy(op->img_data, fb_image->data, size); | ||
238 | |||
239 | op->op.ib.data = op->img_data; | ||
240 | spin_lock_irqsave(&qfbdev->delayed_ops_lock, flags); | ||
241 | list_add_tail(&op->head, &qfbdev->delayed_ops); | ||
242 | spin_unlock_irqrestore(&qfbdev->delayed_ops_lock, flags); | ||
243 | } | ||
244 | |||
245 | static void qxl_fb_fillrect_internal(struct fb_info *info, | ||
246 | const struct fb_fillrect *fb_rect) | ||
169 | { | 247 | { |
170 | struct qxl_fbdev *qfbdev = info->par; | 248 | struct qxl_fbdev *qfbdev = info->par; |
171 | struct qxl_device *qdev = qfbdev->qdev; | 249 | struct qxl_device *qdev = qfbdev->qdev; |
@@ -203,17 +281,28 @@ static void qxl_fb_fillrect(struct fb_info *info, | |||
203 | qxl_draw_fill_rec.rect = rect; | 281 | qxl_draw_fill_rec.rect = rect; |
204 | qxl_draw_fill_rec.color = color; | 282 | qxl_draw_fill_rec.color = color; |
205 | qxl_draw_fill_rec.rop = rop; | 283 | qxl_draw_fill_rec.rop = rop; |
284 | |||
285 | qxl_draw_fill(&qxl_draw_fill_rec); | ||
286 | } | ||
287 | |||
288 | static void qxl_fb_fillrect(struct fb_info *info, | ||
289 | const struct fb_fillrect *fb_rect) | ||
290 | { | ||
291 | struct qxl_fbdev *qfbdev = info->par; | ||
292 | struct qxl_device *qdev = qfbdev->qdev; | ||
293 | |||
206 | if (!drm_can_sleep()) { | 294 | if (!drm_can_sleep()) { |
207 | qxl_io_log(qdev, | 295 | qxl_fb_delayed_fillrect(qfbdev, fb_rect); |
208 | "%s: TODO use RCU, mysterious locks with spin_lock\n", | 296 | schedule_work(&qdev->fb_work); |
209 | __func__); | ||
210 | return; | 297 | return; |
211 | } | 298 | } |
212 | qxl_draw_fill(&qxl_draw_fill_rec); | 299 | /* make sure any previous work is done */ |
300 | flush_work(&qdev->fb_work); | ||
301 | qxl_fb_fillrect_internal(info, fb_rect); | ||
213 | } | 302 | } |
214 | 303 | ||
215 | static void qxl_fb_copyarea(struct fb_info *info, | 304 | static void qxl_fb_copyarea_internal(struct fb_info *info, |
216 | const struct fb_copyarea *region) | 305 | const struct fb_copyarea *region) |
217 | { | 306 | { |
218 | struct qxl_fbdev *qfbdev = info->par; | 307 | struct qxl_fbdev *qfbdev = info->par; |
219 | 308 | ||
@@ -223,37 +312,89 @@ static void qxl_fb_copyarea(struct fb_info *info, | |||
223 | region->dx, region->dy); | 312 | region->dx, region->dy); |
224 | } | 313 | } |
225 | 314 | ||
315 | static void qxl_fb_copyarea(struct fb_info *info, | ||
316 | const struct fb_copyarea *region) | ||
317 | { | ||
318 | struct qxl_fbdev *qfbdev = info->par; | ||
319 | struct qxl_device *qdev = qfbdev->qdev; | ||
320 | |||
321 | if (!drm_can_sleep()) { | ||
322 | qxl_fb_delayed_copyarea(qfbdev, region); | ||
323 | schedule_work(&qdev->fb_work); | ||
324 | return; | ||
325 | } | ||
326 | /* make sure any previous work is done */ | ||
327 | flush_work(&qdev->fb_work); | ||
328 | qxl_fb_copyarea_internal(info, region); | ||
329 | } | ||
330 | |||
226 | static void qxl_fb_imageblit_safe(struct qxl_fb_image *qxl_fb_image) | 331 | static void qxl_fb_imageblit_safe(struct qxl_fb_image *qxl_fb_image) |
227 | { | 332 | { |
228 | qxl_draw_opaque_fb(qxl_fb_image, 0); | 333 | qxl_draw_opaque_fb(qxl_fb_image, 0); |
229 | } | 334 | } |
230 | 335 | ||
336 | static void qxl_fb_imageblit_internal(struct fb_info *info, | ||
337 | const struct fb_image *image) | ||
338 | { | ||
339 | struct qxl_fbdev *qfbdev = info->par; | ||
340 | struct qxl_fb_image qxl_fb_image; | ||
341 | |||
342 | /* ensure proper order rendering operations - TODO: must do this | ||
343 | * for everything. */ | ||
344 | qxl_fb_image_init(&qxl_fb_image, qfbdev->qdev, info, image); | ||
345 | qxl_fb_imageblit_safe(&qxl_fb_image); | ||
346 | } | ||
347 | |||
231 | static void qxl_fb_imageblit(struct fb_info *info, | 348 | static void qxl_fb_imageblit(struct fb_info *info, |
232 | const struct fb_image *image) | 349 | const struct fb_image *image) |
233 | { | 350 | { |
234 | struct qxl_fbdev *qfbdev = info->par; | 351 | struct qxl_fbdev *qfbdev = info->par; |
235 | struct qxl_device *qdev = qfbdev->qdev; | 352 | struct qxl_device *qdev = qfbdev->qdev; |
236 | struct qxl_fb_image qxl_fb_image; | ||
237 | 353 | ||
238 | if (!drm_can_sleep()) { | 354 | if (!drm_can_sleep()) { |
239 | /* we cannot do any ttm_bo allocation since that will fail on | 355 | qxl_fb_delayed_imageblit(qfbdev, image); |
240 | * ioremap_wc..__get_vm_area_node, so queue the work item | 356 | schedule_work(&qdev->fb_work); |
241 | * instead This can happen from printk inside an interrupt | ||
242 | * context, i.e.: smp_apic_timer_interrupt..check_cpu_stall */ | ||
243 | qxl_io_log(qdev, | ||
244 | "%s: TODO use RCU, mysterious locks with spin_lock\n", | ||
245 | __func__); | ||
246 | return; | 357 | return; |
247 | } | 358 | } |
359 | /* make sure any previous work is done */ | ||
360 | flush_work(&qdev->fb_work); | ||
361 | qxl_fb_imageblit_internal(info, image); | ||
362 | } | ||
248 | 363 | ||
249 | /* ensure proper order of rendering operations - TODO: must do this | 364 | static void qxl_fb_work(struct work_struct *work) |
250 | * for everything. */ | 365 | { |
251 | qxl_fb_image_init(&qxl_fb_image, qfbdev->qdev, info, image); | 366 | struct qxl_device *qdev = container_of(work, struct qxl_device, fb_work); |
252 | qxl_fb_imageblit_safe(&qxl_fb_image); | 367 | unsigned long flags; |
368 | struct qxl_fb_op *entry, *tmp; | ||
369 | struct qxl_fbdev *qfbdev = qdev->mode_info.qfbdev; | ||
370 | |||
371 | /* since the irq context just adds entries to the end of the | ||
372 | list dropping the lock should be fine, as entry isn't modified | ||
373 | in the operation code */ | ||
374 | spin_lock_irqsave(&qfbdev->delayed_ops_lock, flags); | ||
375 | list_for_each_entry_safe(entry, tmp, &qfbdev->delayed_ops, head) { | ||
376 | spin_unlock_irqrestore(&qfbdev->delayed_ops_lock, flags); | ||
377 | switch (entry->op_type) { | ||
378 | case QXL_FB_OP_FILLRECT: | ||
379 | qxl_fb_fillrect_internal(qfbdev->helper.fbdev, &entry->op.fr); | ||
380 | break; | ||
381 | case QXL_FB_OP_COPYAREA: | ||
382 | qxl_fb_copyarea_internal(qfbdev->helper.fbdev, &entry->op.ca); | ||
383 | break; | ||
384 | case QXL_FB_OP_IMAGEBLIT: | ||
385 | qxl_fb_imageblit_internal(qfbdev->helper.fbdev, &entry->op.ib); | ||
386 | break; | ||
387 | } | ||
388 | spin_lock_irqsave(&qfbdev->delayed_ops_lock, flags); | ||
389 | list_del(&entry->head); | ||
390 | kfree(entry); | ||
391 | } | ||
392 | spin_unlock_irqrestore(&qfbdev->delayed_ops_lock, flags); | ||
253 | } | 393 | } |
254 | 394 | ||
255 | int qxl_fb_init(struct qxl_device *qdev) | 395 | int qxl_fb_init(struct qxl_device *qdev) |
256 | { | 396 | { |
397 | INIT_WORK(&qdev->fb_work, qxl_fb_work); | ||
257 | return 0; | 398 | return 0; |
258 | } | 399 | } |
259 | 400 | ||
@@ -536,7 +677,8 @@ int qxl_fbdev_init(struct qxl_device *qdev) | |||
536 | qfbdev->qdev = qdev; | 677 | qfbdev->qdev = qdev; |
537 | qdev->mode_info.qfbdev = qfbdev; | 678 | qdev->mode_info.qfbdev = qfbdev; |
538 | qfbdev->helper.funcs = &qxl_fb_helper_funcs; | 679 | qfbdev->helper.funcs = &qxl_fb_helper_funcs; |
539 | 680 | spin_lock_init(&qfbdev->delayed_ops_lock); | |
681 | INIT_LIST_HEAD(&qfbdev->delayed_ops); | ||
540 | ret = drm_fb_helper_init(qdev->ddev, &qfbdev->helper, | 682 | ret = drm_fb_helper_init(qdev->ddev, &qfbdev->helper, |
541 | qxl_num_crtc /* num_crtc - QXL supports just 1 */, | 683 | qxl_num_crtc /* num_crtc - QXL supports just 1 */, |
542 | QXLFB_CONN_LIMIT); | 684 | QXLFB_CONN_LIMIT); |