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

ideapad_slidebar.c (8155B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3 * Input driver for slidebars on some Lenovo IdeaPad laptops
      4 *
      5 * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
      6 *
      7 * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
      8 *
      9 * Trademarks are the property of their respective owners.
     10 */
     11
     12/*
     13 * Currently tested and works on:
     14 *	Lenovo IdeaPad Y550
     15 *	Lenovo IdeaPad Y550P
     16 *
     17 * Other models can be added easily. To test,
     18 * load with 'force' parameter set 'true'.
     19 *
     20 * LEDs blinking and input mode are managed via sysfs,
     21 * (hex, unsigned byte value):
     22 * /sys/devices/platform/ideapad_slidebar/slidebar_mode
     23 *
     24 * The value is in byte range, however, I only figured out
     25 * how bits 0b10011001 work. Some other bits, probably,
     26 * are meaningfull too.
     27 *
     28 * Possible states:
     29 *
     30 * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
     31 *
     32 * Meaning:
     33 *           released      touched
     34 * STD       'heartbeat'   lights follow the finger
     35 * ONMOV     no lights     lights follow the finger
     36 * LAST      at last pos   lights follow the finger
     37 * OFF       no lights     no lights
     38 *
     39 * INT       all input events are generated, interrupts are used
     40 * POLL      no input events by default, to get them,
     41 *	     send 0b10000000 (read below)
     42 *
     43 * Commands: write
     44 *
     45 * All      |  0b01001 -> STD_INT
     46 * possible |  0b10001 -> ONMOV_INT
     47 * states   |  0b01000 -> OFF_INT
     48 *
     49 *                      |  0b0 -> LAST_POLL
     50 * STD_INT or ONMOV_INT |
     51 *                      |  0b1 -> STD_INT
     52 *
     53 *                      |  0b0 -> OFF_POLL
     54 * OFF_INT or OFF_POLL  |
     55 *                      |  0b1 -> OFF_INT
     56 *
     57 * Any state |   0b10000000 ->  if the slidebar has updated data,
     58 *				produce one input event (last position),
     59 *				switch to respective POLL mode
     60 *				(like 0x0), if not in POLL mode yet.
     61 *
     62 * Get current state: read
     63 *
     64 * masked by 0x11 read value means:
     65 *
     66 * 0x00   LAST
     67 * 0x01   STD
     68 * 0x10   OFF
     69 * 0x11   ONMOV
     70 */
     71
     72#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
     73
     74#include <linux/module.h>
     75#include <linux/kernel.h>
     76#include <linux/dmi.h>
     77#include <linux/spinlock.h>
     78#include <linux/platform_device.h>
     79#include <linux/input.h>
     80#include <linux/io.h>
     81#include <linux/ioport.h>
     82#include <linux/i8042.h>
     83#include <linux/serio.h>
     84
     85#define IDEAPAD_BASE	0xff29
     86
     87static bool force;
     88module_param(force, bool, 0);
     89MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
     90
     91static DEFINE_SPINLOCK(io_lock);
     92
     93static struct input_dev *slidebar_input_dev;
     94static struct platform_device *slidebar_platform_dev;
     95
     96static u8 slidebar_pos_get(void)
     97{
     98	u8 res;
     99	unsigned long flags;
    100
    101	spin_lock_irqsave(&io_lock, flags);
    102	outb(0xf4, 0xff29);
    103	outb(0xbf, 0xff2a);
    104	res = inb(0xff2b);
    105	spin_unlock_irqrestore(&io_lock, flags);
    106
    107	return res;
    108}
    109
    110static u8 slidebar_mode_get(void)
    111{
    112	u8 res;
    113	unsigned long flags;
    114
    115	spin_lock_irqsave(&io_lock, flags);
    116	outb(0xf7, 0xff29);
    117	outb(0x8b, 0xff2a);
    118	res = inb(0xff2b);
    119	spin_unlock_irqrestore(&io_lock, flags);
    120
    121	return res;
    122}
    123
    124static void slidebar_mode_set(u8 mode)
    125{
    126	unsigned long flags;
    127
    128	spin_lock_irqsave(&io_lock, flags);
    129	outb(0xf7, 0xff29);
    130	outb(0x8b, 0xff2a);
    131	outb(mode, 0xff2b);
    132	spin_unlock_irqrestore(&io_lock, flags);
    133}
    134
    135static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
    136				  struct serio *port)
    137{
    138	static bool extended = false;
    139
    140	/* We are only interested in data coming form KBC port */
    141	if (str & I8042_STR_AUXDATA)
    142		return false;
    143
    144	/* Scancodes: e03b on move, e0bb on release. */
    145	if (data == 0xe0) {
    146		extended = true;
    147		return true;
    148	}
    149
    150	if (!extended)
    151		return false;
    152
    153	extended = false;
    154
    155	if (likely((data & 0x7f) != 0x3b)) {
    156		serio_interrupt(port, 0xe0, 0);
    157		return false;
    158	}
    159
    160	if (data & 0x80) {
    161		input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
    162	} else {
    163		input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
    164		input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
    165	}
    166	input_sync(slidebar_input_dev);
    167
    168	return true;
    169}
    170
    171static ssize_t show_slidebar_mode(struct device *dev,
    172				  struct device_attribute *attr,
    173				  char *buf)
    174{
    175	return sprintf(buf, "%x\n", slidebar_mode_get());
    176}
    177
    178static ssize_t store_slidebar_mode(struct device *dev,
    179				   struct device_attribute *attr,
    180				   const char *buf, size_t count)
    181{
    182	u8 mode;
    183	int error;
    184
    185	error = kstrtou8(buf, 0, &mode);
    186	if (error)
    187		return error;
    188
    189	slidebar_mode_set(mode);
    190
    191	return count;
    192}
    193
    194static DEVICE_ATTR(slidebar_mode, S_IWUSR | S_IRUGO,
    195		   show_slidebar_mode, store_slidebar_mode);
    196
    197static struct attribute *ideapad_attrs[] = {
    198	&dev_attr_slidebar_mode.attr,
    199	NULL
    200};
    201
    202static struct attribute_group ideapad_attr_group = {
    203	.attrs = ideapad_attrs
    204};
    205
    206static const struct attribute_group *ideapad_attr_groups[] = {
    207	&ideapad_attr_group,
    208	NULL
    209};
    210
    211static int __init ideapad_probe(struct platform_device* pdev)
    212{
    213	int err;
    214
    215	if (!request_region(IDEAPAD_BASE, 3, "ideapad_slidebar")) {
    216		dev_err(&pdev->dev, "IO ports are busy\n");
    217		return -EBUSY;
    218	}
    219
    220	slidebar_input_dev = input_allocate_device();
    221	if (!slidebar_input_dev) {
    222		dev_err(&pdev->dev, "Failed to allocate input device\n");
    223		err = -ENOMEM;
    224		goto err_release_ports;
    225	}
    226
    227	slidebar_input_dev->name = "IdeaPad Slidebar";
    228	slidebar_input_dev->id.bustype = BUS_HOST;
    229	slidebar_input_dev->dev.parent = &pdev->dev;
    230	input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
    231	input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
    232	input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
    233
    234	err = i8042_install_filter(slidebar_i8042_filter);
    235	if (err) {
    236		dev_err(&pdev->dev,
    237			"Failed to install i8042 filter: %d\n", err);
    238		goto err_free_dev;
    239	}
    240
    241	err = input_register_device(slidebar_input_dev);
    242	if (err) {
    243		dev_err(&pdev->dev,
    244			"Failed to register input device: %d\n", err);
    245		goto err_remove_filter;
    246	}
    247
    248	return 0;
    249
    250err_remove_filter:
    251	i8042_remove_filter(slidebar_i8042_filter);
    252err_free_dev:
    253	input_free_device(slidebar_input_dev);
    254err_release_ports:
    255	release_region(IDEAPAD_BASE, 3);
    256	return err;
    257}
    258
    259static int ideapad_remove(struct platform_device *pdev)
    260{
    261	i8042_remove_filter(slidebar_i8042_filter);
    262	input_unregister_device(slidebar_input_dev);
    263	release_region(IDEAPAD_BASE, 3);
    264
    265	return 0;
    266}
    267
    268static struct platform_driver slidebar_drv = {
    269	.driver = {
    270		.name = "ideapad_slidebar",
    271	},
    272	.remove = ideapad_remove,
    273};
    274
    275static int __init ideapad_dmi_check(const struct dmi_system_id *id)
    276{
    277	pr_info("Laptop model '%s'\n", id->ident);
    278	return 1;
    279}
    280
    281static const struct dmi_system_id ideapad_dmi[] __initconst = {
    282	{
    283		.ident = "Lenovo IdeaPad Y550",
    284		.matches = {
    285			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
    286			DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
    287			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
    288		},
    289		.callback = ideapad_dmi_check
    290	},
    291	{
    292		.ident = "Lenovo IdeaPad Y550P",
    293		.matches = {
    294			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
    295			DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
    296			DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
    297		},
    298		.callback = ideapad_dmi_check
    299	},
    300	{ NULL, }
    301};
    302MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
    303
    304static int __init slidebar_init(void)
    305{
    306	int err;
    307
    308	if (!force && !dmi_check_system(ideapad_dmi)) {
    309		pr_err("DMI does not match\n");
    310		return -ENODEV;
    311	}
    312
    313	slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
    314	if (!slidebar_platform_dev) {
    315		pr_err("Not enough memory\n");
    316		return -ENOMEM;
    317	}
    318
    319	slidebar_platform_dev->dev.groups = ideapad_attr_groups;
    320
    321	err = platform_device_add(slidebar_platform_dev);
    322	if (err) {
    323		pr_err("Failed to register platform device\n");
    324		goto err_free_dev;
    325	}
    326
    327	err = platform_driver_probe(&slidebar_drv, ideapad_probe);
    328	if (err) {
    329		pr_err("Failed to register platform driver\n");
    330		goto err_delete_dev;
    331	}
    332
    333	return 0;
    334
    335err_delete_dev:
    336	platform_device_del(slidebar_platform_dev);
    337err_free_dev:
    338	platform_device_put(slidebar_platform_dev);
    339	return err;
    340}
    341
    342static void __exit slidebar_exit(void)
    343{
    344	platform_device_unregister(slidebar_platform_dev);
    345	platform_driver_unregister(&slidebar_drv);
    346}
    347
    348module_init(slidebar_init);
    349module_exit(slidebar_exit);
    350
    351MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
    352MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
    353MODULE_LICENSE("GPL");