Commit | Line | Data |
---|---|---|
f64122c1 DA |
1 | /* |
2 | * Copyright © 2013 Red Hat | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | * DEALINGS IN THE SOFTWARE. | |
22 | * | |
23 | * Authors: | |
24 | * David Airlie | |
25 | */ | |
26 | #include <linux/module.h> | |
27 | #include <linux/fb.h> | |
28 | ||
29 | #include "drmP.h" | |
30 | #include "drm/drm.h" | |
31 | #include "drm/drm_crtc.h" | |
32 | #include "drm/drm_crtc_helper.h" | |
33 | #include "qxl_drv.h" | |
34 | ||
35 | #include "qxl_object.h" | |
36 | #include "drm_fb_helper.h" | |
37 | ||
38 | #define QXL_DIRTY_DELAY (HZ / 30) | |
39 | ||
0665f9f8 DA |
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 | ||
f64122c1 DA |
55 | struct qxl_fbdev { |
56 | struct drm_fb_helper helper; | |
57 | struct qxl_framebuffer qfb; | |
58 | struct list_head fbdev_list; | |
59 | struct qxl_device *qdev; | |
60 | ||
0665f9f8 DA |
61 | spinlock_t delayed_ops_lock; |
62 | struct list_head delayed_ops; | |
f64122c1 DA |
63 | void *shadow; |
64 | int size; | |
65 | ||
66 | /* dirty memory logging */ | |
67 | struct { | |
68 | spinlock_t lock; | |
69 | bool active; | |
70 | unsigned x1; | |
71 | unsigned y1; | |
72 | unsigned x2; | |
73 | unsigned y2; | |
74 | } dirty; | |
75 | }; | |
76 | ||
77 | static void qxl_fb_image_init(struct qxl_fb_image *qxl_fb_image, | |
78 | struct qxl_device *qdev, struct fb_info *info, | |
79 | const struct fb_image *image) | |
80 | { | |
81 | qxl_fb_image->qdev = qdev; | |
82 | if (info) { | |
83 | qxl_fb_image->visual = info->fix.visual; | |
84 | if (qxl_fb_image->visual == FB_VISUAL_TRUECOLOR || | |
85 | qxl_fb_image->visual == FB_VISUAL_DIRECTCOLOR) | |
86 | memcpy(&qxl_fb_image->pseudo_palette, | |
87 | info->pseudo_palette, | |
88 | sizeof(qxl_fb_image->pseudo_palette)); | |
89 | } else { | |
90 | /* fallback */ | |
91 | if (image->depth == 1) | |
92 | qxl_fb_image->visual = FB_VISUAL_MONO10; | |
93 | else | |
94 | qxl_fb_image->visual = FB_VISUAL_DIRECTCOLOR; | |
95 | } | |
96 | if (image) { | |
97 | memcpy(&qxl_fb_image->fb_image, image, | |
98 | sizeof(qxl_fb_image->fb_image)); | |
99 | } | |
100 | } | |
101 | ||
102 | static void qxl_fb_dirty_flush(struct fb_info *info) | |
103 | { | |
104 | struct qxl_fbdev *qfbdev = info->par; | |
105 | struct qxl_device *qdev = qfbdev->qdev; | |
106 | struct qxl_fb_image qxl_fb_image; | |
107 | struct fb_image *image = &qxl_fb_image.fb_image; | |
108 | u32 x1, x2, y1, y2; | |
109 | ||
110 | /* TODO: hard coding 32 bpp */ | |
111 | int stride = qfbdev->qfb.base.pitches[0] * 4; | |
112 | ||
113 | x1 = qfbdev->dirty.x1; | |
114 | x2 = qfbdev->dirty.x2; | |
115 | y1 = qfbdev->dirty.y1; | |
116 | y2 = qfbdev->dirty.y2; | |
117 | /* | |
118 | * we are using a shadow draw buffer, at qdev->surface0_shadow | |
119 | */ | |
120 | qxl_io_log(qdev, "dirty x[%d, %d], y[%d, %d]", x1, x2, y1, y2); | |
121 | image->dx = x1; | |
122 | image->dy = y1; | |
123 | image->width = x2 - x1; | |
124 | image->height = y2 - y1; | |
125 | image->fg_color = 0xffffffff; /* unused, just to avoid uninitialized | |
126 | warnings */ | |
127 | image->bg_color = 0; | |
128 | image->depth = 32; /* TODO: take from somewhere? */ | |
129 | image->cmap.start = 0; | |
130 | image->cmap.len = 0; | |
131 | image->cmap.red = NULL; | |
132 | image->cmap.green = NULL; | |
133 | image->cmap.blue = NULL; | |
134 | image->cmap.transp = NULL; | |
135 | image->data = qfbdev->shadow + (x1 * 4) + (stride * y1); | |
136 | ||
137 | qxl_fb_image_init(&qxl_fb_image, qdev, info, NULL); | |
138 | qxl_draw_opaque_fb(&qxl_fb_image, stride); | |
139 | qfbdev->dirty.x1 = 0; | |
140 | qfbdev->dirty.x2 = 0; | |
141 | qfbdev->dirty.y1 = 0; | |
142 | qfbdev->dirty.y2 = 0; | |
143 | } | |
144 | ||
145 | static void qxl_deferred_io(struct fb_info *info, | |
146 | struct list_head *pagelist) | |
147 | { | |
148 | struct qxl_fbdev *qfbdev = info->par; | |
149 | unsigned long start, end, min, max; | |
150 | struct page *page; | |
151 | int y1, y2; | |
152 | ||
153 | min = ULONG_MAX; | |
154 | max = 0; | |
155 | list_for_each_entry(page, pagelist, lru) { | |
156 | start = page->index << PAGE_SHIFT; | |
157 | end = start + PAGE_SIZE - 1; | |
158 | min = min(min, start); | |
159 | max = max(max, end); | |
160 | } | |
161 | ||
162 | if (min < max) { | |
163 | y1 = min / info->fix.line_length; | |
164 | y2 = (max / info->fix.line_length) + 1; | |
165 | ||
166 | /* TODO: add spin lock? */ | |
167 | /* spin_lock_irqsave(&qfbdev->dirty.lock, flags); */ | |
168 | qfbdev->dirty.x1 = 0; | |
169 | qfbdev->dirty.y1 = y1; | |
170 | qfbdev->dirty.x2 = info->var.xres; | |
171 | qfbdev->dirty.y2 = y2; | |
172 | /* spin_unlock_irqrestore(&qfbdev->dirty.lock, flags); */ | |
173 | } | |
174 | ||
175 | qxl_fb_dirty_flush(info); | |
176 | }; | |
177 | ||
178 | ||
6d01f1f5 | 179 | static struct fb_deferred_io qxl_defio = { |
f64122c1 DA |
180 | .delay = QXL_DIRTY_DELAY, |
181 | .deferred_io = qxl_deferred_io, | |
182 | }; | |
183 | ||
0665f9f8 DA |
184 | static void qxl_fb_delayed_fillrect(struct qxl_fbdev *qfbdev, |
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) | |
f64122c1 DA |
247 | { |
248 | struct qxl_fbdev *qfbdev = info->par; | |
249 | struct qxl_device *qdev = qfbdev->qdev; | |
250 | struct qxl_rect rect; | |
251 | uint32_t color; | |
252 | int x = fb_rect->dx; | |
253 | int y = fb_rect->dy; | |
254 | int width = fb_rect->width; | |
255 | int height = fb_rect->height; | |
256 | uint16_t rop; | |
257 | struct qxl_draw_fill qxl_draw_fill_rec; | |
258 | ||
259 | if (info->fix.visual == FB_VISUAL_TRUECOLOR || | |
260 | info->fix.visual == FB_VISUAL_DIRECTCOLOR) | |
261 | color = ((u32 *) (info->pseudo_palette))[fb_rect->color]; | |
262 | else | |
263 | color = fb_rect->color; | |
264 | rect.left = x; | |
265 | rect.right = x + width; | |
266 | rect.top = y; | |
267 | rect.bottom = y + height; | |
268 | switch (fb_rect->rop) { | |
269 | case ROP_XOR: | |
270 | rop = SPICE_ROPD_OP_XOR; | |
271 | break; | |
272 | case ROP_COPY: | |
273 | rop = SPICE_ROPD_OP_PUT; | |
274 | break; | |
275 | default: | |
276 | pr_err("qxl_fb_fillrect(): unknown rop, " | |
277 | "defaulting to SPICE_ROPD_OP_PUT\n"); | |
278 | rop = SPICE_ROPD_OP_PUT; | |
279 | } | |
280 | qxl_draw_fill_rec.qdev = qdev; | |
281 | qxl_draw_fill_rec.rect = rect; | |
282 | qxl_draw_fill_rec.color = color; | |
283 | qxl_draw_fill_rec.rop = rop; | |
0665f9f8 DA |
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 | ||
f64122c1 | 294 | if (!drm_can_sleep()) { |
0665f9f8 DA |
295 | qxl_fb_delayed_fillrect(qfbdev, fb_rect); |
296 | schedule_work(&qdev->fb_work); | |
f64122c1 DA |
297 | return; |
298 | } | |
0665f9f8 DA |
299 | /* make sure any previous work is done */ |
300 | flush_work(&qdev->fb_work); | |
301 | qxl_fb_fillrect_internal(info, fb_rect); | |
f64122c1 DA |
302 | } |
303 | ||
0665f9f8 DA |
304 | static void qxl_fb_copyarea_internal(struct fb_info *info, |
305 | const struct fb_copyarea *region) | |
f64122c1 DA |
306 | { |
307 | struct qxl_fbdev *qfbdev = info->par; | |
308 | ||
309 | qxl_draw_copyarea(qfbdev->qdev, | |
310 | region->width, region->height, | |
311 | region->sx, region->sy, | |
312 | region->dx, region->dy); | |
313 | } | |
314 | ||
0665f9f8 DA |
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 | ||
f64122c1 DA |
331 | static void qxl_fb_imageblit_safe(struct qxl_fb_image *qxl_fb_image) |
332 | { | |
333 | qxl_draw_opaque_fb(qxl_fb_image, 0); | |
334 | } | |
335 | ||
0665f9f8 DA |
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 | ||
f64122c1 DA |
348 | static void qxl_fb_imageblit(struct fb_info *info, |
349 | const struct fb_image *image) | |
350 | { | |
351 | struct qxl_fbdev *qfbdev = info->par; | |
352 | struct qxl_device *qdev = qfbdev->qdev; | |
f64122c1 DA |
353 | |
354 | if (!drm_can_sleep()) { | |
0665f9f8 DA |
355 | qxl_fb_delayed_imageblit(qfbdev, image); |
356 | schedule_work(&qdev->fb_work); | |
f64122c1 DA |
357 | return; |
358 | } | |
0665f9f8 DA |
359 | /* make sure any previous work is done */ |
360 | flush_work(&qdev->fb_work); | |
361 | qxl_fb_imageblit_internal(info, image); | |
362 | } | |
f64122c1 | 363 | |
0665f9f8 DA |
364 | static void qxl_fb_work(struct work_struct *work) |
365 | { | |
366 | struct qxl_device *qdev = container_of(work, struct qxl_device, fb_work); | |
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); | |
f64122c1 DA |
393 | } |
394 | ||
395 | int qxl_fb_init(struct qxl_device *qdev) | |
396 | { | |
0665f9f8 | 397 | INIT_WORK(&qdev->fb_work, qxl_fb_work); |
f64122c1 DA |
398 | return 0; |
399 | } | |
400 | ||
401 | static struct fb_ops qxlfb_ops = { | |
402 | .owner = THIS_MODULE, | |
403 | .fb_check_var = drm_fb_helper_check_var, | |
404 | .fb_set_par = drm_fb_helper_set_par, /* TODO: copy vmwgfx */ | |
405 | .fb_fillrect = qxl_fb_fillrect, | |
406 | .fb_copyarea = qxl_fb_copyarea, | |
407 | .fb_imageblit = qxl_fb_imageblit, | |
408 | .fb_pan_display = drm_fb_helper_pan_display, | |
409 | .fb_blank = drm_fb_helper_blank, | |
410 | .fb_setcmap = drm_fb_helper_setcmap, | |
411 | .fb_debug_enter = drm_fb_helper_debug_enter, | |
412 | .fb_debug_leave = drm_fb_helper_debug_leave, | |
413 | }; | |
414 | ||
415 | static void qxlfb_destroy_pinned_object(struct drm_gem_object *gobj) | |
416 | { | |
417 | struct qxl_bo *qbo = gem_to_qxl_bo(gobj); | |
418 | int ret; | |
419 | ||
420 | ret = qxl_bo_reserve(qbo, false); | |
421 | if (likely(ret == 0)) { | |
422 | qxl_bo_kunmap(qbo); | |
423 | qxl_bo_unpin(qbo); | |
424 | qxl_bo_unreserve(qbo); | |
425 | } | |
426 | drm_gem_object_unreference_unlocked(gobj); | |
427 | } | |
428 | ||
429 | int qxl_get_handle_for_primary_fb(struct qxl_device *qdev, | |
430 | struct drm_file *file_priv, | |
431 | uint32_t *handle) | |
432 | { | |
433 | int r; | |
434 | struct drm_gem_object *gobj = qdev->fbdev_qfb->obj; | |
435 | ||
436 | BUG_ON(!gobj); | |
437 | /* drm_get_handle_create adds a reference - good */ | |
438 | r = drm_gem_handle_create(file_priv, gobj, handle); | |
439 | if (r) | |
440 | return r; | |
441 | return 0; | |
442 | } | |
443 | ||
444 | static int qxlfb_create_pinned_object(struct qxl_fbdev *qfbdev, | |
445 | struct drm_mode_fb_cmd2 *mode_cmd, | |
446 | struct drm_gem_object **gobj_p) | |
447 | { | |
448 | struct qxl_device *qdev = qfbdev->qdev; | |
449 | struct drm_gem_object *gobj = NULL; | |
450 | struct qxl_bo *qbo = NULL; | |
451 | int ret; | |
452 | int aligned_size, size; | |
453 | int height = mode_cmd->height; | |
454 | int bpp; | |
455 | int depth; | |
456 | ||
457 | drm_fb_get_bpp_depth(mode_cmd->pixel_format, &bpp, &depth); | |
458 | ||
459 | size = mode_cmd->pitches[0] * height; | |
460 | aligned_size = ALIGN(size, PAGE_SIZE); | |
461 | /* TODO: unallocate and reallocate surface0 for real. Hack to just | |
462 | * have a large enough surface0 for 1024x768 Xorg 32bpp mode */ | |
463 | ret = qxl_gem_object_create(qdev, aligned_size, 0, | |
464 | QXL_GEM_DOMAIN_SURFACE, | |
465 | false, /* is discardable */ | |
466 | false, /* is kernel (false means device) */ | |
467 | NULL, | |
468 | &gobj); | |
469 | if (ret) { | |
470 | pr_err("failed to allocate framebuffer (%d)\n", | |
471 | aligned_size); | |
472 | return -ENOMEM; | |
473 | } | |
474 | qbo = gem_to_qxl_bo(gobj); | |
475 | ||
476 | qbo->surf.width = mode_cmd->width; | |
477 | qbo->surf.height = mode_cmd->height; | |
478 | qbo->surf.stride = mode_cmd->pitches[0]; | |
479 | qbo->surf.format = SPICE_SURFACE_FMT_32_xRGB; | |
480 | ret = qxl_bo_reserve(qbo, false); | |
481 | if (unlikely(ret != 0)) | |
482 | goto out_unref; | |
483 | ret = qxl_bo_pin(qbo, QXL_GEM_DOMAIN_SURFACE, NULL); | |
484 | if (ret) { | |
485 | qxl_bo_unreserve(qbo); | |
486 | goto out_unref; | |
487 | } | |
488 | ret = qxl_bo_kmap(qbo, NULL); | |
489 | qxl_bo_unreserve(qbo); /* unreserve, will be mmaped */ | |
490 | if (ret) | |
491 | goto out_unref; | |
492 | ||
493 | *gobj_p = gobj; | |
494 | return 0; | |
495 | out_unref: | |
496 | qxlfb_destroy_pinned_object(gobj); | |
497 | *gobj_p = NULL; | |
498 | return ret; | |
499 | } | |
500 | ||
501 | static int qxlfb_create(struct qxl_fbdev *qfbdev, | |
502 | struct drm_fb_helper_surface_size *sizes) | |
503 | { | |
504 | struct qxl_device *qdev = qfbdev->qdev; | |
505 | struct fb_info *info; | |
506 | struct drm_framebuffer *fb = NULL; | |
507 | struct drm_mode_fb_cmd2 mode_cmd; | |
508 | struct drm_gem_object *gobj = NULL; | |
509 | struct qxl_bo *qbo = NULL; | |
510 | struct device *device = &qdev->pdev->dev; | |
511 | int ret; | |
512 | int size; | |
513 | int bpp = sizes->surface_bpp; | |
514 | int depth = sizes->surface_depth; | |
515 | void *shadow; | |
516 | ||
517 | mode_cmd.width = sizes->surface_width; | |
518 | mode_cmd.height = sizes->surface_height; | |
519 | ||
520 | mode_cmd.pitches[0] = ALIGN(mode_cmd.width * ((bpp + 1) / 8), 64); | |
521 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp, depth); | |
522 | ||
523 | ret = qxlfb_create_pinned_object(qfbdev, &mode_cmd, &gobj); | |
524 | qbo = gem_to_qxl_bo(gobj); | |
525 | QXL_INFO(qdev, "%s: %dx%d %d\n", __func__, mode_cmd.width, | |
526 | mode_cmd.height, mode_cmd.pitches[0]); | |
527 | ||
528 | shadow = vmalloc(mode_cmd.pitches[0] * mode_cmd.height); | |
529 | /* TODO: what's the usual response to memory allocation errors? */ | |
530 | BUG_ON(!shadow); | |
531 | QXL_INFO(qdev, | |
532 | "surface0 at gpu offset %lld, mmap_offset %lld (virt %p, shadow %p)\n", | |
533 | qxl_bo_gpu_offset(qbo), | |
534 | qxl_bo_mmap_offset(qbo), | |
535 | qbo->kptr, | |
536 | shadow); | |
537 | size = mode_cmd.pitches[0] * mode_cmd.height; | |
538 | ||
539 | info = framebuffer_alloc(0, device); | |
540 | if (info == NULL) { | |
541 | ret = -ENOMEM; | |
542 | goto out_unref; | |
543 | } | |
544 | ||
545 | info->par = qfbdev; | |
546 | ||
547 | qxl_framebuffer_init(qdev->ddev, &qfbdev->qfb, &mode_cmd, gobj); | |
548 | ||
549 | fb = &qfbdev->qfb.base; | |
550 | ||
551 | /* setup helper with fb data */ | |
552 | qfbdev->helper.fb = fb; | |
553 | qfbdev->helper.fbdev = info; | |
554 | qfbdev->shadow = shadow; | |
555 | strcpy(info->fix.id, "qxldrmfb"); | |
556 | ||
557 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); | |
558 | ||
559 | info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT; | |
560 | info->fbops = &qxlfb_ops; | |
561 | ||
562 | /* | |
563 | * TODO: using gobj->size in various places in this function. Not sure | |
564 | * what the difference between the different sizes is. | |
565 | */ | |
566 | info->fix.smem_start = qdev->vram_base; /* TODO - correct? */ | |
567 | info->fix.smem_len = gobj->size; | |
568 | info->screen_base = qfbdev->shadow; | |
569 | info->screen_size = gobj->size; | |
570 | ||
571 | drm_fb_helper_fill_var(info, &qfbdev->helper, sizes->fb_width, | |
572 | sizes->fb_height); | |
573 | ||
574 | /* setup aperture base/size for vesafb takeover */ | |
575 | info->apertures = alloc_apertures(1); | |
576 | if (!info->apertures) { | |
577 | ret = -ENOMEM; | |
578 | goto out_unref; | |
579 | } | |
580 | info->apertures->ranges[0].base = qdev->ddev->mode_config.fb_base; | |
581 | info->apertures->ranges[0].size = qdev->vram_size; | |
582 | ||
583 | info->fix.mmio_start = 0; | |
584 | info->fix.mmio_len = 0; | |
585 | ||
586 | if (info->screen_base == NULL) { | |
587 | ret = -ENOSPC; | |
588 | goto out_unref; | |
589 | } | |
590 | ||
591 | ret = fb_alloc_cmap(&info->cmap, 256, 0); | |
592 | if (ret) { | |
593 | ret = -ENOMEM; | |
594 | goto out_unref; | |
595 | } | |
596 | ||
597 | info->fbdefio = &qxl_defio; | |
598 | fb_deferred_io_init(info); | |
599 | ||
600 | qdev->fbdev_info = info; | |
601 | qdev->fbdev_qfb = &qfbdev->qfb; | |
602 | DRM_INFO("fb mappable at 0x%lX, size %lu\n", info->fix.smem_start, (unsigned long)info->screen_size); | |
603 | DRM_INFO("fb: depth %d, pitch %d, width %d, height %d\n", fb->depth, fb->pitches[0], fb->width, fb->height); | |
604 | return 0; | |
605 | ||
606 | out_unref: | |
607 | if (qbo) { | |
608 | ret = qxl_bo_reserve(qbo, false); | |
609 | if (likely(ret == 0)) { | |
610 | qxl_bo_kunmap(qbo); | |
611 | qxl_bo_unpin(qbo); | |
612 | qxl_bo_unreserve(qbo); | |
613 | } | |
614 | } | |
615 | if (fb && ret) { | |
616 | drm_gem_object_unreference(gobj); | |
617 | drm_framebuffer_cleanup(fb); | |
618 | kfree(fb); | |
619 | } | |
620 | drm_gem_object_unreference(gobj); | |
621 | return ret; | |
622 | } | |
623 | ||
624 | static int qxl_fb_find_or_create_single( | |
625 | struct drm_fb_helper *helper, | |
626 | struct drm_fb_helper_surface_size *sizes) | |
627 | { | |
628 | struct qxl_fbdev *qfbdev = (struct qxl_fbdev *)helper; | |
629 | int new_fb = 0; | |
630 | int ret; | |
631 | ||
632 | if (!helper->fb) { | |
633 | ret = qxlfb_create(qfbdev, sizes); | |
634 | if (ret) | |
635 | return ret; | |
636 | new_fb = 1; | |
637 | } | |
638 | return new_fb; | |
639 | } | |
640 | ||
641 | static int qxl_fbdev_destroy(struct drm_device *dev, struct qxl_fbdev *qfbdev) | |
642 | { | |
643 | struct fb_info *info; | |
644 | struct qxl_framebuffer *qfb = &qfbdev->qfb; | |
645 | ||
646 | if (qfbdev->helper.fbdev) { | |
647 | info = qfbdev->helper.fbdev; | |
648 | ||
649 | unregister_framebuffer(info); | |
650 | framebuffer_release(info); | |
651 | } | |
652 | if (qfb->obj) { | |
653 | qxlfb_destroy_pinned_object(qfb->obj); | |
654 | qfb->obj = NULL; | |
655 | } | |
656 | drm_fb_helper_fini(&qfbdev->helper); | |
657 | vfree(qfbdev->shadow); | |
658 | drm_framebuffer_cleanup(&qfb->base); | |
659 | ||
660 | return 0; | |
661 | } | |
662 | ||
663 | static struct drm_fb_helper_funcs qxl_fb_helper_funcs = { | |
f64122c1 DA |
664 | .fb_probe = qxl_fb_find_or_create_single, |
665 | }; | |
666 | ||
667 | int qxl_fbdev_init(struct qxl_device *qdev) | |
668 | { | |
669 | struct qxl_fbdev *qfbdev; | |
670 | int bpp_sel = 32; /* TODO: parameter from somewhere? */ | |
671 | int ret; | |
672 | ||
673 | qfbdev = kzalloc(sizeof(struct qxl_fbdev), GFP_KERNEL); | |
674 | if (!qfbdev) | |
675 | return -ENOMEM; | |
676 | ||
677 | qfbdev->qdev = qdev; | |
678 | qdev->mode_info.qfbdev = qfbdev; | |
679 | qfbdev->helper.funcs = &qxl_fb_helper_funcs; | |
0665f9f8 DA |
680 | spin_lock_init(&qfbdev->delayed_ops_lock); |
681 | INIT_LIST_HEAD(&qfbdev->delayed_ops); | |
f64122c1 | 682 | ret = drm_fb_helper_init(qdev->ddev, &qfbdev->helper, |
07f8d9bd | 683 | qxl_num_crtc /* num_crtc - QXL supports just 1 */, |
f64122c1 DA |
684 | QXLFB_CONN_LIMIT); |
685 | if (ret) { | |
686 | kfree(qfbdev); | |
687 | return ret; | |
688 | } | |
689 | ||
690 | drm_fb_helper_single_add_all_connectors(&qfbdev->helper); | |
691 | drm_fb_helper_initial_config(&qfbdev->helper, bpp_sel); | |
692 | return 0; | |
693 | } | |
694 | ||
695 | void qxl_fbdev_fini(struct qxl_device *qdev) | |
696 | { | |
697 | if (!qdev->mode_info.qfbdev) | |
698 | return; | |
699 | ||
700 | qxl_fbdev_destroy(qdev->ddev, qdev->mode_info.qfbdev); | |
701 | kfree(qdev->mode_info.qfbdev); | |
702 | qdev->mode_info.qfbdev = NULL; | |
703 | } | |
704 | ||
b86487a6 DA |
705 | void qxl_fbdev_set_suspend(struct qxl_device *qdev, int state) |
706 | { | |
707 | fb_set_suspend(qdev->mode_info.qfbdev->helper.fbdev, state); | |
708 | } | |
f64122c1 | 709 | |
b86487a6 DA |
710 | bool qxl_fbdev_qobj_is_fb(struct qxl_device *qdev, struct qxl_bo *qobj) |
711 | { | |
712 | if (qobj == gem_to_qxl_bo(qdev->mode_info.qfbdev->qfb.obj)) | |
713 | return true; | |
714 | return false; | |
715 | } |