linux/drivers/char/ttyprintk.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *  linux/drivers/char/ttyprintk.c
   4 *
   5 *  Copyright (C) 2010  Samo Pogacnik
   6 */
   7
   8/*
   9 * This pseudo device allows user to make printk messages. It is possible
  10 * to store "console" messages inline with kernel messages for better analyses
  11 * of the boot process, for example.
  12 */
  13
  14#include <linux/device.h>
  15#include <linux/serial.h>
  16#include <linux/tty.h>
  17#include <linux/module.h>
  18#include <linux/spinlock.h>
  19
  20struct ttyprintk_port {
  21        struct tty_port port;
  22        spinlock_t spinlock;
  23};
  24
  25static struct ttyprintk_port tpk_port;
  26
  27/*
  28 * Our simple preformatting supports transparent output of (time-stamped)
  29 * printk messages (also suitable for logging service):
  30 * - any cr is replaced by nl
  31 * - adds a ttyprintk source tag in front of each line
  32 * - too long message is fragmented, with '\'nl between fragments
  33 * - TPK_STR_SIZE isn't really the write_room limiting factor, because
  34 *   it is emptied on the fly during preformatting.
  35 */
  36#define TPK_STR_SIZE 508 /* should be bigger then max expected line length */
  37#define TPK_MAX_ROOM 4096 /* we could assume 4K for instance */
  38#define TPK_PREFIX KERN_SOH __stringify(CONFIG_TTY_PRINTK_LEVEL)
  39
  40static int tpk_curr;
  41
  42static char tpk_buffer[TPK_STR_SIZE + 4];
  43
  44static void tpk_flush(void)
  45{
  46        if (tpk_curr > 0) {
  47                tpk_buffer[tpk_curr] = '\0';
  48                printk(TPK_PREFIX "[U] %s\n", tpk_buffer);
  49                tpk_curr = 0;
  50        }
  51}
  52
  53static int tpk_printk(const unsigned char *buf, int count)
  54{
  55        int i = tpk_curr;
  56
  57        if (buf == NULL) {
  58                tpk_flush();
  59                return i;
  60        }
  61
  62        for (i = 0; i < count; i++) {
  63                if (tpk_curr >= TPK_STR_SIZE) {
  64                        /* end of tmp buffer reached: cut the message in two */
  65                        tpk_buffer[tpk_curr++] = '\\';
  66                        tpk_flush();
  67                }
  68
  69                switch (buf[i]) {
  70                case '\r':
  71                        tpk_flush();
  72                        if ((i + 1) < count && buf[i + 1] == '\n')
  73                                i++;
  74                        break;
  75                case '\n':
  76                        tpk_flush();
  77                        break;
  78                default:
  79                        tpk_buffer[tpk_curr++] = buf[i];
  80                        break;
  81                }
  82        }
  83
  84        return count;
  85}
  86
  87/*
  88 * TTY operations open function.
  89 */
  90static int tpk_open(struct tty_struct *tty, struct file *filp)
  91{
  92        tty->driver_data = &tpk_port;
  93
  94        return tty_port_open(&tpk_port.port, tty, filp);
  95}
  96
  97/*
  98 * TTY operations close function.
  99 */
 100static void tpk_close(struct tty_struct *tty, struct file *filp)
 101{
 102        struct ttyprintk_port *tpkp = tty->driver_data;
 103        unsigned long flags;
 104
 105        spin_lock_irqsave(&tpkp->spinlock, flags);
 106        /* flush tpk_printk buffer */
 107        tpk_printk(NULL, 0);
 108        spin_unlock_irqrestore(&tpkp->spinlock, flags);
 109
 110        tty_port_close(&tpkp->port, tty, filp);
 111}
 112
 113/*
 114 * TTY operations write function.
 115 */
 116static int tpk_write(struct tty_struct *tty,
 117                const unsigned char *buf, int count)
 118{
 119        struct ttyprintk_port *tpkp = tty->driver_data;
 120        unsigned long flags;
 121        int ret;
 122
 123
 124        /* exclusive use of tpk_printk within this tty */
 125        spin_lock_irqsave(&tpkp->spinlock, flags);
 126        ret = tpk_printk(buf, count);
 127        spin_unlock_irqrestore(&tpkp->spinlock, flags);
 128
 129        return ret;
 130}
 131
 132/*
 133 * TTY operations write_room function.
 134 */
 135static int tpk_write_room(struct tty_struct *tty)
 136{
 137        return TPK_MAX_ROOM;
 138}
 139
 140/*
 141 * TTY operations ioctl function.
 142 */
 143static int tpk_ioctl(struct tty_struct *tty,
 144                        unsigned int cmd, unsigned long arg)
 145{
 146        struct ttyprintk_port *tpkp = tty->driver_data;
 147
 148        if (!tpkp)
 149                return -EINVAL;
 150
 151        switch (cmd) {
 152        /* Stop TIOCCONS */
 153        case TIOCCONS:
 154                return -EOPNOTSUPP;
 155        default:
 156                return -ENOIOCTLCMD;
 157        }
 158        return 0;
 159}
 160
 161/*
 162 * TTY operations hangup function.
 163 */
 164static void tpk_hangup(struct tty_struct *tty)
 165{
 166        struct ttyprintk_port *tpkp = tty->driver_data;
 167
 168        tty_port_hangup(&tpkp->port);
 169}
 170
 171static const struct tty_operations ttyprintk_ops = {
 172        .open = tpk_open,
 173        .close = tpk_close,
 174        .write = tpk_write,
 175        .write_room = tpk_write_room,
 176        .ioctl = tpk_ioctl,
 177        .hangup = tpk_hangup,
 178};
 179
 180static const struct tty_port_operations null_ops = { };
 181
 182static struct tty_driver *ttyprintk_driver;
 183
 184static int __init ttyprintk_init(void)
 185{
 186        int ret;
 187
 188        spin_lock_init(&tpk_port.spinlock);
 189
 190        ttyprintk_driver = tty_alloc_driver(1,
 191                        TTY_DRIVER_RESET_TERMIOS |
 192                        TTY_DRIVER_REAL_RAW |
 193                        TTY_DRIVER_UNNUMBERED_NODE);
 194        if (IS_ERR(ttyprintk_driver))
 195                return PTR_ERR(ttyprintk_driver);
 196
 197        tty_port_init(&tpk_port.port);
 198        tpk_port.port.ops = &null_ops;
 199
 200        ttyprintk_driver->driver_name = "ttyprintk";
 201        ttyprintk_driver->name = "ttyprintk";
 202        ttyprintk_driver->major = TTYAUX_MAJOR;
 203        ttyprintk_driver->minor_start = 3;
 204        ttyprintk_driver->type = TTY_DRIVER_TYPE_CONSOLE;
 205        ttyprintk_driver->init_termios = tty_std_termios;
 206        ttyprintk_driver->init_termios.c_oflag = OPOST | OCRNL | ONOCR | ONLRET;
 207        tty_set_operations(ttyprintk_driver, &ttyprintk_ops);
 208        tty_port_link_device(&tpk_port.port, ttyprintk_driver, 0);
 209
 210        ret = tty_register_driver(ttyprintk_driver);
 211        if (ret < 0) {
 212                printk(KERN_ERR "Couldn't register ttyprintk driver\n");
 213                goto error;
 214        }
 215
 216        return 0;
 217
 218error:
 219        put_tty_driver(ttyprintk_driver);
 220        tty_port_destroy(&tpk_port.port);
 221        return ret;
 222}
 223
 224static void __exit ttyprintk_exit(void)
 225{
 226        tty_unregister_driver(ttyprintk_driver);
 227        put_tty_driver(ttyprintk_driver);
 228        tty_port_destroy(&tpk_port.port);
 229}
 230
 231device_initcall(ttyprintk_init);
 232module_exit(ttyprintk_exit);
 233
 234MODULE_LICENSE("GPL");
 235