1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Character line display core support 4 * 5 * Copyright (C) 2016 Imagination Technologies 6 * Author: Paul Burton <paul.burton@mips.com> 7 * 8 * Copyright (C) 2021 Glider bv 9 * Copyright (C) 2025 Jean-François Lessard 10 */ 11 12 #ifndef CONFIG_PANEL_BOOT_MESSAGE 13 #include <generated/utsrelease.h> 14 #endif 15 16 #include <linux/cleanup.h> 17 #include <linux/device.h> 18 #include <linux/export.h> 19 #include <linux/idr.h> 20 #include <linux/jiffies.h> 21 #include <linux/kstrtox.h> 22 #include <linux/list.h> 23 #include <linux/module.h> 24 #include <linux/slab.h> 25 #include <linux/spinlock.h> 26 #include <linux/string.h> 27 #include <linux/sysfs.h> 28 #include <linux/timer.h> 29 30 #include <linux/map_to_7segment.h> 31 #include <linux/map_to_14segment.h> 32 33 #include "line-display.h" 34 35 #define DEFAULT_SCROLL_RATE (HZ / 2) 36 37 /** 38 * struct linedisp_attachment - Holds the device to linedisp mapping 39 * @list: List entry for the linedisp_attachments list 40 * @device: Pointer to the device where linedisp attributes are added 41 * @linedisp: Pointer to the linedisp mapped to the device 42 * @direct: true for directly attached device using linedisp_attach(), 43 * false for child registered device using linedisp_register() 44 */ 45 struct linedisp_attachment { 46 struct list_head list; 47 struct device *device; 48 struct linedisp *linedisp; 49 bool direct; 50 }; 51 52 static LIST_HEAD(linedisp_attachments); 53 static DEFINE_SPINLOCK(linedisp_attachments_lock); 54 55 static int create_attachment(struct device *dev, struct linedisp *linedisp, bool direct) 56 { 57 struct linedisp_attachment *attachment; 58 59 attachment = kzalloc(sizeof(*attachment), GFP_KERNEL); 60 if (!attachment) 61 return -ENOMEM; 62 63 attachment->device = dev; 64 attachment->linedisp = linedisp; 65 attachment->direct = direct; 66 67 guard(spinlock)(&linedisp_attachments_lock); 68 list_add(&attachment->list, &linedisp_attachments); 69 70 return 0; 71 } 72 73 static struct linedisp *delete_attachment(struct device *dev, bool direct) 74 { 75 struct linedisp_attachment *attachment; 76 struct linedisp *linedisp; 77 78 guard(spinlock)(&linedisp_attachments_lock); 79 80 list_for_each_entry(attachment, &linedisp_attachments, list) { 81 if (attachment->device == dev && 82 attachment->direct == direct) 83 break; 84 } 85 86 if (list_entry_is_head(attachment, &linedisp_attachments, list)) 87 return NULL; 88 89 linedisp = attachment->linedisp; 90 list_del(&attachment->list); 91 kfree(attachment); 92 93 return linedisp; 94 } 95 96 static struct linedisp *to_linedisp(struct device *dev) 97 { 98 struct linedisp_attachment *attachment; 99 100 guard(spinlock)(&linedisp_attachments_lock); 101 102 list_for_each_entry(attachment, &linedisp_attachments, list) { 103 if (attachment->device == dev) 104 break; 105 } 106 107 if (list_entry_is_head(attachment, &linedisp_attachments, list)) 108 return NULL; 109 110 return attachment->linedisp; 111 } 112 113 static inline bool should_scroll(struct linedisp *linedisp) 114 { 115 return linedisp->message_len > linedisp->num_chars && linedisp->scroll_rate; 116 } 117 118 /** 119 * linedisp_scroll() - scroll the display by a character 120 * @t: really a pointer to the private data structure 121 * 122 * Scroll the current message along the display by one character, rearming the 123 * timer if required. 124 */ 125 static void linedisp_scroll(struct timer_list *t) 126 { 127 struct linedisp *linedisp = timer_container_of(linedisp, t, timer); 128 unsigned int i, ch = linedisp->scroll_pos; 129 unsigned int num_chars = linedisp->num_chars; 130 131 /* update the current message string */ 132 for (i = 0; i < num_chars;) { 133 /* copy as many characters from the string as possible */ 134 for (; i < num_chars && ch < linedisp->message_len; i++, ch++) 135 linedisp->buf[i] = linedisp->message[ch]; 136 137 /* wrap around to the start of the string */ 138 ch = 0; 139 } 140 141 /* update the display */ 142 linedisp->ops->update(linedisp); 143 144 /* move on to the next character */ 145 linedisp->scroll_pos++; 146 linedisp->scroll_pos %= linedisp->message_len; 147 148 /* rearm the timer */ 149 mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate); 150 } 151 152 /** 153 * linedisp_display() - set the message to be displayed 154 * @linedisp: pointer to the private data structure 155 * @msg: the message to display 156 * @count: length of msg, or -1 157 * 158 * Display a new message @msg on the display. @msg can be longer than the 159 * number of characters the display can display, in which case it will begin 160 * scrolling across the display. 161 * 162 * Return: 0 on success, -ENOMEM on memory allocation failure 163 */ 164 static int linedisp_display(struct linedisp *linedisp, const char *msg, 165 ssize_t count) 166 { 167 char *new_msg; 168 169 /* stop the scroll timer */ 170 timer_delete_sync(&linedisp->timer); 171 172 if (count == -1) 173 count = strlen(msg); 174 175 /* if the string ends with a newline, trim it */ 176 if (msg[count - 1] == '\n') 177 count--; 178 179 if (!count) { 180 /* Clear the display */ 181 kfree(linedisp->message); 182 linedisp->message = NULL; 183 linedisp->message_len = 0; 184 memset(linedisp->buf, ' ', linedisp->num_chars); 185 linedisp->ops->update(linedisp); 186 return 0; 187 } 188 189 new_msg = kmemdup_nul(msg, count, GFP_KERNEL); 190 if (!new_msg) 191 return -ENOMEM; 192 193 kfree(linedisp->message); 194 195 linedisp->message = new_msg; 196 linedisp->message_len = count; 197 linedisp->scroll_pos = 0; 198 199 if (should_scroll(linedisp)) { 200 /* display scrolling message */ 201 linedisp_scroll(&linedisp->timer); 202 } else { 203 /* display static message */ 204 memset(linedisp->buf, ' ', linedisp->num_chars); 205 memcpy(linedisp->buf, linedisp->message, 206 umin(linedisp->num_chars, linedisp->message_len)); 207 linedisp->ops->update(linedisp); 208 } 209 210 return 0; 211 } 212 213 /** 214 * message_show() - read message via sysfs 215 * @dev: the display device 216 * @attr: the display message attribute 217 * @buf: the buffer to read the message into 218 * 219 * Read the current message being displayed or scrolled across the display into 220 * @buf, for reads from sysfs. 221 * 222 * Return: the number of characters written to @buf 223 */ 224 static ssize_t message_show(struct device *dev, struct device_attribute *attr, 225 char *buf) 226 { 227 struct linedisp *linedisp = to_linedisp(dev); 228 229 return sysfs_emit(buf, "%s\n", linedisp->message); 230 } 231 232 /** 233 * message_store() - write a new message via sysfs 234 * @dev: the display device 235 * @attr: the display message attribute 236 * @buf: the buffer containing the new message 237 * @count: the size of the message in @buf 238 * 239 * Write a new message to display or scroll across the display from sysfs. 240 * 241 * Return: the size of the message on success, else -ERRNO 242 */ 243 static ssize_t message_store(struct device *dev, struct device_attribute *attr, 244 const char *buf, size_t count) 245 { 246 struct linedisp *linedisp = to_linedisp(dev); 247 int err; 248 249 err = linedisp_display(linedisp, buf, count); 250 return err ?: count; 251 } 252 253 static DEVICE_ATTR_RW(message); 254 255 static ssize_t num_chars_show(struct device *dev, struct device_attribute *attr, 256 char *buf) 257 { 258 struct linedisp *linedisp = to_linedisp(dev); 259 260 return sysfs_emit(buf, "%u\n", linedisp->num_chars); 261 } 262 263 static DEVICE_ATTR_RO(num_chars); 264 265 static ssize_t scroll_step_ms_show(struct device *dev, 266 struct device_attribute *attr, char *buf) 267 { 268 struct linedisp *linedisp = to_linedisp(dev); 269 270 return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate)); 271 } 272 273 static ssize_t scroll_step_ms_store(struct device *dev, 274 struct device_attribute *attr, 275 const char *buf, size_t count) 276 { 277 struct linedisp *linedisp = to_linedisp(dev); 278 unsigned int ms; 279 int err; 280 281 err = kstrtouint(buf, 10, &ms); 282 if (err) 283 return err; 284 285 timer_delete_sync(&linedisp->timer); 286 287 linedisp->scroll_rate = msecs_to_jiffies(ms); 288 289 if (should_scroll(linedisp)) 290 linedisp_scroll(&linedisp->timer); 291 292 return count; 293 } 294 295 static DEVICE_ATTR_RW(scroll_step_ms); 296 297 static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf) 298 { 299 struct linedisp *linedisp = to_linedisp(dev); 300 struct linedisp_map *map = linedisp->map; 301 302 memcpy(buf, &map->map, map->size); 303 return map->size; 304 } 305 306 static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr, 307 const char *buf, size_t count) 308 { 309 struct linedisp *linedisp = to_linedisp(dev); 310 struct linedisp_map *map = linedisp->map; 311 312 if (count != map->size) 313 return -EINVAL; 314 315 memcpy(&map->map, buf, count); 316 return count; 317 } 318 319 static const SEG7_DEFAULT_MAP(initial_map_seg7); 320 static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store); 321 322 static const SEG14_DEFAULT_MAP(initial_map_seg14); 323 static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store); 324 325 static struct attribute *linedisp_attrs[] = { 326 &dev_attr_message.attr, 327 &dev_attr_num_chars.attr, 328 &dev_attr_scroll_step_ms.attr, 329 &dev_attr_map_seg7.attr, 330 &dev_attr_map_seg14.attr, 331 NULL 332 }; 333 334 static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) 335 { 336 struct device *dev = kobj_to_dev(kobj); 337 struct linedisp *linedisp = to_linedisp(dev); 338 struct linedisp_map *map = linedisp->map; 339 umode_t mode = attr->mode; 340 341 if (attr == &dev_attr_map_seg7.attr) { 342 if (!map) 343 return 0; 344 if (map->type != LINEDISP_MAP_SEG7) 345 return 0; 346 } 347 348 if (attr == &dev_attr_map_seg14.attr) { 349 if (!map) 350 return 0; 351 if (map->type != LINEDISP_MAP_SEG14) 352 return 0; 353 } 354 355 return mode; 356 }; 357 358 static const struct attribute_group linedisp_group = { 359 .is_visible = linedisp_attr_is_visible, 360 .attrs = linedisp_attrs, 361 }; 362 __ATTRIBUTE_GROUPS(linedisp); 363 364 static DEFINE_IDA(linedisp_id); 365 366 static void linedisp_release(struct device *dev) 367 { 368 struct linedisp *linedisp = to_linedisp(dev); 369 370 kfree(linedisp->map); 371 kfree(linedisp->message); 372 kfree(linedisp->buf); 373 ida_free(&linedisp_id, linedisp->id); 374 } 375 376 static const struct device_type linedisp_type = { 377 .groups = linedisp_groups, 378 .release = linedisp_release, 379 }; 380 381 static int linedisp_init_map(struct linedisp *linedisp) 382 { 383 struct linedisp_map *map; 384 int err; 385 386 if (!linedisp->ops->get_map_type) 387 return 0; 388 389 err = linedisp->ops->get_map_type(linedisp); 390 if (err < 0) 391 return err; 392 393 map = kmalloc(sizeof(*map), GFP_KERNEL); 394 if (!map) 395 return -ENOMEM; 396 397 map->type = err; 398 399 /* assign initial mapping */ 400 switch (map->type) { 401 case LINEDISP_MAP_SEG7: 402 map->map.seg7 = initial_map_seg7; 403 map->size = sizeof(map->map.seg7); 404 break; 405 case LINEDISP_MAP_SEG14: 406 map->map.seg14 = initial_map_seg14; 407 map->size = sizeof(map->map.seg14); 408 break; 409 default: 410 kfree(map); 411 return -EINVAL; 412 } 413 414 linedisp->map = map; 415 416 return 0; 417 } 418 419 #ifdef CONFIG_PANEL_BOOT_MESSAGE 420 #define LINEDISP_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE 421 #else 422 #define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " " 423 #endif 424 425 /** 426 * linedisp_attach - attach a character line display 427 * @linedisp: pointer to character line display structure 428 * @dev: pointer of the device to attach to 429 * @num_chars: the number of characters that can be displayed 430 * @ops: character line display operations 431 * 432 * Directly attach the line-display sysfs attributes to the passed device. 433 * The caller is responsible for calling linedisp_detach() to release resources 434 * after use. 435 * 436 * Return: zero on success, else a negative error code. 437 */ 438 int linedisp_attach(struct linedisp *linedisp, struct device *dev, 439 unsigned int num_chars, const struct linedisp_ops *ops) 440 { 441 int err; 442 443 memset(linedisp, 0, sizeof(*linedisp)); 444 linedisp->ops = ops; 445 linedisp->num_chars = num_chars; 446 linedisp->scroll_rate = DEFAULT_SCROLL_RATE; 447 448 linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL); 449 if (!linedisp->buf) 450 return -ENOMEM; 451 452 /* initialise a character mapping, if required */ 453 err = linedisp_init_map(linedisp); 454 if (err) 455 goto out_free_buf; 456 457 /* initialise a timer for scrolling the message */ 458 timer_setup(&linedisp->timer, linedisp_scroll, 0); 459 460 err = create_attachment(dev, linedisp, true); 461 if (err) 462 goto out_del_timer; 463 464 /* display a default message */ 465 err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1); 466 if (err) 467 goto out_del_attach; 468 469 /* add attribute groups to target device */ 470 err = device_add_groups(dev, linedisp_groups); 471 if (err) 472 goto out_del_attach; 473 474 return 0; 475 476 out_del_attach: 477 delete_attachment(dev, true); 478 out_del_timer: 479 timer_delete_sync(&linedisp->timer); 480 out_free_buf: 481 kfree(linedisp->buf); 482 return err; 483 } 484 EXPORT_SYMBOL_NS_GPL(linedisp_attach, "LINEDISP"); 485 486 /** 487 * linedisp_detach - detach a character line display 488 * @dev: pointer of the device to detach from, that was previously 489 * attached with linedisp_attach() 490 */ 491 void linedisp_detach(struct device *dev) 492 { 493 struct linedisp *linedisp; 494 495 linedisp = delete_attachment(dev, true); 496 if (!linedisp) 497 return; 498 499 timer_delete_sync(&linedisp->timer); 500 501 device_remove_groups(dev, linedisp_groups); 502 503 kfree(linedisp->map); 504 kfree(linedisp->message); 505 kfree(linedisp->buf); 506 } 507 EXPORT_SYMBOL_NS_GPL(linedisp_detach, "LINEDISP"); 508 509 /** 510 * linedisp_register - register a character line display 511 * @linedisp: pointer to character line display structure 512 * @parent: parent device 513 * @num_chars: the number of characters that can be displayed 514 * @ops: character line display operations 515 * 516 * Register the line-display sysfs attributes to a new device named 517 * "linedisp.N" added to the passed parent device. 518 * The caller is responsible for calling linedisp_unregister() to release 519 * resources after use. 520 * 521 * Return: zero on success, else a negative error code. 522 */ 523 int linedisp_register(struct linedisp *linedisp, struct device *parent, 524 unsigned int num_chars, const struct linedisp_ops *ops) 525 { 526 int err; 527 528 memset(linedisp, 0, sizeof(*linedisp)); 529 linedisp->dev.parent = parent; 530 linedisp->dev.type = &linedisp_type; 531 linedisp->ops = ops; 532 linedisp->num_chars = num_chars; 533 linedisp->scroll_rate = DEFAULT_SCROLL_RATE; 534 535 err = ida_alloc(&linedisp_id, GFP_KERNEL); 536 if (err < 0) 537 return err; 538 linedisp->id = err; 539 540 device_initialize(&linedisp->dev); 541 dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id); 542 543 err = -ENOMEM; 544 linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL); 545 if (!linedisp->buf) 546 goto out_put_device; 547 548 /* initialise a character mapping, if required */ 549 err = linedisp_init_map(linedisp); 550 if (err) 551 goto out_put_device; 552 553 /* initialise a timer for scrolling the message */ 554 timer_setup(&linedisp->timer, linedisp_scroll, 0); 555 556 err = create_attachment(&linedisp->dev, linedisp, false); 557 if (err) 558 goto out_del_timer; 559 560 /* display a default message */ 561 err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1); 562 if (err) 563 goto out_del_attach; 564 565 err = device_add(&linedisp->dev); 566 if (err) 567 goto out_del_attach; 568 569 return 0; 570 571 out_del_attach: 572 delete_attachment(&linedisp->dev, false); 573 out_del_timer: 574 timer_delete_sync(&linedisp->timer); 575 out_put_device: 576 put_device(&linedisp->dev); 577 return err; 578 } 579 EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP"); 580 581 /** 582 * linedisp_unregister - unregister a character line display 583 * @linedisp: pointer to character line display structure registered previously 584 * with linedisp_register() 585 */ 586 void linedisp_unregister(struct linedisp *linedisp) 587 { 588 device_del(&linedisp->dev); 589 delete_attachment(&linedisp->dev, false); 590 timer_delete_sync(&linedisp->timer); 591 put_device(&linedisp->dev); 592 } 593 EXPORT_SYMBOL_NS_GPL(linedisp_unregister, "LINEDISP"); 594 595 MODULE_DESCRIPTION("Character line display core support"); 596 MODULE_LICENSE("GPL"); 597