66 * Author: Paul Burton <paul.burton@mips.com>
77 *
88 * Copyright (C) 2021 Glider bv
9+ * Copyright (C) 2025 Jean-François Lessard
910 */
1011
1112#ifndef CONFIG_PANEL_BOOT_MESSAGE
1213#include <generated/utsrelease.h>
1314#endif
1415
15- #include <linux/container_of .h>
16+ #include <linux/cleanup .h>
1617#include <linux/device.h>
1718#include <linux/export.h>
1819#include <linux/idr.h>
1920#include <linux/jiffies.h>
2021#include <linux/kstrtox.h>
22+ #include <linux/list.h>
2123#include <linux/module.h>
2224#include <linux/slab.h>
25+ #include <linux/spinlock.h>
2326#include <linux/string.h>
2427#include <linux/sysfs.h>
2528#include <linux/timer.h>
3134
3235#define DEFAULT_SCROLL_RATE (HZ / 2)
3336
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+
3496static struct linedisp * to_linedisp (struct device * dev )
3597{
36- return container_of (dev , struct linedisp , dev );
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 ;
37111}
38112
39113static inline bool should_scroll (struct linedisp * linedisp )
@@ -348,13 +422,102 @@ static int linedisp_init_map(struct linedisp *linedisp)
348422#define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " "
349423#endif
350424
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+
351509/**
352510 * linedisp_register - register a character line display
353511 * @linedisp: pointer to character line display structure
354512 * @parent: parent device
355513 * @num_chars: the number of characters that can be displayed
356514 * @ops: character line display operations
357515 *
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+ *
358521 * Return: zero on success, else a negative error code.
359522 */
360523int linedisp_register (struct linedisp * linedisp , struct device * parent ,
@@ -390,19 +553,23 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent,
390553 /* initialise a timer for scrolling the message */
391554 timer_setup (& linedisp -> timer , linedisp_scroll , 0 );
392555
393- err = device_add (& linedisp -> dev );
556+ err = create_attachment (& linedisp -> dev , linedisp , false );
394557 if (err )
395558 goto out_del_timer ;
396559
397560 /* display a default message */
398561 err = linedisp_display (linedisp , LINEDISP_INIT_TEXT , -1 );
399562 if (err )
400- goto out_del_dev ;
563+ goto out_del_attach ;
564+
565+ err = device_add (& linedisp -> dev );
566+ if (err )
567+ goto out_del_attach ;
401568
402569 return 0 ;
403570
404- out_del_dev :
405- device_del (& linedisp -> dev );
571+ out_del_attach :
572+ delete_attachment (& linedisp -> dev , false );
406573out_del_timer :
407574 timer_delete_sync (& linedisp -> timer );
408575out_put_device :
@@ -419,6 +586,7 @@ EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP");
419586void linedisp_unregister (struct linedisp * linedisp )
420587{
421588 device_del (& linedisp -> dev );
589+ delete_attachment (& linedisp -> dev , false);
422590 timer_delete_sync (& linedisp -> timer );
423591 put_device (& linedisp -> dev );
424592}
0 commit comments