1 | /* |
2 | * Copyright (C) 2015 Red Hat, Inc. |
3 | * All Rights Reserved. |
4 | * |
5 | * Authors: |
6 | * Dave Airlie |
7 | * Alon Levy |
8 | * |
9 | * Permission is hereby granted, free of charge, to any person obtaining a |
10 | * copy of this software and associated documentation files (the "Software"), |
11 | * to deal in the Software without restriction, including without limitation |
12 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
13 | * and/or sell copies of the Software, and to permit persons to whom the |
14 | * Software is furnished to do so, subject to the following conditions: |
15 | * |
16 | * The above copyright notice and this permission notice shall be included in |
17 | * all copies or substantial portions of the Software. |
18 | * |
19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
22 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
23 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
24 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
25 | * OTHER DEALINGS IN THE SOFTWARE. |
26 | */ |
27 | |
28 | #include <drm/drm_atomic_helper.h> |
29 | #include <drm/drm_damage_helper.h> |
30 | #include <drm/drm_edid.h> |
31 | #include <drm/drm_fourcc.h> |
32 | #include <drm/drm_gem_framebuffer_helper.h> |
33 | #include <drm/drm_probe_helper.h> |
34 | #include <drm/drm_simple_kms_helper.h> |
35 | |
36 | #include "virtgpu_drv.h" |
37 | |
38 | #define XRES_MIN 32 |
39 | #define YRES_MIN 32 |
40 | |
41 | #define XRES_DEF 1024 |
42 | #define YRES_DEF 768 |
43 | |
44 | #define XRES_MAX 8192 |
45 | #define YRES_MAX 8192 |
46 | |
47 | #define drm_connector_to_virtio_gpu_output(x) \ |
48 | container_of(x, struct virtio_gpu_output, conn) |
49 | |
50 | static const struct drm_crtc_funcs virtio_gpu_crtc_funcs = { |
51 | .set_config = drm_atomic_helper_set_config, |
52 | .destroy = drm_crtc_cleanup, |
53 | |
54 | .page_flip = drm_atomic_helper_page_flip, |
55 | .reset = drm_atomic_helper_crtc_reset, |
56 | .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, |
57 | .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, |
58 | }; |
59 | |
60 | static const struct drm_framebuffer_funcs virtio_gpu_fb_funcs = { |
61 | .create_handle = drm_gem_fb_create_handle, |
62 | .destroy = drm_gem_fb_destroy, |
63 | .dirty = drm_atomic_helper_dirtyfb, |
64 | }; |
65 | |
66 | static int |
67 | virtio_gpu_framebuffer_init(struct drm_device *dev, |
68 | struct virtio_gpu_framebuffer *vgfb, |
69 | const struct drm_mode_fb_cmd2 *mode_cmd, |
70 | struct drm_gem_object *obj) |
71 | { |
72 | int ret; |
73 | |
74 | vgfb->base.obj[0] = obj; |
75 | |
76 | drm_helper_mode_fill_fb_struct(dev, fb: &vgfb->base, mode_cmd); |
77 | |
78 | ret = drm_framebuffer_init(dev, fb: &vgfb->base, funcs: &virtio_gpu_fb_funcs); |
79 | if (ret) { |
80 | vgfb->base.obj[0] = NULL; |
81 | return ret; |
82 | } |
83 | return 0; |
84 | } |
85 | |
86 | static void virtio_gpu_crtc_mode_set_nofb(struct drm_crtc *crtc) |
87 | { |
88 | struct drm_device *dev = crtc->dev; |
89 | struct virtio_gpu_device *vgdev = dev->dev_private; |
90 | struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc); |
91 | |
92 | virtio_gpu_cmd_set_scanout(vgdev, scanout_id: output->index, resource_id: 0, |
93 | width: crtc->mode.hdisplay, |
94 | height: crtc->mode.vdisplay, x: 0, y: 0); |
95 | virtio_gpu_notify(vgdev); |
96 | } |
97 | |
98 | static void virtio_gpu_crtc_atomic_enable(struct drm_crtc *crtc, |
99 | struct drm_atomic_state *state) |
100 | { |
101 | } |
102 | |
103 | static void virtio_gpu_crtc_atomic_disable(struct drm_crtc *crtc, |
104 | struct drm_atomic_state *state) |
105 | { |
106 | struct drm_device *dev = crtc->dev; |
107 | struct virtio_gpu_device *vgdev = dev->dev_private; |
108 | struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc); |
109 | |
110 | virtio_gpu_cmd_set_scanout(vgdev, scanout_id: output->index, resource_id: 0, width: 0, height: 0, x: 0, y: 0); |
111 | virtio_gpu_notify(vgdev); |
112 | } |
113 | |
114 | static int virtio_gpu_crtc_atomic_check(struct drm_crtc *crtc, |
115 | struct drm_atomic_state *state) |
116 | { |
117 | return 0; |
118 | } |
119 | |
120 | static void virtio_gpu_crtc_atomic_flush(struct drm_crtc *crtc, |
121 | struct drm_atomic_state *state) |
122 | { |
123 | struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, |
124 | crtc); |
125 | struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc); |
126 | |
127 | /* |
128 | * virtio-gpu can't do modeset and plane update operations |
129 | * independent from each other. So the actual modeset happens |
130 | * in the plane update callback, and here we just check |
131 | * whenever we must force the modeset. |
132 | */ |
133 | if (drm_atomic_crtc_needs_modeset(state: crtc_state)) { |
134 | output->needs_modeset = true; |
135 | } |
136 | } |
137 | |
138 | static const struct drm_crtc_helper_funcs virtio_gpu_crtc_helper_funcs = { |
139 | .mode_set_nofb = virtio_gpu_crtc_mode_set_nofb, |
140 | .atomic_check = virtio_gpu_crtc_atomic_check, |
141 | .atomic_flush = virtio_gpu_crtc_atomic_flush, |
142 | .atomic_enable = virtio_gpu_crtc_atomic_enable, |
143 | .atomic_disable = virtio_gpu_crtc_atomic_disable, |
144 | }; |
145 | |
146 | static void virtio_gpu_enc_mode_set(struct drm_encoder *encoder, |
147 | struct drm_display_mode *mode, |
148 | struct drm_display_mode *adjusted_mode) |
149 | { |
150 | } |
151 | |
152 | static void virtio_gpu_enc_enable(struct drm_encoder *encoder) |
153 | { |
154 | } |
155 | |
156 | static void virtio_gpu_enc_disable(struct drm_encoder *encoder) |
157 | { |
158 | } |
159 | |
160 | static int virtio_gpu_conn_get_modes(struct drm_connector *connector) |
161 | { |
162 | struct virtio_gpu_output *output = |
163 | drm_connector_to_virtio_gpu_output(connector); |
164 | struct drm_display_mode *mode = NULL; |
165 | int count, width, height; |
166 | |
167 | if (output->edid) { |
168 | count = drm_add_edid_modes(connector, edid: output->edid); |
169 | if (count) |
170 | return count; |
171 | } |
172 | |
173 | width = le32_to_cpu(output->info.r.width); |
174 | height = le32_to_cpu(output->info.r.height); |
175 | count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX); |
176 | |
177 | if (width == 0 || height == 0) { |
178 | drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF); |
179 | } else { |
180 | DRM_DEBUG("add mode: %dx%d\n" , width, height); |
181 | mode = drm_cvt_mode(dev: connector->dev, hdisplay: width, vdisplay: height, vrefresh: 60, |
182 | reduced: false, interlaced: false, margins: false); |
183 | if (!mode) |
184 | return count; |
185 | mode->type |= DRM_MODE_TYPE_PREFERRED; |
186 | drm_mode_probed_add(connector, mode); |
187 | count++; |
188 | } |
189 | |
190 | return count; |
191 | } |
192 | |
193 | static enum drm_mode_status virtio_gpu_conn_mode_valid(struct drm_connector *connector, |
194 | struct drm_display_mode *mode) |
195 | { |
196 | struct virtio_gpu_output *output = |
197 | drm_connector_to_virtio_gpu_output(connector); |
198 | int width, height; |
199 | |
200 | width = le32_to_cpu(output->info.r.width); |
201 | height = le32_to_cpu(output->info.r.height); |
202 | |
203 | if (!(mode->type & DRM_MODE_TYPE_PREFERRED)) |
204 | return MODE_OK; |
205 | if (mode->hdisplay == XRES_DEF && mode->vdisplay == YRES_DEF) |
206 | return MODE_OK; |
207 | if (mode->hdisplay <= width && mode->hdisplay >= width - 16 && |
208 | mode->vdisplay <= height && mode->vdisplay >= height - 16) |
209 | return MODE_OK; |
210 | |
211 | DRM_DEBUG("del mode: %dx%d\n" , mode->hdisplay, mode->vdisplay); |
212 | return MODE_BAD; |
213 | } |
214 | |
215 | static const struct drm_encoder_helper_funcs virtio_gpu_enc_helper_funcs = { |
216 | .mode_set = virtio_gpu_enc_mode_set, |
217 | .enable = virtio_gpu_enc_enable, |
218 | .disable = virtio_gpu_enc_disable, |
219 | }; |
220 | |
221 | static const struct drm_connector_helper_funcs virtio_gpu_conn_helper_funcs = { |
222 | .get_modes = virtio_gpu_conn_get_modes, |
223 | .mode_valid = virtio_gpu_conn_mode_valid, |
224 | }; |
225 | |
226 | static enum drm_connector_status virtio_gpu_conn_detect( |
227 | struct drm_connector *connector, |
228 | bool force) |
229 | { |
230 | struct virtio_gpu_output *output = |
231 | drm_connector_to_virtio_gpu_output(connector); |
232 | |
233 | if (output->info.enabled) |
234 | return connector_status_connected; |
235 | else |
236 | return connector_status_disconnected; |
237 | } |
238 | |
239 | static void virtio_gpu_conn_destroy(struct drm_connector *connector) |
240 | { |
241 | drm_connector_unregister(connector); |
242 | drm_connector_cleanup(connector); |
243 | } |
244 | |
245 | static const struct drm_connector_funcs virtio_gpu_connector_funcs = { |
246 | .detect = virtio_gpu_conn_detect, |
247 | .fill_modes = drm_helper_probe_single_connector_modes, |
248 | .destroy = virtio_gpu_conn_destroy, |
249 | .reset = drm_atomic_helper_connector_reset, |
250 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, |
251 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
252 | }; |
253 | |
254 | static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index) |
255 | { |
256 | struct drm_device *dev = vgdev->ddev; |
257 | struct virtio_gpu_output *output = vgdev->outputs + index; |
258 | struct drm_connector *connector = &output->conn; |
259 | struct drm_encoder *encoder = &output->enc; |
260 | struct drm_crtc *crtc = &output->crtc; |
261 | struct drm_plane *primary, *cursor; |
262 | |
263 | output->index = index; |
264 | if (index == 0) { |
265 | output->info.enabled = cpu_to_le32(true); |
266 | output->info.r.width = cpu_to_le32(XRES_DEF); |
267 | output->info.r.height = cpu_to_le32(YRES_DEF); |
268 | } |
269 | |
270 | primary = virtio_gpu_plane_init(vgdev, type: DRM_PLANE_TYPE_PRIMARY, index); |
271 | if (IS_ERR(ptr: primary)) |
272 | return PTR_ERR(ptr: primary); |
273 | cursor = virtio_gpu_plane_init(vgdev, type: DRM_PLANE_TYPE_CURSOR, index); |
274 | if (IS_ERR(ptr: cursor)) |
275 | return PTR_ERR(ptr: cursor); |
276 | drm_crtc_init_with_planes(dev, crtc, primary, cursor, |
277 | funcs: &virtio_gpu_crtc_funcs, NULL); |
278 | drm_crtc_helper_add(crtc, funcs: &virtio_gpu_crtc_helper_funcs); |
279 | |
280 | drm_connector_init(dev, connector, funcs: &virtio_gpu_connector_funcs, |
281 | DRM_MODE_CONNECTOR_VIRTUAL); |
282 | drm_connector_helper_add(connector, funcs: &virtio_gpu_conn_helper_funcs); |
283 | if (vgdev->has_edid) |
284 | drm_connector_attach_edid_property(connector); |
285 | |
286 | drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL); |
287 | drm_encoder_helper_add(encoder, funcs: &virtio_gpu_enc_helper_funcs); |
288 | encoder->possible_crtcs = 1 << index; |
289 | |
290 | drm_connector_attach_encoder(connector, encoder); |
291 | drm_connector_register(connector); |
292 | return 0; |
293 | } |
294 | |
295 | static struct drm_framebuffer * |
296 | virtio_gpu_user_framebuffer_create(struct drm_device *dev, |
297 | struct drm_file *file_priv, |
298 | const struct drm_mode_fb_cmd2 *mode_cmd) |
299 | { |
300 | struct drm_gem_object *obj = NULL; |
301 | struct virtio_gpu_framebuffer *virtio_gpu_fb; |
302 | int ret; |
303 | |
304 | if (mode_cmd->pixel_format != DRM_FORMAT_HOST_XRGB8888 && |
305 | mode_cmd->pixel_format != DRM_FORMAT_HOST_ARGB8888) |
306 | return ERR_PTR(error: -ENOENT); |
307 | |
308 | /* lookup object associated with res handle */ |
309 | obj = drm_gem_object_lookup(filp: file_priv, handle: mode_cmd->handles[0]); |
310 | if (!obj) |
311 | return ERR_PTR(error: -EINVAL); |
312 | |
313 | virtio_gpu_fb = kzalloc(size: sizeof(*virtio_gpu_fb), GFP_KERNEL); |
314 | if (virtio_gpu_fb == NULL) { |
315 | drm_gem_object_put(obj); |
316 | return ERR_PTR(error: -ENOMEM); |
317 | } |
318 | |
319 | ret = virtio_gpu_framebuffer_init(dev, vgfb: virtio_gpu_fb, mode_cmd, obj); |
320 | if (ret) { |
321 | kfree(objp: virtio_gpu_fb); |
322 | drm_gem_object_put(obj); |
323 | return NULL; |
324 | } |
325 | |
326 | return &virtio_gpu_fb->base; |
327 | } |
328 | |
329 | static const struct drm_mode_config_funcs virtio_gpu_mode_funcs = { |
330 | .fb_create = virtio_gpu_user_framebuffer_create, |
331 | .atomic_check = drm_atomic_helper_check, |
332 | .atomic_commit = drm_atomic_helper_commit, |
333 | }; |
334 | |
335 | int virtio_gpu_modeset_init(struct virtio_gpu_device *vgdev) |
336 | { |
337 | int i, ret; |
338 | |
339 | if (!vgdev->num_scanouts) |
340 | return 0; |
341 | |
342 | ret = drmm_mode_config_init(dev: vgdev->ddev); |
343 | if (ret) |
344 | return ret; |
345 | |
346 | vgdev->ddev->mode_config.quirk_addfb_prefer_host_byte_order = true; |
347 | vgdev->ddev->mode_config.funcs = &virtio_gpu_mode_funcs; |
348 | |
349 | /* modes will be validated against the framebuffer size */ |
350 | vgdev->ddev->mode_config.min_width = XRES_MIN; |
351 | vgdev->ddev->mode_config.min_height = YRES_MIN; |
352 | vgdev->ddev->mode_config.max_width = XRES_MAX; |
353 | vgdev->ddev->mode_config.max_height = YRES_MAX; |
354 | |
355 | vgdev->ddev->mode_config.fb_modifiers_not_supported = true; |
356 | |
357 | for (i = 0 ; i < vgdev->num_scanouts; ++i) |
358 | vgdev_output_init(vgdev, index: i); |
359 | |
360 | drm_mode_config_reset(dev: vgdev->ddev); |
361 | return 0; |
362 | } |
363 | |
364 | void virtio_gpu_modeset_fini(struct virtio_gpu_device *vgdev) |
365 | { |
366 | int i; |
367 | |
368 | if (!vgdev->num_scanouts) |
369 | return; |
370 | |
371 | for (i = 0 ; i < vgdev->num_scanouts; ++i) |
372 | kfree(objp: vgdev->outputs[i].edid); |
373 | } |
374 | |