line-display.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  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. * Copyright (C) 2025 Jean-François Lessard
  10. */
  11. #ifndef CONFIG_PANEL_BOOT_MESSAGE
  12. #include <generated/utsrelease.h>
  13. #endif
  14. #include <linux/cleanup.h>
  15. #include <linux/device.h>
  16. #include <linux/export.h>
  17. #include <linux/idr.h>
  18. #include <linux/jiffies.h>
  19. #include <linux/kstrtox.h>
  20. #include <linux/list.h>
  21. #include <linux/module.h>
  22. #include <linux/slab.h>
  23. #include <linux/spinlock.h>
  24. #include <linux/string.h>
  25. #include <linux/sysfs.h>
  26. #include <linux/timer.h>
  27. #include <linux/map_to_7segment.h>
  28. #include <linux/map_to_14segment.h>
  29. #include "line-display.h"
  30. #define DEFAULT_SCROLL_RATE (HZ / 2)
  31. /**
  32. * struct linedisp_attachment - Holds the device to linedisp mapping
  33. * @list: List entry for the linedisp_attachments list
  34. * @device: Pointer to the device where linedisp attributes are added
  35. * @linedisp: Pointer to the linedisp mapped to the device
  36. * @direct: true for directly attached device using linedisp_attach(),
  37. * false for child registered device using linedisp_register()
  38. */
  39. struct linedisp_attachment {
  40. struct list_head list;
  41. struct device *device;
  42. struct linedisp *linedisp;
  43. bool direct;
  44. };
  45. static LIST_HEAD(linedisp_attachments);
  46. static DEFINE_SPINLOCK(linedisp_attachments_lock);
  47. static int create_attachment(struct device *dev, struct linedisp *linedisp, bool direct)
  48. {
  49. struct linedisp_attachment *attachment;
  50. attachment = kzalloc_obj(*attachment);
  51. if (!attachment)
  52. return -ENOMEM;
  53. attachment->device = dev;
  54. attachment->linedisp = linedisp;
  55. attachment->direct = direct;
  56. guard(spinlock)(&linedisp_attachments_lock);
  57. list_add(&attachment->list, &linedisp_attachments);
  58. return 0;
  59. }
  60. static struct linedisp *delete_attachment(struct device *dev, bool direct)
  61. {
  62. struct linedisp_attachment *attachment;
  63. struct linedisp *linedisp;
  64. guard(spinlock)(&linedisp_attachments_lock);
  65. list_for_each_entry(attachment, &linedisp_attachments, list) {
  66. if (attachment->device == dev &&
  67. attachment->direct == direct)
  68. break;
  69. }
  70. if (list_entry_is_head(attachment, &linedisp_attachments, list))
  71. return NULL;
  72. linedisp = attachment->linedisp;
  73. list_del(&attachment->list);
  74. kfree(attachment);
  75. return linedisp;
  76. }
  77. static struct linedisp *to_linedisp(struct device *dev)
  78. {
  79. struct linedisp_attachment *attachment;
  80. guard(spinlock)(&linedisp_attachments_lock);
  81. list_for_each_entry(attachment, &linedisp_attachments, list) {
  82. if (attachment->device == dev)
  83. break;
  84. }
  85. if (list_entry_is_head(attachment, &linedisp_attachments, list))
  86. return NULL;
  87. return attachment->linedisp;
  88. }
  89. static inline bool should_scroll(struct linedisp *linedisp)
  90. {
  91. return linedisp->message_len > linedisp->num_chars && linedisp->scroll_rate;
  92. }
  93. /**
  94. * linedisp_scroll() - scroll the display by a character
  95. * @t: really a pointer to the private data structure
  96. *
  97. * Scroll the current message along the display by one character, rearming the
  98. * timer if required.
  99. */
  100. static void linedisp_scroll(struct timer_list *t)
  101. {
  102. struct linedisp *linedisp = timer_container_of(linedisp, t, timer);
  103. unsigned int i, ch = linedisp->scroll_pos;
  104. unsigned int num_chars = linedisp->num_chars;
  105. /* update the current message string */
  106. for (i = 0; i < num_chars;) {
  107. /* copy as many characters from the string as possible */
  108. for (; i < num_chars && ch < linedisp->message_len; i++, ch++)
  109. linedisp->buf[i] = linedisp->message[ch];
  110. /* wrap around to the start of the string */
  111. ch = 0;
  112. }
  113. /* update the display */
  114. linedisp->ops->update(linedisp);
  115. /* move on to the next character */
  116. linedisp->scroll_pos++;
  117. linedisp->scroll_pos %= linedisp->message_len;
  118. /* rearm the timer */
  119. mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate);
  120. }
  121. /**
  122. * linedisp_display() - set the message to be displayed
  123. * @linedisp: pointer to the private data structure
  124. * @msg: the message to display
  125. * @count: length of msg, or -1
  126. *
  127. * Display a new message @msg on the display. @msg can be longer than the
  128. * number of characters the display can display, in which case it will begin
  129. * scrolling across the display.
  130. *
  131. * Return: 0 on success, -ENOMEM on memory allocation failure
  132. */
  133. static int linedisp_display(struct linedisp *linedisp, const char *msg,
  134. ssize_t count)
  135. {
  136. char *new_msg;
  137. /* stop the scroll timer */
  138. timer_delete_sync(&linedisp->timer);
  139. if (count == -1)
  140. count = strlen(msg);
  141. /* if the string ends with a newline, trim it */
  142. if (msg[count - 1] == '\n')
  143. count--;
  144. if (!count) {
  145. /* Clear the display */
  146. kfree(linedisp->message);
  147. linedisp->message = NULL;
  148. linedisp->message_len = 0;
  149. memset(linedisp->buf, ' ', linedisp->num_chars);
  150. linedisp->ops->update(linedisp);
  151. return 0;
  152. }
  153. new_msg = kmemdup_nul(msg, count, GFP_KERNEL);
  154. if (!new_msg)
  155. return -ENOMEM;
  156. kfree(linedisp->message);
  157. linedisp->message = new_msg;
  158. linedisp->message_len = count;
  159. linedisp->scroll_pos = 0;
  160. if (should_scroll(linedisp)) {
  161. /* display scrolling message */
  162. linedisp_scroll(&linedisp->timer);
  163. } else {
  164. /* display static message */
  165. memset(linedisp->buf, ' ', linedisp->num_chars);
  166. memcpy(linedisp->buf, linedisp->message,
  167. umin(linedisp->num_chars, linedisp->message_len));
  168. linedisp->ops->update(linedisp);
  169. }
  170. return 0;
  171. }
  172. /**
  173. * message_show() - read message via sysfs
  174. * @dev: the display device
  175. * @attr: the display message attribute
  176. * @buf: the buffer to read the message into
  177. *
  178. * Read the current message being displayed or scrolled across the display into
  179. * @buf, for reads from sysfs.
  180. *
  181. * Return: the number of characters written to @buf
  182. */
  183. static ssize_t message_show(struct device *dev, struct device_attribute *attr,
  184. char *buf)
  185. {
  186. struct linedisp *linedisp = to_linedisp(dev);
  187. return sysfs_emit(buf, "%s\n", linedisp->message);
  188. }
  189. /**
  190. * message_store() - write a new message via sysfs
  191. * @dev: the display device
  192. * @attr: the display message attribute
  193. * @buf: the buffer containing the new message
  194. * @count: the size of the message in @buf
  195. *
  196. * Write a new message to display or scroll across the display from sysfs.
  197. *
  198. * Return: the size of the message on success, else -ERRNO
  199. */
  200. static ssize_t message_store(struct device *dev, struct device_attribute *attr,
  201. const char *buf, size_t count)
  202. {
  203. struct linedisp *linedisp = to_linedisp(dev);
  204. int err;
  205. err = linedisp_display(linedisp, buf, count);
  206. return err ?: count;
  207. }
  208. static DEVICE_ATTR_RW(message);
  209. static ssize_t num_chars_show(struct device *dev, struct device_attribute *attr,
  210. char *buf)
  211. {
  212. struct linedisp *linedisp = to_linedisp(dev);
  213. return sysfs_emit(buf, "%u\n", linedisp->num_chars);
  214. }
  215. static DEVICE_ATTR_RO(num_chars);
  216. static ssize_t scroll_step_ms_show(struct device *dev,
  217. struct device_attribute *attr, char *buf)
  218. {
  219. struct linedisp *linedisp = to_linedisp(dev);
  220. return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate));
  221. }
  222. static ssize_t scroll_step_ms_store(struct device *dev,
  223. struct device_attribute *attr,
  224. const char *buf, size_t count)
  225. {
  226. struct linedisp *linedisp = to_linedisp(dev);
  227. unsigned int ms;
  228. int err;
  229. err = kstrtouint(buf, 10, &ms);
  230. if (err)
  231. return err;
  232. timer_delete_sync(&linedisp->timer);
  233. linedisp->scroll_rate = msecs_to_jiffies(ms);
  234. if (should_scroll(linedisp))
  235. linedisp_scroll(&linedisp->timer);
  236. return count;
  237. }
  238. static DEVICE_ATTR_RW(scroll_step_ms);
  239. static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf)
  240. {
  241. struct linedisp *linedisp = to_linedisp(dev);
  242. struct linedisp_map *map = linedisp->map;
  243. memcpy(buf, &map->map, map->size);
  244. return map->size;
  245. }
  246. static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr,
  247. const char *buf, size_t count)
  248. {
  249. struct linedisp *linedisp = to_linedisp(dev);
  250. struct linedisp_map *map = linedisp->map;
  251. if (count != map->size)
  252. return -EINVAL;
  253. memcpy(&map->map, buf, count);
  254. return count;
  255. }
  256. static const SEG7_DEFAULT_MAP(initial_map_seg7);
  257. static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store);
  258. static const SEG14_DEFAULT_MAP(initial_map_seg14);
  259. static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store);
  260. static struct attribute *linedisp_attrs[] = {
  261. &dev_attr_message.attr,
  262. &dev_attr_num_chars.attr,
  263. &dev_attr_scroll_step_ms.attr,
  264. &dev_attr_map_seg7.attr,
  265. &dev_attr_map_seg14.attr,
  266. NULL
  267. };
  268. static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n)
  269. {
  270. struct device *dev = kobj_to_dev(kobj);
  271. struct linedisp *linedisp = to_linedisp(dev);
  272. struct linedisp_map *map = linedisp->map;
  273. umode_t mode = attr->mode;
  274. if (attr == &dev_attr_map_seg7.attr) {
  275. if (!map)
  276. return 0;
  277. if (map->type != LINEDISP_MAP_SEG7)
  278. return 0;
  279. }
  280. if (attr == &dev_attr_map_seg14.attr) {
  281. if (!map)
  282. return 0;
  283. if (map->type != LINEDISP_MAP_SEG14)
  284. return 0;
  285. }
  286. return mode;
  287. };
  288. static const struct attribute_group linedisp_group = {
  289. .is_visible = linedisp_attr_is_visible,
  290. .attrs = linedisp_attrs,
  291. };
  292. __ATTRIBUTE_GROUPS(linedisp);
  293. static DEFINE_IDA(linedisp_id);
  294. static void linedisp_release(struct device *dev)
  295. {
  296. struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
  297. kfree(linedisp->map);
  298. kfree(linedisp->message);
  299. kfree(linedisp->buf);
  300. ida_free(&linedisp_id, linedisp->id);
  301. }
  302. static const struct device_type linedisp_type = {
  303. .groups = linedisp_groups,
  304. .release = linedisp_release,
  305. };
  306. static int linedisp_init_map(struct linedisp *linedisp)
  307. {
  308. struct linedisp_map *map;
  309. int err;
  310. if (!linedisp->ops->get_map_type)
  311. return 0;
  312. err = linedisp->ops->get_map_type(linedisp);
  313. if (err < 0)
  314. return err;
  315. map = kmalloc_obj(*map);
  316. if (!map)
  317. return -ENOMEM;
  318. map->type = err;
  319. /* assign initial mapping */
  320. switch (map->type) {
  321. case LINEDISP_MAP_SEG7:
  322. map->map.seg7 = initial_map_seg7;
  323. map->size = sizeof(map->map.seg7);
  324. break;
  325. case LINEDISP_MAP_SEG14:
  326. map->map.seg14 = initial_map_seg14;
  327. map->size = sizeof(map->map.seg14);
  328. break;
  329. default:
  330. kfree(map);
  331. return -EINVAL;
  332. }
  333. linedisp->map = map;
  334. return 0;
  335. }
  336. #ifdef CONFIG_PANEL_BOOT_MESSAGE
  337. #define LINEDISP_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
  338. #else
  339. #define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " "
  340. #endif
  341. /**
  342. * linedisp_attach - attach a character line display
  343. * @linedisp: pointer to character line display structure
  344. * @dev: pointer of the device to attach to
  345. * @num_chars: the number of characters that can be displayed
  346. * @ops: character line display operations
  347. *
  348. * Directly attach the line-display sysfs attributes to the passed device.
  349. * The caller is responsible for calling linedisp_detach() to release resources
  350. * after use.
  351. *
  352. * Return: zero on success, else a negative error code.
  353. */
  354. int linedisp_attach(struct linedisp *linedisp, struct device *dev,
  355. unsigned int num_chars, const struct linedisp_ops *ops)
  356. {
  357. int err;
  358. memset(linedisp, 0, sizeof(*linedisp));
  359. linedisp->ops = ops;
  360. linedisp->num_chars = num_chars;
  361. linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
  362. linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL);
  363. if (!linedisp->buf)
  364. return -ENOMEM;
  365. /* initialise a character mapping, if required */
  366. err = linedisp_init_map(linedisp);
  367. if (err)
  368. goto out_free_buf;
  369. /* initialise a timer for scrolling the message */
  370. timer_setup(&linedisp->timer, linedisp_scroll, 0);
  371. err = create_attachment(dev, linedisp, true);
  372. if (err)
  373. goto out_del_timer;
  374. /* display a default message */
  375. err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1);
  376. if (err)
  377. goto out_del_attach;
  378. /* add attribute groups to target device */
  379. err = device_add_groups(dev, linedisp_groups);
  380. if (err)
  381. goto out_del_attach;
  382. return 0;
  383. out_del_attach:
  384. delete_attachment(dev, true);
  385. out_del_timer:
  386. timer_delete_sync(&linedisp->timer);
  387. out_free_buf:
  388. kfree(linedisp->buf);
  389. return err;
  390. }
  391. EXPORT_SYMBOL_NS_GPL(linedisp_attach, "LINEDISP");
  392. /**
  393. * linedisp_detach - detach a character line display
  394. * @dev: pointer of the device to detach from, that was previously
  395. * attached with linedisp_attach()
  396. */
  397. void linedisp_detach(struct device *dev)
  398. {
  399. struct linedisp *linedisp;
  400. linedisp = delete_attachment(dev, true);
  401. if (!linedisp)
  402. return;
  403. timer_delete_sync(&linedisp->timer);
  404. device_remove_groups(dev, linedisp_groups);
  405. kfree(linedisp->map);
  406. kfree(linedisp->message);
  407. kfree(linedisp->buf);
  408. }
  409. EXPORT_SYMBOL_NS_GPL(linedisp_detach, "LINEDISP");
  410. /**
  411. * linedisp_register - register a character line display
  412. * @linedisp: pointer to character line display structure
  413. * @parent: parent device
  414. * @num_chars: the number of characters that can be displayed
  415. * @ops: character line display operations
  416. *
  417. * Register the line-display sysfs attributes to a new device named
  418. * "linedisp.N" added to the passed parent device.
  419. * The caller is responsible for calling linedisp_unregister() to release
  420. * resources after use.
  421. *
  422. * Return: zero on success, else a negative error code.
  423. */
  424. int linedisp_register(struct linedisp *linedisp, struct device *parent,
  425. unsigned int num_chars, const struct linedisp_ops *ops)
  426. {
  427. int err;
  428. memset(linedisp, 0, sizeof(*linedisp));
  429. linedisp->dev.parent = parent;
  430. linedisp->dev.type = &linedisp_type;
  431. linedisp->ops = ops;
  432. linedisp->num_chars = num_chars;
  433. linedisp->scroll_rate = DEFAULT_SCROLL_RATE;
  434. err = ida_alloc(&linedisp_id, GFP_KERNEL);
  435. if (err < 0)
  436. return err;
  437. linedisp->id = err;
  438. device_initialize(&linedisp->dev);
  439. dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id);
  440. err = -ENOMEM;
  441. linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL);
  442. if (!linedisp->buf)
  443. goto out_put_device;
  444. /* initialise a character mapping, if required */
  445. err = linedisp_init_map(linedisp);
  446. if (err)
  447. goto out_put_device;
  448. /* initialise a timer for scrolling the message */
  449. timer_setup(&linedisp->timer, linedisp_scroll, 0);
  450. err = create_attachment(&linedisp->dev, linedisp, false);
  451. if (err)
  452. goto out_del_timer;
  453. /* display a default message */
  454. err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1);
  455. if (err)
  456. goto out_del_attach;
  457. err = device_add(&linedisp->dev);
  458. if (err)
  459. goto out_del_attach;
  460. return 0;
  461. out_del_attach:
  462. delete_attachment(&linedisp->dev, false);
  463. out_del_timer:
  464. timer_delete_sync(&linedisp->timer);
  465. out_put_device:
  466. put_device(&linedisp->dev);
  467. return err;
  468. }
  469. EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP");
  470. /**
  471. * linedisp_unregister - unregister a character line display
  472. * @linedisp: pointer to character line display structure registered previously
  473. * with linedisp_register()
  474. */
  475. void linedisp_unregister(struct linedisp *linedisp)
  476. {
  477. device_del(&linedisp->dev);
  478. delete_attachment(&linedisp->dev, false);
  479. timer_delete_sync(&linedisp->timer);
  480. put_device(&linedisp->dev);
  481. }
  482. EXPORT_SYMBOL_NS_GPL(linedisp_unregister, "LINEDISP");
  483. MODULE_DESCRIPTION("Character line display core support");
  484. MODULE_LICENSE("GPL");