1 | // SPDX-License-Identifier: MIT |
2 | |
3 | #include <linux/moduleparam.h> |
4 | #include <linux/vmalloc.h> |
5 | |
6 | #include <drm/drm_crtc_helper.h> |
7 | #include <drm/drm_drv.h> |
8 | #include <drm/drm_fb_helper.h> |
9 | #include <drm/drm_framebuffer.h> |
10 | #include <drm/drm_gem.h> |
11 | #include <drm/drm_print.h> |
12 | |
13 | #include <drm/drm_fbdev_generic.h> |
14 | |
15 | /* @user: 1=userspace, 0=fbcon */ |
16 | static int drm_fbdev_generic_fb_open(struct fb_info *info, int user) |
17 | { |
18 | struct drm_fb_helper *fb_helper = info->par; |
19 | |
20 | /* No need to take a ref for fbcon because it unbinds on unregister */ |
21 | if (user && !try_module_get(module: fb_helper->dev->driver->fops->owner)) |
22 | return -ENODEV; |
23 | |
24 | return 0; |
25 | } |
26 | |
27 | static int drm_fbdev_generic_fb_release(struct fb_info *info, int user) |
28 | { |
29 | struct drm_fb_helper *fb_helper = info->par; |
30 | |
31 | if (user) |
32 | module_put(module: fb_helper->dev->driver->fops->owner); |
33 | |
34 | return 0; |
35 | } |
36 | |
37 | FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(drm_fbdev_generic, |
38 | drm_fb_helper_damage_range, |
39 | drm_fb_helper_damage_area); |
40 | |
41 | static void drm_fbdev_generic_fb_destroy(struct fb_info *info) |
42 | { |
43 | struct drm_fb_helper *fb_helper = info->par; |
44 | void *shadow = info->screen_buffer; |
45 | |
46 | if (!fb_helper->dev) |
47 | return; |
48 | |
49 | fb_deferred_io_cleanup(info); |
50 | drm_fb_helper_fini(helper: fb_helper); |
51 | vfree(addr: shadow); |
52 | drm_client_framebuffer_delete(buffer: fb_helper->buffer); |
53 | |
54 | drm_client_release(client: &fb_helper->client); |
55 | drm_fb_helper_unprepare(fb_helper); |
56 | kfree(objp: fb_helper); |
57 | } |
58 | |
59 | static const struct fb_ops drm_fbdev_generic_fb_ops = { |
60 | .owner = THIS_MODULE, |
61 | .fb_open = drm_fbdev_generic_fb_open, |
62 | .fb_release = drm_fbdev_generic_fb_release, |
63 | FB_DEFAULT_DEFERRED_OPS(drm_fbdev_generic), |
64 | DRM_FB_HELPER_DEFAULT_OPS, |
65 | .fb_destroy = drm_fbdev_generic_fb_destroy, |
66 | }; |
67 | |
68 | /* |
69 | * This function uses the client API to create a framebuffer backed by a dumb buffer. |
70 | */ |
71 | static int drm_fbdev_generic_helper_fb_probe(struct drm_fb_helper *fb_helper, |
72 | struct drm_fb_helper_surface_size *sizes) |
73 | { |
74 | struct drm_client_dev *client = &fb_helper->client; |
75 | struct drm_device *dev = fb_helper->dev; |
76 | struct drm_client_buffer *buffer; |
77 | struct fb_info *info; |
78 | size_t screen_size; |
79 | void *screen_buffer; |
80 | u32 format; |
81 | int ret; |
82 | |
83 | drm_dbg_kms(dev, "surface width(%d), height(%d) and bpp(%d)\n" , |
84 | sizes->surface_width, sizes->surface_height, |
85 | sizes->surface_bpp); |
86 | |
87 | format = drm_mode_legacy_fb_format(bpp: sizes->surface_bpp, depth: sizes->surface_depth); |
88 | buffer = drm_client_framebuffer_create(client, width: sizes->surface_width, |
89 | height: sizes->surface_height, format); |
90 | if (IS_ERR(ptr: buffer)) |
91 | return PTR_ERR(ptr: buffer); |
92 | |
93 | fb_helper->buffer = buffer; |
94 | fb_helper->fb = buffer->fb; |
95 | |
96 | screen_size = buffer->gem->size; |
97 | screen_buffer = vzalloc(size: screen_size); |
98 | if (!screen_buffer) { |
99 | ret = -ENOMEM; |
100 | goto err_drm_client_framebuffer_delete; |
101 | } |
102 | |
103 | info = drm_fb_helper_alloc_info(fb_helper); |
104 | if (IS_ERR(ptr: info)) { |
105 | ret = PTR_ERR(ptr: info); |
106 | goto err_vfree; |
107 | } |
108 | |
109 | drm_fb_helper_fill_info(info, fb_helper, sizes); |
110 | |
111 | info->fbops = &drm_fbdev_generic_fb_ops; |
112 | |
113 | /* screen */ |
114 | info->flags |= FBINFO_VIRTFB | FBINFO_READS_FAST; |
115 | info->screen_buffer = screen_buffer; |
116 | info->fix.smem_start = page_to_phys(vmalloc_to_page(info->screen_buffer)); |
117 | info->fix.smem_len = screen_size; |
118 | |
119 | /* deferred I/O */ |
120 | fb_helper->fbdefio.delay = HZ / 20; |
121 | fb_helper->fbdefio.deferred_io = drm_fb_helper_deferred_io; |
122 | |
123 | info->fbdefio = &fb_helper->fbdefio; |
124 | ret = fb_deferred_io_init(info); |
125 | if (ret) |
126 | goto err_drm_fb_helper_release_info; |
127 | |
128 | return 0; |
129 | |
130 | err_drm_fb_helper_release_info: |
131 | drm_fb_helper_release_info(fb_helper); |
132 | err_vfree: |
133 | vfree(addr: screen_buffer); |
134 | err_drm_client_framebuffer_delete: |
135 | fb_helper->fb = NULL; |
136 | fb_helper->buffer = NULL; |
137 | drm_client_framebuffer_delete(buffer); |
138 | return ret; |
139 | } |
140 | |
141 | static void drm_fbdev_generic_damage_blit_real(struct drm_fb_helper *fb_helper, |
142 | struct drm_clip_rect *clip, |
143 | struct iosys_map *dst) |
144 | { |
145 | struct drm_framebuffer *fb = fb_helper->fb; |
146 | size_t offset = clip->y1 * fb->pitches[0]; |
147 | size_t len = clip->x2 - clip->x1; |
148 | unsigned int y; |
149 | void *src; |
150 | |
151 | switch (drm_format_info_bpp(info: fb->format, plane: 0)) { |
152 | case 1: |
153 | offset += clip->x1 / 8; |
154 | len = DIV_ROUND_UP(len + clip->x1 % 8, 8); |
155 | break; |
156 | case 2: |
157 | offset += clip->x1 / 4; |
158 | len = DIV_ROUND_UP(len + clip->x1 % 4, 4); |
159 | break; |
160 | case 4: |
161 | offset += clip->x1 / 2; |
162 | len = DIV_ROUND_UP(len + clip->x1 % 2, 2); |
163 | break; |
164 | default: |
165 | offset += clip->x1 * fb->format->cpp[0]; |
166 | len *= fb->format->cpp[0]; |
167 | break; |
168 | } |
169 | |
170 | src = fb_helper->info->screen_buffer + offset; |
171 | iosys_map_incr(map: dst, incr: offset); /* go to first pixel within clip rect */ |
172 | |
173 | for (y = clip->y1; y < clip->y2; y++) { |
174 | iosys_map_memcpy_to(dst, dst_offset: 0, src, len); |
175 | iosys_map_incr(map: dst, incr: fb->pitches[0]); |
176 | src += fb->pitches[0]; |
177 | } |
178 | } |
179 | |
180 | static int drm_fbdev_generic_damage_blit(struct drm_fb_helper *fb_helper, |
181 | struct drm_clip_rect *clip) |
182 | { |
183 | struct drm_client_buffer *buffer = fb_helper->buffer; |
184 | struct iosys_map map, dst; |
185 | int ret; |
186 | |
187 | /* |
188 | * We have to pin the client buffer to its current location while |
189 | * flushing the shadow buffer. In the general case, concurrent |
190 | * modesetting operations could try to move the buffer and would |
191 | * fail. The modeset has to be serialized by acquiring the reservation |
192 | * object of the underlying BO here. |
193 | * |
194 | * For fbdev emulation, we only have to protect against fbdev modeset |
195 | * operations. Nothing else will involve the client buffer's BO. So it |
196 | * is sufficient to acquire struct drm_fb_helper.lock here. |
197 | */ |
198 | mutex_lock(&fb_helper->lock); |
199 | |
200 | ret = drm_client_buffer_vmap(buffer, map: &map); |
201 | if (ret) |
202 | goto out; |
203 | |
204 | dst = map; |
205 | drm_fbdev_generic_damage_blit_real(fb_helper, clip, dst: &dst); |
206 | |
207 | drm_client_buffer_vunmap(buffer); |
208 | |
209 | out: |
210 | mutex_unlock(lock: &fb_helper->lock); |
211 | |
212 | return ret; |
213 | } |
214 | |
215 | static int drm_fbdev_generic_helper_fb_dirty(struct drm_fb_helper *helper, |
216 | struct drm_clip_rect *clip) |
217 | { |
218 | struct drm_device *dev = helper->dev; |
219 | int ret; |
220 | |
221 | /* Call damage handlers only if necessary */ |
222 | if (!(clip->x1 < clip->x2 && clip->y1 < clip->y2)) |
223 | return 0; |
224 | |
225 | ret = drm_fbdev_generic_damage_blit(fb_helper: helper, clip); |
226 | if (drm_WARN_ONCE(dev, ret, "Damage blitter failed: ret=%d\n" , ret)) |
227 | return ret; |
228 | |
229 | if (helper->fb->funcs->dirty) { |
230 | ret = helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, clip, 1); |
231 | if (drm_WARN_ONCE(dev, ret, "Dirty helper failed: ret=%d\n" , ret)) |
232 | return ret; |
233 | } |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static const struct drm_fb_helper_funcs drm_fbdev_generic_helper_funcs = { |
239 | .fb_probe = drm_fbdev_generic_helper_fb_probe, |
240 | .fb_dirty = drm_fbdev_generic_helper_fb_dirty, |
241 | }; |
242 | |
243 | static void drm_fbdev_generic_client_unregister(struct drm_client_dev *client) |
244 | { |
245 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
246 | |
247 | if (fb_helper->info) { |
248 | drm_fb_helper_unregister_info(fb_helper); |
249 | } else { |
250 | drm_client_release(client: &fb_helper->client); |
251 | drm_fb_helper_unprepare(fb_helper); |
252 | kfree(objp: fb_helper); |
253 | } |
254 | } |
255 | |
256 | static int drm_fbdev_generic_client_restore(struct drm_client_dev *client) |
257 | { |
258 | drm_fb_helper_lastclose(dev: client->dev); |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | static int drm_fbdev_generic_client_hotplug(struct drm_client_dev *client) |
264 | { |
265 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
266 | struct drm_device *dev = client->dev; |
267 | int ret; |
268 | |
269 | if (dev->fb_helper) |
270 | return drm_fb_helper_hotplug_event(fb_helper: dev->fb_helper); |
271 | |
272 | ret = drm_fb_helper_init(dev, helper: fb_helper); |
273 | if (ret) |
274 | goto err_drm_err; |
275 | |
276 | if (!drm_drv_uses_atomic_modeset(dev)) |
277 | drm_helper_disable_unused_functions(dev); |
278 | |
279 | ret = drm_fb_helper_initial_config(fb_helper); |
280 | if (ret) |
281 | goto err_drm_fb_helper_fini; |
282 | |
283 | return 0; |
284 | |
285 | err_drm_fb_helper_fini: |
286 | drm_fb_helper_fini(helper: fb_helper); |
287 | err_drm_err: |
288 | drm_err(dev, "fbdev: Failed to setup generic emulation (ret=%d)\n" , ret); |
289 | return ret; |
290 | } |
291 | |
292 | static const struct drm_client_funcs drm_fbdev_generic_client_funcs = { |
293 | .owner = THIS_MODULE, |
294 | .unregister = drm_fbdev_generic_client_unregister, |
295 | .restore = drm_fbdev_generic_client_restore, |
296 | .hotplug = drm_fbdev_generic_client_hotplug, |
297 | }; |
298 | |
299 | /** |
300 | * drm_fbdev_generic_setup() - Setup generic fbdev emulation |
301 | * @dev: DRM device |
302 | * @preferred_bpp: Preferred bits per pixel for the device. |
303 | * |
304 | * This function sets up generic fbdev emulation for drivers that supports |
305 | * dumb buffers with a virtual address and that can be mmap'ed. |
306 | * drm_fbdev_generic_setup() shall be called after the DRM driver registered |
307 | * the new DRM device with drm_dev_register(). |
308 | * |
309 | * Restore, hotplug events and teardown are all taken care of. Drivers that do |
310 | * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. |
311 | * Simple drivers might use drm_mode_config_helper_suspend(). |
312 | * |
313 | * In order to provide fixed mmap-able memory ranges, generic fbdev emulation |
314 | * uses a shadow buffer in system memory. The implementation blits the shadow |
315 | * fbdev buffer onto the real buffer in regular intervals. |
316 | * |
317 | * This function is safe to call even when there are no connectors present. |
318 | * Setup will be retried on the next hotplug event. |
319 | * |
320 | * The fbdev is destroyed by drm_dev_unregister(). |
321 | */ |
322 | void drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) |
323 | { |
324 | struct drm_fb_helper *fb_helper; |
325 | int ret; |
326 | |
327 | drm_WARN(dev, !dev->registered, "Device has not been registered.\n" ); |
328 | drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n" ); |
329 | |
330 | fb_helper = kzalloc(size: sizeof(*fb_helper), GFP_KERNEL); |
331 | if (!fb_helper) |
332 | return; |
333 | drm_fb_helper_prepare(dev, helper: fb_helper, preferred_bpp, funcs: &drm_fbdev_generic_helper_funcs); |
334 | |
335 | ret = drm_client_init(dev, client: &fb_helper->client, name: "fbdev" , funcs: &drm_fbdev_generic_client_funcs); |
336 | if (ret) { |
337 | drm_err(dev, "Failed to register client: %d\n" , ret); |
338 | goto err_drm_client_init; |
339 | } |
340 | |
341 | drm_client_register(client: &fb_helper->client); |
342 | |
343 | return; |
344 | |
345 | err_drm_client_init: |
346 | drm_fb_helper_unprepare(fb_helper); |
347 | kfree(objp: fb_helper); |
348 | return; |
349 | } |
350 | EXPORT_SYMBOL(drm_fbdev_generic_setup); |
351 | |