cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

charlcd.c (15522B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * Character LCD driver for Linux
      4 *
      5 * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
      6 * Copyright (C) 2016-2017 Glider bvba
      7 */
      8
      9#include <linux/atomic.h>
     10#include <linux/ctype.h>
     11#include <linux/fs.h>
     12#include <linux/miscdevice.h>
     13#include <linux/module.h>
     14#include <linux/notifier.h>
     15#include <linux/reboot.h>
     16#include <linux/slab.h>
     17#include <linux/uaccess.h>
     18#include <linux/workqueue.h>
     19
     20#include <generated/utsrelease.h>
     21
     22#include "charlcd.h"
     23
     24/* Keep the backlight on this many seconds for each flash */
     25#define LCD_BL_TEMPO_PERIOD	4
     26
     27#define LCD_ESCAPE_LEN		24	/* Max chars for LCD escape command */
     28#define LCD_ESCAPE_CHAR		27	/* Use char 27 for escape command */
     29
     30struct charlcd_priv {
     31	struct charlcd lcd;
     32
     33	struct delayed_work bl_work;
     34	struct mutex bl_tempo_lock;	/* Protects access to bl_tempo */
     35	bool bl_tempo;
     36
     37	bool must_clear;
     38
     39	/* contains the LCD config state */
     40	unsigned long flags;
     41
     42	/* Current escape sequence and it's length or -1 if outside */
     43	struct {
     44		char buf[LCD_ESCAPE_LEN + 1];
     45		int len;
     46	} esc_seq;
     47
     48	unsigned long long drvdata[];
     49};
     50
     51#define charlcd_to_priv(p)	container_of(p, struct charlcd_priv, lcd)
     52
     53/* Device single-open policy control */
     54static atomic_t charlcd_available = ATOMIC_INIT(1);
     55
     56/* turn the backlight on or off */
     57void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
     58{
     59	struct charlcd_priv *priv = charlcd_to_priv(lcd);
     60
     61	if (!lcd->ops->backlight)
     62		return;
     63
     64	mutex_lock(&priv->bl_tempo_lock);
     65	if (!priv->bl_tempo)
     66		lcd->ops->backlight(lcd, on);
     67	mutex_unlock(&priv->bl_tempo_lock);
     68}
     69EXPORT_SYMBOL_GPL(charlcd_backlight);
     70
     71static void charlcd_bl_off(struct work_struct *work)
     72{
     73	struct delayed_work *dwork = to_delayed_work(work);
     74	struct charlcd_priv *priv =
     75		container_of(dwork, struct charlcd_priv, bl_work);
     76
     77	mutex_lock(&priv->bl_tempo_lock);
     78	if (priv->bl_tempo) {
     79		priv->bl_tempo = false;
     80		if (!(priv->flags & LCD_FLAG_L))
     81			priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
     82	}
     83	mutex_unlock(&priv->bl_tempo_lock);
     84}
     85
     86/* turn the backlight on for a little while */
     87void charlcd_poke(struct charlcd *lcd)
     88{
     89	struct charlcd_priv *priv = charlcd_to_priv(lcd);
     90
     91	if (!lcd->ops->backlight)
     92		return;
     93
     94	cancel_delayed_work_sync(&priv->bl_work);
     95
     96	mutex_lock(&priv->bl_tempo_lock);
     97	if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
     98		lcd->ops->backlight(lcd, CHARLCD_ON);
     99	priv->bl_tempo = true;
    100	schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
    101	mutex_unlock(&priv->bl_tempo_lock);
    102}
    103EXPORT_SYMBOL_GPL(charlcd_poke);
    104
    105static void charlcd_home(struct charlcd *lcd)
    106{
    107	lcd->addr.x = 0;
    108	lcd->addr.y = 0;
    109	lcd->ops->home(lcd);
    110}
    111
    112static void charlcd_print(struct charlcd *lcd, char c)
    113{
    114	if (lcd->addr.x >= lcd->width)
    115		return;
    116
    117	if (lcd->char_conv)
    118		c = lcd->char_conv[(unsigned char)c];
    119
    120	if (!lcd->ops->print(lcd, c))
    121		lcd->addr.x++;
    122
    123	/* prevents the cursor from wrapping onto the next line */
    124	if (lcd->addr.x == lcd->width)
    125		lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
    126}
    127
    128static void charlcd_clear_display(struct charlcd *lcd)
    129{
    130	lcd->ops->clear_display(lcd);
    131	lcd->addr.x = 0;
    132	lcd->addr.y = 0;
    133}
    134
    135/*
    136 * Parses a movement command of the form "(.*);", where the group can be
    137 * any number of subcommands of the form "(x|y)[0-9]+".
    138 *
    139 * Returns whether the command is valid. The position arguments are
    140 * only written if the parsing was successful.
    141 *
    142 * For instance:
    143 *   - ";"          returns (<original x>, <original y>).
    144 *   - "x1;"        returns (1, <original y>).
    145 *   - "y2x1;"      returns (1, 2).
    146 *   - "x12y34x56;" returns (56, 34).
    147 *   - ""           fails.
    148 *   - "x"          fails.
    149 *   - "x;"         fails.
    150 *   - "x1"         fails.
    151 *   - "xy12;"      fails.
    152 *   - "x12yy12;"   fails.
    153 *   - "xx"         fails.
    154 */
    155static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
    156{
    157	unsigned long new_x = *x;
    158	unsigned long new_y = *y;
    159	char *p;
    160
    161	for (;;) {
    162		if (!*s)
    163			return false;
    164
    165		if (*s == ';')
    166			break;
    167
    168		if (*s == 'x') {
    169			new_x = simple_strtoul(s + 1, &p, 10);
    170			if (p == s + 1)
    171				return false;
    172			s = p;
    173		} else if (*s == 'y') {
    174			new_y = simple_strtoul(s + 1, &p, 10);
    175			if (p == s + 1)
    176				return false;
    177			s = p;
    178		} else {
    179			return false;
    180		}
    181	}
    182
    183	*x = new_x;
    184	*y = new_y;
    185	return true;
    186}
    187
    188/*
    189 * These are the file operation function for user access to /dev/lcd
    190 * This function can also be called from inside the kernel, by
    191 * setting file and ppos to NULL.
    192 *
    193 */
    194
    195static inline int handle_lcd_special_code(struct charlcd *lcd)
    196{
    197	struct charlcd_priv *priv = charlcd_to_priv(lcd);
    198
    199	/* LCD special codes */
    200
    201	int processed = 0;
    202
    203	char *esc = priv->esc_seq.buf + 2;
    204	int oldflags = priv->flags;
    205
    206	/* check for display mode flags */
    207	switch (*esc) {
    208	case 'D':	/* Display ON */
    209		priv->flags |= LCD_FLAG_D;
    210		if (priv->flags != oldflags)
    211			lcd->ops->display(lcd, CHARLCD_ON);
    212
    213		processed = 1;
    214		break;
    215	case 'd':	/* Display OFF */
    216		priv->flags &= ~LCD_FLAG_D;
    217		if (priv->flags != oldflags)
    218			lcd->ops->display(lcd, CHARLCD_OFF);
    219
    220		processed = 1;
    221		break;
    222	case 'C':	/* Cursor ON */
    223		priv->flags |= LCD_FLAG_C;
    224		if (priv->flags != oldflags)
    225			lcd->ops->cursor(lcd, CHARLCD_ON);
    226
    227		processed = 1;
    228		break;
    229	case 'c':	/* Cursor OFF */
    230		priv->flags &= ~LCD_FLAG_C;
    231		if (priv->flags != oldflags)
    232			lcd->ops->cursor(lcd, CHARLCD_OFF);
    233
    234		processed = 1;
    235		break;
    236	case 'B':	/* Blink ON */
    237		priv->flags |= LCD_FLAG_B;
    238		if (priv->flags != oldflags)
    239			lcd->ops->blink(lcd, CHARLCD_ON);
    240
    241		processed = 1;
    242		break;
    243	case 'b':	/* Blink OFF */
    244		priv->flags &= ~LCD_FLAG_B;
    245		if (priv->flags != oldflags)
    246			lcd->ops->blink(lcd, CHARLCD_OFF);
    247
    248		processed = 1;
    249		break;
    250	case '+':	/* Back light ON */
    251		priv->flags |= LCD_FLAG_L;
    252		if (priv->flags != oldflags)
    253			charlcd_backlight(lcd, CHARLCD_ON);
    254
    255		processed = 1;
    256		break;
    257	case '-':	/* Back light OFF */
    258		priv->flags &= ~LCD_FLAG_L;
    259		if (priv->flags != oldflags)
    260			charlcd_backlight(lcd, CHARLCD_OFF);
    261
    262		processed = 1;
    263		break;
    264	case '*':	/* Flash back light */
    265		charlcd_poke(lcd);
    266		processed = 1;
    267		break;
    268	case 'f':	/* Small Font */
    269		priv->flags &= ~LCD_FLAG_F;
    270		if (priv->flags != oldflags)
    271			lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
    272
    273		processed = 1;
    274		break;
    275	case 'F':	/* Large Font */
    276		priv->flags |= LCD_FLAG_F;
    277		if (priv->flags != oldflags)
    278			lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
    279
    280		processed = 1;
    281		break;
    282	case 'n':	/* One Line */
    283		priv->flags &= ~LCD_FLAG_N;
    284		if (priv->flags != oldflags)
    285			lcd->ops->lines(lcd, CHARLCD_LINES_1);
    286
    287		processed = 1;
    288		break;
    289	case 'N':	/* Two Lines */
    290		priv->flags |= LCD_FLAG_N;
    291		if (priv->flags != oldflags)
    292			lcd->ops->lines(lcd, CHARLCD_LINES_2);
    293
    294		processed = 1;
    295		break;
    296	case 'l':	/* Shift Cursor Left */
    297		if (lcd->addr.x > 0) {
    298			if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
    299				lcd->addr.x--;
    300		}
    301
    302		processed = 1;
    303		break;
    304	case 'r':	/* shift cursor right */
    305		if (lcd->addr.x < lcd->width) {
    306			if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
    307				lcd->addr.x++;
    308		}
    309
    310		processed = 1;
    311		break;
    312	case 'L':	/* shift display left */
    313		lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
    314		processed = 1;
    315		break;
    316	case 'R':	/* shift display right */
    317		lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
    318		processed = 1;
    319		break;
    320	case 'k': {	/* kill end of line */
    321		int x, xs, ys;
    322
    323		xs = lcd->addr.x;
    324		ys = lcd->addr.y;
    325		for (x = lcd->addr.x; x < lcd->width; x++)
    326			lcd->ops->print(lcd, ' ');
    327
    328		/* restore cursor position */
    329		lcd->addr.x = xs;
    330		lcd->addr.y = ys;
    331		lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
    332		processed = 1;
    333		break;
    334	}
    335	case 'I':	/* reinitialize display */
    336		lcd->ops->init_display(lcd);
    337		priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
    338			LCD_FLAG_C | LCD_FLAG_B;
    339		processed = 1;
    340		break;
    341	case 'G':
    342		if (lcd->ops->redefine_char)
    343			processed = lcd->ops->redefine_char(lcd, esc);
    344		else
    345			processed = 1;
    346		break;
    347
    348	case 'x':	/* gotoxy : LxXXX[yYYY]; */
    349	case 'y':	/* gotoxy : LyYYY[xXXX]; */
    350		if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
    351			break;
    352
    353		/* If the command is valid, move to the new address */
    354		if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
    355			lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
    356
    357		/* Regardless of its validity, mark as processed */
    358		processed = 1;
    359		break;
    360	}
    361
    362	return processed;
    363}
    364
    365static void charlcd_write_char(struct charlcd *lcd, char c)
    366{
    367	struct charlcd_priv *priv = charlcd_to_priv(lcd);
    368
    369	/* first, we'll test if we're in escape mode */
    370	if ((c != '\n') && priv->esc_seq.len >= 0) {
    371		/* yes, let's add this char to the buffer */
    372		priv->esc_seq.buf[priv->esc_seq.len++] = c;
    373		priv->esc_seq.buf[priv->esc_seq.len] = '\0';
    374	} else {
    375		/* aborts any previous escape sequence */
    376		priv->esc_seq.len = -1;
    377
    378		switch (c) {
    379		case LCD_ESCAPE_CHAR:
    380			/* start of an escape sequence */
    381			priv->esc_seq.len = 0;
    382			priv->esc_seq.buf[priv->esc_seq.len] = '\0';
    383			break;
    384		case '\b':
    385			/* go back one char and clear it */
    386			if (lcd->addr.x > 0) {
    387				/* back one char */
    388				if (!lcd->ops->shift_cursor(lcd,
    389							CHARLCD_SHIFT_LEFT))
    390					lcd->addr.x--;
    391			}
    392			/* replace with a space */
    393			charlcd_print(lcd, ' ');
    394			/* back one char again */
    395			if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
    396				lcd->addr.x--;
    397
    398			break;
    399		case '\f':
    400			/* quickly clear the display */
    401			charlcd_clear_display(lcd);
    402			break;
    403		case '\n':
    404			/*
    405			 * flush the remainder of the current line and
    406			 * go to the beginning of the next line
    407			 */
    408			for (; lcd->addr.x < lcd->width; lcd->addr.x++)
    409				lcd->ops->print(lcd, ' ');
    410
    411			lcd->addr.x = 0;
    412			lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
    413			lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
    414			break;
    415		case '\r':
    416			/* go to the beginning of the same line */
    417			lcd->addr.x = 0;
    418			lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
    419			break;
    420		case '\t':
    421			/* print a space instead of the tab */
    422			charlcd_print(lcd, ' ');
    423			break;
    424		default:
    425			/* simply print this char */
    426			charlcd_print(lcd, c);
    427			break;
    428		}
    429	}
    430
    431	/*
    432	 * now we'll see if we're in an escape mode and if the current
    433	 * escape sequence can be understood.
    434	 */
    435	if (priv->esc_seq.len >= 2) {
    436		int processed = 0;
    437
    438		if (!strcmp(priv->esc_seq.buf, "[2J")) {
    439			/* clear the display */
    440			charlcd_clear_display(lcd);
    441			processed = 1;
    442		} else if (!strcmp(priv->esc_seq.buf, "[H")) {
    443			/* cursor to home */
    444			charlcd_home(lcd);
    445			processed = 1;
    446		}
    447		/* codes starting with ^[[L */
    448		else if ((priv->esc_seq.len >= 3) &&
    449			 (priv->esc_seq.buf[0] == '[') &&
    450			 (priv->esc_seq.buf[1] == 'L')) {
    451			processed = handle_lcd_special_code(lcd);
    452		}
    453
    454		/* LCD special escape codes */
    455		/*
    456		 * flush the escape sequence if it's been processed
    457		 * or if it is getting too long.
    458		 */
    459		if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
    460			priv->esc_seq.len = -1;
    461	} /* escape codes */
    462}
    463
    464static struct charlcd *the_charlcd;
    465
    466static ssize_t charlcd_write(struct file *file, const char __user *buf,
    467			     size_t count, loff_t *ppos)
    468{
    469	const char __user *tmp = buf;
    470	char c;
    471
    472	for (; count-- > 0; (*ppos)++, tmp++) {
    473		if (((count + 1) & 0x1f) == 0) {
    474			/*
    475			 * charlcd_write() is invoked as a VFS->write() callback
    476			 * and as such it is always invoked from preemptible
    477			 * context and may sleep.
    478			 */
    479			cond_resched();
    480		}
    481
    482		if (get_user(c, tmp))
    483			return -EFAULT;
    484
    485		charlcd_write_char(the_charlcd, c);
    486	}
    487
    488	return tmp - buf;
    489}
    490
    491static int charlcd_open(struct inode *inode, struct file *file)
    492{
    493	struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
    494	int ret;
    495
    496	ret = -EBUSY;
    497	if (!atomic_dec_and_test(&charlcd_available))
    498		goto fail;	/* open only once at a time */
    499
    500	ret = -EPERM;
    501	if (file->f_mode & FMODE_READ)	/* device is write-only */
    502		goto fail;
    503
    504	if (priv->must_clear) {
    505		priv->lcd.ops->clear_display(&priv->lcd);
    506		priv->must_clear = false;
    507		priv->lcd.addr.x = 0;
    508		priv->lcd.addr.y = 0;
    509	}
    510	return nonseekable_open(inode, file);
    511
    512 fail:
    513	atomic_inc(&charlcd_available);
    514	return ret;
    515}
    516
    517static int charlcd_release(struct inode *inode, struct file *file)
    518{
    519	atomic_inc(&charlcd_available);
    520	return 0;
    521}
    522
    523static const struct file_operations charlcd_fops = {
    524	.write   = charlcd_write,
    525	.open    = charlcd_open,
    526	.release = charlcd_release,
    527	.llseek  = no_llseek,
    528};
    529
    530static struct miscdevice charlcd_dev = {
    531	.minor	= LCD_MINOR,
    532	.name	= "lcd",
    533	.fops	= &charlcd_fops,
    534};
    535
    536static void charlcd_puts(struct charlcd *lcd, const char *s)
    537{
    538	const char *tmp = s;
    539	int count = strlen(s);
    540
    541	for (; count-- > 0; tmp++) {
    542		if (((count + 1) & 0x1f) == 0)
    543			cond_resched();
    544
    545		charlcd_write_char(lcd, *tmp);
    546	}
    547}
    548
    549#ifdef CONFIG_PANEL_BOOT_MESSAGE
    550#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
    551#else
    552#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
    553#endif
    554
    555#ifdef CONFIG_CHARLCD_BL_ON
    556#define LCD_INIT_BL "\x1b[L+"
    557#elif defined(CONFIG_CHARLCD_BL_FLASH)
    558#define LCD_INIT_BL "\x1b[L*"
    559#else
    560#define LCD_INIT_BL "\x1b[L-"
    561#endif
    562
    563/* initialize the LCD driver */
    564static int charlcd_init(struct charlcd *lcd)
    565{
    566	struct charlcd_priv *priv = charlcd_to_priv(lcd);
    567	int ret;
    568
    569	priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
    570		      LCD_FLAG_C | LCD_FLAG_B;
    571	if (lcd->ops->backlight) {
    572		mutex_init(&priv->bl_tempo_lock);
    573		INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
    574	}
    575
    576	/*
    577	 * before this line, we must NOT send anything to the display.
    578	 * Since charlcd_init_display() needs to write data, we have to
    579	 * enable mark the LCD initialized just before.
    580	 */
    581	if (WARN_ON(!lcd->ops->init_display))
    582		return -EINVAL;
    583
    584	ret = lcd->ops->init_display(lcd);
    585	if (ret)
    586		return ret;
    587
    588	/* display a short message */
    589	charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
    590
    591	/* clear the display on the next device opening */
    592	priv->must_clear = true;
    593	charlcd_home(lcd);
    594	return 0;
    595}
    596
    597struct charlcd *charlcd_alloc(void)
    598{
    599	struct charlcd_priv *priv;
    600	struct charlcd *lcd;
    601
    602	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    603	if (!priv)
    604		return NULL;
    605
    606	priv->esc_seq.len = -1;
    607
    608	lcd = &priv->lcd;
    609
    610	return lcd;
    611}
    612EXPORT_SYMBOL_GPL(charlcd_alloc);
    613
    614void charlcd_free(struct charlcd *lcd)
    615{
    616	kfree(charlcd_to_priv(lcd));
    617}
    618EXPORT_SYMBOL_GPL(charlcd_free);
    619
    620static int panel_notify_sys(struct notifier_block *this, unsigned long code,
    621			    void *unused)
    622{
    623	struct charlcd *lcd = the_charlcd;
    624
    625	switch (code) {
    626	case SYS_DOWN:
    627		charlcd_puts(lcd,
    628			     "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
    629		break;
    630	case SYS_HALT:
    631		charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
    632		break;
    633	case SYS_POWER_OFF:
    634		charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
    635		break;
    636	default:
    637		break;
    638	}
    639	return NOTIFY_DONE;
    640}
    641
    642static struct notifier_block panel_notifier = {
    643	.notifier_call = panel_notify_sys,
    644};
    645
    646int charlcd_register(struct charlcd *lcd)
    647{
    648	int ret;
    649
    650	ret = charlcd_init(lcd);
    651	if (ret)
    652		return ret;
    653
    654	ret = misc_register(&charlcd_dev);
    655	if (ret)
    656		return ret;
    657
    658	the_charlcd = lcd;
    659	register_reboot_notifier(&panel_notifier);
    660	return 0;
    661}
    662EXPORT_SYMBOL_GPL(charlcd_register);
    663
    664int charlcd_unregister(struct charlcd *lcd)
    665{
    666	struct charlcd_priv *priv = charlcd_to_priv(lcd);
    667
    668	unregister_reboot_notifier(&panel_notifier);
    669	charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
    670	misc_deregister(&charlcd_dev);
    671	the_charlcd = NULL;
    672	if (lcd->ops->backlight) {
    673		cancel_delayed_work_sync(&priv->bl_work);
    674		priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
    675	}
    676
    677	return 0;
    678}
    679EXPORT_SYMBOL_GPL(charlcd_unregister);
    680
    681MODULE_LICENSE("GPL");