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

dell-rbtn.c (10752B)


      1// SPDX-License-Identifier: GPL-2.0-or-later
      2/*
      3    Dell Airplane Mode Switch driver
      4    Copyright (C) 2014-2015  Pali Rohár <pali@kernel.org>
      5
      6*/
      7
      8#include <linux/module.h>
      9#include <linux/acpi.h>
     10#include <linux/rfkill.h>
     11#include <linux/input.h>
     12
     13#include "dell-rbtn.h"
     14
     15enum rbtn_type {
     16	RBTN_UNKNOWN,
     17	RBTN_TOGGLE,
     18	RBTN_SLIDER,
     19};
     20
     21struct rbtn_data {
     22	enum rbtn_type type;
     23	struct rfkill *rfkill;
     24	struct input_dev *input_dev;
     25	bool suspended;
     26};
     27
     28
     29/*
     30 * acpi functions
     31 */
     32
     33static enum rbtn_type rbtn_check(struct acpi_device *device)
     34{
     35	unsigned long long output;
     36	acpi_status status;
     37
     38	status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output);
     39	if (ACPI_FAILURE(status))
     40		return RBTN_UNKNOWN;
     41
     42	switch (output) {
     43	case 0:
     44	case 1:
     45		return RBTN_TOGGLE;
     46	case 2:
     47	case 3:
     48		return RBTN_SLIDER;
     49	default:
     50		return RBTN_UNKNOWN;
     51	}
     52}
     53
     54static int rbtn_get(struct acpi_device *device)
     55{
     56	unsigned long long output;
     57	acpi_status status;
     58
     59	status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output);
     60	if (ACPI_FAILURE(status))
     61		return -EINVAL;
     62
     63	return !output;
     64}
     65
     66static int rbtn_acquire(struct acpi_device *device, bool enable)
     67{
     68	struct acpi_object_list input;
     69	union acpi_object param;
     70	acpi_status status;
     71
     72	param.type = ACPI_TYPE_INTEGER;
     73	param.integer.value = enable;
     74	input.count = 1;
     75	input.pointer = &param;
     76
     77	status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL);
     78	if (ACPI_FAILURE(status))
     79		return -EINVAL;
     80
     81	return 0;
     82}
     83
     84
     85/*
     86 * rfkill device
     87 */
     88
     89static void rbtn_rfkill_query(struct rfkill *rfkill, void *data)
     90{
     91	struct acpi_device *device = data;
     92	int state;
     93
     94	state = rbtn_get(device);
     95	if (state < 0)
     96		return;
     97
     98	rfkill_set_states(rfkill, state, state);
     99}
    100
    101static int rbtn_rfkill_set_block(void *data, bool blocked)
    102{
    103	/* NOTE: setting soft rfkill state is not supported */
    104	return -EINVAL;
    105}
    106
    107static const struct rfkill_ops rbtn_ops = {
    108	.query = rbtn_rfkill_query,
    109	.set_block = rbtn_rfkill_set_block,
    110};
    111
    112static int rbtn_rfkill_init(struct acpi_device *device)
    113{
    114	struct rbtn_data *rbtn_data = device->driver_data;
    115	int ret;
    116
    117	if (rbtn_data->rfkill)
    118		return 0;
    119
    120	/*
    121	 * NOTE: rbtn controls all radio devices, not only WLAN
    122	 *       but rfkill interface does not support "ANY" type
    123	 *       so "WLAN" type is used
    124	 */
    125	rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev,
    126					 RFKILL_TYPE_WLAN, &rbtn_ops, device);
    127	if (!rbtn_data->rfkill)
    128		return -ENOMEM;
    129
    130	ret = rfkill_register(rbtn_data->rfkill);
    131	if (ret) {
    132		rfkill_destroy(rbtn_data->rfkill);
    133		rbtn_data->rfkill = NULL;
    134		return ret;
    135	}
    136
    137	return 0;
    138}
    139
    140static void rbtn_rfkill_exit(struct acpi_device *device)
    141{
    142	struct rbtn_data *rbtn_data = device->driver_data;
    143
    144	if (!rbtn_data->rfkill)
    145		return;
    146
    147	rfkill_unregister(rbtn_data->rfkill);
    148	rfkill_destroy(rbtn_data->rfkill);
    149	rbtn_data->rfkill = NULL;
    150}
    151
    152static void rbtn_rfkill_event(struct acpi_device *device)
    153{
    154	struct rbtn_data *rbtn_data = device->driver_data;
    155
    156	if (rbtn_data->rfkill)
    157		rbtn_rfkill_query(rbtn_data->rfkill, device);
    158}
    159
    160
    161/*
    162 * input device
    163 */
    164
    165static int rbtn_input_init(struct rbtn_data *rbtn_data)
    166{
    167	int ret;
    168
    169	rbtn_data->input_dev = input_allocate_device();
    170	if (!rbtn_data->input_dev)
    171		return -ENOMEM;
    172
    173	rbtn_data->input_dev->name = "DELL Wireless hotkeys";
    174	rbtn_data->input_dev->phys = "dellabce/input0";
    175	rbtn_data->input_dev->id.bustype = BUS_HOST;
    176	rbtn_data->input_dev->evbit[0] = BIT(EV_KEY);
    177	set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit);
    178
    179	ret = input_register_device(rbtn_data->input_dev);
    180	if (ret) {
    181		input_free_device(rbtn_data->input_dev);
    182		rbtn_data->input_dev = NULL;
    183		return ret;
    184	}
    185
    186	return 0;
    187}
    188
    189static void rbtn_input_exit(struct rbtn_data *rbtn_data)
    190{
    191	input_unregister_device(rbtn_data->input_dev);
    192	rbtn_data->input_dev = NULL;
    193}
    194
    195static void rbtn_input_event(struct rbtn_data *rbtn_data)
    196{
    197	input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1);
    198	input_sync(rbtn_data->input_dev);
    199	input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0);
    200	input_sync(rbtn_data->input_dev);
    201}
    202
    203
    204/*
    205 * acpi driver
    206 */
    207
    208static int rbtn_add(struct acpi_device *device);
    209static int rbtn_remove(struct acpi_device *device);
    210static void rbtn_notify(struct acpi_device *device, u32 event);
    211
    212static const struct acpi_device_id rbtn_ids[] = {
    213	{ "DELRBTN", 0 },
    214	{ "DELLABCE", 0 },
    215
    216	/*
    217	 * This driver can also handle the "DELLABC6" device that
    218	 * appears on the XPS 13 9350, but that device is disabled by
    219	 * the DSDT unless booted with acpi_osi="!Windows 2012"
    220	 * acpi_osi="!Windows 2013".
    221	 *
    222	 * According to Mario at Dell:
    223	 *
    224	 *  DELLABC6 is a custom interface that was created solely to
    225	 *  have airplane mode support for Windows 7.  For Windows 10
    226	 *  the proper interface is to use that which is handled by
    227	 *  intel-hid. A OEM airplane mode driver is not used.
    228	 *
    229	 *  Since the kernel doesn't identify as Windows 7 it would be
    230	 *  incorrect to do attempt to use that interface.
    231	 *
    232	 * Even if we override _OSI and bind to DELLABC6, we end up with
    233	 * inconsistent behavior in which userspace can get out of sync
    234	 * with the rfkill state as it conflicts with events from
    235	 * intel-hid.
    236	 *
    237	 * The upshot is that it is better to just ignore DELLABC6
    238	 * devices.
    239	 */
    240
    241	{ "", 0 },
    242};
    243
    244#ifdef CONFIG_PM_SLEEP
    245static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context)
    246{
    247	struct rbtn_data *rbtn_data = context;
    248
    249	rbtn_data->suspended = false;
    250}
    251
    252static int rbtn_suspend(struct device *dev)
    253{
    254	struct acpi_device *device = to_acpi_device(dev);
    255	struct rbtn_data *rbtn_data = acpi_driver_data(device);
    256
    257	rbtn_data->suspended = true;
    258
    259	return 0;
    260}
    261
    262static int rbtn_resume(struct device *dev)
    263{
    264	struct acpi_device *device = to_acpi_device(dev);
    265	struct rbtn_data *rbtn_data = acpi_driver_data(device);
    266	acpi_status status;
    267
    268	/*
    269	 * Upon resume, some BIOSes send an ACPI notification thet triggers
    270	 * an unwanted input event. In order to ignore it, we use a flag
    271	 * that we set at suspend and clear once we have received the extra
    272	 * ACPI notification. Since ACPI notifications are delivered
    273	 * asynchronously to drivers, we clear the flag from the workqueue
    274	 * used to deliver the notifications. This should be enough
    275	 * to have the flag cleared only after we received the extra
    276	 * notification, if any.
    277	 */
    278	status = acpi_os_execute(OSL_NOTIFY_HANDLER,
    279			 rbtn_clear_suspended_flag, rbtn_data);
    280	if (ACPI_FAILURE(status))
    281		rbtn_clear_suspended_flag(rbtn_data);
    282
    283	return 0;
    284}
    285#endif
    286
    287static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume);
    288
    289static struct acpi_driver rbtn_driver = {
    290	.name = "dell-rbtn",
    291	.ids = rbtn_ids,
    292	.drv.pm = &rbtn_pm_ops,
    293	.ops = {
    294		.add = rbtn_add,
    295		.remove = rbtn_remove,
    296		.notify = rbtn_notify,
    297	},
    298	.owner = THIS_MODULE,
    299};
    300
    301
    302/*
    303 * notifier export functions
    304 */
    305
    306static bool auto_remove_rfkill = true;
    307
    308static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head);
    309
    310static int rbtn_inc_count(struct device *dev, void *data)
    311{
    312	struct acpi_device *device = to_acpi_device(dev);
    313	struct rbtn_data *rbtn_data = device->driver_data;
    314	int *count = data;
    315
    316	if (rbtn_data->type == RBTN_SLIDER)
    317		(*count)++;
    318
    319	return 0;
    320}
    321
    322static int rbtn_switch_dev(struct device *dev, void *data)
    323{
    324	struct acpi_device *device = to_acpi_device(dev);
    325	struct rbtn_data *rbtn_data = device->driver_data;
    326	bool enable = data;
    327
    328	if (rbtn_data->type != RBTN_SLIDER)
    329		return 0;
    330
    331	if (enable)
    332		rbtn_rfkill_init(device);
    333	else
    334		rbtn_rfkill_exit(device);
    335
    336	return 0;
    337}
    338
    339int dell_rbtn_notifier_register(struct notifier_block *nb)
    340{
    341	bool first;
    342	int count;
    343	int ret;
    344
    345	count = 0;
    346	ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count,
    347				     rbtn_inc_count);
    348	if (ret || count == 0)
    349		return -ENODEV;
    350
    351	first = !rbtn_chain_head.head;
    352
    353	ret = atomic_notifier_chain_register(&rbtn_chain_head, nb);
    354	if (ret != 0)
    355		return ret;
    356
    357	if (auto_remove_rfkill && first)
    358		ret = driver_for_each_device(&rbtn_driver.drv, NULL,
    359					     (void *)false, rbtn_switch_dev);
    360
    361	return ret;
    362}
    363EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register);
    364
    365int dell_rbtn_notifier_unregister(struct notifier_block *nb)
    366{
    367	int ret;
    368
    369	ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb);
    370	if (ret != 0)
    371		return ret;
    372
    373	if (auto_remove_rfkill && !rbtn_chain_head.head)
    374		ret = driver_for_each_device(&rbtn_driver.drv, NULL,
    375					     (void *)true, rbtn_switch_dev);
    376
    377	return ret;
    378}
    379EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister);
    380
    381
    382/*
    383 * acpi driver functions
    384 */
    385
    386static int rbtn_add(struct acpi_device *device)
    387{
    388	struct rbtn_data *rbtn_data;
    389	enum rbtn_type type;
    390	int ret = 0;
    391
    392	type = rbtn_check(device);
    393	if (type == RBTN_UNKNOWN) {
    394		dev_info(&device->dev, "Unknown device type\n");
    395		return -EINVAL;
    396	}
    397
    398	ret = rbtn_acquire(device, true);
    399	if (ret < 0) {
    400		dev_err(&device->dev, "Cannot enable device\n");
    401		return ret;
    402	}
    403
    404	rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL);
    405	if (!rbtn_data)
    406		return -ENOMEM;
    407
    408	rbtn_data->type = type;
    409	device->driver_data = rbtn_data;
    410
    411	switch (rbtn_data->type) {
    412	case RBTN_TOGGLE:
    413		ret = rbtn_input_init(rbtn_data);
    414		break;
    415	case RBTN_SLIDER:
    416		if (auto_remove_rfkill && rbtn_chain_head.head)
    417			ret = 0;
    418		else
    419			ret = rbtn_rfkill_init(device);
    420		break;
    421	default:
    422		ret = -EINVAL;
    423	}
    424
    425	return ret;
    426
    427}
    428
    429static int rbtn_remove(struct acpi_device *device)
    430{
    431	struct rbtn_data *rbtn_data = device->driver_data;
    432
    433	switch (rbtn_data->type) {
    434	case RBTN_TOGGLE:
    435		rbtn_input_exit(rbtn_data);
    436		break;
    437	case RBTN_SLIDER:
    438		rbtn_rfkill_exit(device);
    439		break;
    440	default:
    441		break;
    442	}
    443
    444	rbtn_acquire(device, false);
    445	device->driver_data = NULL;
    446
    447	return 0;
    448}
    449
    450static void rbtn_notify(struct acpi_device *device, u32 event)
    451{
    452	struct rbtn_data *rbtn_data = device->driver_data;
    453
    454	/*
    455	 * Some BIOSes send a notification at resume.
    456	 * Ignore it to prevent unwanted input events.
    457	 */
    458	if (rbtn_data->suspended) {
    459		dev_dbg(&device->dev, "ACPI notification ignored\n");
    460		return;
    461	}
    462
    463	if (event != 0x80) {
    464		dev_info(&device->dev, "Received unknown event (0x%x)\n",
    465			 event);
    466		return;
    467	}
    468
    469	switch (rbtn_data->type) {
    470	case RBTN_TOGGLE:
    471		rbtn_input_event(rbtn_data);
    472		break;
    473	case RBTN_SLIDER:
    474		rbtn_rfkill_event(device);
    475		atomic_notifier_call_chain(&rbtn_chain_head, event, device);
    476		break;
    477	default:
    478		break;
    479	}
    480}
    481
    482
    483/*
    484 * module functions
    485 */
    486
    487module_acpi_driver(rbtn_driver);
    488
    489module_param(auto_remove_rfkill, bool, 0444);
    490
    491MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when "
    492				     "other modules start receiving events "
    493				     "from this module and re-add them when "
    494				     "the last module stops receiving events "
    495				     "(default true)");
    496MODULE_DEVICE_TABLE(acpi, rbtn_ids);
    497MODULE_DESCRIPTION("Dell Airplane Mode Switch driver");
    498MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
    499MODULE_LICENSE("GPL");