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

ps2mult.c (6710B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * TQC PS/2 Multiplexer driver
      4 *
      5 * Copyright (C) 2010 Dmitry Eremin-Solenikov
      6 */
      7
      8
      9#include <linux/kernel.h>
     10#include <linux/slab.h>
     11#include <linux/module.h>
     12#include <linux/serio.h>
     13
     14MODULE_AUTHOR("Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>");
     15MODULE_DESCRIPTION("TQC PS/2 Multiplexer driver");
     16MODULE_LICENSE("GPL");
     17
     18#define PS2MULT_KB_SELECTOR		0xA0
     19#define PS2MULT_MS_SELECTOR		0xA1
     20#define PS2MULT_ESCAPE			0x7D
     21#define PS2MULT_BSYNC			0x7E
     22#define PS2MULT_SESSION_START		0x55
     23#define PS2MULT_SESSION_END		0x56
     24
     25struct ps2mult_port {
     26	struct serio *serio;
     27	unsigned char sel;
     28	bool registered;
     29};
     30
     31#define PS2MULT_NUM_PORTS	2
     32#define PS2MULT_KBD_PORT	0
     33#define PS2MULT_MOUSE_PORT	1
     34
     35struct ps2mult {
     36	struct serio *mx_serio;
     37	struct ps2mult_port ports[PS2MULT_NUM_PORTS];
     38
     39	spinlock_t lock;
     40	struct ps2mult_port *in_port;
     41	struct ps2mult_port *out_port;
     42	bool escape;
     43};
     44
     45/* First MUST come PS2MULT_NUM_PORTS selectors */
     46static const unsigned char ps2mult_controls[] = {
     47	PS2MULT_KB_SELECTOR, PS2MULT_MS_SELECTOR,
     48	PS2MULT_ESCAPE, PS2MULT_BSYNC,
     49	PS2MULT_SESSION_START, PS2MULT_SESSION_END,
     50};
     51
     52static const struct serio_device_id ps2mult_serio_ids[] = {
     53	{
     54		.type	= SERIO_RS232,
     55		.proto	= SERIO_PS2MULT,
     56		.id	= SERIO_ANY,
     57		.extra	= SERIO_ANY,
     58	},
     59	{ 0 }
     60};
     61
     62MODULE_DEVICE_TABLE(serio, ps2mult_serio_ids);
     63
     64static void ps2mult_select_port(struct ps2mult *psm, struct ps2mult_port *port)
     65{
     66	struct serio *mx_serio = psm->mx_serio;
     67
     68	serio_write(mx_serio, port->sel);
     69	psm->out_port = port;
     70	dev_dbg(&mx_serio->dev, "switched to sel %02x\n", port->sel);
     71}
     72
     73static int ps2mult_serio_write(struct serio *serio, unsigned char data)
     74{
     75	struct serio *mx_port = serio->parent;
     76	struct ps2mult *psm = serio_get_drvdata(mx_port);
     77	struct ps2mult_port *port = serio->port_data;
     78	bool need_escape;
     79	unsigned long flags;
     80
     81	spin_lock_irqsave(&psm->lock, flags);
     82
     83	if (psm->out_port != port)
     84		ps2mult_select_port(psm, port);
     85
     86	need_escape = memchr(ps2mult_controls, data, sizeof(ps2mult_controls));
     87
     88	dev_dbg(&serio->dev,
     89		"write: %s%02x\n", need_escape ? "ESC " : "", data);
     90
     91	if (need_escape)
     92		serio_write(mx_port, PS2MULT_ESCAPE);
     93
     94	serio_write(mx_port, data);
     95
     96	spin_unlock_irqrestore(&psm->lock, flags);
     97
     98	return 0;
     99}
    100
    101static int ps2mult_serio_start(struct serio *serio)
    102{
    103	struct ps2mult *psm = serio_get_drvdata(serio->parent);
    104	struct ps2mult_port *port = serio->port_data;
    105	unsigned long flags;
    106
    107	spin_lock_irqsave(&psm->lock, flags);
    108	port->registered = true;
    109	spin_unlock_irqrestore(&psm->lock, flags);
    110
    111	return 0;
    112}
    113
    114static void ps2mult_serio_stop(struct serio *serio)
    115{
    116	struct ps2mult *psm = serio_get_drvdata(serio->parent);
    117	struct ps2mult_port *port = serio->port_data;
    118	unsigned long flags;
    119
    120	spin_lock_irqsave(&psm->lock, flags);
    121	port->registered = false;
    122	spin_unlock_irqrestore(&psm->lock, flags);
    123}
    124
    125static int ps2mult_create_port(struct ps2mult *psm, int i)
    126{
    127	struct serio *mx_serio = psm->mx_serio;
    128	struct serio *serio;
    129
    130	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
    131	if (!serio)
    132		return -ENOMEM;
    133
    134	strlcpy(serio->name, "TQC PS/2 Multiplexer", sizeof(serio->name));
    135	snprintf(serio->phys, sizeof(serio->phys),
    136		 "%s/port%d", mx_serio->phys, i);
    137	serio->id.type = SERIO_8042;
    138	serio->write = ps2mult_serio_write;
    139	serio->start = ps2mult_serio_start;
    140	serio->stop = ps2mult_serio_stop;
    141	serio->parent = psm->mx_serio;
    142	serio->port_data = &psm->ports[i];
    143
    144	psm->ports[i].serio = serio;
    145
    146	return 0;
    147}
    148
    149static void ps2mult_reset(struct ps2mult *psm)
    150{
    151	unsigned long flags;
    152
    153	spin_lock_irqsave(&psm->lock, flags);
    154
    155	serio_write(psm->mx_serio, PS2MULT_SESSION_END);
    156	serio_write(psm->mx_serio, PS2MULT_SESSION_START);
    157
    158	ps2mult_select_port(psm, &psm->ports[PS2MULT_KBD_PORT]);
    159
    160	spin_unlock_irqrestore(&psm->lock, flags);
    161}
    162
    163static int ps2mult_connect(struct serio *serio, struct serio_driver *drv)
    164{
    165	struct ps2mult *psm;
    166	int i;
    167	int error;
    168
    169	if (!serio->write)
    170		return -EINVAL;
    171
    172	psm = kzalloc(sizeof(*psm), GFP_KERNEL);
    173	if (!psm)
    174		return -ENOMEM;
    175
    176	spin_lock_init(&psm->lock);
    177	psm->mx_serio = serio;
    178
    179	for (i = 0; i < PS2MULT_NUM_PORTS; i++) {
    180		psm->ports[i].sel = ps2mult_controls[i];
    181		error = ps2mult_create_port(psm, i);
    182		if (error)
    183			goto err_out;
    184	}
    185
    186	psm->in_port = psm->out_port = &psm->ports[PS2MULT_KBD_PORT];
    187
    188	serio_set_drvdata(serio, psm);
    189	error = serio_open(serio, drv);
    190	if (error)
    191		goto err_out;
    192
    193	ps2mult_reset(psm);
    194
    195	for (i = 0; i <  PS2MULT_NUM_PORTS; i++) {
    196		struct serio *s = psm->ports[i].serio;
    197
    198		dev_info(&serio->dev, "%s port at %s\n", s->name, serio->phys);
    199		serio_register_port(s);
    200	}
    201
    202	return 0;
    203
    204err_out:
    205	while (--i >= 0)
    206		kfree(psm->ports[i].serio);
    207	kfree(psm);
    208	return error;
    209}
    210
    211static void ps2mult_disconnect(struct serio *serio)
    212{
    213	struct ps2mult *psm = serio_get_drvdata(serio);
    214
    215	/* Note that serio core already take care of children ports */
    216	serio_write(serio, PS2MULT_SESSION_END);
    217	serio_close(serio);
    218	kfree(psm);
    219
    220	serio_set_drvdata(serio, NULL);
    221}
    222
    223static int ps2mult_reconnect(struct serio *serio)
    224{
    225	struct ps2mult *psm = serio_get_drvdata(serio);
    226
    227	ps2mult_reset(psm);
    228
    229	return 0;
    230}
    231
    232static irqreturn_t ps2mult_interrupt(struct serio *serio,
    233				     unsigned char data, unsigned int dfl)
    234{
    235	struct ps2mult *psm = serio_get_drvdata(serio);
    236	struct ps2mult_port *in_port;
    237	unsigned long flags;
    238
    239	dev_dbg(&serio->dev, "Received %02x flags %02x\n", data, dfl);
    240
    241	spin_lock_irqsave(&psm->lock, flags);
    242
    243	if (psm->escape) {
    244		psm->escape = false;
    245		in_port = psm->in_port;
    246		if (in_port->registered)
    247			serio_interrupt(in_port->serio, data, dfl);
    248		goto out;
    249	}
    250
    251	switch (data) {
    252	case PS2MULT_ESCAPE:
    253		dev_dbg(&serio->dev, "ESCAPE\n");
    254		psm->escape = true;
    255		break;
    256
    257	case PS2MULT_BSYNC:
    258		dev_dbg(&serio->dev, "BSYNC\n");
    259		psm->in_port = psm->out_port;
    260		break;
    261
    262	case PS2MULT_SESSION_START:
    263		dev_dbg(&serio->dev, "SS\n");
    264		break;
    265
    266	case PS2MULT_SESSION_END:
    267		dev_dbg(&serio->dev, "SE\n");
    268		break;
    269
    270	case PS2MULT_KB_SELECTOR:
    271		dev_dbg(&serio->dev, "KB\n");
    272		psm->in_port = &psm->ports[PS2MULT_KBD_PORT];
    273		break;
    274
    275	case PS2MULT_MS_SELECTOR:
    276		dev_dbg(&serio->dev, "MS\n");
    277		psm->in_port = &psm->ports[PS2MULT_MOUSE_PORT];
    278		break;
    279
    280	default:
    281		in_port = psm->in_port;
    282		if (in_port->registered)
    283			serio_interrupt(in_port->serio, data, dfl);
    284		break;
    285	}
    286
    287 out:
    288	spin_unlock_irqrestore(&psm->lock, flags);
    289	return IRQ_HANDLED;
    290}
    291
    292static struct serio_driver ps2mult_drv = {
    293	.driver		= {
    294		.name	= "ps2mult",
    295	},
    296	.description	= "TQC PS/2 Multiplexer driver",
    297	.id_table	= ps2mult_serio_ids,
    298	.interrupt	= ps2mult_interrupt,
    299	.connect	= ps2mult_connect,
    300	.disconnect	= ps2mult_disconnect,
    301	.reconnect	= ps2mult_reconnect,
    302};
    303
    304module_serio_driver(ps2mult_drv);