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_buffer_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 = font_data_glyph_buf(font->data, font->width, font->height, 126 (unsigned char)s[i]); 127 if (src) 128 drm_log_blit(&map, fb->pitches[0], src, font_pitch, 129 scanout->scaled_font_h, scanout->scaled_font_w, 130 px_width, color); 131 iosys_map_incr(&map, scanout->scaled_font_w * px_width); 132 } 133 134 scanout->line++; 135 if (scanout->line >= scanout->rows) 136 scanout->line = 0; 137 drm_client_buffer_vunmap_local(scanout->buffer); 138 drm_client_buffer_flush(scanout->buffer, &r); 139 } 140 141 static void drm_log_draw_new_line(struct drm_log_scanout *scanout, 142 const char *s, unsigned int len, unsigned int prefix_len) 143 { 144 if (scanout->line == 0) { 145 drm_log_clear_line(scanout, 0); 146 drm_log_clear_line(scanout, 1); 147 drm_log_clear_line(scanout, 2); 148 } else if (scanout->line + 2 < scanout->rows) 149 drm_log_clear_line(scanout, scanout->line + 2); 150 151 drm_log_draw_line(scanout, s, len, prefix_len); 152 } 153 154 /* 155 * Depends on print_time() in printk.c 156 * Timestamp is written with "[%5lu.%06lu]" 157 */ 158 #define TS_PREFIX_LEN 13 159 160 static void drm_log_draw_kmsg_record(struct drm_log_scanout *scanout, 161 const char *s, unsigned int len) 162 { 163 u32 prefix_len = 0; 164 165 if (len > TS_PREFIX_LEN && s[0] == '[' && s[6] == '.' && s[TS_PREFIX_LEN] == ']') 166 prefix_len = TS_PREFIX_LEN + 1; 167 168 /* do not print the ending \n character */ 169 if (s[len - 1] == '\n') 170 len--; 171 172 while (len > scanout->columns) { 173 drm_log_draw_new_line(scanout, s, scanout->columns, prefix_len); 174 s += scanout->columns; 175 len -= scanout->columns; 176 prefix_len = 0; 177 } 178 if (len) 179 drm_log_draw_new_line(scanout, s, len, prefix_len); 180 } 181 182 static u32 drm_log_find_usable_format(struct drm_plane *plane) 183 { 184 int i; 185 186 for (i = 0; i < plane->format_count; i++) 187 if (drm_draw_can_convert_from_xrgb8888(plane->format_types[i])) 188 return plane->format_types[i]; 189 return DRM_FORMAT_INVALID; 190 } 191 192 static int drm_log_setup_modeset(struct drm_client_dev *client, 193 struct drm_mode_set *mode_set, 194 struct drm_log_scanout *scanout) 195 { 196 struct drm_crtc *crtc = mode_set->crtc; 197 u32 width = mode_set->mode->hdisplay; 198 u32 height = mode_set->mode->vdisplay; 199 u32 format; 200 201 scanout->font = get_default_font(width, height, NULL, NULL); 202 if (!scanout->font) 203 return -ENOENT; 204 205 format = drm_log_find_usable_format(crtc->primary); 206 if (format == DRM_FORMAT_INVALID) 207 return -EINVAL; 208 209 scanout->buffer = drm_client_buffer_create_dumb(client, width, height, format); 210 if (IS_ERR(scanout->buffer)) { 211 drm_warn(client->dev, "drm_log can't create framebuffer %d %d %p4cc\n", 212 width, height, &format); 213 return -ENOMEM; 214 } 215 mode_set->fb = scanout->buffer->fb; 216 scanout->scaled_font_h = scanout->font->height * scale; 217 scanout->scaled_font_w = scanout->font->width * scale; 218 scanout->rows = height / scanout->scaled_font_h; 219 scanout->columns = width / scanout->scaled_font_w; 220 scanout->front_color = drm_draw_color_from_xrgb8888(0xffffff, format); 221 scanout->prefix_color = drm_draw_color_from_xrgb8888(0x4e9a06, format); 222 return 0; 223 } 224 225 static int drm_log_count_modeset(struct drm_client_dev *client) 226 { 227 struct drm_mode_set *mode_set; 228 int count = 0; 229 230 mutex_lock(&client->modeset_mutex); 231 drm_client_for_each_modeset(mode_set, client) 232 count++; 233 mutex_unlock(&client->modeset_mutex); 234 return count; 235 } 236 237 static void drm_log_init_client(struct drm_log *dlog) 238 { 239 struct drm_client_dev *client = &dlog->client; 240 struct drm_mode_set *mode_set; 241 int i, max_modeset; 242 int n_modeset = 0; 243 244 dlog->probed = true; 245 246 if (drm_client_modeset_probe(client, 0, 0)) 247 return; 248 249 max_modeset = drm_log_count_modeset(client); 250 if (!max_modeset) 251 return; 252 253 dlog->scanout = kzalloc_objs(*dlog->scanout, max_modeset); 254 if (!dlog->scanout) 255 return; 256 257 mutex_lock(&client->modeset_mutex); 258 drm_client_for_each_modeset(mode_set, client) { 259 if (!mode_set->mode) 260 continue; 261 if (drm_log_setup_modeset(client, mode_set, &dlog->scanout[n_modeset])) 262 continue; 263 n_modeset++; 264 } 265 mutex_unlock(&client->modeset_mutex); 266 if (n_modeset == 0) 267 goto err_nomodeset; 268 269 if (drm_client_modeset_commit(client)) 270 goto err_failed_commit; 271 272 dlog->n_scanout = n_modeset; 273 return; 274 275 err_failed_commit: 276 for (i = 0; i < n_modeset; i++) 277 drm_client_buffer_delete(dlog->scanout[i].buffer); 278 279 err_nomodeset: 280 kfree(dlog->scanout); 281 dlog->scanout = NULL; 282 } 283 284 static void drm_log_free_scanout(struct drm_client_dev *client) 285 { 286 struct drm_log *dlog = client_to_drm_log(client); 287 int i; 288 289 if (dlog->n_scanout) { 290 for (i = 0; i < dlog->n_scanout; i++) 291 drm_client_buffer_delete(dlog->scanout[i].buffer); 292 dlog->n_scanout = 0; 293 kfree(dlog->scanout); 294 dlog->scanout = NULL; 295 } 296 } 297 298 static void drm_log_client_free(struct drm_client_dev *client) 299 { 300 struct drm_log *dlog = client_to_drm_log(client); 301 struct drm_device *dev = client->dev; 302 303 kfree(dlog); 304 305 drm_dbg(dev, "Unregistered with drm log\n"); 306 } 307 308 static void drm_log_client_unregister(struct drm_client_dev *client) 309 { 310 struct drm_log *dlog = client_to_drm_log(client); 311 312 unregister_console(&dlog->con); 313 314 mutex_lock(&dlog->lock); 315 drm_log_free_scanout(client); 316 mutex_unlock(&dlog->lock); 317 drm_client_release(client); 318 } 319 320 static int drm_log_client_restore(struct drm_client_dev *client, bool force) 321 { 322 int ret; 323 324 if (force) 325 ret = drm_client_modeset_commit_locked(client); 326 else 327 ret = drm_client_modeset_commit(client); 328 329 return ret; 330 } 331 332 static int drm_log_client_hotplug(struct drm_client_dev *client) 333 { 334 struct drm_log *dlog = client_to_drm_log(client); 335 336 mutex_lock(&dlog->lock); 337 drm_log_free_scanout(client); 338 dlog->probed = false; 339 mutex_unlock(&dlog->lock); 340 return 0; 341 } 342 343 static int drm_log_client_suspend(struct drm_client_dev *client) 344 { 345 struct drm_log *dlog = client_to_drm_log(client); 346 347 console_suspend(&dlog->con); 348 349 return 0; 350 } 351 352 static int drm_log_client_resume(struct drm_client_dev *client) 353 { 354 struct drm_log *dlog = client_to_drm_log(client); 355 356 console_resume(&dlog->con); 357 358 return 0; 359 } 360 361 static const struct drm_client_funcs drm_log_client_funcs = { 362 .owner = THIS_MODULE, 363 .free = drm_log_client_free, 364 .unregister = drm_log_client_unregister, 365 .restore = drm_log_client_restore, 366 .hotplug = drm_log_client_hotplug, 367 .suspend = drm_log_client_suspend, 368 .resume = drm_log_client_resume, 369 }; 370 371 static void drm_log_write_thread(struct console *con, struct nbcon_write_context *wctxt) 372 { 373 struct drm_log *dlog = console_to_drm_log(con); 374 int i; 375 376 if (!dlog->probed) 377 drm_log_init_client(dlog); 378 379 /* Check that we are still the master before drawing */ 380 if (drm_master_internal_acquire(dlog->client.dev)) { 381 drm_master_internal_release(dlog->client.dev); 382 383 for (i = 0; i < dlog->n_scanout; i++) 384 drm_log_draw_kmsg_record(&dlog->scanout[i], wctxt->outbuf, wctxt->len); 385 } 386 } 387 388 static void drm_log_lock(struct console *con, unsigned long *flags) 389 { 390 struct drm_log *dlog = console_to_drm_log(con); 391 392 mutex_lock(&dlog->lock); 393 migrate_disable(); 394 } 395 396 static void drm_log_unlock(struct console *con, unsigned long flags) 397 { 398 struct drm_log *dlog = console_to_drm_log(con); 399 400 migrate_enable(); 401 mutex_unlock(&dlog->lock); 402 } 403 404 static void drm_log_register_console(struct console *con) 405 { 406 strscpy(con->name, "drm_log"); 407 con->write_thread = drm_log_write_thread; 408 con->device_lock = drm_log_lock; 409 con->device_unlock = drm_log_unlock; 410 con->flags = CON_PRINTBUFFER | CON_NBCON; 411 con->index = -1; 412 413 register_console(con); 414 } 415 416 /** 417 * drm_log_register() - Register a drm device to drm_log 418 * @dev: the drm device to register. 419 */ 420 void drm_log_register(struct drm_device *dev) 421 { 422 struct drm_log *new; 423 424 new = kzalloc_obj(*new); 425 if (!new) 426 goto err_warn; 427 428 mutex_init(&new->lock); 429 if (drm_client_init(dev, &new->client, "drm_log", &drm_log_client_funcs)) 430 goto err_free; 431 432 drm_client_register(&new->client); 433 434 drm_log_register_console(&new->con); 435 436 drm_dbg(dev, "Registered with drm log as %s\n", new->con.name); 437 return; 438 439 err_free: 440 kfree(new); 441 err_warn: 442 drm_warn(dev, "Failed to register with drm log\n"); 443 } 444