1 // SPDX-License-Identifier: GPL-2.0 or MIT 2 /* 3 * Copyright (c) 2024 Red Hat. 4 * Author: Jocelyn Falempe <jfalempe@redhat.com> 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 } 421