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 | ||
40 | struct qxl_fbdev { | |
41 | struct drm_fb_helper helper; | |
42 | struct qxl_framebuffer qfb; | |
43 | struct list_head fbdev_list; | |
44 | struct qxl_device *qdev; | |
45 | ||
46 | void *shadow; | |
47 | int size; | |
48 | ||
49 | /* dirty memory logging */ | |
50 | struct { | |
51 | spinlock_t lock; | |
52 | bool active; | |
53 | unsigned x1; | |
54 | unsigned y1; | |
55 | unsigned x2; | |
56 | unsigned y2; | |
57 | } dirty; | |
58 | }; | |
59 | ||
60 | static void qxl_fb_image_init(struct qxl_fb_image *qxl_fb_image, | |
61 | struct qxl_device *qdev, struct fb_info *info, | |
62 | const struct fb_image *image) | |
63 | { | |
64 | qxl_fb_image->qdev = qdev; | |
65 | if (info) { | |
66 | qxl_fb_image->visual = info->fix.visual; | |
67 | if (qxl_fb_image->visual == FB_VISUAL_TRUECOLOR || | |
68 | qxl_fb_image->visual == FB_VISUAL_DIRECTCOLOR) | |
69 | memcpy(&qxl_fb_image->pseudo_palette, | |
70 | info->pseudo_palette, | |
71 | sizeof(qxl_fb_image->pseudo_palette)); | |
72 | } else { | |
73 | /* fallback */ | |
74 | if (image->depth == 1) | |
75 | qxl_fb_image->visual = FB_VISUAL_MONO10; | |
76 | else | |
77 | qxl_fb_image->visual = FB_VISUAL_DIRECTCOLOR; | |
78 | } | |
79 | if (image) { | |
80 | memcpy(&qxl_fb_image->fb_image, image, | |
81 | sizeof(qxl_fb_image->fb_image)); | |
82 | } | |
83 | } | |
84 | ||
85 | static void qxl_fb_dirty_flush(struct fb_info *info) | |
86 | { | |
87 | struct qxl_fbdev *qfbdev = info->par; | |
88 | struct qxl_device *qdev = qfbdev->qdev; | |
89 | struct qxl_fb_image qxl_fb_image; | |
90 | struct fb_image *image = &qxl_fb_image.fb_image; | |
91 | u32 x1, x2, y1, y2; | |
92 | ||
93 | /* TODO: hard coding 32 bpp */ | |
94 | int stride = qfbdev->qfb.base.pitches[0] * 4; | |
95 | ||
96 | x1 = qfbdev->dirty.x1; | |
97 | x2 = qfbdev->dirty.x2; | |
98 | y1 = qfbdev->dirty.y1; | |
99 | y2 = qfbdev->dirty.y2; | |
100 | /* | |
101 | * we are using a shadow draw buffer, at qdev->surface0_shadow | |
102 | */ | |
103 | qxl_io_log(qdev, "dirty x[%d, %d], y[%d, %d]", x1, x2, y1, y2); | |
104 | image->dx = x1; | |
105 | image->dy = y1; | |
106 | image->width = x2 - x1; | |
107 | image->height = y2 - y1; | |
108 | image->fg_color = 0xffffffff; /* unused, just to avoid uninitialized | |
109 | warnings */ | |
110 | image->bg_color = 0; | |
111 | image->depth = 32; /* TODO: take from somewhere? */ | |
112 | image->cmap.start = 0; | |
113 | image->cmap.len = 0; | |
114 | image->cmap.red = NULL; | |
115 | image->cmap.green = NULL; | |
116 | image->cmap.blue = NULL; | |
117 | image->cmap.transp = NULL; | |
118 | image->data = qfbdev->shadow + (x1 * 4) + (stride * y1); | |
119 | ||
120 | qxl_fb_image_init(&qxl_fb_image, qdev, info, NULL); | |
121 | qxl_draw_opaque_fb(&qxl_fb_image, stride); | |
122 | qfbdev->dirty.x1 = 0; | |
123 | qfbdev->dirty.x2 = 0; | |
124 | qfbdev->dirty.y1 = 0; | |
125 | qfbdev->dirty.y2 = 0; | |
126 | } | |
127 | ||
128 | static void qxl_deferred_io(struct fb_info *info, | |
129 | struct list_head *pagelist) | |
130 | { | |
131 | struct qxl_fbdev *qfbdev = info->par; | |
132 | unsigned long start, end, min, max; | |
133 | struct page *page; | |
134 | int y1, y2; | |
135 | ||
136 | min = ULONG_MAX; | |
137 | max = 0; | |
138 | list_for_each_entry(page, pagelist, lru) { | |
139 | start = page->index << PAGE_SHIFT; | |
140 | end = start + PAGE_SIZE - 1; | |
141 | min = min(min, start); | |
142 | max = max(max, end); | |
143 | } | |
144 | ||
145 | if (min < max) { | |
146 | y1 = min / info->fix.line_length; | |
147 | y2 = (max / info->fix.line_length) + 1; | |
148 | ||
149 | /* TODO: add spin lock? */ | |
150 | /* spin_lock_irqsave(&qfbdev->dirty.lock, flags); */ | |
151 | qfbdev->dirty.x1 = 0; | |
152 | qfbdev->dirty.y1 = y1; | |
153 | qfbdev->dirty.x2 = info->var.xres; | |
154 | qfbdev->dirty.y2 = y2; | |
155 | /* spin_unlock_irqrestore(&qfbdev->dirty.lock, flags); */ | |
156 | } | |
157 | ||
158 | qxl_fb_dirty_flush(info); | |
159 | }; | |
160 | ||
161 | ||
162 | struct fb_deferred_io qxl_defio = { | |
163 | .delay = QXL_DIRTY_DELAY, | |
164 | .deferred_io = qxl_deferred_io, | |
165 | }; | |
166 | ||
167 | static void qxl_fb_fillrect(struct fb_info *info, | |
168 | const struct fb_fillrect *fb_rect) | |
169 | { | |
170 | struct qxl_fbdev *qfbdev = info->par; | |
171 | struct qxl_device *qdev = qfbdev->qdev; | |
172 | struct qxl_rect rect; | |
173 | uint32_t color; | |
174 | int x = fb_rect->dx; | |
175 | int y = fb_rect->dy; | |
176 | int width = fb_rect->width; | |
177 | int height = fb_rect->height; | |
178 | uint16_t rop; | |
179 | struct qxl_draw_fill qxl_draw_fill_rec; | |
180 | ||
181 | if (info->fix.visual == FB_VISUAL_TRUECOLOR || | |
182 | info->fix.visual == FB_VISUAL_DIRECTCOLOR) | |
183 | color = ((u32 *) (info->pseudo_palette))[fb_rect->color]; | |
184 | else | |
185 | color = fb_rect->color; | |
186 | rect.left = x; | |
187 | rect.right = x + width; | |
188 | rect.top = y; | |
189 | rect.bottom = y + height; | |
190 | switch (fb_rect->rop) { | |
191 | case ROP_XOR: | |
192 | rop = SPICE_ROPD_OP_XOR; | |
193 | break; | |
194 | case ROP_COPY: | |
195 | rop = SPICE_ROPD_OP_PUT; | |
196 | break; | |
197 | default: | |
198 | pr_err("qxl_fb_fillrect(): unknown rop, " | |
199 | "defaulting to SPICE_ROPD_OP_PUT\n"); | |
200 | rop = SPICE_ROPD_OP_PUT; | |
201 | } | |
202 | qxl_draw_fill_rec.qdev = qdev; | |
203 | qxl_draw_fill_rec.rect = rect; | |
204 | qxl_draw_fill_rec.color = color; | |
205 | qxl_draw_fill_rec.rop = rop; | |
206 | if (!drm_can_sleep()) { | |
207 | qxl_io_log(qdev, | |
208 | "%s: TODO use RCU, mysterious locks with spin_lock\n", | |
209 | __func__); | |
210 | return; | |
211 | } | |
212 | qxl_draw_fill(&qxl_draw_fill_rec); | |
213 | } | |
214 | ||
215 | static void qxl_fb_copyarea(struct fb_info *info, | |
216 | const struct fb_copyarea *region) | |
217 | { | |
218 | struct qxl_fbdev *qfbdev = info->par; | |
219 | ||
220 | qxl_draw_copyarea(qfbdev->qdev, | |
221 | region->width, region->height, | |
222 | region->sx, region->sy, | |
223 | region->dx, region->dy); | |
224 | } | |
225 | ||
226 | static void qxl_fb_imageblit_safe(struct qxl_fb_image *qxl_fb_image) | |
227 | { | |
228 | qxl_draw_opaque_fb(qxl_fb_image, 0); | |
229 | } | |
230 | ||
231 | static void qxl_fb_imageblit(struct fb_info *info, | |
232 | const struct fb_image *image) | |
233 | { | |
234 | struct qxl_fbdev *qfbdev = info->par; | |
235 | struct qxl_device *qdev = qfbdev->qdev; | |
236 | struct qxl_fb_image qxl_fb_image; | |
237 | ||
238 | if (!drm_can_sleep()) { | |
239 | /* we cannot do any ttm_bo allocation since that will fail on | |
240 | * ioremap_wc..__get_vm_area_node, so queue the work item | |
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; | |
247 | } | |
248 | ||
249 | /* ensure proper order of rendering operations - TODO: must do this | |
250 | * for everything. */ | |
251 | qxl_fb_image_init(&qxl_fb_image, qfbdev->qdev, info, image); | |
252 | qxl_fb_imageblit_safe(&qxl_fb_image); | |
253 | } | |
254 | ||
255 | int qxl_fb_init(struct qxl_device *qdev) | |
256 | { | |
257 | return 0; | |
258 | } | |
259 | ||
260 | static struct fb_ops qxlfb_ops = { | |
261 | .owner = THIS_MODULE, | |
262 | .fb_check_var = drm_fb_helper_check_var, | |
263 | .fb_set_par = drm_fb_helper_set_par, /* TODO: copy vmwgfx */ | |
264 | .fb_fillrect = qxl_fb_fillrect, | |
265 | .fb_copyarea = qxl_fb_copyarea, | |
266 | .fb_imageblit = qxl_fb_imageblit, | |
267 | .fb_pan_display = drm_fb_helper_pan_display, | |
268 | .fb_blank = drm_fb_helper_blank, | |
269 | .fb_setcmap = drm_fb_helper_setcmap, | |
270 | .fb_debug_enter = drm_fb_helper_debug_enter, | |
271 | .fb_debug_leave = drm_fb_helper_debug_leave, | |
272 | }; | |
273 | ||
274 | static void qxlfb_destroy_pinned_object(struct drm_gem_object *gobj) | |
275 | { | |
276 | struct qxl_bo *qbo = gem_to_qxl_bo(gobj); | |
277 | int ret; | |
278 | ||
279 | ret = qxl_bo_reserve(qbo, false); | |
280 | if (likely(ret == 0)) { | |
281 | qxl_bo_kunmap(qbo); | |
282 | qxl_bo_unpin(qbo); | |
283 | qxl_bo_unreserve(qbo); | |
284 | } | |
285 | drm_gem_object_unreference_unlocked(gobj); | |
286 | } | |
287 | ||
288 | int qxl_get_handle_for_primary_fb(struct qxl_device *qdev, | |
289 | struct drm_file *file_priv, | |
290 | uint32_t *handle) | |
291 | { | |
292 | int r; | |
293 | struct drm_gem_object *gobj = qdev->fbdev_qfb->obj; | |
294 | ||
295 | BUG_ON(!gobj); | |
296 | /* drm_get_handle_create adds a reference - good */ | |
297 | r = drm_gem_handle_create(file_priv, gobj, handle); | |
298 | if (r) | |
299 | return r; | |
300 | return 0; | |
301 | } | |
302 | ||
303 | static int qxlfb_create_pinned_object(struct qxl_fbdev *qfbdev, | |
304 | struct drm_mode_fb_cmd2 *mode_cmd, | |
305 | struct drm_gem_object **gobj_p) | |
306 | { | |
307 | struct qxl_device *qdev = qfbdev->qdev; | |
308 | struct drm_gem_object *gobj = NULL; | |
309 | struct qxl_bo *qbo = NULL; | |
310 | int ret; | |
311 | int aligned_size, size; | |
312 | int height = mode_cmd->height; | |
313 | int bpp; | |
314 | int depth; | |
315 | ||
316 | drm_fb_get_bpp_depth(mode_cmd->pixel_format, &bpp, &depth); | |
317 | ||
318 | size = mode_cmd->pitches[0] * height; | |
319 | aligned_size = ALIGN(size, PAGE_SIZE); | |
320 | /* TODO: unallocate and reallocate surface0 for real. Hack to just | |
321 | * have a large enough surface0 for 1024x768 Xorg 32bpp mode */ | |
322 | ret = qxl_gem_object_create(qdev, aligned_size, 0, | |
323 | QXL_GEM_DOMAIN_SURFACE, | |
324 | false, /* is discardable */ | |
325 | false, /* is kernel (false means device) */ | |
326 | NULL, | |
327 | &gobj); | |
328 | if (ret) { | |
329 | pr_err("failed to allocate framebuffer (%d)\n", | |
330 | aligned_size); | |
331 | return -ENOMEM; | |
332 | } | |
333 | qbo = gem_to_qxl_bo(gobj); | |
334 | ||
335 | qbo->surf.width = mode_cmd->width; | |
336 | qbo->surf.height = mode_cmd->height; | |
337 | qbo->surf.stride = mode_cmd->pitches[0]; | |
338 | qbo->surf.format = SPICE_SURFACE_FMT_32_xRGB; | |
339 | ret = qxl_bo_reserve(qbo, false); | |
340 | if (unlikely(ret != 0)) | |
341 | goto out_unref; | |
342 | ret = qxl_bo_pin(qbo, QXL_GEM_DOMAIN_SURFACE, NULL); | |
343 | if (ret) { | |
344 | qxl_bo_unreserve(qbo); | |
345 | goto out_unref; | |
346 | } | |
347 | ret = qxl_bo_kmap(qbo, NULL); | |
348 | qxl_bo_unreserve(qbo); /* unreserve, will be mmaped */ | |
349 | if (ret) | |
350 | goto out_unref; | |
351 | ||
352 | *gobj_p = gobj; | |
353 | return 0; | |
354 | out_unref: | |
355 | qxlfb_destroy_pinned_object(gobj); | |
356 | *gobj_p = NULL; | |
357 | return ret; | |
358 | } | |
359 | ||
360 | static int qxlfb_create(struct qxl_fbdev *qfbdev, | |
361 | struct drm_fb_helper_surface_size *sizes) | |
362 | { | |
363 | struct qxl_device *qdev = qfbdev->qdev; | |
364 | struct fb_info *info; | |
365 | struct drm_framebuffer *fb = NULL; | |
366 | struct drm_mode_fb_cmd2 mode_cmd; | |
367 | struct drm_gem_object *gobj = NULL; | |
368 | struct qxl_bo *qbo = NULL; | |
369 | struct device *device = &qdev->pdev->dev; | |
370 | int ret; | |
371 | int size; | |
372 | int bpp = sizes->surface_bpp; | |
373 | int depth = sizes->surface_depth; | |
374 | void *shadow; | |
375 | ||
376 | mode_cmd.width = sizes->surface_width; | |
377 | mode_cmd.height = sizes->surface_height; | |
378 | ||
379 | mode_cmd.pitches[0] = ALIGN(mode_cmd.width * ((bpp + 1) / 8), 64); | |
380 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp, depth); | |
381 | ||
382 | ret = qxlfb_create_pinned_object(qfbdev, &mode_cmd, &gobj); | |
383 | qbo = gem_to_qxl_bo(gobj); | |
384 | QXL_INFO(qdev, "%s: %dx%d %d\n", __func__, mode_cmd.width, | |
385 | mode_cmd.height, mode_cmd.pitches[0]); | |
386 | ||
387 | shadow = vmalloc(mode_cmd.pitches[0] * mode_cmd.height); | |
388 | /* TODO: what's the usual response to memory allocation errors? */ | |
389 | BUG_ON(!shadow); | |
390 | QXL_INFO(qdev, | |
391 | "surface0 at gpu offset %lld, mmap_offset %lld (virt %p, shadow %p)\n", | |
392 | qxl_bo_gpu_offset(qbo), | |
393 | qxl_bo_mmap_offset(qbo), | |
394 | qbo->kptr, | |
395 | shadow); | |
396 | size = mode_cmd.pitches[0] * mode_cmd.height; | |
397 | ||
398 | info = framebuffer_alloc(0, device); | |
399 | if (info == NULL) { | |
400 | ret = -ENOMEM; | |
401 | goto out_unref; | |
402 | } | |
403 | ||
404 | info->par = qfbdev; | |
405 | ||
406 | qxl_framebuffer_init(qdev->ddev, &qfbdev->qfb, &mode_cmd, gobj); | |
407 | ||
408 | fb = &qfbdev->qfb.base; | |
409 | ||
410 | /* setup helper with fb data */ | |
411 | qfbdev->helper.fb = fb; | |
412 | qfbdev->helper.fbdev = info; | |
413 | qfbdev->shadow = shadow; | |
414 | strcpy(info->fix.id, "qxldrmfb"); | |
415 | ||
416 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth); | |
417 | ||
418 | info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT; | |
419 | info->fbops = &qxlfb_ops; | |
420 | ||
421 | /* | |
422 | * TODO: using gobj->size in various places in this function. Not sure | |
423 | * what the difference between the different sizes is. | |
424 | */ | |
425 | info->fix.smem_start = qdev->vram_base; /* TODO - correct? */ | |
426 | info->fix.smem_len = gobj->size; | |
427 | info->screen_base = qfbdev->shadow; | |
428 | info->screen_size = gobj->size; | |
429 | ||
430 | drm_fb_helper_fill_var(info, &qfbdev->helper, sizes->fb_width, | |
431 | sizes->fb_height); | |
432 | ||
433 | /* setup aperture base/size for vesafb takeover */ | |
434 | info->apertures = alloc_apertures(1); | |
435 | if (!info->apertures) { | |
436 | ret = -ENOMEM; | |
437 | goto out_unref; | |
438 | } | |
439 | info->apertures->ranges[0].base = qdev->ddev->mode_config.fb_base; | |
440 | info->apertures->ranges[0].size = qdev->vram_size; | |
441 | ||
442 | info->fix.mmio_start = 0; | |
443 | info->fix.mmio_len = 0; | |
444 | ||
445 | if (info->screen_base == NULL) { | |
446 | ret = -ENOSPC; | |
447 | goto out_unref; | |
448 | } | |
449 | ||
450 | ret = fb_alloc_cmap(&info->cmap, 256, 0); | |
451 | if (ret) { | |
452 | ret = -ENOMEM; | |
453 | goto out_unref; | |
454 | } | |
455 | ||
456 | info->fbdefio = &qxl_defio; | |
457 | fb_deferred_io_init(info); | |
458 | ||
459 | qdev->fbdev_info = info; | |
460 | qdev->fbdev_qfb = &qfbdev->qfb; | |
461 | DRM_INFO("fb mappable at 0x%lX, size %lu\n", info->fix.smem_start, (unsigned long)info->screen_size); | |
462 | DRM_INFO("fb: depth %d, pitch %d, width %d, height %d\n", fb->depth, fb->pitches[0], fb->width, fb->height); | |
463 | return 0; | |
464 | ||
465 | out_unref: | |
466 | if (qbo) { | |
467 | ret = qxl_bo_reserve(qbo, false); | |
468 | if (likely(ret == 0)) { | |
469 | qxl_bo_kunmap(qbo); | |
470 | qxl_bo_unpin(qbo); | |
471 | qxl_bo_unreserve(qbo); | |
472 | } | |
473 | } | |
474 | if (fb && ret) { | |
475 | drm_gem_object_unreference(gobj); | |
476 | drm_framebuffer_cleanup(fb); | |
477 | kfree(fb); | |
478 | } | |
479 | drm_gem_object_unreference(gobj); | |
480 | return ret; | |
481 | } | |
482 | ||
483 | static int qxl_fb_find_or_create_single( | |
484 | struct drm_fb_helper *helper, | |
485 | struct drm_fb_helper_surface_size *sizes) | |
486 | { | |
487 | struct qxl_fbdev *qfbdev = (struct qxl_fbdev *)helper; | |
488 | int new_fb = 0; | |
489 | int ret; | |
490 | ||
491 | if (!helper->fb) { | |
492 | ret = qxlfb_create(qfbdev, sizes); | |
493 | if (ret) | |
494 | return ret; | |
495 | new_fb = 1; | |
496 | } | |
497 | return new_fb; | |
498 | } | |
499 | ||
500 | static int qxl_fbdev_destroy(struct drm_device *dev, struct qxl_fbdev *qfbdev) | |
501 | { | |
502 | struct fb_info *info; | |
503 | struct qxl_framebuffer *qfb = &qfbdev->qfb; | |
504 | ||
505 | if (qfbdev->helper.fbdev) { | |
506 | info = qfbdev->helper.fbdev; | |
507 | ||
508 | unregister_framebuffer(info); | |
509 | framebuffer_release(info); | |
510 | } | |
511 | if (qfb->obj) { | |
512 | qxlfb_destroy_pinned_object(qfb->obj); | |
513 | qfb->obj = NULL; | |
514 | } | |
515 | drm_fb_helper_fini(&qfbdev->helper); | |
516 | vfree(qfbdev->shadow); | |
517 | drm_framebuffer_cleanup(&qfb->base); | |
518 | ||
519 | return 0; | |
520 | } | |
521 | ||
522 | static struct drm_fb_helper_funcs qxl_fb_helper_funcs = { | |
523 | /* TODO | |
524 | .gamma_set = qxl_crtc_fb_gamma_set, | |
525 | .gamma_get = qxl_crtc_fb_gamma_get, | |
526 | */ | |
527 | .fb_probe = qxl_fb_find_or_create_single, | |
528 | }; | |
529 | ||
530 | int qxl_fbdev_init(struct qxl_device *qdev) | |
531 | { | |
532 | struct qxl_fbdev *qfbdev; | |
533 | int bpp_sel = 32; /* TODO: parameter from somewhere? */ | |
534 | int ret; | |
535 | ||
536 | qfbdev = kzalloc(sizeof(struct qxl_fbdev), GFP_KERNEL); | |
537 | if (!qfbdev) | |
538 | return -ENOMEM; | |
539 | ||
540 | qfbdev->qdev = qdev; | |
541 | qdev->mode_info.qfbdev = qfbdev; | |
542 | qfbdev->helper.funcs = &qxl_fb_helper_funcs; | |
543 | ||
544 | ret = drm_fb_helper_init(qdev->ddev, &qfbdev->helper, | |
545 | 1 /* num_crtc - QXL supports just 1 */, | |
546 | QXLFB_CONN_LIMIT); | |
547 | if (ret) { | |
548 | kfree(qfbdev); | |
549 | return ret; | |
550 | } | |
551 | ||
552 | drm_fb_helper_single_add_all_connectors(&qfbdev->helper); | |
553 | drm_fb_helper_initial_config(&qfbdev->helper, bpp_sel); | |
554 | return 0; | |
555 | } | |
556 | ||
557 | void qxl_fbdev_fini(struct qxl_device *qdev) | |
558 | { | |
559 | if (!qdev->mode_info.qfbdev) | |
560 | return; | |
561 | ||
562 | qxl_fbdev_destroy(qdev->ddev, qdev->mode_info.qfbdev); | |
563 | kfree(qdev->mode_info.qfbdev); | |
564 | qdev->mode_info.qfbdev = NULL; | |
565 | } | |
566 | ||
567 |