]> Git Repo - linux.git/blob - drivers/gpu/drm/clients/drm_log.c
Merge tag 'linux-watchdog-6.14-rc1' of git://www.linux-watchdog.org/linux-watchdog
[linux.git] / drivers / gpu / drm / clients / drm_log.c
1 // SPDX-License-Identifier: GPL-2.0 or MIT
2 /*
3  * Copyright (c) 2024 Red Hat.
4  * Author: Jocelyn Falempe <[email protected]>
5  */
6
7 #include <linux/console.h>
8 #include <linux/font.h>
9 #include <linux/init.h>
10 #include <linux/iosys-map.h>
11 #include <linux/module.h>
12 #include <linux/types.h>
13
14 #include <drm/drm_client.h>
15 #include <drm/drm_drv.h>
16 #include <drm/drm_fourcc.h>
17 #include <drm/drm_framebuffer.h>
18 #include <drm/drm_print.h>
19
20 #include "drm_client_internal.h"
21 #include "drm_draw_internal.h"
22 #include "drm_internal.h"
23
24 MODULE_AUTHOR("Jocelyn Falempe");
25 MODULE_DESCRIPTION("DRM boot logger");
26 MODULE_LICENSE("GPL");
27
28 static unsigned int scale = 1;
29 module_param(scale, uint, 0444);
30 MODULE_PARM_DESC(scale, "Integer scaling factor for drm_log, default is 1");
31
32 /**
33  * DOC: overview
34  *
35  * This is a simple graphic logger, to print the kernel message on screen, until
36  * a userspace application is able to take over.
37  * It is only for debugging purpose.
38  */
39
40 struct drm_log_scanout {
41         struct drm_client_buffer *buffer;
42         const struct font_desc *font;
43         u32 rows;
44         u32 columns;
45         u32 scaled_font_h;
46         u32 scaled_font_w;
47         u32 line;
48         u32 format;
49         u32 px_width;
50         u32 front_color;
51         u32 prefix_color;
52 };
53
54 struct drm_log {
55         struct mutex lock;
56         struct drm_client_dev client;
57         struct console con;
58         bool probed;
59         u32 n_scanout;
60         struct drm_log_scanout *scanout;
61 };
62
63 static struct drm_log *client_to_drm_log(struct drm_client_dev *client)
64 {
65         return container_of(client, struct drm_log, client);
66 }
67
68 static struct drm_log *console_to_drm_log(struct console *con)
69 {
70         return container_of(con, struct drm_log, con);
71 }
72
73 static void drm_log_blit(struct iosys_map *dst, unsigned int dst_pitch,
74                          const u8 *src, unsigned int src_pitch,
75                          u32 height, u32 width, u32 px_width, u32 color)
76 {
77         switch (px_width) {
78         case 2:
79                 drm_draw_blit16(dst, dst_pitch, src, src_pitch, height, width, scale, color);
80                 break;
81         case 3:
82                 drm_draw_blit24(dst, dst_pitch, src, src_pitch, height, width, scale, color);
83                 break;
84         case 4:
85                 drm_draw_blit32(dst, dst_pitch, src, src_pitch, height, width, scale, color);
86                 break;
87         default:
88                 WARN_ONCE(1, "Can't blit with pixel width %d\n", px_width);
89         }
90 }
91
92 static void drm_log_clear_line(struct drm_log_scanout *scanout, u32 line)
93 {
94         struct drm_framebuffer *fb = scanout->buffer->fb;
95         unsigned long height = scanout->scaled_font_h;
96         struct iosys_map map;
97         struct drm_rect r = DRM_RECT_INIT(0, line * height, fb->width, height);
98
99         if (drm_client_buffer_vmap_local(scanout->buffer, &map))
100                 return;
101         iosys_map_memset(&map, r.y1 * fb->pitches[0], 0, height * fb->pitches[0]);
102         drm_client_buffer_vunmap_local(scanout->buffer);
103         drm_client_framebuffer_flush(scanout->buffer, &r);
104 }
105
106 static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s,
107                               unsigned int len, unsigned int prefix_len)
108 {
109         struct drm_framebuffer *fb = scanout->buffer->fb;
110         struct iosys_map map;
111         const struct font_desc *font = scanout->font;
112         size_t font_pitch = DIV_ROUND_UP(font->width, 8);
113         const u8 *src;
114         u32 px_width = fb->format->cpp[0];
115         struct drm_rect r = DRM_RECT_INIT(0, scanout->line * scanout->scaled_font_h,
116                                           fb->width, (scanout->line + 1) * scanout->scaled_font_h);
117         u32 i;
118
119         if (drm_client_buffer_vmap_local(scanout->buffer, &map))
120                 return;
121
122         iosys_map_incr(&map, r.y1 * fb->pitches[0]);
123         for (i = 0; i < len && i < scanout->columns; i++) {
124                 u32 color = (i < prefix_len) ? scanout->prefix_color : scanout->front_color;
125                 src = drm_draw_get_char_bitmap(font, s[i], font_pitch);
126                 drm_log_blit(&map, fb->pitches[0], src, font_pitch,
127                              scanout->scaled_font_h, scanout->scaled_font_w,
128                              px_width, color);
129                 iosys_map_incr(&map, scanout->scaled_font_w * px_width);
130         }
131
132         scanout->line++;
133         if (scanout->line >= scanout->rows)
134                 scanout->line = 0;
135         drm_client_buffer_vunmap_local(scanout->buffer);
136         drm_client_framebuffer_flush(scanout->buffer, &r);
137 }
138
139 static void drm_log_draw_new_line(struct drm_log_scanout *scanout,
140                                   const char *s, unsigned int len, unsigned int prefix_len)
141 {
142         if (scanout->line == 0) {
143                 drm_log_clear_line(scanout, 0);
144                 drm_log_clear_line(scanout, 1);
145                 drm_log_clear_line(scanout, 2);
146         } else if (scanout->line + 2 < scanout->rows)
147                 drm_log_clear_line(scanout, scanout->line + 2);
148
149         drm_log_draw_line(scanout, s, len, prefix_len);
150 }
151
152 /*
153  * Depends on print_time() in printk.c
154  * Timestamp is written with "[%5lu.%06lu]"
155  */
156 #define TS_PREFIX_LEN 13
157
158 static void drm_log_draw_kmsg_record(struct drm_log_scanout *scanout,
159                                      const char *s, unsigned int len)
160 {
161         u32 prefix_len = 0;
162
163         if (len > TS_PREFIX_LEN && s[0] == '[' && s[6] == '.' && s[TS_PREFIX_LEN] == ']')
164                 prefix_len = TS_PREFIX_LEN + 1;
165
166         /* do not print the ending \n character */
167         if (s[len - 1] == '\n')
168                 len--;
169
170         while (len > scanout->columns) {
171                 drm_log_draw_new_line(scanout, s, scanout->columns, prefix_len);
172                 s += scanout->columns;
173                 len -= scanout->columns;
174                 prefix_len = 0;
175         }
176         if (len)
177                 drm_log_draw_new_line(scanout, s, len, prefix_len);
178 }
179
180 static u32 drm_log_find_usable_format(struct drm_plane *plane)
181 {
182         int i;
183
184         for (i = 0; i < plane->format_count; i++)
185                 if (drm_draw_color_from_xrgb8888(0xffffff, plane->format_types[i]) != 0)
186                         return plane->format_types[i];
187         return DRM_FORMAT_INVALID;
188 }
189
190 static int drm_log_setup_modeset(struct drm_client_dev *client,
191                                  struct drm_mode_set *mode_set,
192                                  struct drm_log_scanout *scanout)
193 {
194         struct drm_crtc *crtc = mode_set->crtc;
195         u32 width = mode_set->mode->hdisplay;
196         u32 height = mode_set->mode->vdisplay;
197         u32 format;
198
199         scanout->font = get_default_font(width, height, NULL, NULL);
200         if (!scanout->font)
201                 return -ENOENT;
202
203         format = drm_log_find_usable_format(crtc->primary);
204         if (format == DRM_FORMAT_INVALID)
205                 return -EINVAL;
206
207         scanout->buffer = drm_client_framebuffer_create(client, width, height, format);
208         if (IS_ERR(scanout->buffer)) {
209                 drm_warn(client->dev, "drm_log can't create framebuffer %d %d %p4cc\n",
210                          width, height, &format);
211                 return -ENOMEM;
212         }
213         mode_set->fb = scanout->buffer->fb;
214         scanout->scaled_font_h = scanout->font->height * scale;
215         scanout->scaled_font_w = scanout->font->width * scale;
216         scanout->rows = height / scanout->scaled_font_h;
217         scanout->columns = width / scanout->scaled_font_w;
218         scanout->front_color = drm_draw_color_from_xrgb8888(0xffffff, format);
219         scanout->prefix_color = drm_draw_color_from_xrgb8888(0x4e9a06, format);
220         return 0;
221 }
222
223 static int drm_log_count_modeset(struct drm_client_dev *client)
224 {
225         struct drm_mode_set *mode_set;
226         int count = 0;
227
228         mutex_lock(&client->modeset_mutex);
229         drm_client_for_each_modeset(mode_set, client)
230                 count++;
231         mutex_unlock(&client->modeset_mutex);
232         return count;
233 }
234
235 static void drm_log_init_client(struct drm_log *dlog)
236 {
237         struct drm_client_dev *client = &dlog->client;
238         struct drm_mode_set *mode_set;
239         int i, max_modeset;
240         int n_modeset = 0;
241
242         dlog->probed = true;
243
244         if (drm_client_modeset_probe(client, 0, 0))
245                 return;
246
247         max_modeset = drm_log_count_modeset(client);
248         if (!max_modeset)
249                 return;
250
251         dlog->scanout = kcalloc(max_modeset, sizeof(*dlog->scanout), GFP_KERNEL);
252         if (!dlog->scanout)
253                 return;
254
255         mutex_lock(&client->modeset_mutex);
256         drm_client_for_each_modeset(mode_set, client) {
257                 if (!mode_set->mode)
258                         continue;
259                 if (drm_log_setup_modeset(client, mode_set, &dlog->scanout[n_modeset]))
260                         continue;
261                 n_modeset++;
262         }
263         mutex_unlock(&client->modeset_mutex);
264         if (n_modeset == 0)
265                 goto err_nomodeset;
266
267         if (drm_client_modeset_commit(client))
268                 goto err_failed_commit;
269
270         dlog->n_scanout = n_modeset;
271         return;
272
273 err_failed_commit:
274         for (i = 0; i < n_modeset; i++)
275                 drm_client_framebuffer_delete(dlog->scanout[i].buffer);
276
277 err_nomodeset:
278         kfree(dlog->scanout);
279         dlog->scanout = NULL;
280 }
281
282 static void drm_log_free_scanout(struct drm_client_dev *client)
283 {
284         struct drm_log *dlog = client_to_drm_log(client);
285         int i;
286
287         if (dlog->n_scanout) {
288                 for (i = 0; i < dlog->n_scanout; i++)
289                         drm_client_framebuffer_delete(dlog->scanout[i].buffer);
290                 dlog->n_scanout = 0;
291                 kfree(dlog->scanout);
292                 dlog->scanout = NULL;
293         }
294 }
295
296 static void drm_log_client_unregister(struct drm_client_dev *client)
297 {
298         struct drm_log *dlog = client_to_drm_log(client);
299         struct drm_device *dev = client->dev;
300
301         unregister_console(&dlog->con);
302
303         mutex_lock(&dlog->lock);
304         drm_log_free_scanout(client);
305         drm_client_release(client);
306         mutex_unlock(&dlog->lock);
307         kfree(dlog);
308         drm_dbg(dev, "Unregistered with drm log\n");
309 }
310
311 static int drm_log_client_hotplug(struct drm_client_dev *client)
312 {
313         struct drm_log *dlog = client_to_drm_log(client);
314
315         mutex_lock(&dlog->lock);
316         drm_log_free_scanout(client);
317         dlog->probed = false;
318         mutex_unlock(&dlog->lock);
319         return 0;
320 }
321
322 static int drm_log_client_suspend(struct drm_client_dev *client, bool _console_lock)
323 {
324         struct drm_log *dlog = client_to_drm_log(client);
325
326         console_stop(&dlog->con);
327
328         return 0;
329 }
330
331 static int drm_log_client_resume(struct drm_client_dev *client, bool _console_lock)
332 {
333         struct drm_log *dlog = client_to_drm_log(client);
334
335         console_start(&dlog->con);
336
337         return 0;
338 }
339
340 static const struct drm_client_funcs drm_log_client_funcs = {
341         .owner          = THIS_MODULE,
342         .unregister     = drm_log_client_unregister,
343         .hotplug        = drm_log_client_hotplug,
344         .suspend        = drm_log_client_suspend,
345         .resume         = drm_log_client_resume,
346 };
347
348 static void drm_log_write_thread(struct console *con, struct nbcon_write_context *wctxt)
349 {
350         struct drm_log *dlog = console_to_drm_log(con);
351         int i;
352
353         if (!dlog->probed)
354                 drm_log_init_client(dlog);
355
356         /* Check that we are still the master before drawing */
357         if (drm_master_internal_acquire(dlog->client.dev)) {
358                 drm_master_internal_release(dlog->client.dev);
359
360                 for (i = 0; i < dlog->n_scanout; i++)
361                         drm_log_draw_kmsg_record(&dlog->scanout[i], wctxt->outbuf, wctxt->len);
362         }
363 }
364
365 static void drm_log_lock(struct console *con, unsigned long *flags)
366 {
367         struct drm_log *dlog = console_to_drm_log(con);
368
369         mutex_lock(&dlog->lock);
370         migrate_disable();
371 }
372
373 static void drm_log_unlock(struct console *con, unsigned long flags)
374 {
375         struct drm_log *dlog = console_to_drm_log(con);
376
377         migrate_enable();
378         mutex_unlock(&dlog->lock);
379 }
380
381 static void drm_log_register_console(struct console *con)
382 {
383         strscpy(con->name, "drm_log");
384         con->write_thread = drm_log_write_thread;
385         con->device_lock = drm_log_lock;
386         con->device_unlock = drm_log_unlock;
387         con->flags = CON_PRINTBUFFER | CON_NBCON;
388         con->index = -1;
389
390         register_console(con);
391 }
392
393 /**
394  * drm_log_register() - Register a drm device to drm_log
395  * @dev: the drm device to register.
396  */
397 void drm_log_register(struct drm_device *dev)
398 {
399         struct drm_log *new;
400
401         new = kzalloc(sizeof(*new), GFP_KERNEL);
402         if (!new)
403                 goto err_warn;
404
405         mutex_init(&new->lock);
406         if (drm_client_init(dev, &new->client, "drm_log", &drm_log_client_funcs))
407                 goto err_free;
408
409         drm_client_register(&new->client);
410
411         drm_log_register_console(&new->con);
412
413         drm_dbg(dev, "Registered with drm log as %s\n", new->con.name);
414         return;
415
416 err_free:
417         kfree(new);
418 err_warn:
419         drm_warn(dev, "Failed to register with drm log\n");
420 }
This page took 0.054824 seconds and 4 git commands to generate.