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