]>
Commit | Line | Data |
---|---|---|
dc5698e8 DA |
1 | /* |
2 | * Copyright (C) 2015 Red Hat, Inc. | |
3 | * All Rights Reserved. | |
4 | * | |
5 | * Permission is hereby granted, free of charge, to any person obtaining | |
6 | * a copy of this software and associated documentation files (the | |
7 | * "Software"), to deal in the Software without restriction, including | |
8 | * without limitation the rights to use, copy, modify, merge, publish, | |
9 | * distribute, sublicense, and/or sell copies of the Software, and to | |
10 | * permit persons to whom the Software is furnished to do so, subject to | |
11 | * the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice (including the | |
14 | * next paragraph) shall be included in all copies or substantial | |
15 | * portions of the Software. | |
16 | * | |
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
20 | * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE | |
21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
22 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
24 | */ | |
25 | ||
26 | #include <drm/drmP.h> | |
27 | #include <drm/drm_fb_helper.h> | |
28 | #include "virtgpu_drv.h" | |
29 | ||
30 | #define VIRTIO_GPU_FBCON_POLL_PERIOD (HZ / 60) | |
31 | ||
32 | struct virtio_gpu_fbdev { | |
33 | struct drm_fb_helper helper; | |
34 | struct virtio_gpu_framebuffer vgfb; | |
dc5698e8 DA |
35 | struct virtio_gpu_device *vgdev; |
36 | struct delayed_work work; | |
37 | }; | |
38 | ||
39 | static int virtio_gpu_dirty_update(struct virtio_gpu_framebuffer *fb, | |
40 | bool store, int x, int y, | |
41 | int width, int height) | |
42 | { | |
43 | struct drm_device *dev = fb->base.dev; | |
44 | struct virtio_gpu_device *vgdev = dev->dev_private; | |
45 | bool store_for_later = false; | |
272725c7 | 46 | int bpp = fb->base.format->cpp[0]; |
dc5698e8 DA |
47 | int x2, y2; |
48 | unsigned long flags; | |
49 | struct virtio_gpu_object *obj = gem_to_virtio_gpu_obj(fb->obj); | |
50 | ||
51 | if ((width <= 0) || | |
52 | (x + width > fb->base.width) || | |
53 | (y + height > fb->base.height)) { | |
54 | DRM_DEBUG("values out of range %dx%d+%d+%d, fb %dx%d\n", | |
55 | width, height, x, y, | |
56 | fb->base.width, fb->base.height); | |
57 | return -EINVAL; | |
58 | } | |
59 | ||
60 | /* | |
61 | * Can be called with pretty much any context (console output | |
62 | * path). If we are in atomic just store the dirty rect info | |
63 | * to send out the update later. | |
64 | * | |
65 | * Can't test inside spin lock. | |
66 | */ | |
67 | if (in_atomic() || store) | |
68 | store_for_later = true; | |
69 | ||
70 | x2 = x + width - 1; | |
71 | y2 = y + height - 1; | |
72 | ||
73 | spin_lock_irqsave(&fb->dirty_lock, flags); | |
74 | ||
75 | if (fb->y1 < y) | |
76 | y = fb->y1; | |
77 | if (fb->y2 > y2) | |
78 | y2 = fb->y2; | |
79 | if (fb->x1 < x) | |
80 | x = fb->x1; | |
81 | if (fb->x2 > x2) | |
82 | x2 = fb->x2; | |
83 | ||
84 | if (store_for_later) { | |
85 | fb->x1 = x; | |
86 | fb->x2 = x2; | |
87 | fb->y1 = y; | |
88 | fb->y2 = y2; | |
89 | spin_unlock_irqrestore(&fb->dirty_lock, flags); | |
90 | return 0; | |
91 | } | |
92 | ||
93 | fb->x1 = fb->y1 = INT_MAX; | |
94 | fb->x2 = fb->y2 = 0; | |
95 | ||
96 | spin_unlock_irqrestore(&fb->dirty_lock, flags); | |
97 | ||
98 | { | |
99 | uint32_t offset; | |
100 | uint32_t w = x2 - x + 1; | |
101 | uint32_t h = y2 - y + 1; | |
102 | ||
103 | offset = (y * fb->base.pitches[0]) + x * bpp; | |
104 | ||
105 | virtio_gpu_cmd_transfer_to_host_2d(vgdev, obj->hw_res_handle, | |
106 | offset, | |
107 | cpu_to_le32(w), | |
108 | cpu_to_le32(h), | |
109 | cpu_to_le32(x), | |
110 | cpu_to_le32(y), | |
111 | NULL); | |
112 | ||
113 | } | |
114 | virtio_gpu_cmd_resource_flush(vgdev, obj->hw_res_handle, | |
115 | x, y, x2 - x + 1, y2 - y + 1); | |
116 | return 0; | |
117 | } | |
118 | ||
119 | int virtio_gpu_surface_dirty(struct virtio_gpu_framebuffer *vgfb, | |
120 | struct drm_clip_rect *clips, | |
121 | unsigned num_clips) | |
122 | { | |
123 | struct virtio_gpu_device *vgdev = vgfb->base.dev->dev_private; | |
124 | struct virtio_gpu_object *obj = gem_to_virtio_gpu_obj(vgfb->obj); | |
125 | struct drm_clip_rect norect; | |
126 | struct drm_clip_rect *clips_ptr; | |
127 | int left, right, top, bottom; | |
128 | int i; | |
129 | int inc = 1; | |
130 | if (!num_clips) { | |
131 | num_clips = 1; | |
132 | clips = &norect; | |
133 | norect.x1 = norect.y1 = 0; | |
134 | norect.x2 = vgfb->base.width; | |
135 | norect.y2 = vgfb->base.height; | |
136 | } | |
137 | left = clips->x1; | |
138 | right = clips->x2; | |
139 | top = clips->y1; | |
140 | bottom = clips->y2; | |
141 | ||
142 | /* skip the first clip rect */ | |
143 | for (i = 1, clips_ptr = clips + inc; | |
144 | i < num_clips; i++, clips_ptr += inc) { | |
145 | left = min_t(int, left, (int)clips_ptr->x1); | |
146 | right = max_t(int, right, (int)clips_ptr->x2); | |
147 | top = min_t(int, top, (int)clips_ptr->y1); | |
148 | bottom = max_t(int, bottom, (int)clips_ptr->y2); | |
149 | } | |
150 | ||
151 | if (obj->dumb) | |
152 | return virtio_gpu_dirty_update(vgfb, false, left, top, | |
153 | right - left, bottom - top); | |
154 | ||
155 | virtio_gpu_cmd_resource_flush(vgdev, obj->hw_res_handle, | |
156 | left, top, right - left, bottom - top); | |
157 | return 0; | |
158 | } | |
159 | ||
160 | static void virtio_gpu_fb_dirty_work(struct work_struct *work) | |
161 | { | |
162 | struct delayed_work *delayed_work = to_delayed_work(work); | |
163 | struct virtio_gpu_fbdev *vfbdev = | |
164 | container_of(delayed_work, struct virtio_gpu_fbdev, work); | |
165 | struct virtio_gpu_framebuffer *vgfb = &vfbdev->vgfb; | |
166 | ||
167 | virtio_gpu_dirty_update(&vfbdev->vgfb, false, vgfb->x1, vgfb->y1, | |
168 | vgfb->x2 - vgfb->x1, vgfb->y2 - vgfb->y1); | |
169 | } | |
170 | ||
171 | static void virtio_gpu_3d_fillrect(struct fb_info *info, | |
172 | const struct fb_fillrect *rect) | |
173 | { | |
174 | struct virtio_gpu_fbdev *vfbdev = info->par; | |
0843010b | 175 | drm_fb_helper_sys_fillrect(info, rect); |
dc5698e8 DA |
176 | virtio_gpu_dirty_update(&vfbdev->vgfb, true, rect->dx, rect->dy, |
177 | rect->width, rect->height); | |
178 | schedule_delayed_work(&vfbdev->work, VIRTIO_GPU_FBCON_POLL_PERIOD); | |
179 | } | |
180 | ||
181 | static void virtio_gpu_3d_copyarea(struct fb_info *info, | |
182 | const struct fb_copyarea *area) | |
183 | { | |
184 | struct virtio_gpu_fbdev *vfbdev = info->par; | |
0843010b | 185 | drm_fb_helper_sys_copyarea(info, area); |
dc5698e8 DA |
186 | virtio_gpu_dirty_update(&vfbdev->vgfb, true, area->dx, area->dy, |
187 | area->width, area->height); | |
188 | schedule_delayed_work(&vfbdev->work, VIRTIO_GPU_FBCON_POLL_PERIOD); | |
189 | } | |
190 | ||
191 | static void virtio_gpu_3d_imageblit(struct fb_info *info, | |
192 | const struct fb_image *image) | |
193 | { | |
194 | struct virtio_gpu_fbdev *vfbdev = info->par; | |
0843010b | 195 | drm_fb_helper_sys_imageblit(info, image); |
dc5698e8 DA |
196 | virtio_gpu_dirty_update(&vfbdev->vgfb, true, image->dx, image->dy, |
197 | image->width, image->height); | |
198 | schedule_delayed_work(&vfbdev->work, VIRTIO_GPU_FBCON_POLL_PERIOD); | |
199 | } | |
200 | ||
201 | static struct fb_ops virtio_gpufb_ops = { | |
202 | .owner = THIS_MODULE, | |
e59a03f5 | 203 | DRM_FB_HELPER_DEFAULT_OPS, |
dc5698e8 DA |
204 | .fb_fillrect = virtio_gpu_3d_fillrect, |
205 | .fb_copyarea = virtio_gpu_3d_copyarea, | |
206 | .fb_imageblit = virtio_gpu_3d_imageblit, | |
dc5698e8 DA |
207 | }; |
208 | ||
209 | static int virtio_gpu_vmap_fb(struct virtio_gpu_device *vgdev, | |
210 | struct virtio_gpu_object *obj) | |
211 | { | |
212 | return virtio_gpu_object_kmap(obj, NULL); | |
213 | } | |
214 | ||
215 | static int virtio_gpufb_create(struct drm_fb_helper *helper, | |
216 | struct drm_fb_helper_surface_size *sizes) | |
217 | { | |
218 | struct virtio_gpu_fbdev *vfbdev = | |
219 | container_of(helper, struct virtio_gpu_fbdev, helper); | |
220 | struct drm_device *dev = helper->dev; | |
221 | struct virtio_gpu_device *vgdev = dev->dev_private; | |
222 | struct fb_info *info; | |
223 | struct drm_framebuffer *fb; | |
224 | struct drm_mode_fb_cmd2 mode_cmd = {}; | |
225 | struct virtio_gpu_object *obj; | |
dc5698e8 DA |
226 | uint32_t resid, format, size; |
227 | int ret; | |
228 | ||
229 | mode_cmd.width = sizes->surface_width; | |
230 | mode_cmd.height = sizes->surface_height; | |
231 | mode_cmd.pitches[0] = mode_cmd.width * 4; | |
232 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(32, 24); | |
233 | ||
d519cb76 GH |
234 | format = virtio_gpu_translate_format(mode_cmd.pixel_format); |
235 | if (format == 0) | |
dc5698e8 | 236 | return -EINVAL; |
dc5698e8 DA |
237 | |
238 | size = mode_cmd.pitches[0] * mode_cmd.height; | |
239 | obj = virtio_gpu_alloc_object(dev, size, false, true); | |
2b7edcad DC |
240 | if (IS_ERR(obj)) |
241 | return PTR_ERR(obj); | |
dc5698e8 DA |
242 | |
243 | virtio_gpu_resource_id_get(vgdev, &resid); | |
244 | virtio_gpu_cmd_create_resource(vgdev, resid, format, | |
245 | mode_cmd.width, mode_cmd.height); | |
246 | ||
247 | ret = virtio_gpu_vmap_fb(vgdev, obj); | |
248 | if (ret) { | |
249 | DRM_ERROR("failed to vmap fb %d\n", ret); | |
250 | goto err_obj_vmap; | |
251 | } | |
252 | ||
253 | /* attach the object to the resource */ | |
254 | ret = virtio_gpu_object_attach(vgdev, obj, resid, NULL); | |
255 | if (ret) | |
256 | goto err_obj_attach; | |
257 | ||
0843010b AT |
258 | info = drm_fb_helper_alloc_fbi(helper); |
259 | if (IS_ERR(info)) { | |
260 | ret = PTR_ERR(info); | |
dc5698e8 DA |
261 | goto err_fb_alloc; |
262 | } | |
263 | ||
dc5698e8 DA |
264 | info->par = helper; |
265 | ||
266 | ret = virtio_gpu_framebuffer_init(dev, &vfbdev->vgfb, | |
267 | &mode_cmd, &obj->gem_base); | |
268 | if (ret) | |
da7bdda2 | 269 | goto err_fb_alloc; |
dc5698e8 DA |
270 | |
271 | fb = &vfbdev->vgfb.base; | |
272 | ||
273 | vfbdev->helper.fb = fb; | |
dc5698e8 DA |
274 | |
275 | strcpy(info->fix.id, "virtiodrmfb"); | |
dc5698e8 DA |
276 | info->fbops = &virtio_gpufb_ops; |
277 | info->pixmap.flags = FB_PIXMAP_SYSTEM; | |
278 | ||
71d3f6ef | 279 | info->screen_buffer = obj->vmap; |
dc5698e8 | 280 | info->screen_size = obj->gem_base.size; |
b00c600e | 281 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth); |
dc5698e8 DA |
282 | drm_fb_helper_fill_var(info, &vfbdev->helper, |
283 | sizes->fb_width, sizes->fb_height); | |
284 | ||
285 | info->fix.mmio_start = 0; | |
286 | info->fix.mmio_len = 0; | |
287 | return 0; | |
288 | ||
dc5698e8 DA |
289 | err_fb_alloc: |
290 | virtio_gpu_cmd_resource_inval_backing(vgdev, resid); | |
291 | err_obj_attach: | |
292 | err_obj_vmap: | |
293 | virtio_gpu_gem_free_object(&obj->gem_base); | |
294 | return ret; | |
295 | } | |
296 | ||
297 | static int virtio_gpu_fbdev_destroy(struct drm_device *dev, | |
298 | struct virtio_gpu_fbdev *vgfbdev) | |
299 | { | |
dc5698e8 DA |
300 | struct virtio_gpu_framebuffer *vgfb = &vgfbdev->vgfb; |
301 | ||
0843010b | 302 | drm_fb_helper_unregister_fbi(&vgfbdev->helper); |
dc5698e8 | 303 | |
dc5698e8 DA |
304 | if (vgfb->obj) |
305 | vgfb->obj = NULL; | |
306 | drm_fb_helper_fini(&vgfbdev->helper); | |
307 | drm_framebuffer_cleanup(&vgfb->base); | |
308 | ||
309 | return 0; | |
310 | } | |
311 | static struct drm_fb_helper_funcs virtio_gpu_fb_helper_funcs = { | |
312 | .fb_probe = virtio_gpufb_create, | |
313 | }; | |
314 | ||
315 | int virtio_gpu_fbdev_init(struct virtio_gpu_device *vgdev) | |
316 | { | |
317 | struct virtio_gpu_fbdev *vgfbdev; | |
318 | int bpp_sel = 32; /* TODO: parameter from somewhere? */ | |
319 | int ret; | |
320 | ||
321 | vgfbdev = kzalloc(sizeof(struct virtio_gpu_fbdev), GFP_KERNEL); | |
322 | if (!vgfbdev) | |
323 | return -ENOMEM; | |
324 | ||
325 | vgfbdev->vgdev = vgdev; | |
326 | vgdev->vgfbdev = vgfbdev; | |
327 | INIT_DELAYED_WORK(&vgfbdev->work, virtio_gpu_fb_dirty_work); | |
328 | ||
329 | drm_fb_helper_prepare(vgdev->ddev, &vgfbdev->helper, | |
330 | &virtio_gpu_fb_helper_funcs); | |
331 | ret = drm_fb_helper_init(vgdev->ddev, &vgfbdev->helper, | |
dc5698e8 DA |
332 | VIRTIO_GPUFB_CONN_LIMIT); |
333 | if (ret) { | |
334 | kfree(vgfbdev); | |
335 | return ret; | |
336 | } | |
337 | ||
338 | drm_fb_helper_single_add_all_connectors(&vgfbdev->helper); | |
339 | drm_fb_helper_initial_config(&vgfbdev->helper, bpp_sel); | |
340 | return 0; | |
341 | } | |
342 | ||
343 | void virtio_gpu_fbdev_fini(struct virtio_gpu_device *vgdev) | |
344 | { | |
345 | if (!vgdev->vgfbdev) | |
346 | return; | |
347 | ||
348 | virtio_gpu_fbdev_destroy(vgdev->ddev, vgdev->vgfbdev); | |
349 | kfree(vgdev->vgfbdev); | |
350 | vgdev->vgfbdev = NULL; | |
351 | } |