Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/auxdisplay/line-display.c
49646 views
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 <[email protected]>
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
598