1a6377d90SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
224b4b67dSSamo Pogacnik /*
324b4b67dSSamo Pogacnik * linux/drivers/char/ttyprintk.c
424b4b67dSSamo Pogacnik *
524b4b67dSSamo Pogacnik * Copyright (C) 2010 Samo Pogacnik
624b4b67dSSamo Pogacnik */
724b4b67dSSamo Pogacnik
824b4b67dSSamo Pogacnik /*
924b4b67dSSamo Pogacnik * This pseudo device allows user to make printk messages. It is possible
1024b4b67dSSamo Pogacnik * to store "console" messages inline with kernel messages for better analyses
1124b4b67dSSamo Pogacnik * of the boot process, for example.
1224b4b67dSSamo Pogacnik */
1324b4b67dSSamo Pogacnik
147ea4aa70SVincent Whitchurch #include <linux/console.h>
1524b4b67dSSamo Pogacnik #include <linux/device.h>
1624b4b67dSSamo Pogacnik #include <linux/serial.h>
1724b4b67dSSamo Pogacnik #include <linux/tty.h>
18b24313a8STakashi Iwai #include <linux/module.h>
199a655c77SZhenzhong Duan #include <linux/spinlock.h>
2024b4b67dSSamo Pogacnik
2124b4b67dSSamo Pogacnik struct ttyprintk_port {
2224b4b67dSSamo Pogacnik struct tty_port port;
239a655c77SZhenzhong Duan spinlock_t spinlock;
2424b4b67dSSamo Pogacnik };
2524b4b67dSSamo Pogacnik
2624b4b67dSSamo Pogacnik static struct ttyprintk_port tpk_port;
2724b4b67dSSamo Pogacnik
2824b4b67dSSamo Pogacnik /*
2924b4b67dSSamo Pogacnik * Our simple preformatting supports transparent output of (time-stamped)
3024b4b67dSSamo Pogacnik * printk messages (also suitable for logging service):
3124b4b67dSSamo Pogacnik * - any cr is replaced by nl
3224b4b67dSSamo Pogacnik * - adds a ttyprintk source tag in front of each line
330b3191d4SJoe Perches * - too long message is fragmented, with '\'nl between fragments
340b3191d4SJoe Perches * - TPK_STR_SIZE isn't really the write_room limiting factor, because
3524b4b67dSSamo Pogacnik * it is emptied on the fly during preformatting.
3624b4b67dSSamo Pogacnik */
3724b4b67dSSamo Pogacnik #define TPK_STR_SIZE 508 /* should be bigger then max expected line length */
3824b4b67dSSamo Pogacnik #define TPK_MAX_ROOM 4096 /* we could assume 4K for instance */
39acef6660SPeter Korsgaard #define TPK_PREFIX KERN_SOH __stringify(CONFIG_TTY_PRINTK_LEVEL)
40acef6660SPeter Korsgaard
4124b4b67dSSamo Pogacnik static int tpk_curr;
4224b4b67dSSamo Pogacnik
430b3191d4SJoe Perches static char tpk_buffer[TPK_STR_SIZE + 4];
440b3191d4SJoe Perches
tpk_flush(void)450b3191d4SJoe Perches static void tpk_flush(void)
460b3191d4SJoe Perches {
470b3191d4SJoe Perches if (tpk_curr > 0) {
480b3191d4SJoe Perches tpk_buffer[tpk_curr] = '\0';
49acef6660SPeter Korsgaard printk(TPK_PREFIX "[U] %s\n", tpk_buffer);
500b3191d4SJoe Perches tpk_curr = 0;
510b3191d4SJoe Perches }
520b3191d4SJoe Perches }
530b3191d4SJoe Perches
tpk_printk(const u8 * buf,int count)5469851e4aSJiri Slaby (SUSE) static int tpk_printk(const u8 *buf, int count)
5524b4b67dSSamo Pogacnik {
5618c092e5SColin Ian King int i;
5724b4b67dSSamo Pogacnik
5824b4b67dSSamo Pogacnik for (i = 0; i < count; i++) {
590b3191d4SJoe Perches if (tpk_curr >= TPK_STR_SIZE) {
600b3191d4SJoe Perches /* end of tmp buffer reached: cut the message in two */
610b3191d4SJoe Perches tpk_buffer[tpk_curr++] = '\\';
620b3191d4SJoe Perches tpk_flush();
630b3191d4SJoe Perches }
640b3191d4SJoe Perches
6524b4b67dSSamo Pogacnik switch (buf[i]) {
6624b4b67dSSamo Pogacnik case '\r':
670b3191d4SJoe Perches tpk_flush();
68ee8b593aSJiri Slaby if ((i + 1) < count && buf[i + 1] == '\n')
6924b4b67dSSamo Pogacnik i++;
7024b4b67dSSamo Pogacnik break;
7124b4b67dSSamo Pogacnik case '\n':
720b3191d4SJoe Perches tpk_flush();
7324b4b67dSSamo Pogacnik break;
7424b4b67dSSamo Pogacnik default:
750b3191d4SJoe Perches tpk_buffer[tpk_curr++] = buf[i];
760b3191d4SJoe Perches break;
7724b4b67dSSamo Pogacnik }
7824b4b67dSSamo Pogacnik }
7924b4b67dSSamo Pogacnik
8024b4b67dSSamo Pogacnik return count;
8124b4b67dSSamo Pogacnik }
8224b4b67dSSamo Pogacnik
8324b4b67dSSamo Pogacnik /*
8424b4b67dSSamo Pogacnik * TTY operations open function.
8524b4b67dSSamo Pogacnik */
tpk_open(struct tty_struct * tty,struct file * filp)8624b4b67dSSamo Pogacnik static int tpk_open(struct tty_struct *tty, struct file *filp)
8724b4b67dSSamo Pogacnik {
8824b4b67dSSamo Pogacnik tty->driver_data = &tpk_port;
8924b4b67dSSamo Pogacnik
9024b4b67dSSamo Pogacnik return tty_port_open(&tpk_port.port, tty, filp);
9124b4b67dSSamo Pogacnik }
9224b4b67dSSamo Pogacnik
9324b4b67dSSamo Pogacnik /*
9424b4b67dSSamo Pogacnik * TTY operations close function.
9524b4b67dSSamo Pogacnik */
tpk_close(struct tty_struct * tty,struct file * filp)9624b4b67dSSamo Pogacnik static void tpk_close(struct tty_struct *tty, struct file *filp)
9724b4b67dSSamo Pogacnik {
9824b4b67dSSamo Pogacnik struct ttyprintk_port *tpkp = tty->driver_data;
9924b4b67dSSamo Pogacnik
10024b4b67dSSamo Pogacnik tty_port_close(&tpkp->port, tty, filp);
10124b4b67dSSamo Pogacnik }
10224b4b67dSSamo Pogacnik
10324b4b67dSSamo Pogacnik /*
10424b4b67dSSamo Pogacnik * TTY operations write function.
10524b4b67dSSamo Pogacnik */
tpk_write(struct tty_struct * tty,const u8 * buf,size_t count)106*95713967SJiri Slaby (SUSE) static ssize_t tpk_write(struct tty_struct *tty, const u8 *buf, size_t count)
10724b4b67dSSamo Pogacnik {
10824b4b67dSSamo Pogacnik struct ttyprintk_port *tpkp = tty->driver_data;
1099a655c77SZhenzhong Duan unsigned long flags;
11024b4b67dSSamo Pogacnik int ret;
11124b4b67dSSamo Pogacnik
11224b4b67dSSamo Pogacnik /* exclusive use of tpk_printk within this tty */
1139a655c77SZhenzhong Duan spin_lock_irqsave(&tpkp->spinlock, flags);
11424b4b67dSSamo Pogacnik ret = tpk_printk(buf, count);
1159a655c77SZhenzhong Duan spin_unlock_irqrestore(&tpkp->spinlock, flags);
11624b4b67dSSamo Pogacnik
11724b4b67dSSamo Pogacnik return ret;
11824b4b67dSSamo Pogacnik }
11924b4b67dSSamo Pogacnik
12024b4b67dSSamo Pogacnik /*
12124b4b67dSSamo Pogacnik * TTY operations write_room function.
12224b4b67dSSamo Pogacnik */
tpk_write_room(struct tty_struct * tty)12303b3b1a2SJiri Slaby static unsigned int tpk_write_room(struct tty_struct *tty)
12424b4b67dSSamo Pogacnik {
12524b4b67dSSamo Pogacnik return TPK_MAX_ROOM;
12624b4b67dSSamo Pogacnik }
12724b4b67dSSamo Pogacnik
12824b4b67dSSamo Pogacnik /*
129c0070e1eSTetsuo Handa * TTY operations hangup function.
130c0070e1eSTetsuo Handa */
tpk_hangup(struct tty_struct * tty)131c0070e1eSTetsuo Handa static void tpk_hangup(struct tty_struct *tty)
132c0070e1eSTetsuo Handa {
133c0070e1eSTetsuo Handa struct ttyprintk_port *tpkp = tty->driver_data;
134c0070e1eSTetsuo Handa
135c0070e1eSTetsuo Handa tty_port_hangup(&tpkp->port);
136c0070e1eSTetsuo Handa }
137c0070e1eSTetsuo Handa
138bf3d6ab9SSamo Pogačnik /*
139bf3d6ab9SSamo Pogačnik * TTY port operations shutdown function.
140bf3d6ab9SSamo Pogačnik */
tpk_port_shutdown(struct tty_port * tport)141bf3d6ab9SSamo Pogačnik static void tpk_port_shutdown(struct tty_port *tport)
142bf3d6ab9SSamo Pogačnik {
143bf3d6ab9SSamo Pogačnik struct ttyprintk_port *tpkp =
144bf3d6ab9SSamo Pogačnik container_of(tport, struct ttyprintk_port, port);
145bf3d6ab9SSamo Pogačnik unsigned long flags;
146bf3d6ab9SSamo Pogačnik
147bf3d6ab9SSamo Pogačnik spin_lock_irqsave(&tpkp->spinlock, flags);
148bf3d6ab9SSamo Pogačnik tpk_flush();
149bf3d6ab9SSamo Pogačnik spin_unlock_irqrestore(&tpkp->spinlock, flags);
150bf3d6ab9SSamo Pogačnik }
151bf3d6ab9SSamo Pogačnik
15224b4b67dSSamo Pogacnik static const struct tty_operations ttyprintk_ops = {
15324b4b67dSSamo Pogacnik .open = tpk_open,
15424b4b67dSSamo Pogacnik .close = tpk_close,
15524b4b67dSSamo Pogacnik .write = tpk_write,
15624b4b67dSSamo Pogacnik .write_room = tpk_write_room,
157c0070e1eSTetsuo Handa .hangup = tpk_hangup,
15824b4b67dSSamo Pogacnik };
15924b4b67dSSamo Pogacnik
160bf3d6ab9SSamo Pogačnik static const struct tty_port_operations tpk_port_ops = {
161bf3d6ab9SSamo Pogačnik .shutdown = tpk_port_shutdown,
162bf3d6ab9SSamo Pogačnik };
16324b4b67dSSamo Pogacnik
16424b4b67dSSamo Pogacnik static struct tty_driver *ttyprintk_driver;
16524b4b67dSSamo Pogacnik
ttyprintk_console_device(struct console * c,int * index)1667ea4aa70SVincent Whitchurch static struct tty_driver *ttyprintk_console_device(struct console *c,
1677ea4aa70SVincent Whitchurch int *index)
1687ea4aa70SVincent Whitchurch {
1697ea4aa70SVincent Whitchurch *index = 0;
1707ea4aa70SVincent Whitchurch return ttyprintk_driver;
1717ea4aa70SVincent Whitchurch }
1727ea4aa70SVincent Whitchurch
1737ea4aa70SVincent Whitchurch static struct console ttyprintk_console = {
1747ea4aa70SVincent Whitchurch .name = "ttyprintk",
1757ea4aa70SVincent Whitchurch .device = ttyprintk_console_device,
1767ea4aa70SVincent Whitchurch };
1777ea4aa70SVincent Whitchurch
ttyprintk_init(void)17824b4b67dSSamo Pogacnik static int __init ttyprintk_init(void)
17924b4b67dSSamo Pogacnik {
18087758935SColin Ian King int ret;
18124b4b67dSSamo Pogacnik
1829a655c77SZhenzhong Duan spin_lock_init(&tpk_port.spinlock);
183536a3440SJiri Slaby
1840019b408SJiri Slaby ttyprintk_driver = tty_alloc_driver(1,
1850019b408SJiri Slaby TTY_DRIVER_RESET_TERMIOS |
1860019b408SJiri Slaby TTY_DRIVER_REAL_RAW |
1870019b408SJiri Slaby TTY_DRIVER_UNNUMBERED_NODE);
188c3a6344aSDan Carpenter if (IS_ERR(ttyprintk_driver))
189c3a6344aSDan Carpenter return PTR_ERR(ttyprintk_driver);
19024b4b67dSSamo Pogacnik
191191c5f10SJiri Slaby tty_port_init(&tpk_port.port);
192bf3d6ab9SSamo Pogačnik tpk_port.port.ops = &tpk_port_ops;
193191c5f10SJiri Slaby
19424b4b67dSSamo Pogacnik ttyprintk_driver->driver_name = "ttyprintk";
19524b4b67dSSamo Pogacnik ttyprintk_driver->name = "ttyprintk";
19624b4b67dSSamo Pogacnik ttyprintk_driver->major = TTYAUX_MAJOR;
19724b4b67dSSamo Pogacnik ttyprintk_driver->minor_start = 3;
19824b4b67dSSamo Pogacnik ttyprintk_driver->type = TTY_DRIVER_TYPE_CONSOLE;
19924b4b67dSSamo Pogacnik ttyprintk_driver->init_termios = tty_std_termios;
20024b4b67dSSamo Pogacnik ttyprintk_driver->init_termios.c_oflag = OPOST | OCRNL | ONOCR | ONLRET;
20124b4b67dSSamo Pogacnik tty_set_operations(ttyprintk_driver, &ttyprintk_ops);
202b19e2ca7SJiri Slaby tty_port_link_device(&tpk_port.port, ttyprintk_driver, 0);
20324b4b67dSSamo Pogacnik
20424b4b67dSSamo Pogacnik ret = tty_register_driver(ttyprintk_driver);
20524b4b67dSSamo Pogacnik if (ret < 0) {
20624b4b67dSSamo Pogacnik printk(KERN_ERR "Couldn't register ttyprintk driver\n");
20724b4b67dSSamo Pogacnik goto error;
20824b4b67dSSamo Pogacnik }
20924b4b67dSSamo Pogacnik
2107ea4aa70SVincent Whitchurch register_console(&ttyprintk_console);
2117ea4aa70SVincent Whitchurch
21224b4b67dSSamo Pogacnik return 0;
21324b4b67dSSamo Pogacnik
21424b4b67dSSamo Pogacnik error:
2159f90a4ddSJiri Slaby tty_driver_kref_put(ttyprintk_driver);
216191c5f10SJiri Slaby tty_port_destroy(&tpk_port.port);
21724b4b67dSSamo Pogacnik return ret;
21824b4b67dSSamo Pogacnik }
219b24313a8STakashi Iwai
ttyprintk_exit(void)220b24313a8STakashi Iwai static void __exit ttyprintk_exit(void)
221b24313a8STakashi Iwai {
2227ea4aa70SVincent Whitchurch unregister_console(&ttyprintk_console);
223b24313a8STakashi Iwai tty_unregister_driver(ttyprintk_driver);
2249f90a4ddSJiri Slaby tty_driver_kref_put(ttyprintk_driver);
225b24313a8STakashi Iwai tty_port_destroy(&tpk_port.port);
226b24313a8STakashi Iwai }
227b24313a8STakashi Iwai
228db50d2f6SPaul Gortmaker device_initcall(ttyprintk_init);
229b24313a8STakashi Iwai module_exit(ttyprintk_exit);
230b24313a8STakashi Iwai
231b24313a8STakashi Iwai MODULE_LICENSE("GPL");
232