Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Character line display core support
0004  *
0005  * Copyright (C) 2016 Imagination Technologies
0006  * Author: Paul Burton <paul.burton@mips.com>
0007  *
0008  * Copyright (C) 2021 Glider bv
0009  */
0010 
0011 #include <generated/utsrelease.h>
0012 
0013 #include <linux/device.h>
0014 #include <linux/module.h>
0015 #include <linux/slab.h>
0016 #include <linux/string.h>
0017 #include <linux/sysfs.h>
0018 #include <linux/timer.h>
0019 
0020 #include "line-display.h"
0021 
0022 #define DEFAULT_SCROLL_RATE (HZ / 2)
0023 
0024 /**
0025  * linedisp_scroll() - scroll the display by a character
0026  * @t: really a pointer to the private data structure
0027  *
0028  * Scroll the current message along the display by one character, rearming the
0029  * timer if required.
0030  */
0031 static void linedisp_scroll(struct timer_list *t)
0032 {
0033     struct linedisp *linedisp = from_timer(linedisp, t, timer);
0034     unsigned int i, ch = linedisp->scroll_pos;
0035     unsigned int num_chars = linedisp->num_chars;
0036 
0037     /* update the current message string */
0038     for (i = 0; i < num_chars;) {
0039         /* copy as many characters from the string as possible */
0040         for (; i < num_chars && ch < linedisp->message_len; i++, ch++)
0041             linedisp->buf[i] = linedisp->message[ch];
0042 
0043         /* wrap around to the start of the string */
0044         ch = 0;
0045     }
0046 
0047     /* update the display */
0048     linedisp->update(linedisp);
0049 
0050     /* move on to the next character */
0051     linedisp->scroll_pos++;
0052     linedisp->scroll_pos %= linedisp->message_len;
0053 
0054     /* rearm the timer */
0055     if (linedisp->message_len > num_chars && linedisp->scroll_rate)
0056         mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate);
0057 }
0058 
0059 /**
0060  * linedisp_display() - set the message to be displayed
0061  * @linedisp: pointer to the private data structure
0062  * @msg: the message to display
0063  * @count: length of msg, or -1
0064  *
0065  * Display a new message @msg on the display. @msg can be longer than the
0066  * number of characters the display can display, in which case it will begin
0067  * scrolling across the display.
0068  *
0069  * Return: 0 on success, -ENOMEM on memory allocation failure
0070  */
0071 static int linedisp_display(struct linedisp *linedisp, const char *msg,
0072                 ssize_t count)
0073 {
0074     char *new_msg;
0075 
0076     /* stop the scroll timer */
0077     del_timer_sync(&linedisp->timer);
0078 
0079     if (count == -1)
0080         count = strlen(msg);
0081 
0082     /* if the string ends with a newline, trim it */
0083     if (msg[count - 1] == '\n')
0084         count--;
0085 
0086     if (!count) {
0087         /* Clear the display */
0088         kfree(linedisp->message);
0089         linedisp->message = NULL;
0090         linedisp->message_len = 0;
0091         memset(linedisp->buf, ' ', linedisp->num_chars);
0092         linedisp->update(linedisp);
0093         return 0;
0094     }
0095 
0096     new_msg = kmemdup_nul(msg, count, GFP_KERNEL);
0097     if (!new_msg)
0098         return -ENOMEM;
0099 
0100     kfree(linedisp->message);
0101 
0102     linedisp->message = new_msg;
0103     linedisp->message_len = count;
0104     linedisp->scroll_pos = 0;
0105 
0106     /* update the display */
0107     linedisp_scroll(&linedisp->timer);
0108 
0109     return 0;
0110 }
0111 
0112 /**
0113  * message_show() - read message via sysfs
0114  * @dev: the display device
0115  * @attr: the display message attribute
0116  * @buf: the buffer to read the message into
0117  *
0118  * Read the current message being displayed or scrolled across the display into
0119  * @buf, for reads from sysfs.
0120  *
0121  * Return: the number of characters written to @buf
0122  */
0123 static ssize_t message_show(struct device *dev, struct device_attribute *attr,
0124                 char *buf)
0125 {
0126     struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
0127 
0128     return sysfs_emit(buf, "%s\n", linedisp->message);
0129 }
0130 
0131 /**
0132  * message_store() - write a new message via sysfs
0133  * @dev: the display device
0134  * @attr: the display message attribute
0135  * @buf: the buffer containing the new message
0136  * @count: the size of the message in @buf
0137  *
0138  * Write a new message to display or scroll across the display from sysfs.
0139  *
0140  * Return: the size of the message on success, else -ERRNO
0141  */
0142 static ssize_t message_store(struct device *dev, struct device_attribute *attr,
0143                  const char *buf, size_t count)
0144 {
0145     struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
0146     int err;
0147 
0148     err = linedisp_display(linedisp, buf, count);
0149     return err ?: count;
0150 }
0151 
0152 static DEVICE_ATTR_RW(message);
0153 
0154 static ssize_t scroll_step_ms_show(struct device *dev,
0155                    struct device_attribute *attr, char *buf)
0156 {
0157     struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
0158 
0159     return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate));
0160 }
0161 
0162 static ssize_t scroll_step_ms_store(struct device *dev,
0163                     struct device_attribute *attr,
0164                     const char *buf, size_t count)
0165 {
0166     struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
0167     unsigned int ms;
0168 
0169     if (kstrtouint(buf, 10, &ms) != 0)
0170         return -EINVAL;
0171 
0172     linedisp->scroll_rate = msecs_to_jiffies(ms);
0173     if (linedisp->message && linedisp->message_len > linedisp->num_chars) {
0174         del_timer_sync(&linedisp->timer);
0175         if (linedisp->scroll_rate)
0176             linedisp_scroll(&linedisp->timer);
0177     }
0178 
0179     return count;
0180 }
0181 
0182 static DEVICE_ATTR_RW(scroll_step_ms);
0183 
0184 static struct attribute *linedisp_attrs[] = {
0185     &dev_attr_message.attr,
0186     &dev_attr_scroll_step_ms.attr,
0187     NULL,
0188 };
0189 ATTRIBUTE_GROUPS(linedisp);
0190 
0191 static const struct device_type linedisp_type = {
0192     .groups = linedisp_groups,
0193 };
0194 
0195 /**
0196  * linedisp_register - register a character line display
0197  * @linedisp: pointer to character line display structure
0198  * @parent: parent device
0199  * @num_chars: the number of characters that can be displayed
0200  * @buf: pointer to a buffer that can hold @num_chars characters
0201  * @update: Function called to update the display.  This must not sleep!
0202  *
0203  * Return: zero on success, else a negative error code.
0204  */
0205 int linedisp_register(struct linedisp *linedisp, struct device *parent,
0206               unsigned int num_chars, char *buf,
0207               void (*update)(struct linedisp *linedisp))
0208 {
0209     static atomic_t linedisp_id = ATOMIC_INIT(-1);
0210     int err;
0211 
0212     memset(linedisp, 0, sizeof(*linedisp));
0213     linedisp->dev.parent = parent;
0214     linedisp->dev.type = &linedisp_type;
0215     linedisp->update = update;
0216     linedisp->buf = buf;
0217     linedisp->num_chars = num_chars;
0218     linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
0219 
0220     device_initialize(&linedisp->dev);
0221     dev_set_name(&linedisp->dev, "linedisp.%lu",
0222              (unsigned long)atomic_inc_return(&linedisp_id));
0223 
0224     /* initialise a timer for scrolling the message */
0225     timer_setup(&linedisp->timer, linedisp_scroll, 0);
0226 
0227     err = device_add(&linedisp->dev);
0228     if (err)
0229         goto out_del_timer;
0230 
0231     /* display a default message */
0232     err = linedisp_display(linedisp, "Linux " UTS_RELEASE "       ", -1);
0233     if (err)
0234         goto out_del_dev;
0235 
0236     return 0;
0237 
0238 out_del_dev:
0239     device_del(&linedisp->dev);
0240 out_del_timer:
0241     del_timer_sync(&linedisp->timer);
0242     put_device(&linedisp->dev);
0243     return err;
0244 }
0245 EXPORT_SYMBOL_GPL(linedisp_register);
0246 
0247 /**
0248  * linedisp_unregister - unregister a character line display
0249  * @linedisp: pointer to character line display structure registered previously
0250  *        with linedisp_register()
0251  */
0252 void linedisp_unregister(struct linedisp *linedisp)
0253 {
0254     device_del(&linedisp->dev);
0255     del_timer_sync(&linedisp->timer);
0256     kfree(linedisp->message);
0257     put_device(&linedisp->dev);
0258 }
0259 EXPORT_SYMBOL_GPL(linedisp_unregister);
0260 
0261 MODULE_LICENSE("GPL");