line-display.c (6972B)
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 */ 10 11#include <generated/utsrelease.h> 12 13#include <linux/device.h> 14#include <linux/module.h> 15#include <linux/slab.h> 16#include <linux/string.h> 17#include <linux/sysfs.h> 18#include <linux/timer.h> 19 20#include "line-display.h" 21 22#define DEFAULT_SCROLL_RATE (HZ / 2) 23 24/** 25 * linedisp_scroll() - scroll the display by a character 26 * @t: really a pointer to the private data structure 27 * 28 * Scroll the current message along the display by one character, rearming the 29 * timer if required. 30 */ 31static void linedisp_scroll(struct timer_list *t) 32{ 33 struct linedisp *linedisp = from_timer(linedisp, t, timer); 34 unsigned int i, ch = linedisp->scroll_pos; 35 unsigned int num_chars = linedisp->num_chars; 36 37 /* update the current message string */ 38 for (i = 0; i < num_chars;) { 39 /* copy as many characters from the string as possible */ 40 for (; i < num_chars && ch < linedisp->message_len; i++, ch++) 41 linedisp->buf[i] = linedisp->message[ch]; 42 43 /* wrap around to the start of the string */ 44 ch = 0; 45 } 46 47 /* update the display */ 48 linedisp->update(linedisp); 49 50 /* move on to the next character */ 51 linedisp->scroll_pos++; 52 linedisp->scroll_pos %= linedisp->message_len; 53 54 /* rearm the timer */ 55 if (linedisp->message_len > num_chars && linedisp->scroll_rate) 56 mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate); 57} 58 59/** 60 * linedisp_display() - set the message to be displayed 61 * @linedisp: pointer to the private data structure 62 * @msg: the message to display 63 * @count: length of msg, or -1 64 * 65 * Display a new message @msg on the display. @msg can be longer than the 66 * number of characters the display can display, in which case it will begin 67 * scrolling across the display. 68 * 69 * Return: 0 on success, -ENOMEM on memory allocation failure 70 */ 71static int linedisp_display(struct linedisp *linedisp, const char *msg, 72 ssize_t count) 73{ 74 char *new_msg; 75 76 /* stop the scroll timer */ 77 del_timer_sync(&linedisp->timer); 78 79 if (count == -1) 80 count = strlen(msg); 81 82 /* if the string ends with a newline, trim it */ 83 if (msg[count - 1] == '\n') 84 count--; 85 86 if (!count) { 87 /* Clear the display */ 88 kfree(linedisp->message); 89 linedisp->message = NULL; 90 linedisp->message_len = 0; 91 memset(linedisp->buf, ' ', linedisp->num_chars); 92 linedisp->update(linedisp); 93 return 0; 94 } 95 96 new_msg = kmemdup_nul(msg, count, GFP_KERNEL); 97 if (!new_msg) 98 return -ENOMEM; 99 100 kfree(linedisp->message); 101 102 linedisp->message = new_msg; 103 linedisp->message_len = count; 104 linedisp->scroll_pos = 0; 105 106 /* update the display */ 107 linedisp_scroll(&linedisp->timer); 108 109 return 0; 110} 111 112/** 113 * message_show() - read message via sysfs 114 * @dev: the display device 115 * @attr: the display message attribute 116 * @buf: the buffer to read the message into 117 * 118 * Read the current message being displayed or scrolled across the display into 119 * @buf, for reads from sysfs. 120 * 121 * Return: the number of characters written to @buf 122 */ 123static ssize_t message_show(struct device *dev, struct device_attribute *attr, 124 char *buf) 125{ 126 struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 127 128 return sysfs_emit(buf, "%s\n", linedisp->message); 129} 130 131/** 132 * message_store() - write a new message via sysfs 133 * @dev: the display device 134 * @attr: the display message attribute 135 * @buf: the buffer containing the new message 136 * @count: the size of the message in @buf 137 * 138 * Write a new message to display or scroll across the display from sysfs. 139 * 140 * Return: the size of the message on success, else -ERRNO 141 */ 142static ssize_t message_store(struct device *dev, struct device_attribute *attr, 143 const char *buf, size_t count) 144{ 145 struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 146 int err; 147 148 err = linedisp_display(linedisp, buf, count); 149 return err ?: count; 150} 151 152static DEVICE_ATTR_RW(message); 153 154static ssize_t scroll_step_ms_show(struct device *dev, 155 struct device_attribute *attr, char *buf) 156{ 157 struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 158 159 return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate)); 160} 161 162static ssize_t scroll_step_ms_store(struct device *dev, 163 struct device_attribute *attr, 164 const char *buf, size_t count) 165{ 166 struct linedisp *linedisp = container_of(dev, struct linedisp, dev); 167 unsigned int ms; 168 169 if (kstrtouint(buf, 10, &ms) != 0) 170 return -EINVAL; 171 172 linedisp->scroll_rate = msecs_to_jiffies(ms); 173 if (linedisp->message && linedisp->message_len > linedisp->num_chars) { 174 del_timer_sync(&linedisp->timer); 175 if (linedisp->scroll_rate) 176 linedisp_scroll(&linedisp->timer); 177 } 178 179 return count; 180} 181 182static DEVICE_ATTR_RW(scroll_step_ms); 183 184static struct attribute *linedisp_attrs[] = { 185 &dev_attr_message.attr, 186 &dev_attr_scroll_step_ms.attr, 187 NULL, 188}; 189ATTRIBUTE_GROUPS(linedisp); 190 191static const struct device_type linedisp_type = { 192 .groups = linedisp_groups, 193}; 194 195/** 196 * linedisp_register - register a character line display 197 * @linedisp: pointer to character line display structure 198 * @parent: parent device 199 * @num_chars: the number of characters that can be displayed 200 * @buf: pointer to a buffer that can hold @num_chars characters 201 * @update: Function called to update the display. This must not sleep! 202 * 203 * Return: zero on success, else a negative error code. 204 */ 205int linedisp_register(struct linedisp *linedisp, struct device *parent, 206 unsigned int num_chars, char *buf, 207 void (*update)(struct linedisp *linedisp)) 208{ 209 static atomic_t linedisp_id = ATOMIC_INIT(-1); 210 int err; 211 212 memset(linedisp, 0, sizeof(*linedisp)); 213 linedisp->dev.parent = parent; 214 linedisp->dev.type = &linedisp_type; 215 linedisp->update = update; 216 linedisp->buf = buf; 217 linedisp->num_chars = num_chars; 218 linedisp->scroll_rate = DEFAULT_SCROLL_RATE; 219 220 device_initialize(&linedisp->dev); 221 dev_set_name(&linedisp->dev, "linedisp.%lu", 222 (unsigned long)atomic_inc_return(&linedisp_id)); 223 224 /* initialise a timer for scrolling the message */ 225 timer_setup(&linedisp->timer, linedisp_scroll, 0); 226 227 err = device_add(&linedisp->dev); 228 if (err) 229 goto out_del_timer; 230 231 /* display a default message */ 232 err = linedisp_display(linedisp, "Linux " UTS_RELEASE " ", -1); 233 if (err) 234 goto out_del_dev; 235 236 return 0; 237 238out_del_dev: 239 device_del(&linedisp->dev); 240out_del_timer: 241 del_timer_sync(&linedisp->timer); 242 put_device(&linedisp->dev); 243 return err; 244} 245EXPORT_SYMBOL_GPL(linedisp_register); 246 247/** 248 * linedisp_unregister - unregister a character line display 249 * @linedisp: pointer to character line display structure registered previously 250 * with linedisp_register() 251 */ 252void linedisp_unregister(struct linedisp *linedisp) 253{ 254 device_del(&linedisp->dev); 255 del_timer_sync(&linedisp->timer); 256 kfree(linedisp->message); 257 put_device(&linedisp->dev); 258} 259EXPORT_SYMBOL_GPL(linedisp_unregister); 260 261MODULE_LICENSE("GPL");