From c167b9c7e3d6131b4a4865c112a3dbc86d2e997d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:51 +0100 Subject: platform/surface: Add Surface Aggregator subsystem Add Surface System Aggregator Module core and Surface Serial Hub driver, required for the embedded controller found on Microsoft Surface devices. The Surface System Aggregator Module (SSAM, SAM or Surface Aggregator) is an embedded controller (EC) found on 4th and later generation Microsoft Surface devices, with the exception of the Surface Go series. This EC provides various functionality, depending on the device in question. This can include battery status and thermal reporting (5th and later generations), but also HID keyboard (6th+) and touchpad input (7th+) on Surface Laptop and Surface Book 3 series devices. This patch provides the basic necessities for communication with the SAM EC on 5th and later generation devices. On these devices, the EC provides an interface that acts as serial device, called the Surface Serial Hub (SSH). 4th generation devices, on which the EC interface is provided via an HID-over-I2C device, are not supported by this patch. Specifically, this patch adds a driver for the SSH device (device HID MSHW0084 in ACPI), as well as a controller structure and associated API. This represents the functional core of the Surface Aggregator kernel subsystem, introduced with this patch, and will be expanded upon in subsequent commits. The SSH driver acts as the main attachment point for this subsystem and sets-up and manages the controller structure. The controller in turn provides a basic communication interface, allowing to send requests from host to EC and receiving the corresponding responses, as well as managing and receiving events, sent from EC to host. It is structured into multiple layers, with the top layer presenting the API used by other kernel drivers and the lower layers modeled after the serial protocol used for communication. Said other drivers are then responsible for providing the (Surface model specific) functionality accessible through the EC (e.g. battery status reporting, thermal information, ...) via said controller structure and API, and will be added in future commits. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20201221183959.1186143-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/Kconfig | 2 + drivers/platform/surface/Makefile | 1 + drivers/platform/surface/aggregator/Kconfig | 42 + drivers/platform/surface/aggregator/Makefile | 10 + drivers/platform/surface/aggregator/controller.c | 2504 ++++++++++++++++++++ drivers/platform/surface/aggregator/controller.h | 276 +++ drivers/platform/surface/aggregator/core.c | 787 ++++++ drivers/platform/surface/aggregator/ssh_msgb.h | 205 ++ .../platform/surface/aggregator/ssh_packet_layer.c | 1710 +++++++++++++ .../platform/surface/aggregator/ssh_packet_layer.h | 187 ++ drivers/platform/surface/aggregator/ssh_parser.c | 228 ++ drivers/platform/surface/aggregator/ssh_parser.h | 154 ++ .../surface/aggregator/ssh_request_layer.c | 1211 ++++++++++ .../surface/aggregator/ssh_request_layer.h | 143 ++ 14 files changed, 7460 insertions(+) create mode 100644 drivers/platform/surface/aggregator/Kconfig create mode 100644 drivers/platform/surface/aggregator/Makefile create mode 100644 drivers/platform/surface/aggregator/controller.c create mode 100644 drivers/platform/surface/aggregator/controller.h create mode 100644 drivers/platform/surface/aggregator/core.c create mode 100644 drivers/platform/surface/aggregator/ssh_msgb.h create mode 100644 drivers/platform/surface/aggregator/ssh_packet_layer.c create mode 100644 drivers/platform/surface/aggregator/ssh_packet_layer.h create mode 100644 drivers/platform/surface/aggregator/ssh_parser.c create mode 100644 drivers/platform/surface/aggregator/ssh_parser.h create mode 100644 drivers/platform/surface/aggregator/ssh_request_layer.c create mode 100644 drivers/platform/surface/aggregator/ssh_request_layer.h (limited to 'drivers') diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 2c941cdac9ee..2916a47b130e 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -56,4 +56,6 @@ config SURFACE_PRO3_BUTTON help This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. +source "drivers/platform/surface/aggregator/Kconfig" + endif # SURFACE_PLATFORMS diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index cedfb027ded1..034134fe0264 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -7,5 +7,6 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o +obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig new file mode 100644 index 000000000000..e9f4ad96e40a --- /dev/null +++ b/drivers/platform/surface/aggregator/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2019-2020 Maximilian Luz + +menuconfig SURFACE_AGGREGATOR + tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" + depends on SERIAL_DEV_BUS + select CRC_CCITT + help + The Surface System Aggregator Module (Surface SAM or SSAM) is an + embedded controller (EC) found on 5th- and later-generation Microsoft + Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, + and newer, with exception of Surface Go series devices). + + Depending on the device in question, this EC provides varying + functionality, including: + - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) + - battery status information (all devices) + - thermal sensor access (all devices) + - performance mode / cooling mode control (all devices) + - clipboard detachment system control (Surface Book 2 and 3) + - HID / keyboard input (Surface Laptops, Surface Book 3) + + This option controls whether the Surface SAM subsystem core will be + built. This includes a driver for the Surface Serial Hub (SSH), which + is the device responsible for the communication with the EC, and a + basic kernel interface exposing the EC functionality to other client + drivers, i.e. allowing them to make requests to the EC and receive + events from it. Selecting this option alone will not provide any + client drivers and therefore no functionality beyond the in-kernel + interface. Said functionality is the responsibility of the respective + client drivers. + + Note: While 4th-generation Surface devices also make use of a SAM EC, + due to a difference in the communication interface of the controller, + only 5th and later generations are currently supported. Specifically, + devices using SAM-over-SSH are supported, whereas devices using + SAM-over-HID, which is used on the 4th generation, are currently not + supported. + + Choose m if you want to build the SAM subsystem core and SSH driver as + module, y if you want to build it into the kernel and n if you don't + want it at all. diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile new file mode 100644 index 000000000000..faad18d4a7f2 --- /dev/null +++ b/drivers/platform/surface/aggregator/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2019-2020 Maximilian Luz + +obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o + +surface_aggregator-objs := core.o +surface_aggregator-objs += ssh_parser.o +surface_aggregator-objs += ssh_packet_layer.o +surface_aggregator-objs += ssh_request_layer.o +surface_aggregator-objs += controller.o diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c new file mode 100644 index 000000000000..488318cf2098 --- /dev/null +++ b/drivers/platform/surface/aggregator/controller.c @@ -0,0 +1,2504 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Main SSAM/SSH controller structure and functionality. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "controller.h" +#include "ssh_msgb.h" +#include "ssh_request_layer.h" + + +/* -- Safe counters. -------------------------------------------------------- */ + +/** + * ssh_seq_reset() - Reset/initialize sequence ID counter. + * @c: The counter to reset. + */ +static void ssh_seq_reset(struct ssh_seq_counter *c) +{ + WRITE_ONCE(c->value, 0); +} + +/** + * ssh_seq_next() - Get next sequence ID. + * @c: The counter providing the sequence IDs. + * + * Return: Returns the next sequence ID of the counter. + */ +static u8 ssh_seq_next(struct ssh_seq_counter *c) +{ + u8 old = READ_ONCE(c->value); + u8 new = old + 1; + u8 ret; + + while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { + old = ret; + new = old + 1; + } + + return old; +} + +/** + * ssh_rqid_reset() - Reset/initialize request ID counter. + * @c: The counter to reset. + */ +static void ssh_rqid_reset(struct ssh_rqid_counter *c) +{ + WRITE_ONCE(c->value, 0); +} + +/** + * ssh_rqid_next() - Get next request ID. + * @c: The counter providing the request IDs. + * + * Return: Returns the next request ID of the counter, skipping any reserved + * request IDs. + */ +static u16 ssh_rqid_next(struct ssh_rqid_counter *c) +{ + u16 old = READ_ONCE(c->value); + u16 new = ssh_rqid_next_valid(old); + u16 ret; + + while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { + old = ret; + new = ssh_rqid_next_valid(old); + } + + return old; +} + + +/* -- Event notifier/callbacks. --------------------------------------------- */ +/* + * The notifier system is based on linux/notifier.h, specifically the SRCU + * implementation. The difference to that is, that some bits of the notifier + * call return value can be tracked across multiple calls. This is done so + * that handling of events can be tracked and a warning can be issued in case + * an event goes unhandled. The idea of that warning is that it should help + * discover and identify new/currently unimplemented features. + */ + +/** + * ssam_event_matches_notifier() - Test if an event matches a notifier. + * @n: The event notifier to test against. + * @event: The event to test. + * + * Return: Returns %true if the given event matches the given notifier + * according to the rules set in the notifier's event mask, %false otherwise. + */ +static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, + const struct ssam_event *event) +{ + bool match = n->event.id.target_category == event->target_category; + + if (n->event.mask & SSAM_EVENT_MASK_TARGET) + match &= n->event.reg.target_id == event->target_id; + + if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) + match &= n->event.id.instance == event->instance_id; + + return match; +} + +/** + * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. + * @nh: The notifier head for which the notifier callbacks should be called. + * @event: The event data provided to the callbacks. + * + * Call all registered notifier callbacks in order of their priority until + * either no notifier is left or a notifier returns a value with the + * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via + * ssam_notifier_from_errno() on any non-zero error value. + * + * Return: Returns the notifier status value, which contains the notifier + * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a + * potential error value returned from the last executed notifier callback. + * Use ssam_notifier_to_errno() to convert this value to the original error + * value. + */ +static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) +{ + struct ssam_event_notifier *nf; + int ret = 0, idx; + + idx = srcu_read_lock(&nh->srcu); + + list_for_each_entry_rcu(nf, &nh->head, base.node, + srcu_read_lock_held(&nh->srcu)) { + if (ssam_event_matches_notifier(nf, event)) { + ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); + if (ret & SSAM_NOTIF_STOP) + break; + } + } + + srcu_read_unlock(&nh->srcu, idx); + return ret; +} + +/** + * ssam_nfblk_insert() - Insert a new notifier block into the given notifier + * list. + * @nh: The notifier head into which the block should be inserted. + * @nb: The notifier block to add. + * + * Note: This function must be synchronized by the caller with respect to other + * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. + * + * Return: Returns zero on success, %-EEXIST if the notifier block has already + * been registered. + */ +static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) +{ + struct ssam_notifier_block *p; + struct list_head *h; + + /* Runs under lock, no need for RCU variant. */ + list_for_each(h, &nh->head) { + p = list_entry(h, struct ssam_notifier_block, node); + + if (unlikely(p == nb)) { + WARN(1, "double register detected"); + return -EEXIST; + } + + if (nb->priority > p->priority) + break; + } + + list_add_tail_rcu(&nb->node, h); + return 0; +} + +/** + * ssam_nfblk_find() - Check if a notifier block is registered on the given + * notifier head. + * list. + * @nh: The notifier head on which to search. + * @nb: The notifier block to search for. + * + * Note: This function must be synchronized by the caller with respect to other + * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. + * + * Return: Returns true if the given notifier block is registered on the given + * notifier head, false otherwise. + */ +static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) +{ + struct ssam_notifier_block *p; + + /* Runs under lock, no need for RCU variant. */ + list_for_each_entry(p, &nh->head, node) { + if (p == nb) + return true; + } + + return false; +} + +/** + * ssam_nfblk_remove() - Remove a notifier block from its notifier list. + * @nb: The notifier block to be removed. + * + * Note: This function must be synchronized by the caller with respect to + * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. + * Furthermore, the caller _must_ ensure SRCU synchronization by calling + * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to + * ensure that the removed notifier block is not in use any more. + */ +static void ssam_nfblk_remove(struct ssam_notifier_block *nb) +{ + list_del_rcu(&nb->node); +} + +/** + * ssam_nf_head_init() - Initialize the given notifier head. + * @nh: The notifier head to initialize. + */ +static int ssam_nf_head_init(struct ssam_nf_head *nh) +{ + int status; + + status = init_srcu_struct(&nh->srcu); + if (status) + return status; + + INIT_LIST_HEAD(&nh->head); + return 0; +} + +/** + * ssam_nf_head_destroy() - Deinitialize the given notifier head. + * @nh: The notifier head to deinitialize. + */ +static void ssam_nf_head_destroy(struct ssam_nf_head *nh) +{ + cleanup_srcu_struct(&nh->srcu); +} + + +/* -- Event/notification registry. ------------------------------------------ */ + +/** + * struct ssam_nf_refcount_key - Key used for event activation reference + * counting. + * @reg: The registry via which the event is enabled/disabled. + * @id: The ID uniquely describing the event. + */ +struct ssam_nf_refcount_key { + struct ssam_event_registry reg; + struct ssam_event_id id; +}; + +/** + * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event + * activations. + * @node: The node of this entry in the rb-tree. + * @key: The key of the event. + * @refcount: The reference-count of the event. + * @flags: The flags used when enabling the event. + */ +struct ssam_nf_refcount_entry { + struct rb_node node; + struct ssam_nf_refcount_key key; + int refcount; + u8 flags; +}; + +/** + * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given + * event. + * @nf: The notifier system reference. + * @reg: The registry used to enable/disable the event. + * @id: The event ID. + * + * Increments the reference-/activation-count associated with the specified + * event type/ID, allocating a new entry for this event ID if necessary. A + * newly allocated entry will have a refcount of one. + * + * Note: ``nf->lock`` must be held when calling this function. + * + * Return: Returns the refcount entry on success. Returns an error pointer + * with %-ENOSPC if there have already been %INT_MAX events of the specified + * ID and type registered, or %-ENOMEM if the entry could not be allocated. + */ +static struct ssam_nf_refcount_entry * +ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, + struct ssam_event_id id) +{ + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_refcount_key key; + struct rb_node **link = &nf->refcount.rb_node; + struct rb_node *parent = NULL; + int cmp; + + lockdep_assert_held(&nf->lock); + + key.reg = reg; + key.id = id; + + while (*link) { + entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); + parent = *link; + + cmp = memcmp(&key, &entry->key, sizeof(key)); + if (cmp < 0) { + link = &(*link)->rb_left; + } else if (cmp > 0) { + link = &(*link)->rb_right; + } else if (entry->refcount < INT_MAX) { + entry->refcount++; + return entry; + } else { + WARN_ON(1); + return ERR_PTR(-ENOSPC); + } + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return ERR_PTR(-ENOMEM); + + entry->key = key; + entry->refcount = 1; + + rb_link_node(&entry->node, parent, link); + rb_insert_color(&entry->node, &nf->refcount); + + return entry; +} + +/** + * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given + * event. + * @nf: The notifier system reference. + * @reg: The registry used to enable/disable the event. + * @id: The event ID. + * + * Decrements the reference-/activation-count of the specified event, + * returning its entry. If the returned entry has a refcount of zero, the + * caller is responsible for freeing it using kfree(). + * + * Note: ``nf->lock`` must be held when calling this function. + * + * Return: Returns the refcount entry on success or %NULL if the entry has not + * been found. + */ +static struct ssam_nf_refcount_entry * +ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, + struct ssam_event_id id) +{ + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_refcount_key key; + struct rb_node *node = nf->refcount.rb_node; + int cmp; + + lockdep_assert_held(&nf->lock); + + key.reg = reg; + key.id = id; + + while (node) { + entry = rb_entry(node, struct ssam_nf_refcount_entry, node); + + cmp = memcmp(&key, &entry->key, sizeof(key)); + if (cmp < 0) { + node = node->rb_left; + } else if (cmp > 0) { + node = node->rb_right; + } else { + entry->refcount--; + if (entry->refcount == 0) + rb_erase(&entry->node, &nf->refcount); + + return entry; + } + } + + return NULL; +} + +/** + * ssam_nf_refcount_empty() - Test if the notification system has any + * enabled/active events. + * @nf: The notification system. + */ +static bool ssam_nf_refcount_empty(struct ssam_nf *nf) +{ + return RB_EMPTY_ROOT(&nf->refcount); +} + +/** + * ssam_nf_call() - Call notification callbacks for the provided event. + * @nf: The notifier system + * @dev: The associated device, only used for logging. + * @rqid: The request ID of the event. + * @event: The event provided to the callbacks. + * + * Execute registered callbacks in order of their priority until either no + * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP + * bit set. Note that this bit is set automatically when converting non-zero + * error values via ssam_notifier_from_errno() to notifier values. + * + * Also note that any callback that could handle an event should return a value + * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go + * unhandled/ignored. In case no registered callback could handle an event, + * this function will emit a warning. + * + * In case a callback failed, this function will emit an error message. + */ +static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, + struct ssam_event *event) +{ + struct ssam_nf_head *nf_head; + int status, nf_ret; + + if (!ssh_rqid_is_event(rqid)) { + dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); + return; + } + + nf_head = &nf->head[ssh_rqid_to_event(rqid)]; + nf_ret = ssam_nfblk_call_chain(nf_head, event); + status = ssam_notifier_to_errno(nf_ret); + + if (status < 0) { + dev_err(dev, + "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", + status, event->target_category, event->target_id, + event->command_id, event->instance_id); + } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { + dev_warn(dev, + "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", + rqid, event->target_category, event->target_id, + event->command_id, event->instance_id); + } +} + +/** + * ssam_nf_init() - Initialize the notifier system. + * @nf: The notifier system to initialize. + */ +static int ssam_nf_init(struct ssam_nf *nf) +{ + int i, status; + + for (i = 0; i < SSH_NUM_EVENTS; i++) { + status = ssam_nf_head_init(&nf->head[i]); + if (status) + break; + } + + if (status) { + while (i--) + ssam_nf_head_destroy(&nf->head[i]); + + return status; + } + + mutex_init(&nf->lock); + return 0; +} + +/** + * ssam_nf_destroy() - Deinitialize the notifier system. + * @nf: The notifier system to deinitialize. + */ +static void ssam_nf_destroy(struct ssam_nf *nf) +{ + int i; + + for (i = 0; i < SSH_NUM_EVENTS; i++) + ssam_nf_head_destroy(&nf->head[i]); + + mutex_destroy(&nf->lock); +} + + +/* -- Event/async request completion system. -------------------------------- */ + +#define SSAM_CPLT_WQ_NAME "ssam_cpltq" + +/* + * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per + * work execution. Used to prevent livelocking of the workqueue. Value chosen + * via educated guess, may be adjusted. + */ +#define SSAM_CPLT_WQ_BATCH 10 + +/** + * ssam_event_item_alloc() - Allocate an event item with the given payload size. + * @len: The event payload length. + * @flags: The flags used for allocation. + * + * Allocate an event item with the given payload size. Sets the item + * operations and payload length values. The item free callback (``ops.free``) + * should not be overwritten after this call. + * + * Return: Returns the newly allocated event item. + */ +static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) +{ + struct ssam_event_item *item; + + item = kzalloc(struct_size(item, event.data, len), flags); + if (!item) + return NULL; + + item->event.length = len; + return item; +} + +/** + * ssam_event_queue_push() - Push an event item to the event queue. + * @q: The event queue. + * @item: The item to add. + */ +static void ssam_event_queue_push(struct ssam_event_queue *q, + struct ssam_event_item *item) +{ + spin_lock(&q->lock); + list_add_tail(&item->node, &q->head); + spin_unlock(&q->lock); +} + +/** + * ssam_event_queue_pop() - Pop the next event item from the event queue. + * @q: The event queue. + * + * Returns and removes the next event item from the queue. Returns %NULL If + * there is no event item left. + */ +static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q) +{ + struct ssam_event_item *item; + + spin_lock(&q->lock); + item = list_first_entry_or_null(&q->head, struct ssam_event_item, node); + if (item) + list_del(&item->node); + spin_unlock(&q->lock); + + return item; +} + +/** + * ssam_event_queue_is_empty() - Check if the event queue is empty. + * @q: The event queue. + */ +static bool ssam_event_queue_is_empty(struct ssam_event_queue *q) +{ + bool empty; + + spin_lock(&q->lock); + empty = list_empty(&q->head); + spin_unlock(&q->lock); + + return empty; +} + +/** + * ssam_cplt_get_event_queue() - Get the event queue for the given parameters. + * @cplt: The completion system on which to look for the queue. + * @tid: The target ID of the queue. + * @rqid: The request ID representing the event ID for which to get the queue. + * + * Return: Returns the event queue corresponding to the event type described + * by the given parameters. If the request ID does not represent an event, + * this function returns %NULL. If the target ID is not supported, this + * function will fall back to the default target ID (``tid = 1``). + */ +static +struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt, + u8 tid, u16 rqid) +{ + u16 event = ssh_rqid_to_event(rqid); + u16 tidx = ssh_tid_to_index(tid); + + if (!ssh_rqid_is_event(rqid)) { + dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid); + return NULL; + } + + if (!ssh_tid_is_valid(tid)) { + dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid); + tidx = 0; + } + + return &cplt->event.target[tidx].queue[event]; +} + +/** + * ssam_cplt_submit() - Submit a work item to the completion system workqueue. + * @cplt: The completion system. + * @work: The work item to submit. + */ +static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) +{ + return queue_work(cplt->wq, work); +} + +/** + * ssam_cplt_submit_event() - Submit an event to the completion system. + * @cplt: The completion system. + * @item: The event item to submit. + * + * Submits the event to the completion system by queuing it on the event item + * queue and queuing the respective event queue work item on the completion + * workqueue, which will eventually complete the event. + * + * Return: Returns zero on success, %-EINVAL if there is no event queue that + * can handle the given event item. + */ +static int ssam_cplt_submit_event(struct ssam_cplt *cplt, + struct ssam_event_item *item) +{ + struct ssam_event_queue *evq; + + evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid); + if (!evq) + return -EINVAL; + + ssam_event_queue_push(evq, item); + ssam_cplt_submit(cplt, &evq->work); + return 0; +} + +/** + * ssam_cplt_flush() - Flush the completion system. + * @cplt: The completion system. + * + * Flush the completion system by waiting until all currently submitted work + * items have been completed. + * + * Note: This function does not guarantee that all events will have been + * handled once this call terminates. In case of a larger number of + * to-be-completed events, the event queue work function may re-schedule its + * work item, which this flush operation will ignore. + * + * This operation is only intended to, during normal operation prior to + * shutdown, try to complete most events and requests to get them out of the + * system while the system is still fully operational. It does not aim to + * provide any guarantee that all of them have been handled. + */ +static void ssam_cplt_flush(struct ssam_cplt *cplt) +{ + flush_workqueue(cplt->wq); +} + +static void ssam_event_queue_work_fn(struct work_struct *work) +{ + struct ssam_event_queue *queue; + struct ssam_event_item *item; + struct ssam_nf *nf; + struct device *dev; + unsigned int iterations = SSAM_CPLT_WQ_BATCH; + + queue = container_of(work, struct ssam_event_queue, work); + nf = &queue->cplt->event.notif; + dev = queue->cplt->dev; + + /* Limit number of processed events to avoid livelocking. */ + do { + item = ssam_event_queue_pop(queue); + if (!item) + return; + + ssam_nf_call(nf, dev, item->rqid, &item->event); + kfree(item); + } while (--iterations); + + if (!ssam_event_queue_is_empty(queue)) + ssam_cplt_submit(queue->cplt, &queue->work); +} + +/** + * ssam_event_queue_init() - Initialize an event queue. + * @cplt: The completion system on which the queue resides. + * @evq: The event queue to initialize. + */ +static void ssam_event_queue_init(struct ssam_cplt *cplt, + struct ssam_event_queue *evq) +{ + evq->cplt = cplt; + spin_lock_init(&evq->lock); + INIT_LIST_HEAD(&evq->head); + INIT_WORK(&evq->work, ssam_event_queue_work_fn); +} + +/** + * ssam_cplt_init() - Initialize completion system. + * @cplt: The completion system to initialize. + * @dev: The device used for logging. + */ +static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) +{ + struct ssam_event_target *target; + int status, c, i; + + cplt->dev = dev; + + cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME); + if (!cplt->wq) + return -ENOMEM; + + for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) { + target = &cplt->event.target[c]; + + for (i = 0; i < ARRAY_SIZE(target->queue); i++) + ssam_event_queue_init(cplt, &target->queue[i]); + } + + status = ssam_nf_init(&cplt->event.notif); + if (status) + destroy_workqueue(cplt->wq); + + return status; +} + +/** + * ssam_cplt_destroy() - Deinitialize the completion system. + * @cplt: The completion system to deinitialize. + * + * Deinitialize the given completion system and ensure that all pending, i.e. + * yet-to-be-completed, event items and requests have been handled. + */ +static void ssam_cplt_destroy(struct ssam_cplt *cplt) +{ + /* + * Note: destroy_workqueue ensures that all currently queued work will + * be fully completed and the workqueue drained. This means that this + * call will inherently also free any queued ssam_event_items, thus we + * don't have to take care of that here explicitly. + */ + destroy_workqueue(cplt->wq); + ssam_nf_destroy(&cplt->event.notif); +} + + +/* -- Main SSAM device structures. ------------------------------------------ */ + +/** + * ssam_controller_device() - Get the &struct device associated with this + * controller. + * @c: The controller for which to get the device. + * + * Return: Returns the &struct device associated with this controller, + * providing its lower-level transport. + */ +struct device *ssam_controller_device(struct ssam_controller *c) +{ + return ssh_rtl_get_device(&c->rtl); +} +EXPORT_SYMBOL_GPL(ssam_controller_device); + +static void __ssam_controller_release(struct kref *kref) +{ + struct ssam_controller *ctrl = to_ssam_controller(kref, kref); + + /* + * The lock-call here is to satisfy lockdep. At this point we really + * expect this to be the last remaining reference to the controller. + * Anything else is a bug. + */ + ssam_controller_lock(ctrl); + ssam_controller_destroy(ctrl); + ssam_controller_unlock(ctrl); + + kfree(ctrl); +} + +/** + * ssam_controller_get() - Increment reference count of controller. + * @c: The controller. + * + * Return: Returns the controller provided as input. + */ +struct ssam_controller *ssam_controller_get(struct ssam_controller *c) +{ + if (c) + kref_get(&c->kref); + return c; +} +EXPORT_SYMBOL_GPL(ssam_controller_get); + +/** + * ssam_controller_put() - Decrement reference count of controller. + * @c: The controller. + */ +void ssam_controller_put(struct ssam_controller *c) +{ + if (c) + kref_put(&c->kref, __ssam_controller_release); +} +EXPORT_SYMBOL_GPL(ssam_controller_put); + +/** + * ssam_controller_statelock() - Lock the controller against state transitions. + * @c: The controller to lock. + * + * Lock the controller against state transitions. Holding this lock guarantees + * that the controller will not transition between states, i.e. if the + * controller is in state "started", when this lock has been acquired, it will + * remain in this state at least until the lock has been released. + * + * Multiple clients may concurrently hold this lock. In other words: The + * ``statelock`` functions represent the read-lock part of a r/w-semaphore. + * Actions causing state transitions of the controller must be executed while + * holding the write-part of this r/w-semaphore (see ssam_controller_lock() + * and ssam_controller_unlock() for that). + * + * See ssam_controller_stateunlock() for the corresponding unlock function. + */ +void ssam_controller_statelock(struct ssam_controller *c) +{ + down_read(&c->lock); +} +EXPORT_SYMBOL_GPL(ssam_controller_statelock); + +/** + * ssam_controller_stateunlock() - Unlock controller state transitions. + * @c: The controller to unlock. + * + * See ssam_controller_statelock() for the corresponding lock function. + */ +void ssam_controller_stateunlock(struct ssam_controller *c) +{ + up_read(&c->lock); +} +EXPORT_SYMBOL_GPL(ssam_controller_stateunlock); + +/** + * ssam_controller_lock() - Acquire the main controller lock. + * @c: The controller to lock. + * + * This lock must be held for any state transitions, including transition to + * suspend/resumed states and during shutdown. See ssam_controller_statelock() + * for more details on controller locking. + * + * See ssam_controller_unlock() for the corresponding unlock function. + */ +void ssam_controller_lock(struct ssam_controller *c) +{ + down_write(&c->lock); +} + +/* + * ssam_controller_unlock() - Release the main controller lock. + * @c: The controller to unlock. + * + * See ssam_controller_lock() for the corresponding lock function. + */ +void ssam_controller_unlock(struct ssam_controller *c) +{ + up_write(&c->lock); +} + +static void ssam_handle_event(struct ssh_rtl *rtl, + const struct ssh_command *cmd, + const struct ssam_span *data) +{ + struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl); + struct ssam_event_item *item; + + item = ssam_event_item_alloc(data->len, GFP_KERNEL); + if (!item) + return; + + item->rqid = get_unaligned_le16(&cmd->rqid); + item->event.target_category = cmd->tc; + item->event.target_id = cmd->tid_in; + item->event.command_id = cmd->cid; + item->event.instance_id = cmd->iid; + memcpy(&item->event.data[0], data->ptr, data->len); + + if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) + kfree(item); +} + +static const struct ssh_rtl_ops ssam_rtl_ops = { + .handle_event = ssam_handle_event, +}; + +static bool ssam_notifier_is_empty(struct ssam_controller *ctrl); +static void ssam_notifier_unregister_all(struct ssam_controller *ctrl); + +#define SSAM_SSH_DSM_REVISION 0 + +/* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */ +static const guid_t SSAM_SSH_DSM_GUID = + GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, + 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); + +enum ssh_dsm_fn { + SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, + SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, + SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, + SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, + SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, +}; + +static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) +{ + union acpi_object *obj; + u64 mask = 0; + int i; + + *funcs = 0; + + /* + * The _DSM function is only present on newer models. It is not + * present on 5th and 6th generation devices (i.e. up to and including + * Surface Pro 6, Surface Laptop 2, Surface Book 2). + * + * If the _DSM is not present, indicate that no function is supported. + * This will result in default values being set. + */ + if (!acpi_has_method(handle, "_DSM")) + return 0; + + obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, + SSAM_SSH_DSM_REVISION, 0, NULL, + ACPI_TYPE_BUFFER); + if (!obj) + return -EIO; + + for (i = 0; i < obj->buffer.length && i < 8; i++) + mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); + + if (mask & BIT(0)) + *funcs = mask; + + ACPI_FREE(obj); + return 0; +} + +static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) +{ + union acpi_object *obj; + u64 val; + + if (!(funcs & BIT(func))) + return 0; /* Not supported, leave *ret at its default value */ + + obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, + SSAM_SSH_DSM_REVISION, func, NULL, + ACPI_TYPE_INTEGER); + if (!obj) + return -EIO; + + val = obj->integer.value; + ACPI_FREE(obj); + + if (val > U32_MAX) + return -ERANGE; + + *ret = val; + return 0; +} + +/** + * ssam_controller_caps_load_from_acpi() - Load controller capabilities from + * ACPI _DSM. + * @handle: The handle of the ACPI controller/SSH device. + * @caps: Where to store the capabilities in. + * + * Initializes the given controller capabilities with default values, then + * checks and, if the respective _DSM functions are available, loads the + * actual capabilities from the _DSM. + * + * Return: Returns zero on success, a negative error code on failure. + */ +static +int ssam_controller_caps_load_from_acpi(acpi_handle handle, + struct ssam_controller_caps *caps) +{ + u32 d3_closes_handle = false; + u64 funcs; + int status; + + /* Set defaults. */ + caps->ssh_power_profile = U32_MAX; + caps->screen_on_sleep_idle_timeout = U32_MAX; + caps->screen_off_sleep_idle_timeout = U32_MAX; + caps->d3_closes_handle = false; + caps->ssh_buffer_size = U32_MAX; + + /* Pre-load supported DSM functions. */ + status = ssam_dsm_get_functions(handle, &funcs); + if (status) + return status; + + /* Load actual values from ACPI, if present. */ + status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, + &caps->ssh_power_profile); + if (status) + return status; + + status = ssam_dsm_load_u32(handle, funcs, + SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, + &caps->screen_on_sleep_idle_timeout); + if (status) + return status; + + status = ssam_dsm_load_u32(handle, funcs, + SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, + &caps->screen_off_sleep_idle_timeout); + if (status) + return status; + + status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, + &d3_closes_handle); + if (status) + return status; + + caps->d3_closes_handle = !!d3_closes_handle; + + status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, + &caps->ssh_buffer_size); + if (status) + return status; + + return 0; +} + +/** + * ssam_controller_init() - Initialize SSAM controller. + * @ctrl: The controller to initialize. + * @serdev: The serial device representing the underlying data transport. + * + * Initializes the given controller. Does neither start receiver nor + * transmitter threads. After this call, the controller has to be hooked up to + * the serdev core separately via &struct serdev_device_ops, relaying calls to + * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the + * controller has been hooked up, transmitter and receiver threads may be + * started via ssam_controller_start(). These setup steps need to be completed + * before controller can be used for requests. + */ +int ssam_controller_init(struct ssam_controller *ctrl, + struct serdev_device *serdev) +{ + acpi_handle handle = ACPI_HANDLE(&serdev->dev); + int status; + + init_rwsem(&ctrl->lock); + kref_init(&ctrl->kref); + + status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); + if (status) + return status; + + dev_dbg(&serdev->dev, + "device capabilities:\n" + " ssh_power_profile: %u\n" + " ssh_buffer_size: %u\n" + " screen_on_sleep_idle_timeout: %u\n" + " screen_off_sleep_idle_timeout: %u\n" + " d3_closes_handle: %u\n", + ctrl->caps.ssh_power_profile, + ctrl->caps.ssh_buffer_size, + ctrl->caps.screen_on_sleep_idle_timeout, + ctrl->caps.screen_off_sleep_idle_timeout, + ctrl->caps.d3_closes_handle); + + ssh_seq_reset(&ctrl->counter.seq); + ssh_rqid_reset(&ctrl->counter.rqid); + + /* Initialize event/request completion system. */ + status = ssam_cplt_init(&ctrl->cplt, &serdev->dev); + if (status) + return status; + + /* Initialize request and packet transport layers. */ + status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops); + if (status) { + ssam_cplt_destroy(&ctrl->cplt); + return status; + } + + /* + * Set state via write_once even though we expect to be in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED); + return 0; +} + +/** + * ssam_controller_start() - Start the receiver and transmitter threads of the + * controller. + * @ctrl: The controller. + * + * Note: When this function is called, the controller should be properly + * hooked up to the serdev core via &struct serdev_device_ops. Please refer + * to ssam_controller_init() for more details on controller initialization. + * + * This function must be called with the main controller lock held (i.e. by + * calling ssam_controller_lock()). + */ +int ssam_controller_start(struct ssam_controller *ctrl) +{ + int status; + + lockdep_assert_held_write(&ctrl->lock); + + if (ctrl->state != SSAM_CONTROLLER_INITIALIZED) + return -EINVAL; + + status = ssh_rtl_start(&ctrl->rtl); + if (status) + return status; + + /* + * Set state via write_once even though we expect to be locked/in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); + return 0; +} + +/* + * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during + * shutdown. + * + * Chosen to be larger than one full request timeout, including packets timing + * out. This value should give ample time to complete any outstanding requests + * during normal operation and account for the odd package timeout. + */ +#define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT msecs_to_jiffies(5000) + +/** + * ssam_controller_shutdown() - Shut down the controller. + * @ctrl: The controller. + * + * Shuts down the controller by flushing all pending requests and stopping the + * transmitter and receiver threads. All requests submitted after this call + * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function + * is safe to use in parallel with ongoing request submission. + * + * In the course of this shutdown procedure, all currently registered + * notifiers will be unregistered. It is, however, strongly recommended to not + * rely on this behavior, and instead the party registering the notifier + * should unregister it before the controller gets shut down, e.g. via the + * SSAM bus which guarantees client devices to be removed before a shutdown. + * + * Note that events may still be pending after this call, but, due to the + * notifiers being unregistered, these events will be dropped when the + * controller is subsequently destroyed via ssam_controller_destroy(). + * + * This function must be called with the main controller lock held (i.e. by + * calling ssam_controller_lock()). + */ +void ssam_controller_shutdown(struct ssam_controller *ctrl) +{ + enum ssam_controller_state s = ctrl->state; + int status; + + lockdep_assert_held_write(&ctrl->lock); + + if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED) + return; + + /* + * Try to flush pending events and requests while everything still + * works. Note: There may still be packets and/or requests in the + * system after this call (e.g. via control packets submitted by the + * packet transport layer or flush timeout / failure, ...). Those will + * be handled with the ssh_rtl_shutdown() call below. + */ + status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT); + if (status) { + ssam_err(ctrl, "failed to flush request transport layer: %d\n", + status); + } + + /* Try to flush all currently completing requests and events. */ + ssam_cplt_flush(&ctrl->cplt); + + /* + * We expect all notifiers to have been removed by the respective client + * driver that set them up at this point. If this warning occurs, some + * client driver has not done that... + */ + WARN_ON(!ssam_notifier_is_empty(ctrl)); + + /* + * Nevertheless, we should still take care of drivers that don't behave + * well. Thus disable all enabled events, unregister all notifiers. + */ + ssam_notifier_unregister_all(ctrl); + + /* + * Cancel remaining requests. Ensure no new ones can be queued and stop + * threads. + */ + ssh_rtl_shutdown(&ctrl->rtl); + + /* + * Set state via write_once even though we expect to be locked/in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED); + ctrl->rtl.ptl.serdev = NULL; +} + +/** + * ssam_controller_destroy() - Destroy the controller and free its resources. + * @ctrl: The controller. + * + * Ensures that all resources associated with the controller get freed. This + * function should only be called after the controller has been stopped via + * ssam_controller_shutdown(). In general, this function should not be called + * directly. The only valid place to call this function directly is during + * initialization, before the controller has been fully initialized and passed + * to other processes. This function is called automatically when the + * reference count of the controller reaches zero. + * + * This function must be called with the main controller lock held (i.e. by + * calling ssam_controller_lock()). + */ +void ssam_controller_destroy(struct ssam_controller *ctrl) +{ + lockdep_assert_held_write(&ctrl->lock); + + if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) + return; + + WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); + + /* + * Note: New events could still have been received after the previous + * flush in ssam_controller_shutdown, before the request transport layer + * has been shut down. At this point, after the shutdown, we can be sure + * that no new events will be queued. The call to ssam_cplt_destroy will + * ensure that those remaining are being completed and freed. + */ + + /* Actually free resources. */ + ssam_cplt_destroy(&ctrl->cplt); + ssh_rtl_destroy(&ctrl->rtl); + + /* + * Set state via write_once even though we expect to be locked/in an + * exclusive context, due to smoke-testing in + * ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); +} + +/** + * ssam_controller_suspend() - Suspend the controller. + * @ctrl: The controller to suspend. + * + * Marks the controller as suspended. Note that display-off and D0-exit + * notifications have to be sent manually before transitioning the controller + * into the suspended state via this function. + * + * See ssam_controller_resume() for the corresponding resume function. + * + * Return: Returns %-EINVAL if the controller is currently not in the + * "started" state. + */ +int ssam_controller_suspend(struct ssam_controller *ctrl) +{ + ssam_controller_lock(ctrl); + + if (ctrl->state != SSAM_CONTROLLER_STARTED) { + ssam_controller_unlock(ctrl); + return -EINVAL; + } + + ssam_dbg(ctrl, "pm: suspending controller\n"); + + /* + * Set state via write_once even though we're locked, due to + * smoke-testing in ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED); + + ssam_controller_unlock(ctrl); + return 0; +} + +/** + * ssam_controller_resume() - Resume the controller from suspend. + * @ctrl: The controller to resume. + * + * Resume the controller from the suspended state it was put into via + * ssam_controller_suspend(). This function does not issue display-on and + * D0-entry notifications. If required, those have to be sent manually after + * this call. + * + * Return: Returns %-EINVAL if the controller is currently not suspended. + */ +int ssam_controller_resume(struct ssam_controller *ctrl) +{ + ssam_controller_lock(ctrl); + + if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) { + ssam_controller_unlock(ctrl); + return -EINVAL; + } + + ssam_dbg(ctrl, "pm: resuming controller\n"); + + /* + * Set state via write_once even though we're locked, due to + * smoke-testing in ssam_request_sync_submit(). + */ + WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); + + ssam_controller_unlock(ctrl); + return 0; +} + + +/* -- Top-level request interface ------------------------------------------- */ + +/** + * ssam_request_write_data() - Construct and write SAM request message to + * buffer. + * @buf: The buffer to write the data to. + * @ctrl: The controller via which the request will be sent. + * @spec: The request data and specification. + * + * Constructs a SAM/SSH request message and writes it to the provided buffer. + * The request and transport counters, specifically RQID and SEQ, will be set + * in this call. These counters are obtained from the controller. It is thus + * only valid to send the resulting message via the controller specified here. + * + * For calculation of the required buffer size, refer to the + * SSH_COMMAND_MESSAGE_LENGTH() macro. + * + * Return: Returns the number of bytes used in the buffer on success. Returns + * %-EINVAL if the payload length provided in the request specification is too + * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer + * is too small. + */ +ssize_t ssam_request_write_data(struct ssam_span *buf, + struct ssam_controller *ctrl, + const struct ssam_request *spec) +{ + struct msgbuf msgb; + u16 rqid; + u8 seq; + + if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE) + return -EINVAL; + + if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len) + return -EINVAL; + + msgb_init(&msgb, buf->ptr, buf->len); + seq = ssh_seq_next(&ctrl->counter.seq); + rqid = ssh_rqid_next(&ctrl->counter.rqid); + msgb_push_cmd(&msgb, seq, rqid, spec); + + return msgb_bytes_used(&msgb); +} +EXPORT_SYMBOL_GPL(ssam_request_write_data); + +static void ssam_request_sync_complete(struct ssh_request *rqst, + const struct ssh_command *cmd, + const struct ssam_span *data, int status) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + struct ssam_request_sync *r; + + r = container_of(rqst, struct ssam_request_sync, base); + r->status = status; + + if (r->resp) + r->resp->length = 0; + + if (status) { + rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); + return; + } + + if (!data) /* Handle requests without a response. */ + return; + + if (!r->resp || !r->resp->pointer) { + if (data->len) + rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n"); + return; + } + + if (data->len > r->resp->capacity) { + rtl_err(rtl, + "rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n", + r->resp->capacity, data->len); + r->status = -ENOSPC; + return; + } + + r->resp->length = data->len; + memcpy(r->resp->pointer, data->ptr, data->len); +} + +static void ssam_request_sync_release(struct ssh_request *rqst) +{ + complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp); +} + +static const struct ssh_request_ops ssam_request_sync_ops = { + .release = ssam_request_sync_release, + .complete = ssam_request_sync_complete, +}; + +/** + * ssam_request_sync_alloc() - Allocate a synchronous request. + * @payload_len: The length of the request payload. + * @flags: Flags used for allocation. + * @rqst: Where to store the pointer to the allocated request. + * @buffer: Where to store the buffer descriptor for the message buffer of + * the request. + * + * Allocates a synchronous request with corresponding message buffer. The + * request still needs to be initialized ssam_request_sync_init() before + * it can be submitted, and the message buffer data must still be set to the + * returned buffer via ssam_request_sync_set_data() after it has been filled, + * if need be with adjusted message length. + * + * After use, the request and its corresponding message buffer should be freed + * via ssam_request_sync_free(). The buffer must not be freed separately. + * + * Return: Returns zero on success, %-ENOMEM if the request could not be + * allocated. + */ +int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, + struct ssam_request_sync **rqst, + struct ssam_span *buffer) +{ + size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); + + *rqst = kzalloc(sizeof(**rqst) + msglen, flags); + if (!*rqst) + return -ENOMEM; + + buffer->ptr = (u8 *)(*rqst + 1); + buffer->len = msglen; + + return 0; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_alloc); + +/** + * ssam_request_sync_free() - Free a synchronous request. + * @rqst: The request to be freed. + * + * Free a synchronous request and its corresponding buffer allocated with + * ssam_request_sync_alloc(). Do not use for requests allocated on the stack + * or via any other function. + * + * Warning: The caller must ensure that the request is not in use any more. + * I.e. the caller must ensure that it has the only reference to the request + * and the request is not currently pending. This means that the caller has + * either never submitted the request, request submission has failed, or the + * caller has waited until the submitted request has been completed via + * ssam_request_sync_wait(). + */ +void ssam_request_sync_free(struct ssam_request_sync *rqst) +{ + kfree(rqst); +} +EXPORT_SYMBOL_GPL(ssam_request_sync_free); + +/** + * ssam_request_sync_init() - Initialize a synchronous request struct. + * @rqst: The request to initialize. + * @flags: The request flags. + * + * Initializes the given request struct. Does not initialize the request + * message data. This has to be done explicitly after this call via + * ssam_request_sync_set_data() and the actual message data has to be written + * via ssam_request_write_data(). + * + * Return: Returns zero on success or %-EINVAL if the given flags are invalid. + */ +int ssam_request_sync_init(struct ssam_request_sync *rqst, + enum ssam_request_flags flags) +{ + int status; + + status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops); + if (status) + return status; + + init_completion(&rqst->comp); + rqst->resp = NULL; + rqst->status = 0; + + return 0; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_init); + +/** + * ssam_request_sync_submit() - Submit a synchronous request. + * @ctrl: The controller with which to submit the request. + * @rqst: The request to submit. + * + * Submit a synchronous request. The request has to be initialized and + * properly set up, including response buffer (may be %NULL if no response is + * expected) and command message data. This function does not wait for the + * request to be completed. + * + * If this function succeeds, ssam_request_sync_wait() must be used to ensure + * that the request has been completed before the response data can be + * accessed and/or the request can be freed. On failure, the request may + * immediately be freed. + * + * This function may only be used if the controller is active, i.e. has been + * initialized and not suspended. + */ +int ssam_request_sync_submit(struct ssam_controller *ctrl, + struct ssam_request_sync *rqst) +{ + int status; + + /* + * This is only a superficial check. In general, the caller needs to + * ensure that the controller is initialized and is not (and does not + * get) suspended during use, i.e. until the request has been completed + * (if _absolutely_ necessary, by use of ssam_controller_statelock/ + * ssam_controller_stateunlock, but something like ssam_client_link + * should be preferred as this needs to last until the request has been + * completed). + * + * Note that it is actually safe to use this function while the + * controller is in the process of being shut down (as ssh_rtl_submit + * is safe with regards to this), but it is generally discouraged to do + * so. + */ + if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) { + ssh_request_put(&rqst->base); + return -ENODEV; + } + + status = ssh_rtl_submit(&ctrl->rtl, &rqst->base); + ssh_request_put(&rqst->base); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_submit); + +/** + * ssam_request_sync() - Execute a synchronous request. + * @ctrl: The controller via which the request will be submitted. + * @spec: The request specification and payload. + * @rsp: The response buffer. + * + * Allocates a synchronous request with its message data buffer on the heap + * via ssam_request_sync_alloc(), fully initializes it via the provided + * request specification, submits it, and finally waits for its completion + * before freeing it and returning its status. + * + * Return: Returns the status of the request or any failure during setup. + */ +int ssam_request_sync(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp) +{ + struct ssam_request_sync *rqst; + struct ssam_span buf; + ssize_t len; + int status; + + status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf); + if (status) + return status; + + status = ssam_request_sync_init(rqst, spec->flags); + if (status) + return status; + + ssam_request_sync_set_resp(rqst, rsp); + + len = ssam_request_write_data(&buf, ctrl, spec); + if (len < 0) { + ssam_request_sync_free(rqst); + return len; + } + + ssam_request_sync_set_data(rqst, buf.ptr, len); + + status = ssam_request_sync_submit(ctrl, rqst); + if (!status) + status = ssam_request_sync_wait(rqst); + + ssam_request_sync_free(rqst); + return status; +} +EXPORT_SYMBOL_GPL(ssam_request_sync); + +/** + * ssam_request_sync_with_buffer() - Execute a synchronous request with the + * provided buffer as back-end for the message buffer. + * @ctrl: The controller via which the request will be submitted. + * @spec: The request specification and payload. + * @rsp: The response buffer. + * @buf: The buffer for the request message data. + * + * Allocates a synchronous request struct on the stack, fully initializes it + * using the provided buffer as message data buffer, submits it, and then + * waits for its completion before returning its status. The + * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required + * message buffer size. + * + * This function does essentially the same as ssam_request_sync(), but instead + * of dynamically allocating the request and message data buffer, it uses the + * provided message data buffer and stores the (small) request struct on the + * heap. + * + * Return: Returns the status of the request or any failure during setup. + */ +int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, + const struct ssam_request *spec, + struct ssam_response *rsp, + struct ssam_span *buf) +{ + struct ssam_request_sync rqst; + ssize_t len; + int status; + + status = ssam_request_sync_init(&rqst, spec->flags); + if (status) + return status; + + ssam_request_sync_set_resp(&rqst, rsp); + + len = ssam_request_write_data(buf, ctrl, spec); + if (len < 0) + return len; + + ssam_request_sync_set_data(&rqst, buf->ptr, len); + + status = ssam_request_sync_submit(ctrl, &rqst); + if (!status) + status = ssam_request_sync_wait(&rqst); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); + + +/* -- Internal SAM requests. ------------------------------------------------ */ + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x13, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x15, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x16, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x33, + .instance_id = 0x00, +}); + +static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { + .target_category = SSAM_SSH_TC_SAM, + .target_id = 0x01, + .command_id = 0x34, + .instance_id = 0x00, +}); + +/** + * struct ssh_notification_params - Command payload to enable/disable SSH + * notifications. + * @target_category: The target category for which notifications should be + * enabled/disabled. + * @flags: Flags determining how notifications are being sent. + * @request_id: The request ID that is used to send these notifications. + * @instance_id: The specific instance in the given target category for + * which notifications should be enabled. + */ +struct ssh_notification_params { + u8 target_category; + u8 flags; + __le16 request_id; + u8 instance_id; +} __packed; + +static_assert(sizeof(struct ssh_notification_params) == 5); + +static int __ssam_ssh_event_request(struct ssam_controller *ctrl, + struct ssam_event_registry reg, u8 cid, + struct ssam_event_id id, u8 flags) +{ + struct ssh_notification_params params; + struct ssam_request rqst; + struct ssam_response result; + int status; + + u16 rqid = ssh_tc_to_rqid(id.target_category); + u8 buf = 0; + + /* Only allow RQIDs that lie within the event spectrum. */ + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + params.target_category = id.target_category; + params.instance_id = id.instance; + params.flags = flags; + put_unaligned_le16(rqid, ¶ms.request_id); + + rqst.target_category = reg.target_category; + rqst.target_id = reg.target_id; + rqst.command_id = cid; + rqst.instance_id = 0x00; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = sizeof(params); + rqst.payload = (u8 *)¶ms; + + result.capacity = sizeof(buf); + result.length = 0; + result.pointer = &buf; + + status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result, + sizeof(params)); + + return status < 0 ? status : buf; +} + +/** + * ssam_ssh_event_enable() - Enable SSH event. + * @ctrl: The controller for which to enable the event. + * @reg: The event registry describing what request to use for enabling and + * disabling the event. + * @id: The event identifier. + * @flags: The event flags. + * + * Enables the specified event on the EC. This function does not manage + * reference counting of enabled events and is basically only a wrapper for + * the raw EC request. If the specified event is already enabled, the EC will + * ignore this request. + * + * Return: Returns the status of the executed SAM request (zero on success and + * negative on direct failure) or %-EPROTO if the request response indicates a + * failure. + */ +static int ssam_ssh_event_enable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags) +{ + int status; + + status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); + + if (status < 0 && status != -EINVAL) { + ssam_err(ctrl, + "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", + id.target_category, id.instance, reg.target_category); + } + + if (status > 0) { + ssam_err(ctrl, + "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", + status, id.target_category, id.instance, reg.target_category); + return -EPROTO; + } + + return status; +} + +/** + * ssam_ssh_event_disable() - Disable SSH event. + * @ctrl: The controller for which to disable the event. + * @reg: The event registry describing what request to use for enabling and + * disabling the event (must be same as used when enabling the event). + * @id: The event identifier. + * @flags: The event flags (likely ignored for disabling of events). + * + * Disables the specified event on the EC. This function does not manage + * reference counting of enabled events and is basically only a wrapper for + * the raw EC request. If the specified event is already disabled, the EC will + * ignore this request. + * + * Return: Returns the status of the executed SAM request (zero on success and + * negative on direct failure) or %-EPROTO if the request response indicates a + * failure. + */ +static int ssam_ssh_event_disable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags) +{ + int status; + + status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); + + if (status < 0 && status != -EINVAL) { + ssam_err(ctrl, + "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", + id.target_category, id.instance, reg.target_category); + } + + if (status > 0) { + ssam_err(ctrl, + "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", + status, id.target_category, id.instance, reg.target_category); + return -EPROTO; + } + + return status; +} + + +/* -- Wrappers for internal SAM requests. ----------------------------------- */ + +/** + * ssam_get_firmware_version() - Get the SAM/EC firmware version. + * @ctrl: The controller. + * @version: Where to store the version number. + * + * Return: Returns zero on success or the status of the executed SAM request + * if that request failed. + */ +int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version) +{ + __le32 __version; + int status; + + status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version); + if (status) + return status; + + *version = le32_to_cpu(__version); + return 0; +} + +/** + * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned + * off. + * @ctrl: The controller. + * + * Notify the EC that the display has been turned off and the driver may enter + * a lower-power state. This will prevent events from being sent directly. + * Rather, the EC signals an event by pulling the wakeup GPIO high for as long + * as there are pending events. The events then need to be manually released, + * one by one, via the GPIO callback request. All pending events accumulated + * during this state can also be released by issuing the display-on + * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset + * the GPIO. + * + * On some devices, specifically ones with an integrated keyboard, the keyboard + * backlight will be turned off by this call. + * + * This function will only send the display-off notification command if + * display notifications are supported by the EC. Currently all known devices + * support these notifications. + * + * Use ssam_ctrl_notif_display_on() to reverse the effects of this function. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + ssam_dbg(ctrl, "pm: notifying display off\n"); + + status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + +/** + * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on. + * @ctrl: The controller. + * + * Notify the EC that the display has been turned back on and the driver has + * exited its lower-power state. This notification is the counterpart to the + * display-off notification sent via ssam_ctrl_notif_display_off() and will + * reverse its effects, including resetting events to their default behavior. + * + * This function will only send the display-on notification command if display + * notifications are supported by the EC. Currently all known devices support + * these notifications. + * + * See ssam_ctrl_notif_display_off() for more details. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + ssam_dbg(ctrl, "pm: notifying display on\n"); + + status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + +/** + * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0 + * power state. + * @ctrl: The controller + * + * Notifies the EC that the driver prepares to exit the D0 power state in + * favor of a lower-power state. Exact effects of this function related to the + * EC are currently unknown. + * + * This function will only send the D0-exit notification command if D0-state + * notifications are supported by the EC. Only newer Surface generations + * support these notifications. + * + * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + if (!ctrl->caps.d3_closes_handle) + return 0; + + ssam_dbg(ctrl, "pm: notifying D0 exit\n"); + + status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + +/** + * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0 + * power state. + * @ctrl: The controller + * + * Notifies the EC that the driver has exited a lower-power state and entered + * the D0 power state. Exact effects of this function related to the EC are + * currently unknown. + * + * This function will only send the D0-entry notification command if D0-state + * notifications are supported by the EC. Only newer Surface generations + * support these notifications. + * + * See ssam_ctrl_notif_d0_exit() for more details. + * + * Return: Returns zero on success or if no request has been executed, the + * status of the executed SAM request if that request failed, or %-EPROTO if + * an unexpected response has been received. + */ +int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) +{ + int status; + u8 response; + + if (!ctrl->caps.d3_closes_handle) + return 0; + + ssam_dbg(ctrl, "pm: notifying D0 entry\n"); + + status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response); + if (status) + return status; + + if (response != 0) { + ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n", + response); + return -EPROTO; + } + + return 0; +} + + +/* -- Top-level event registry interface. ----------------------------------- */ + +/** + * ssam_notifier_register() - Register an event notifier. + * @ctrl: The controller to register the notifier on. + * @n: The event notifier to register. + * + * Register an event notifier and increment the usage counter of the + * associated SAM event. If the event was previously not enabled, it will be + * enabled during this call. + * + * Return: Returns zero on success, %-ENOSPC if there have already been + * %INT_MAX notifiers for the event ID/type associated with the notifier block + * registered, %-ENOMEM if the corresponding event entry could not be + * allocated. If this is the first time that a notifier block is registered + * for the specific associated event, returns the status of the event-enable + * EC-command. + */ +int ssam_notifier_register(struct ssam_controller *ctrl, + struct ssam_event_notifier *n) +{ + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_head *nf_head; + struct ssam_nf *nf; + int status; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + nf = &ctrl->cplt.event.notif; + nf_head = &nf->head[ssh_rqid_to_event(rqid)]; + + mutex_lock(&nf->lock); + + entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); + if (IS_ERR(entry)) { + mutex_unlock(&nf->lock); + return PTR_ERR(entry); + } + + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + n->event.reg.target_category, n->event.id.target_category, + n->event.id.instance, entry->refcount); + + status = ssam_nfblk_insert(nf_head, &n->base); + if (status) { + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (entry->refcount == 0) + kfree(entry); + + mutex_unlock(&nf->lock); + return status; + } + + if (entry->refcount == 1) { + status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, + n->event.flags); + if (status) { + ssam_nfblk_remove(&n->base); + kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); + mutex_unlock(&nf->lock); + synchronize_srcu(&nf_head->srcu); + return status; + } + + entry->flags = n->event.flags; + + } else if (entry->flags != n->event.flags) { + ssam_warn(ctrl, + "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + n->event.flags, entry->flags, n->event.reg.target_category, + n->event.id.target_category, n->event.id.instance); + } + + mutex_unlock(&nf->lock); + return 0; +} +EXPORT_SYMBOL_GPL(ssam_notifier_register); + +/** + * ssam_notifier_unregister() - Unregister an event notifier. + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * + * Unregister an event notifier and decrement the usage counter of the + * associated SAM event. If the usage counter reaches zero, the event will be + * disabled. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n) +{ + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; + struct ssam_nf_head *nf_head; + struct ssam_nf *nf; + int status = 0; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + nf = &ctrl->cplt.event.notif; + nf_head = &nf->head[ssh_rqid_to_event(rqid)]; + + mutex_lock(&nf->lock); + + if (!ssam_nfblk_find(nf_head, &n->base)) { + mutex_unlock(&nf->lock); + return -ENOENT; + } + + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (WARN_ON(!entry)) { + /* + * If this does not return an entry, there's a logic error + * somewhere: The notifier block is registered, but the event + * refcount entry is not there. Remove the notifier block + * anyways. + */ + status = -ENOENT; + goto remove; + } + + ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + n->event.reg.target_category, n->event.id.target_category, + n->event.id.instance, entry->refcount); + + if (entry->flags != n->event.flags) { + ssam_warn(ctrl, + "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + n->event.flags, entry->flags, n->event.reg.target_category, + n->event.id.target_category, n->event.id.instance); + } + + if (entry->refcount == 0) { + status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, + n->event.flags); + kfree(entry); + } + +remove: + ssam_nfblk_remove(&n->base); + mutex_unlock(&nf->lock); + synchronize_srcu(&nf_head->srcu); + + return status; +} +EXPORT_SYMBOL_GPL(ssam_notifier_unregister); + +/** + * ssam_notifier_disable_registered() - Disable events for all registered + * notifiers. + * @ctrl: The controller for which to disable the notifiers/events. + * + * Disables events for all currently registered notifiers. In case of an error + * (EC command failing), all previously disabled events will be restored and + * the error code returned. + * + * This function is intended to disable all events prior to hibernation entry. + * See ssam_notifier_restore_registered() to restore/re-enable all events + * disabled with this function. + * + * Note that this function will not disable events for notifiers registered + * after calling this function. It should thus be made sure that no new + * notifiers are going to be added after this call and before the corresponding + * call to ssam_notifier_restore_registered(). + * + * Return: Returns zero on success. In case of failure returns the error code + * returned by the failed EC command to disable an event. + */ +int ssam_notifier_disable_registered(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct rb_node *n; + int status; + + mutex_lock(&nf->lock); + for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { + struct ssam_nf_refcount_entry *e; + + e = rb_entry(n, struct ssam_nf_refcount_entry, node); + status = ssam_ssh_event_disable(ctrl, e->key.reg, + e->key.id, e->flags); + if (status) + goto err; + } + mutex_unlock(&nf->lock); + + return 0; + +err: + for (n = rb_prev(n); n; n = rb_prev(n)) { + struct ssam_nf_refcount_entry *e; + + e = rb_entry(n, struct ssam_nf_refcount_entry, node); + ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); + } + mutex_unlock(&nf->lock); + + return status; +} + +/** + * ssam_notifier_restore_registered() - Restore/re-enable events for all + * registered notifiers. + * @ctrl: The controller for which to restore the notifiers/events. + * + * Restores/re-enables all events for which notifiers have been registered on + * the given controller. In case of a failure, the error is logged and the + * function continues to try and enable the remaining events. + * + * This function is intended to restore/re-enable all registered events after + * hibernation. See ssam_notifier_disable_registered() for the counter part + * disabling the events and more details. + */ +void ssam_notifier_restore_registered(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct rb_node *n; + + mutex_lock(&nf->lock); + for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { + struct ssam_nf_refcount_entry *e; + + e = rb_entry(n, struct ssam_nf_refcount_entry, node); + + /* Ignore errors, will get logged in call. */ + ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); + } + mutex_unlock(&nf->lock); +} + +/** + * ssam_notifier_is_empty() - Check if there are any registered notifiers. + * @ctrl: The controller to check on. + * + * Return: Returns %true if there are currently no notifiers registered on the + * controller, %false otherwise. + */ +static bool ssam_notifier_is_empty(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + bool result; + + mutex_lock(&nf->lock); + result = ssam_nf_refcount_empty(nf); + mutex_unlock(&nf->lock); + + return result; +} + +/** + * ssam_notifier_unregister_all() - Unregister all currently registered + * notifiers. + * @ctrl: The controller to unregister the notifiers on. + * + * Unregisters all currently registered notifiers. This function is used to + * ensure that all notifiers will be unregistered and associated + * entries/resources freed when the controller is being shut down. + */ +static void ssam_notifier_unregister_all(struct ssam_controller *ctrl) +{ + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct ssam_nf_refcount_entry *e, *n; + + mutex_lock(&nf->lock); + rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) { + /* Ignore errors, will get logged in call. */ + ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags); + kfree(e); + } + nf->refcount = RB_ROOT; + mutex_unlock(&nf->lock); +} + + +/* -- Wakeup IRQ. ----------------------------------------------------------- */ + +static irqreturn_t ssam_irq_handle(int irq, void *dev_id) +{ + struct ssam_controller *ctrl = dev_id; + + ssam_dbg(ctrl, "pm: wake irq triggered\n"); + + /* + * Note: Proper wakeup detection is currently unimplemented. + * When the EC is in display-off or any other non-D0 state, it + * does not send events/notifications to the host. Instead it + * signals that there are events available via the wakeup IRQ. + * This driver is responsible for calling back to the EC to + * release these events one-by-one. + * + * This IRQ should not cause a full system resume by its own. + * Instead, events should be handled by their respective subsystem + * drivers, which in turn should signal whether a full system + * resume should be performed. + * + * TODO: Send GPIO callback command repeatedly to EC until callback + * returns 0x00. Return flag of callback is "has more events". + * Each time the command is sent, one event is "released". Once + * all events have been released (return = 0x00), the GPIO is + * re-armed. Detect wakeup events during this process, go back to + * sleep if no wakeup event has been received. + */ + + return IRQ_HANDLED; +} + +/** + * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt. + * @ctrl: The controller for which the IRQ should be set up. + * + * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used + * to wake the device from a low power state. + * + * Note that this IRQ can only be triggered while the EC is in the display-off + * state. In this state, events are not sent to the host in the usual way. + * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending + * events and these events need to be released one-by-one via the GPIO + * callback request, either until there are no events left and the GPIO is + * reset, or all at once by transitioning the EC out of the display-off state, + * which will also clear the GPIO. + * + * Not all events, however, should trigger a full system wakeup. Instead the + * driver should, if necessary, inspect and forward each event to the + * corresponding subsystem, which in turn should decide if the system needs to + * be woken up. This logic has not been implemented yet, thus wakeup by this + * IRQ should be disabled by default to avoid spurious wake-ups, caused, for + * example, by the remaining battery percentage changing. Refer to comments in + * this function and comments in the corresponding IRQ handler for more + * details on how this should be implemented. + * + * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off() + * for functions to transition the EC into and out of the display-off state as + * well as more details on it. + * + * The IRQ is disabled by default and has to be enabled before it can wake up + * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ + * should be freed via ssam_irq_free(). + */ +int ssam_irq_setup(struct ssam_controller *ctrl) +{ + struct device *dev = ssam_controller_device(ctrl); + struct gpio_desc *gpiod; + int irq; + int status; + + /* + * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH. + * However, the GPIO line only gets reset by sending the GPIO callback + * command to SAM (or alternatively the display-on notification). As + * proper handling for this interrupt is not implemented yet, leaving + * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback + * never gets sent and thus the line never gets reset). To avoid this, + * mark the IRQ as TRIGGER_RISING for now, only creating a single + * interrupt, and let the SAM resume callback during the controller + * resume process clear it. + */ + const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; + + gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + + irq = gpiod_to_irq(gpiod); + gpiod_put(gpiod); + + if (irq < 0) + return irq; + + status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, + "ssam_wakeup", ctrl); + if (status) + return status; + + ctrl->irq.num = irq; + disable_irq(ctrl->irq.num); + return 0; +} + +/** + * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt. + * @ctrl: The controller for which the IRQ should be freed. + * + * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup(). + */ +void ssam_irq_free(struct ssam_controller *ctrl) +{ + free_irq(ctrl->irq.num, ctrl); + ctrl->irq.num = -1; +} + +/** + * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled. + * @ctrl: The controller for which the IRQ should be armed. + * + * Sets up the IRQ so that it can be used to wake the device. Specifically, + * this function enables the irq and then, if the device is allowed to wake up + * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the + * corresponding function to disable the IRQ. + * + * This function is intended to arm the IRQ before entering S2idle suspend. + * + * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must + * be balanced. + */ +int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl) +{ + struct device *dev = ssam_controller_device(ctrl); + int status; + + enable_irq(ctrl->irq.num); + if (device_may_wakeup(dev)) { + status = enable_irq_wake(ctrl->irq.num); + if (status) { + ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status); + disable_irq(ctrl->irq.num); + return status; + } + + ctrl->irq.wakeup_enabled = true; + } else { + ctrl->irq.wakeup_enabled = false; + } + + return 0; +} + +/** + * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ. + * @ctrl: The controller for which the IRQ should be disarmed. + * + * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup(). + * + * This function is intended to disarm the IRQ after exiting S2idle suspend. + * + * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must + * be balanced. + */ +void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl) +{ + int status; + + if (ctrl->irq.wakeup_enabled) { + status = disable_irq_wake(ctrl->irq.num); + if (status) + ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status); + + ctrl->irq.wakeup_enabled = false; + } + disable_irq(ctrl->irq.num); +} diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h new file mode 100644 index 000000000000..5ee9e966f1d7 --- /dev/null +++ b/drivers/platform/surface/aggregator/controller.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Main SSAM/SSH controller structure and functionality. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H +#define _SURFACE_AGGREGATOR_CONTROLLER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ssh_request_layer.h" + + +/* -- Safe counters. -------------------------------------------------------- */ + +/** + * struct ssh_seq_counter - Safe counter for SSH sequence IDs. + * @value: The current counter value. + */ +struct ssh_seq_counter { + u8 value; +}; + +/** + * struct ssh_rqid_counter - Safe counter for SSH request IDs. + * @value: The current counter value. + */ +struct ssh_rqid_counter { + u16 value; +}; + + +/* -- Event/notification system. -------------------------------------------- */ + +/** + * struct ssam_nf_head - Notifier head for SSAM events. + * @srcu: The SRCU struct for synchronization. + * @head: List-head for notifier blocks registered under this head. + */ +struct ssam_nf_head { + struct srcu_struct srcu; + struct list_head head; +}; + +/** + * struct ssam_nf - Notifier callback- and activation-registry for SSAM events. + * @lock: Lock guarding (de-)registration of notifier blocks. Note: This + * lock does not need to be held for notifier calls, only + * registration and deregistration. + * @refcount: The root of the RB-tree used for reference-counting enabled + * events/notifications. + * @head: The list of notifier heads for event/notification callbacks. + */ +struct ssam_nf { + struct mutex lock; + struct rb_root refcount; + struct ssam_nf_head head[SSH_NUM_EVENTS]; +}; + + +/* -- Event/async request completion system. -------------------------------- */ + +struct ssam_cplt; + +/** + * struct ssam_event_item - Struct for event queuing and completion. + * @node: The node in the queue. + * @rqid: The request ID of the event. + * @event: Actual event data. + */ +struct ssam_event_item { + struct list_head node; + u16 rqid; + + struct ssam_event event; /* must be last */ +}; + +/** + * struct ssam_event_queue - Queue for completing received events. + * @cplt: Reference to the completion system on which this queue is active. + * @lock: The lock for any operation on the queue. + * @head: The list-head of the queue. + * @work: The &struct work_struct performing completion work for this queue. + */ +struct ssam_event_queue { + struct ssam_cplt *cplt; + + spinlock_t lock; + struct list_head head; + struct work_struct work; +}; + +/** + * struct ssam_event_target - Set of queues for a single SSH target ID. + * @queue: The array of queues, one queue per event ID. + */ +struct ssam_event_target { + struct ssam_event_queue queue[SSH_NUM_EVENTS]; +}; + +/** + * struct ssam_cplt - SSAM event/async request completion system. + * @dev: The device with which this system is associated. Only used + * for logging. + * @wq: The &struct workqueue_struct on which all completion work + * items are queued. + * @event: Event completion management. + * @event.target: Array of &struct ssam_event_target, one for each target. + * @event.notif: Notifier callbacks and event activation reference counting. + */ +struct ssam_cplt { + struct device *dev; + struct workqueue_struct *wq; + + struct { + struct ssam_event_target target[SSH_NUM_TARGETS]; + struct ssam_nf notif; + } event; +}; + + +/* -- Main SSAM device structures. ------------------------------------------ */ + +/** + * enum ssam_controller_state - State values for &struct ssam_controller. + * @SSAM_CONTROLLER_UNINITIALIZED: + * The controller has not been initialized yet or has been deinitialized. + * @SSAM_CONTROLLER_INITIALIZED: + * The controller is initialized, but has not been started yet. + * @SSAM_CONTROLLER_STARTED: + * The controller has been started and is ready to use. + * @SSAM_CONTROLLER_STOPPED: + * The controller has been stopped. + * @SSAM_CONTROLLER_SUSPENDED: + * The controller has been suspended. + */ +enum ssam_controller_state { + SSAM_CONTROLLER_UNINITIALIZED, + SSAM_CONTROLLER_INITIALIZED, + SSAM_CONTROLLER_STARTED, + SSAM_CONTROLLER_STOPPED, + SSAM_CONTROLLER_SUSPENDED, +}; + +/** + * struct ssam_controller_caps - Controller device capabilities. + * @ssh_power_profile: SSH power profile. + * @ssh_buffer_size: SSH driver UART buffer size. + * @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout. + * @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout. + * @d3_closes_handle: SAM closes UART handle in D3. + * + * Controller and SSH device capabilities found in ACPI. + */ +struct ssam_controller_caps { + u32 ssh_power_profile; + u32 ssh_buffer_size; + u32 screen_on_sleep_idle_timeout; + u32 screen_off_sleep_idle_timeout; + u32 d3_closes_handle:1; +}; + +/** + * struct ssam_controller - SSAM controller device. + * @kref: Reference count of the controller. + * @lock: Main lock for the controller, used to guard state changes. + * @state: Controller state. + * @rtl: Request transport layer for SSH I/O. + * @cplt: Completion system for SSH/SSAM events and asynchronous requests. + * @counter: Safe SSH message ID counters. + * @counter.seq: Sequence ID counter. + * @counter.rqid: Request ID counter. + * @irq: Wakeup IRQ resources. + * @irq.num: The wakeup IRQ number. + * @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend. + * @caps: The controller device capabilities. + */ +struct ssam_controller { + struct kref kref; + + struct rw_semaphore lock; + enum ssam_controller_state state; + + struct ssh_rtl rtl; + struct ssam_cplt cplt; + + struct { + struct ssh_seq_counter seq; + struct ssh_rqid_counter rqid; + } counter; + + struct { + int num; + bool wakeup_enabled; + } irq; + + struct ssam_controller_caps caps; +}; + +#define to_ssam_controller(ptr, member) \ + container_of(ptr, struct ssam_controller, member) + +#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) +#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) +#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) +#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) + +/** + * ssam_controller_receive_buf() - Provide input-data to the controller. + * @ctrl: The controller. + * @buf: The input buffer. + * @n: The number of bytes in the input buffer. + * + * Provide input data to be evaluated by the controller, which has been + * received via the lower-level transport. + * + * Return: Returns the number of bytes consumed, or, if the packet transport + * layer of the controller has been shut down, %-ESHUTDOWN. + */ +static inline +int ssam_controller_receive_buf(struct ssam_controller *ctrl, + const unsigned char *buf, size_t n) +{ + return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); +} + +/** + * ssam_controller_write_wakeup() - Notify the controller that the underlying + * device has space available for data to be written. + * @ctrl: The controller. + */ +static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) +{ + ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl); +} + +int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s); +int ssam_controller_start(struct ssam_controller *ctrl); +void ssam_controller_shutdown(struct ssam_controller *ctrl); +void ssam_controller_destroy(struct ssam_controller *ctrl); + +int ssam_notifier_disable_registered(struct ssam_controller *ctrl); +void ssam_notifier_restore_registered(struct ssam_controller *ctrl); + +int ssam_irq_setup(struct ssam_controller *ctrl); +void ssam_irq_free(struct ssam_controller *ctrl); +int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl); +void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl); + +void ssam_controller_lock(struct ssam_controller *c); +void ssam_controller_unlock(struct ssam_controller *c); + +int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version); +int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl); +int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl); +int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl); +int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); + +int ssam_controller_suspend(struct ssam_controller *ctrl); +int ssam_controller_resume(struct ssam_controller *ctrl); + +#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c new file mode 100644 index 000000000000..18e0e9e34e7b --- /dev/null +++ b/drivers/platform/surface/aggregator/core.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Serial Hub (SSH) driver for communication with the Surface/System + * Aggregator Module (SSAM/SAM). + * + * Provides access to a SAM-over-SSH connected EC via a controller device. + * Handles communication via requests as well as enabling, disabling, and + * relaying of events. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "controller.h" + + +/* -- Static controller reference. ------------------------------------------ */ + +/* + * Main controller reference. The corresponding lock must be held while + * accessing (reading/writing) the reference. + */ +static struct ssam_controller *__ssam_controller; +static DEFINE_SPINLOCK(__ssam_controller_lock); + +/** + * ssam_get_controller() - Get reference to SSAM controller. + * + * Returns a reference to the SSAM controller of the system or %NULL if there + * is none, it hasn't been set up yet, or it has already been unregistered. + * This function automatically increments the reference count of the + * controller, thus the calling party must ensure that ssam_controller_put() + * is called when it doesn't need the controller any more. + */ +struct ssam_controller *ssam_get_controller(void) +{ + struct ssam_controller *ctrl; + + spin_lock(&__ssam_controller_lock); + + ctrl = __ssam_controller; + if (!ctrl) + goto out; + + if (WARN_ON(!kref_get_unless_zero(&ctrl->kref))) + ctrl = NULL; + +out: + spin_unlock(&__ssam_controller_lock); + return ctrl; +} +EXPORT_SYMBOL_GPL(ssam_get_controller); + +/** + * ssam_try_set_controller() - Try to set the main controller reference. + * @ctrl: The controller to which the reference should point. + * + * Set the main controller reference to the given pointer if the reference + * hasn't been set already. + * + * Return: Returns zero on success or %-EEXIST if the reference has already + * been set. + */ +static int ssam_try_set_controller(struct ssam_controller *ctrl) +{ + int status = 0; + + spin_lock(&__ssam_controller_lock); + if (!__ssam_controller) + __ssam_controller = ctrl; + else + status = -EEXIST; + spin_unlock(&__ssam_controller_lock); + + return status; +} + +/** + * ssam_clear_controller() - Remove/clear the main controller reference. + * + * Clears the main controller reference, i.e. sets it to %NULL. This function + * should be called before the controller is shut down. + */ +static void ssam_clear_controller(void) +{ + spin_lock(&__ssam_controller_lock); + __ssam_controller = NULL; + spin_unlock(&__ssam_controller_lock); +} + +/** + * ssam_client_link() - Link an arbitrary client device to the controller. + * @c: The controller to link to. + * @client: The client device. + * + * Link an arbitrary client device to the controller by creating a device link + * between it as consumer and the controller device as provider. This function + * can be used for non-SSAM devices (or SSAM devices not registered as child + * under the controller) to guarantee that the controller is valid for as long + * as the driver of the client device is bound, and that proper suspend and + * resume ordering is guaranteed. + * + * The device link does not have to be destructed manually. It is removed + * automatically once the driver of the client device unbinds. + * + * Return: Returns zero on success, %-ENODEV if the controller is not ready or + * going to be removed soon, or %-ENOMEM if the device link could not be + * created for other reasons. + */ +int ssam_client_link(struct ssam_controller *c, struct device *client) +{ + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; + struct device_link *link; + struct device *ctrldev; + + ssam_controller_statelock(c); + + if (c->state != SSAM_CONTROLLER_STARTED) { + ssam_controller_stateunlock(c); + return -ENODEV; + } + + ctrldev = ssam_controller_device(c); + if (!ctrldev) { + ssam_controller_stateunlock(c); + return -ENODEV; + } + + link = device_link_add(client, ctrldev, flags); + if (!link) { + ssam_controller_stateunlock(c); + return -ENOMEM; + } + + /* + * Return -ENODEV if supplier driver is on its way to be removed. In + * this case, the controller won't be around for much longer and the + * device link is not going to save us any more, as unbinding is + * already in progress. + */ + if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { + ssam_controller_stateunlock(c); + return -ENODEV; + } + + ssam_controller_stateunlock(c); + return 0; +} +EXPORT_SYMBOL_GPL(ssam_client_link); + +/** + * ssam_client_bind() - Bind an arbitrary client device to the controller. + * @client: The client device. + * + * Link an arbitrary client device to the controller by creating a device link + * between it as consumer and the main controller device as provider. This + * function can be used for non-SSAM devices to guarantee that the controller + * returned by this function is valid for as long as the driver of the client + * device is bound, and that proper suspend and resume ordering is guaranteed. + * + * This function does essentially the same as ssam_client_link(), except that + * it first fetches the main controller reference, then creates the link, and + * finally returns this reference. Note that this function does not increment + * the reference counter of the controller, as, due to the link, the + * controller lifetime is assured as long as the driver of the client device + * is bound. + * + * It is not valid to use the controller reference obtained by this method + * outside of the driver bound to the client device at the time of calling + * this function, without first incrementing the reference count of the + * controller via ssam_controller_get(). Even after doing this, care must be + * taken that requests are only submitted and notifiers are only + * (un-)registered when the controller is active and not suspended. In other + * words: The device link only lives as long as the client driver is bound and + * any guarantees enforced by this link (e.g. active controller state) can + * only be relied upon as long as this link exists and may need to be enforced + * in other ways afterwards. + * + * The created device link does not have to be destructed manually. It is + * removed automatically once the driver of the client device unbinds. + * + * Return: Returns the controller on success, an error pointer with %-ENODEV + * if the controller is not present, not ready or going to be removed soon, or + * %-ENOMEM if the device link could not be created for other reasons. + */ +struct ssam_controller *ssam_client_bind(struct device *client) +{ + struct ssam_controller *c; + int status; + + c = ssam_get_controller(); + if (!c) + return ERR_PTR(-ENODEV); + + status = ssam_client_link(c, client); + + /* + * Note that we can drop our controller reference in both success and + * failure cases: On success, we have bound the controller lifetime + * inherently to the client driver lifetime, i.e. it the controller is + * now guaranteed to outlive the client driver. On failure, we're not + * going to use the controller any more. + */ + ssam_controller_put(c); + + return status >= 0 ? c : ERR_PTR(status); +} +EXPORT_SYMBOL_GPL(ssam_client_bind); + + +/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ + +static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf, + size_t n) +{ + struct ssam_controller *ctrl; + + ctrl = serdev_device_get_drvdata(dev); + return ssam_controller_receive_buf(ctrl, buf, n); +} + +static void ssam_write_wakeup(struct serdev_device *dev) +{ + ssam_controller_write_wakeup(serdev_device_get_drvdata(dev)); +} + +static const struct serdev_device_ops ssam_serdev_ops = { + .receive_buf = ssam_receive_buf, + .write_wakeup = ssam_write_wakeup, +}; + + +/* -- SysFS and misc. ------------------------------------------------------- */ + +static int ssam_log_firmware_version(struct ssam_controller *ctrl) +{ + u32 version, a, b, c; + int status; + + status = ssam_get_firmware_version(ctrl, &version); + if (status) + return status; + + a = (version >> 24) & 0xff; + b = ((version >> 8) & 0xffff); + c = version & 0xff; + + ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c); + return 0; +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ssam_controller *ctrl = dev_get_drvdata(dev); + u32 version, a, b, c; + int status; + + status = ssam_get_firmware_version(ctrl, &version); + if (status < 0) + return status; + + a = (version >> 24) & 0xff; + b = ((version >> 8) & 0xffff); + c = version & 0xff; + + return sysfs_emit(buf, "%u.%u.%u\n", a, b, c); +} +static DEVICE_ATTR_RO(firmware_version); + +static struct attribute *ssam_sam_attrs[] = { + &dev_attr_firmware_version.attr, + NULL +}; + +static const struct attribute_group ssam_sam_group = { + .name = "sam", + .attrs = ssam_sam_attrs, +}; + + +/* -- ACPI based device setup. ---------------------------------------------- */ + +static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, + void *ctx) +{ + struct serdev_device *serdev = ctx; + struct acpi_resource_common_serialbus *serial; + struct acpi_resource_uart_serialbus *uart; + bool flow_control; + int status = 0; + + if (rsc->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) + return AE_OK; + + serial = &rsc->data.common_serial_bus; + if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) + return AE_OK; + + uart = &rsc->data.uart_serial_bus; + + /* Set up serdev device. */ + serdev_device_set_baudrate(serdev, uart->default_baud_rate); + + /* serdev currently only supports RTSCTS flow control. */ + if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) { + dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n", + uart->flow_control); + } + + /* Set RTSCTS flow control. */ + flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW; + serdev_device_set_flow_control(serdev, flow_control); + + /* serdev currently only supports EVEN/ODD parity. */ + switch (uart->parity) { + case ACPI_UART_PARITY_NONE: + status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); + break; + case ACPI_UART_PARITY_EVEN: + status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); + break; + case ACPI_UART_PARITY_ODD: + status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); + break; + default: + dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n", + uart->parity); + break; + } + + if (status) { + dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n", + uart->parity, status); + return AE_ERROR; + } + + /* We've found the resource and are done. */ + return AE_CTRL_TERMINATE; +} + +static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, + struct serdev_device *serdev) +{ + return acpi_walk_resources(handle, METHOD_NAME__CRS, + ssam_serdev_setup_via_acpi_crs, serdev); +} + + +/* -- Power management. ----------------------------------------------------- */ + +static void ssam_serial_hub_shutdown(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to disable notifiers, signal display-off and D0-exit, ignore any + * errors. + * + * Note: It has not been established yet if this is actually + * necessary/useful for shutdown. + */ + + status = ssam_notifier_disable_registered(c); + if (status) { + ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n", + status); + } + + status = ssam_ctrl_notif_display_off(c); + if (status) + ssam_err(c, "pm: display-off notification failed: %d\n", status); + + status = ssam_ctrl_notif_d0_exit(c); + if (status) + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); +} + +#ifdef CONFIG_PM_SLEEP + +static int ssam_serial_hub_pm_prepare(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to signal display-off, This will quiesce events. + * + * Note: Signaling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, + * signal it here. + */ + + status = ssam_ctrl_notif_display_off(c); + if (status) + ssam_err(c, "pm: display-off notification failed: %d\n", status); + + return status; +} + +static void ssam_serial_hub_pm_complete(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to signal display-on. This will restore events. + * + * Note: Signaling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, + * signal it here. + */ + + status = ssam_ctrl_notif_display_on(c); + if (status) + ssam_err(c, "pm: display-on notification failed: %d\n", status); +} + +static int ssam_serial_hub_pm_suspend(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on + * error. + */ + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + goto err_notif; + } + + status = ssam_irq_arm_for_wakeup(c); + if (status) + goto err_irq; + + WARN_ON(ssam_controller_suspend(c)); + return 0; + +err_irq: + ssam_ctrl_notif_d0_entry(c); +err_notif: + ssam_ctrl_notif_display_on(c); + return status; +} + +static int ssam_serial_hub_pm_resume(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + WARN_ON(ssam_controller_resume(c)); + + /* + * Try to disable IRQ wakeup (if specified) and signal D0-entry. In + * case of errors, log them and try to restore normal operation state + * as far as possible. + * + * Note: Signaling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, + * signal it here. + */ + + ssam_irq_disarm_wakeup(c); + + status = ssam_ctrl_notif_d0_entry(c); + if (status) + ssam_err(c, "pm: D0-entry notification failed: %d\n", status); + + return 0; +} + +static int ssam_serial_hub_pm_freeze(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * During hibernation image creation, we only have to ensure that the + * EC doesn't send us any events. This is done via the display-off + * and D0-exit notifications. Note that this sets up the wakeup IRQ + * on the EC side, however, we have disabled it by default on our side + * and won't enable it here. + * + * See ssam_serial_hub_poweroff() for more details on the hibernation + * process. + */ + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + ssam_ctrl_notif_display_on(c); + return status; + } + + WARN_ON(ssam_controller_suspend(c)); + return 0; +} + +static int ssam_serial_hub_pm_thaw(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + WARN_ON(ssam_controller_resume(c)); + + status = ssam_ctrl_notif_d0_entry(c); + if (status) + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + + return status; +} + +static int ssam_serial_hub_pm_poweroff(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * When entering hibernation and powering off the system, the EC, at + * least on some models, may disable events. Without us taking care of + * that, this leads to events not being enabled/restored when the + * system resumes from hibernation, resulting SAM-HID subsystem devices + * (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being + * gone, etc. + * + * To avoid these issues, we disable all registered events here (this is + * likely not actually required) and restore them during the drivers PM + * restore callback. + * + * Wakeup from the EC interrupt is not supported during hibernation, + * so don't arm the IRQ here. + */ + + status = ssam_notifier_disable_registered(c); + if (status) { + ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n", + status); + return status; + } + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + ssam_notifier_restore_registered(c); + return status; + } + + WARN_ON(ssam_controller_suspend(c)); + return 0; +} + +static int ssam_serial_hub_pm_restore(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* + * Ignore but log errors, try to restore state as much as possible in + * case of failures. See ssam_serial_hub_poweroff() for more details on + * the hibernation process. + */ + + WARN_ON(ssam_controller_resume(c)); + + status = ssam_ctrl_notif_d0_entry(c); + if (status) + ssam_err(c, "pm: D0-entry notification failed: %d\n", status); + + ssam_notifier_restore_registered(c); + return 0; +} + +static const struct dev_pm_ops ssam_serial_hub_pm_ops = { + .prepare = ssam_serial_hub_pm_prepare, + .complete = ssam_serial_hub_pm_complete, + .suspend = ssam_serial_hub_pm_suspend, + .resume = ssam_serial_hub_pm_resume, + .freeze = ssam_serial_hub_pm_freeze, + .thaw = ssam_serial_hub_pm_thaw, + .poweroff = ssam_serial_hub_pm_poweroff, + .restore = ssam_serial_hub_pm_restore, +}; + +#else /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops ssam_serial_hub_pm_ops = { }; + +#endif /* CONFIG_PM_SLEEP */ + + +/* -- Device/driver setup. -------------------------------------------------- */ + +static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; +static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; + +static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { + { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, + { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, + { }, +}; + +static int ssam_serial_hub_probe(struct serdev_device *serdev) +{ + struct ssam_controller *ctrl; + acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); + acpi_status astatus; + int status; + + if (gpiod_count(&serdev->dev, NULL) < 0) + return -ENODEV; + + status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); + if (status) + return status; + + /* Allocate controller. */ + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + /* Initialize controller. */ + status = ssam_controller_init(ctrl, serdev); + if (status) + goto err_ctrl_init; + + ssam_controller_lock(ctrl); + + /* Set up serdev device. */ + serdev_device_set_drvdata(serdev, ctrl); + serdev_device_set_client_ops(serdev, &ssam_serdev_ops); + status = serdev_device_open(serdev); + if (status) + goto err_devopen; + + astatus = ssam_serdev_setup_via_acpi(ssh, serdev); + if (ACPI_FAILURE(astatus)) { + status = -ENXIO; + goto err_devinit; + } + + /* Start controller. */ + status = ssam_controller_start(ctrl); + if (status) + goto err_devinit; + + ssam_controller_unlock(ctrl); + + /* + * Initial SAM requests: Log version and notify default/init power + * states. + */ + status = ssam_log_firmware_version(ctrl); + if (status) + goto err_initrq; + + status = ssam_ctrl_notif_d0_entry(ctrl); + if (status) + goto err_initrq; + + status = ssam_ctrl_notif_display_on(ctrl); + if (status) + goto err_initrq; + + status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); + if (status) + goto err_initrq; + + /* Set up IRQ. */ + status = ssam_irq_setup(ctrl); + if (status) + goto err_irq; + + /* Finally, set main controller reference. */ + status = ssam_try_set_controller(ctrl); + if (WARN_ON(status)) /* Currently, we're the only provider. */ + goto err_mainref; + + /* + * TODO: The EC can wake up the system via the associated GPIO interrupt + * in multiple situations. One of which is the remaining battery + * capacity falling below a certain threshold. Normally, we should + * use the device_init_wakeup function, however, the EC also seems + * to have other reasons for waking up the system and it seems + * that Windows has additional checks whether the system should be + * resumed. In short, this causes some spurious unwanted wake-ups. + * For now let's thus default power/wakeup to false. + */ + device_set_wakeup_capable(&serdev->dev, true); + acpi_walk_dep_device_list(ssh); + + return 0; + +err_mainref: + ssam_irq_free(ctrl); +err_irq: + sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); +err_initrq: + ssam_controller_lock(ctrl); + ssam_controller_shutdown(ctrl); +err_devinit: + serdev_device_close(serdev); +err_devopen: + ssam_controller_destroy(ctrl); + ssam_controller_unlock(ctrl); +err_ctrl_init: + kfree(ctrl); + return status; +} + +static void ssam_serial_hub_remove(struct serdev_device *serdev) +{ + struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev); + int status; + + /* Clear static reference so that no one else can get a new one. */ + ssam_clear_controller(); + + /* Disable and free IRQ. */ + ssam_irq_free(ctrl); + + sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); + ssam_controller_lock(ctrl); + + /* Act as if suspending to silence events. */ + status = ssam_ctrl_notif_display_off(ctrl); + if (status) { + dev_err(&serdev->dev, "display-off notification failed: %d\n", + status); + } + + status = ssam_ctrl_notif_d0_exit(ctrl); + if (status) { + dev_err(&serdev->dev, "D0-exit notification failed: %d\n", + status); + } + + /* Shut down controller and remove serdev device reference from it. */ + ssam_controller_shutdown(ctrl); + + /* Shut down actual transport. */ + serdev_device_wait_until_sent(serdev, 0); + serdev_device_close(serdev); + + /* Drop our controller reference. */ + ssam_controller_unlock(ctrl); + ssam_controller_put(ctrl); + + device_set_wakeup_capable(&serdev->dev, false); +} + +static const struct acpi_device_id ssam_serial_hub_match[] = { + { "MSHW0084", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); + +static struct serdev_device_driver ssam_serial_hub = { + .probe = ssam_serial_hub_probe, + .remove = ssam_serial_hub_remove, + .driver = { + .name = "surface_serial_hub", + .acpi_match_table = ssam_serial_hub_match, + .pm = &ssam_serial_hub_pm_ops, + .shutdown = ssam_serial_hub_shutdown, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_serdev_device_driver(ssam_serial_hub); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h new file mode 100644 index 000000000000..1221f642dda1 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_msgb.h @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH message builder functions. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H +#define _SURFACE_AGGREGATOR_SSH_MSGB_H + +#include +#include + +#include +#include + +/** + * struct msgbuf - Buffer struct to construct SSH messages. + * @begin: Pointer to the beginning of the allocated buffer space. + * @end: Pointer to the end (one past last element) of the allocated buffer + * space. + * @ptr: Pointer to the first free element in the buffer. + */ +struct msgbuf { + u8 *begin; + u8 *end; + u8 *ptr; +}; + +/** + * msgb_init() - Initialize the given message buffer struct. + * @msgb: The buffer struct to initialize + * @ptr: Pointer to the underlying memory by which the buffer will be backed. + * @cap: Size of the underlying memory. + * + * Initialize the given message buffer struct using the provided memory as + * backing. + */ +static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) +{ + msgb->begin = ptr; + msgb->end = ptr + cap; + msgb->ptr = ptr; +} + +/** + * msgb_bytes_used() - Return the current number of bytes used in the buffer. + * @msgb: The message buffer. + */ +static inline size_t msgb_bytes_used(const struct msgbuf *msgb) +{ + return msgb->ptr - msgb->begin; +} + +static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value) +{ + *msgb->ptr = value; + msgb->ptr += sizeof(u8); +} + +static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value) +{ + put_unaligned_le16(value, msgb->ptr); + msgb->ptr += sizeof(u16); +} + +/** + * msgb_push_u16() - Push a u16 value to the buffer. + * @msgb: The message buffer. + * @value: The value to push to the buffer. + */ +static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) +{ + if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) + return; + + __msgb_push_u16(msgb, value); +} + +/** + * msgb_push_syn() - Push SSH SYN bytes to the buffer. + * @msgb: The message buffer. + */ +static inline void msgb_push_syn(struct msgbuf *msgb) +{ + msgb_push_u16(msgb, SSH_MSG_SYN); +} + +/** + * msgb_push_buf() - Push raw data to the buffer. + * @msgb: The message buffer. + * @buf: The data to push to the buffer. + * @len: The length of the data to push to the buffer. + */ +static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) +{ + msgb->ptr = memcpy(msgb->ptr, buf, len) + len; +} + +/** + * msgb_push_crc() - Compute CRC and push it to the buffer. + * @msgb: The message buffer. + * @buf: The data for which the CRC should be computed. + * @len: The length of the data for which the CRC should be computed. + */ +static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) +{ + msgb_push_u16(msgb, ssh_crc(buf, len)); +} + +/** + * msgb_push_frame() - Push a SSH message frame header to the buffer. + * @msgb: The message buffer + * @ty: The type of the frame. + * @len: The length of the payload of the frame. + * @seq: The sequence ID of the frame/packet. + */ +static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) +{ + u8 *const begin = msgb->ptr; + + if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end)) + return; + + __msgb_push_u8(msgb, ty); /* Frame type. */ + __msgb_push_u16(msgb, len); /* Frame payload length. */ + __msgb_push_u8(msgb, seq); /* Frame sequence ID. */ + + msgb_push_crc(msgb, begin, msgb->ptr - begin); +} + +/** + * msgb_push_ack() - Push a SSH ACK frame to the buffer. + * @msgb: The message buffer + * @seq: The sequence ID of the frame/packet to be ACKed. + */ +static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) +{ + /* SYN. */ + msgb_push_syn(msgb); + + /* ACK-type frame + CRC. */ + msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); + + /* Payload CRC (ACK-type frames do not have a payload). */ + msgb_push_crc(msgb, msgb->ptr, 0); +} + +/** + * msgb_push_nak() - Push a SSH NAK frame to the buffer. + * @msgb: The message buffer + */ +static inline void msgb_push_nak(struct msgbuf *msgb) +{ + /* SYN. */ + msgb_push_syn(msgb); + + /* NAK-type frame + CRC. */ + msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); + + /* Payload CRC (ACK-type frames do not have a payload). */ + msgb_push_crc(msgb, msgb->ptr, 0); +} + +/** + * msgb_push_cmd() - Push a SSH command frame with payload to the buffer. + * @msgb: The message buffer. + * @seq: The sequence ID (SEQ) of the frame/packet. + * @rqid: The request ID (RQID) of the request contained in the frame. + * @rqst: The request to wrap in the frame. + */ +static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, + const struct ssam_request *rqst) +{ + const u8 type = SSH_FRAME_TYPE_DATA_SEQ; + u8 *cmd; + + /* SYN. */ + msgb_push_syn(msgb); + + /* Command frame + CRC. */ + msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq); + + /* Frame payload: Command struct + payload. */ + if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end)) + return; + + cmd = msgb->ptr; + + __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ + __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ + __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ + __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ + __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ + __msgb_push_u16(msgb, rqid); /* Request ID. */ + __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ + + /* Command payload. */ + msgb_push_buf(msgb, rqst->payload, rqst->length); + + /* CRC for command struct + payload. */ + msgb_push_crc(msgb, cmd, msgb->ptr - cmd); +} + +#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c new file mode 100644 index 000000000000..66e38fdc7963 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -0,0 +1,1710 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SSH packet transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ssh_msgb.h" +#include "ssh_packet_layer.h" +#include "ssh_parser.h" + +/* + * To simplify reasoning about the code below, we define a few concepts. The + * system below is similar to a state-machine for packets, however, there are + * too many states to explicitly write them down. To (somewhat) manage the + * states and packets we rely on flags, reference counting, and some simple + * concepts. State transitions are triggered by actions. + * + * >> Actions << + * + * - submit + * - transmission start (process next item in queue) + * - transmission finished (guaranteed to never be parallel to transmission + * start) + * - ACK received + * - NAK received (this is equivalent to issuing re-submit for all pending + * packets) + * - timeout (this is equivalent to re-issuing a submit or canceling) + * - cancel (non-pending and pending) + * + * >> Data Structures, Packet Ownership, General Overview << + * + * The code below employs two main data structures: The packet queue, + * containing all packets scheduled for transmission, and the set of pending + * packets, containing all packets awaiting an ACK. + * + * Shared ownership of a packet is controlled via reference counting. Inside + * the transport system are a total of five packet owners: + * + * - the packet queue, + * - the pending set, + * - the transmitter thread, + * - the receiver thread (via ACKing), and + * - the timeout work item. + * + * Normal operation is as follows: The initial reference of the packet is + * obtained by submitting the packet and queuing it. The receiver thread takes + * packets from the queue. By doing this, it does not increment the refcount + * but takes over the reference (removing it from the queue). If the packet is + * sequenced (i.e. needs to be ACKed by the client), the transmitter thread + * sets-up the timeout and adds the packet to the pending set before starting + * to transmit it. As the timeout is handled by a reaper task, no additional + * reference for it is needed. After the transmit is done, the reference held + * by the transmitter thread is dropped. If the packet is unsequenced (i.e. + * does not need an ACK), the packet is completed by the transmitter thread + * before dropping that reference. + * + * On receival of an ACK, the receiver thread removes and obtains the + * reference to the packet from the pending set. The receiver thread will then + * complete the packet and drop its reference. + * + * On receival of a NAK, the receiver thread re-submits all currently pending + * packets. + * + * Packet timeouts are detected by the timeout reaper. This is a task, + * scheduled depending on the earliest packet timeout expiration date, + * checking all currently pending packets if their timeout has expired. If the + * timeout of a packet has expired, it is re-submitted and the number of tries + * of this packet is incremented. If this number reaches its limit, the packet + * will be completed with a failure. + * + * On transmission failure (such as repeated packet timeouts), the completion + * callback is immediately run by on thread on which the error was detected. + * + * To ensure that a packet eventually leaves the system it is marked as + * "locked" directly before it is going to be completed or when it is + * canceled. Marking a packet as "locked" has the effect that passing and + * creating new references of the packet is disallowed. This means that the + * packet cannot be added to the queue, the pending set, and the timeout, or + * be picked up by the transmitter thread or receiver thread. To remove a + * packet from the system it has to be marked as locked and subsequently all + * references from the data structures (queue, pending) have to be removed. + * References held by threads will eventually be dropped automatically as + * their execution progresses. + * + * Note that the packet completion callback is, in case of success and for a + * sequenced packet, guaranteed to run on the receiver thread, thus providing + * a way to reliably identify responses to the packet. The packet completion + * callback is only run once and it does not indicate that the packet has + * fully left the system (for this, one should rely on the release method, + * triggered when the reference count of the packet reaches zero). In case of + * re-submission (and with somewhat unlikely timing), it may be possible that + * the packet is being re-transmitted while the completion callback runs. + * Completion will occur both on success and internal error, as well as when + * the packet is canceled. + * + * >> Flags << + * + * Flags are used to indicate the state and progression of a packet. Some flags + * have stricter guarantees than other: + * + * - locked + * Indicates if the packet is locked. If the packet is locked, passing and/or + * creating additional references to the packet is forbidden. The packet thus + * may not be queued, dequeued, or removed or added to the pending set. Note + * that the packet state flags may still change (e.g. it may be marked as + * ACKed, transmitted, ...). + * + * - completed + * Indicates if the packet completion callback has been executed or is about + * to be executed. This flag is used to ensure that the packet completion + * callback is only run once. + * + * - queued + * Indicates if a packet is present in the submission queue or not. This flag + * must only be modified with the queue lock held, and must be coherent to the + * presence of the packet in the queue. + * + * - pending + * Indicates if a packet is present in the set of pending packets or not. + * This flag must only be modified with the pending lock held, and must be + * coherent to the presence of the packet in the pending set. + * + * - transmitting + * Indicates if the packet is currently transmitting. In case of + * re-transmissions, it is only safe to wait on the "transmitted" completion + * after this flag has been set. The completion will be set both in success + * and error case. + * + * - transmitted + * Indicates if the packet has been transmitted. This flag is not cleared by + * the system, thus it indicates the first transmission only. + * + * - acked + * Indicates if the packet has been acknowledged by the client. There are no + * other guarantees given. For example, the packet may still be canceled + * and/or the completion may be triggered an error even though this bit is + * set. Rely on the status provided to the completion callback instead. + * + * - canceled + * Indicates if the packet has been canceled from the outside. There are no + * other guarantees given. Specifically, the packet may be completed by + * another part of the system before the cancellation attempts to complete it. + * + * >> General Notes << + * + * - To avoid deadlocks, if both queue and pending locks are required, the + * pending lock must be acquired before the queue lock. + * + * - The packet priority must be accessed only while holding the queue lock. + * + * - The packet timestamp must be accessed only while holding the pending + * lock. + */ + +/* + * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. + * + * Maximum number of transmission attempts per sequenced packet in case of + * time-outs. Must be smaller than 16. If the packet times out after this + * amount of tries, the packet will be completed with %-ETIMEDOUT as status + * code. + */ +#define SSH_PTL_MAX_PACKET_TRIES 3 + +/* + * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. + * + * Timeout in jiffies for packet transmission via the underlying serial + * device. If transmitting the packet takes longer than this timeout, the + * packet will be completed with -ETIMEDOUT. It will not be re-submitted. + */ +#define SSH_PTL_TX_TIMEOUT HZ + +/* + * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. + * + * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this + * time-frame after starting transmission, the packet will be re-submitted. + */ +#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) + +/* + * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. + * + * Time-resolution for timeouts. Should be larger than one jiffy to avoid + * direct re-scheduling of reaper work_struct. + */ +#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) + +/* + * SSH_PTL_MAX_PENDING - Maximum number of pending packets. + * + * Maximum number of sequenced packets concurrently waiting for an ACK. + * Packets marked as blocking will not be transmitted while this limit is + * reached. + */ +#define SSH_PTL_MAX_PENDING 1 + +/* + * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. + */ +#define SSH_PTL_RX_BUF_LEN 4096 + +/* + * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. + */ +#define SSH_PTL_RX_FIFO_LEN 4096 + +static void __ssh_ptl_packet_release(struct kref *kref) +{ + struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); + + ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); + p->ops->release(p); +} + +/** + * ssh_packet_get() - Increment reference count of packet. + * @packet: The packet to increment the reference count of. + * + * Increments the reference count of the given packet. See ssh_packet_put() + * for the counter-part of this function. + * + * Return: Returns the packet provided as input. + */ +struct ssh_packet *ssh_packet_get(struct ssh_packet *packet) +{ + if (packet) + kref_get(&packet->refcnt); + return packet; +} +EXPORT_SYMBOL_GPL(ssh_packet_get); + +/** + * ssh_packet_put() - Decrement reference count of packet. + * @packet: The packet to decrement the reference count of. + * + * If the reference count reaches zero, the ``release`` callback specified in + * the packet's &struct ssh_packet_ops, i.e. ``packet->ops->release``, will be + * called. + * + * See ssh_packet_get() for the counter-part of this function. + */ +void ssh_packet_put(struct ssh_packet *packet) +{ + if (packet) + kref_put(&packet->refcnt, __ssh_ptl_packet_release); +} +EXPORT_SYMBOL_GPL(ssh_packet_put); + +static u8 ssh_packet_get_seq(struct ssh_packet *packet) +{ + return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; +} + +/** + * ssh_packet_init() - Initialize SSH packet. + * @packet: The packet to initialize. + * @type: Type-flags of the packet. + * @priority: Priority of the packet. See SSH_PACKET_PRIORITY() for details. + * @ops: Packet operations. + * + * Initializes the given SSH packet. Sets the transmission buffer pointer to + * %NULL and the transmission buffer length to zero. For data-type packets, + * this buffer has to be set separately via ssh_packet_set_data() before + * submission, and must contain a valid SSH message, i.e. frame with optional + * payload of any type. + */ +void ssh_packet_init(struct ssh_packet *packet, unsigned long type, + u8 priority, const struct ssh_packet_ops *ops) +{ + kref_init(&packet->refcnt); + + packet->ptl = NULL; + INIT_LIST_HEAD(&packet->queue_node); + INIT_LIST_HEAD(&packet->pending_node); + + packet->state = type & SSH_PACKET_FLAGS_TY_MASK; + packet->priority = priority; + packet->timestamp = KTIME_MAX; + + packet->data.ptr = NULL; + packet->data.len = 0; + + packet->ops = ops; +} + +/** + * ssh_ctrl_packet_alloc() - Allocate control packet. + * @packet: Where the pointer to the newly allocated packet should be stored. + * @buffer: The buffer corresponding to this packet. + * @flags: Flags used for allocation. + * + * Allocates a packet and corresponding transport buffer. Sets the packet's + * buffer reference to the allocated buffer. The packet must be freed via + * ssh_ctrl_packet_free(), which will also free the corresponding buffer. The + * corresponding buffer must not be freed separately. Intended to be used with + * %ssh_ptl_ctrl_packet_ops as packet operations. + * + * Return: Returns zero on success, %-ENOMEM if the allocation failed. + */ +static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, + struct ssam_span *buffer, gfp_t flags) +{ + *packet = kzalloc(sizeof(**packet) + SSH_MSG_LEN_CTRL, flags); + if (!*packet) + return -ENOMEM; + + buffer->ptr = (u8 *)(*packet + 1); + buffer->len = SSH_MSG_LEN_CTRL; + + return 0; +} + +/** + * ssh_ctrl_packet_free() - Free control packet. + * @p: The packet to free. + */ +static void ssh_ctrl_packet_free(struct ssh_packet *p) +{ + kfree(p); +} + +static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { + .complete = NULL, + .release = ssh_ctrl_packet_free, +}; + +static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, + ktime_t expires) +{ + unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); + ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); + + spin_lock(&ptl->rtx_timeout.lock); + + /* Re-adjust / schedule reaper only if it is above resolution delta. */ + if (ktime_before(aexp, ptl->rtx_timeout.expires)) { + ptl->rtx_timeout.expires = expires; + mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); + } + + spin_unlock(&ptl->rtx_timeout.lock); +} + +/* Must be called with queue lock held. */ +static void ssh_packet_next_try(struct ssh_packet *p) +{ + u8 base = ssh_packet_priority_get_base(p->priority); + u8 try = ssh_packet_priority_get_try(p->priority); + + lockdep_assert_held(&p->ptl->queue.lock); + + p->priority = __SSH_PACKET_PRIORITY(base, try + 1); +} + +/* Must be called with queue lock held. */ +static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) +{ + struct list_head *head; + struct ssh_packet *q; + + lockdep_assert_held(&p->ptl->queue.lock); + + /* + * We generally assume that there are less control (ACK/NAK) packets + * and re-submitted data packets as there are normal data packets (at + * least in situations in which many packets are queued; if there + * aren't many packets queued the decision on how to iterate should be + * basically irrelevant; the number of control/data packets is more or + * less limited via the maximum number of pending packets). Thus, when + * inserting a control or re-submitted data packet, (determined by + * their priority), we search from front to back. Normal data packets + * are, usually queued directly at the tail of the queue, so for those + * search from back to front. + */ + + if (p->priority > SSH_PACKET_PRIORITY(DATA, 0)) { + list_for_each(head, &p->ptl->queue.head) { + q = list_entry(head, struct ssh_packet, queue_node); + + if (q->priority < p->priority) + break; + } + } else { + list_for_each_prev(head, &p->ptl->queue.head) { + q = list_entry(head, struct ssh_packet, queue_node); + + if (q->priority >= p->priority) { + head = head->next; + break; + } + } + } + + return head; +} + +/* Must be called with queue lock held. */ +static int __ssh_ptl_queue_push(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + struct list_head *head; + + lockdep_assert_held(&ptl->queue.lock); + + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return -ESHUTDOWN; + + /* Avoid further transitions when canceling/completing. */ + if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) + return -EINVAL; + + /* If this packet has already been queued, do not add it. */ + if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) + return -EALREADY; + + head = __ssh_ptl_queue_find_entrypoint(packet); + + list_add_tail(&ssh_packet_get(packet)->queue_node, head); + return 0; +} + +static int ssh_ptl_queue_push(struct ssh_packet *packet) +{ + int status; + + spin_lock(&packet->ptl->queue.lock); + status = __ssh_ptl_queue_push(packet); + spin_unlock(&packet->ptl->queue.lock); + + return status; +} + +static void ssh_ptl_queue_remove(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + spin_lock(&ptl->queue.lock); + + if (!test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { + spin_unlock(&ptl->queue.lock); + return; + } + + list_del(&packet->queue_node); + + spin_unlock(&ptl->queue.lock); + ssh_packet_put(packet); +} + +static void ssh_ptl_pending_push(struct ssh_packet *p) +{ + struct ssh_ptl *ptl = p->ptl; + const ktime_t timestamp = ktime_get_coarse_boottime(); + const ktime_t timeout = ptl->rtx_timeout.timeout; + + /* + * Note: We can get the time for the timestamp before acquiring the + * lock as this is the only place we're setting it and this function + * is called only from the transmitter thread. Thus it is not possible + * to overwrite the timestamp with an outdated value below. + */ + + spin_lock(&ptl->pending.lock); + + /* If we are canceling/completing this packet, do not add it. */ + if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) { + spin_unlock(&ptl->pending.lock); + return; + } + + /* + * On re-submission, the packet has already been added the pending + * set. We still need to update the timestamp as the packet timeout is + * reset for each (re-)submission. + */ + p->timestamp = timestamp; + + /* In case it is already pending (e.g. re-submission), do not add it. */ + if (!test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &p->state)) { + atomic_inc(&ptl->pending.count); + list_add_tail(&ssh_packet_get(p)->pending_node, &ptl->pending.head); + } + + spin_unlock(&ptl->pending.lock); + + /* Arm/update timeout reaper. */ + ssh_ptl_timeout_reaper_mod(ptl, timestamp, timestamp + timeout); +} + +static void ssh_ptl_pending_remove(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + spin_lock(&ptl->pending.lock); + + if (!test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { + spin_unlock(&ptl->pending.lock); + return; + } + + list_del(&packet->pending_node); + atomic_dec(&ptl->pending.count); + + spin_unlock(&ptl->pending.lock); + + ssh_packet_put(packet); +} + +/* Warning: Does not check/set "completed" bit. */ +static void __ssh_ptl_complete(struct ssh_packet *p, int status) +{ + struct ssh_ptl *ptl = READ_ONCE(p->ptl); + + ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); + + if (p->ops->complete) + p->ops->complete(p, status); +} + +static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) +{ + /* + * A call to this function should in general be preceded by + * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the + * packet to the structures it's going to be removed from. + * + * The set_bit call does not need explicit memory barriers as the + * implicit barrier of the test_and_set_bit() call below ensure that the + * flag is visible before we actually attempt to remove the packet. + */ + + if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) + return; + + ssh_ptl_queue_remove(p); + ssh_ptl_pending_remove(p); + + __ssh_ptl_complete(p, status); +} + +static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) + return !atomic_read(&ptl->pending.count); + + /* We can always process non-blocking packets. */ + if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) + return true; + + /* If we are already waiting for this packet, send it again. */ + if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) + return true; + + /* Otherwise: Check if we have the capacity to send. */ + return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; +} + +static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) +{ + struct ssh_packet *packet = ERR_PTR(-ENOENT); + struct ssh_packet *p, *n; + + spin_lock(&ptl->queue.lock); + list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { + /* + * If we are canceling or completing this packet, ignore it. + * It's going to be removed from this queue shortly. + */ + if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) + continue; + + /* + * Packets should be ordered non-blocking/to-be-resent first. + * If we cannot process this packet, assume that we can't + * process any following packet either and abort. + */ + if (!ssh_ptl_tx_can_process(p)) { + packet = ERR_PTR(-EBUSY); + break; + } + + /* + * We are allowed to change the state now. Remove it from the + * queue and mark it as being transmitted. + */ + + list_del(&p->queue_node); + + set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); + + /* + * Update number of tries. This directly influences the + * priority in case the packet is re-submitted (e.g. via + * timeout/NAK). Note that all reads and writes to the + * priority after the first submission are guarded by the + * queue lock. + */ + ssh_packet_next_try(p); + + packet = p; + break; + } + spin_unlock(&ptl->queue.lock); + + return packet; +} + +static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) +{ + struct ssh_packet *p; + + p = ssh_ptl_tx_pop(ptl); + if (IS_ERR(p)) + return p; + + if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { + ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); + ssh_ptl_pending_push(p); + } else { + ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); + } + + return p; +} + +static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) +{ + struct ssh_ptl *ptl = packet->ptl; + + ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); + + /* Transition state to "transmitted". */ + set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); + + /* If the packet is unsequenced, we're done: Lock and complete. */ + if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { + set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); + ssh_ptl_remove_and_complete(packet, 0); + } + + /* + * Notify that a packet transmission has finished. In general we're only + * waiting for one packet (if any), so wake_up_all should be fine. + */ + wake_up_all(&ptl->tx.packet_wq); +} + +static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) +{ + /* Transmission failure: Lock the packet and try to complete it. */ + set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); + + ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); + ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); + + ssh_ptl_remove_and_complete(packet, status); + + /* + * Notify that a packet transmission has finished. In general we're only + * waiting for one packet (if any), so wake_up_all should be fine. + */ + wake_up_all(&packet->ptl->tx.packet_wq); +} + +static long ssh_ptl_tx_wait_packet(struct ssh_ptl *ptl) +{ + int status; + + status = wait_for_completion_interruptible(&ptl->tx.thread_cplt_pkt); + reinit_completion(&ptl->tx.thread_cplt_pkt); + + /* + * Ensure completion is cleared before continuing to avoid lost update + * problems. + */ + smp_mb__after_atomic(); + + return status; +} + +static long ssh_ptl_tx_wait_transfer(struct ssh_ptl *ptl, long timeout) +{ + long status; + + status = wait_for_completion_interruptible_timeout(&ptl->tx.thread_cplt_tx, + timeout); + reinit_completion(&ptl->tx.thread_cplt_tx); + + /* + * Ensure completion is cleared before continuing to avoid lost update + * problems. + */ + smp_mb__after_atomic(); + + return status; +} + +static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) +{ + long timeout = SSH_PTL_TX_TIMEOUT; + size_t offset = 0; + + /* Note: Flush-packets don't have any data. */ + if (unlikely(!packet->data.ptr)) + return 0; + + ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); + print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, + packet->data.ptr, packet->data.len, false); + + do { + ssize_t status, len; + u8 *buf; + + buf = packet->data.ptr + offset; + len = packet->data.len - offset; + + status = serdev_device_write_buf(ptl->serdev, buf, len); + if (status < 0) + return status; + + if (status == len) + return 0; + + offset += status; + + timeout = ssh_ptl_tx_wait_transfer(ptl, timeout); + if (kthread_should_stop() || !atomic_read(&ptl->tx.running)) + return -ESHUTDOWN; + + if (timeout < 0) + return -EINTR; + + if (timeout == 0) + return -ETIMEDOUT; + } while (true); +} + +static int ssh_ptl_tx_threadfn(void *data) +{ + struct ssh_ptl *ptl = data; + + while (!kthread_should_stop() && atomic_read(&ptl->tx.running)) { + struct ssh_packet *packet; + int status; + + /* Try to get the next packet. */ + packet = ssh_ptl_tx_next(ptl); + + /* If no packet can be processed, we are done. */ + if (IS_ERR(packet)) { + ssh_ptl_tx_wait_packet(ptl); + continue; + } + + /* Transfer and complete packet. */ + status = ssh_ptl_tx_packet(ptl, packet); + if (status) + ssh_ptl_tx_compl_error(packet, status); + else + ssh_ptl_tx_compl_success(packet); + + ssh_packet_put(packet); + } + + return 0; +} + +/** + * ssh_ptl_tx_wakeup_packet() - Wake up packet transmitter thread for new + * packet. + * @ptl: The packet transport layer. + * + * Wakes up the packet transmitter thread, notifying it that a new packet has + * arrived and is ready for transfer. If the packet transport layer has been + * shut down, calls to this function will be ignored. + */ +static void ssh_ptl_tx_wakeup_packet(struct ssh_ptl *ptl) +{ + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return; + + complete(&ptl->tx.thread_cplt_pkt); +} + +/** + * ssh_ptl_tx_start() - Start packet transmitter thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_tx_start(struct ssh_ptl *ptl) +{ + atomic_set_release(&ptl->tx.running, 1); + + ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "ssam_serial_hub-tx"); + if (IS_ERR(ptl->tx.thread)) + return PTR_ERR(ptl->tx.thread); + + return 0; +} + +/** + * ssh_ptl_tx_stop() - Stop packet transmitter thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_tx_stop(struct ssh_ptl *ptl) +{ + int status = 0; + + if (!IS_ERR_OR_NULL(ptl->tx.thread)) { + /* Tell thread to stop. */ + atomic_set_release(&ptl->tx.running, 0); + + /* + * Wake up thread in case it is paused. Do not use wakeup + * helpers as this may be called when the shutdown bit has + * already been set. + */ + complete(&ptl->tx.thread_cplt_pkt); + complete(&ptl->tx.thread_cplt_tx); + + /* Finally, wait for thread to stop. */ + status = kthread_stop(ptl->tx.thread); + ptl->tx.thread = NULL; + } + + return status; +} + +static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) +{ + struct ssh_packet *packet = ERR_PTR(-ENOENT); + struct ssh_packet *p, *n; + + spin_lock(&ptl->pending.lock); + list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { + /* + * We generally expect packets to be in order, so first packet + * to be added to pending is first to be sent, is first to be + * ACKed. + */ + if (unlikely(ssh_packet_get_seq(p) != seq_id)) + continue; + + /* + * In case we receive an ACK while handling a transmission + * error completion. The packet will be removed shortly. + */ + if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { + packet = ERR_PTR(-EPERM); + break; + } + + /* + * Mark the packet as ACKed and remove it from pending by + * removing its node and decrementing the pending counter. + */ + set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); + /* Ensure that state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + atomic_dec(&ptl->pending.count); + list_del(&p->pending_node); + packet = p; + + break; + } + spin_unlock(&ptl->pending.lock); + + return packet; +} + +static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) +{ + wait_event(packet->ptl->tx.packet_wq, + test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) || + test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); +} + +static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) +{ + struct ssh_packet *p; + + p = ssh_ptl_ack_pop(ptl, seq); + if (IS_ERR(p)) { + if (PTR_ERR(p) == -ENOENT) { + /* + * The packet has not been found in the set of pending + * packets. + */ + ptl_warn(ptl, "ptl: received ACK for non-pending packet\n"); + } else { + /* + * The packet is pending, but we are not allowed to take + * it because it has been locked. + */ + WARN_ON(PTR_ERR(p) != -EPERM); + } + return; + } + + ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); + + /* + * It is possible that the packet has been transmitted, but the state + * has not been updated from "transmitting" to "transmitted" yet. + * In that case, we need to wait for this transition to occur in order + * to determine between success or failure. + * + * On transmission failure, the packet will be locked after this call. + * On success, the transmitted bit will be set. + */ + ssh_ptl_wait_until_transmitted(p); + + /* + * The packet will already be locked in case of a transmission error or + * cancellation. Let the transmitter or cancellation issuer complete the + * packet. + */ + if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { + if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) + ptl_err(ptl, "ptl: received ACK before packet had been fully transmitted\n"); + + ssh_packet_put(p); + return; + } + + ssh_ptl_remove_and_complete(p, 0); + ssh_packet_put(p); + + if (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) + ssh_ptl_tx_wakeup_packet(ptl); +} + +/** + * ssh_ptl_submit() - Submit a packet to the transport layer. + * @ptl: The packet transport layer to submit the packet to. + * @p: The packet to submit. + * + * Submits a new packet to the transport layer, queuing it to be sent. This + * function should not be used for re-submission. + * + * Return: Returns zero on success, %-EINVAL if a packet field is invalid or + * the packet has been canceled prior to submission, %-EALREADY if the packet + * has already been submitted, or %-ESHUTDOWN if the packet transport layer + * has been shut down. + */ +int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) +{ + struct ssh_ptl *ptl_old; + int status; + + /* Validate packet fields. */ + if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { + if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) + return -EINVAL; + } else if (!p->data.ptr) { + return -EINVAL; + } + + /* + * The ptl reference only gets set on or before the first submission. + * After the first submission, it has to be read-only. + * + * Note that ptl may already be set from upper-layer request + * submission, thus we cannot expect it to be NULL. + */ + ptl_old = READ_ONCE(p->ptl); + if (!ptl_old) + WRITE_ONCE(p->ptl, ptl); + else if (WARN_ON(ptl_old != ptl)) + return -EALREADY; /* Submitted on different PTL. */ + + status = ssh_ptl_queue_push(p); + if (status) + return status; + + if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state) || + (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING)) + ssh_ptl_tx_wakeup_packet(ptl); + + return 0; +} + +/* + * __ssh_ptl_resubmit() - Re-submit a packet to the transport layer. + * @packet: The packet to re-submit. + * + * Re-submits the given packet: Checks if it can be re-submitted and queues it + * if it can, resetting the packet timestamp in the process. Must be called + * with the pending lock held. + * + * Return: Returns %-ECANCELED if the packet has exceeded its number of tries, + * %-EINVAL if the packet has been locked, %-EALREADY if the packet is already + * on the queue, and %-ESHUTDOWN if the transmission layer has been shut down. + */ +static int __ssh_ptl_resubmit(struct ssh_packet *packet) +{ + int status; + u8 try; + + lockdep_assert_held(&packet->ptl->pending.lock); + + spin_lock(&packet->ptl->queue.lock); + + /* Check if the packet is out of tries. */ + try = ssh_packet_priority_get_try(packet->priority); + if (try >= SSH_PTL_MAX_PACKET_TRIES) { + spin_unlock(&packet->ptl->queue.lock); + return -ECANCELED; + } + + status = __ssh_ptl_queue_push(packet); + if (status) { + /* + * An error here indicates that the packet has either already + * been queued, been locked, or the transport layer is being + * shut down. In all cases: Ignore the error. + */ + spin_unlock(&packet->ptl->queue.lock); + return status; + } + + packet->timestamp = KTIME_MAX; + + spin_unlock(&packet->ptl->queue.lock); + return 0; +} + +static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) +{ + struct ssh_packet *p; + bool resub = false; + + /* + * Note: We deliberately do not remove/attempt to cancel and complete + * packets that are out of tires in this function. The packet will be + * eventually canceled and completed by the timeout. Removing the packet + * here could lead to overly eager cancellation if the packet has not + * been re-transmitted yet but the tries-counter already updated (i.e + * ssh_ptl_tx_next() removed the packet from the queue and updated the + * counter, but re-transmission for the last try has not actually + * started yet). + */ + + spin_lock(&ptl->pending.lock); + + /* Re-queue all pending packets. */ + list_for_each_entry(p, &ptl->pending.head, pending_node) { + /* + * Re-submission fails if the packet is out of tries, has been + * locked, is already queued, or the layer is being shut down. + * No need to re-schedule tx-thread in those cases. + */ + if (!__ssh_ptl_resubmit(p)) + resub = true; + } + + spin_unlock(&ptl->pending.lock); + + if (resub) + ssh_ptl_tx_wakeup_packet(ptl); +} + +/** + * ssh_ptl_cancel() - Cancel a packet. + * @p: The packet to cancel. + * + * Cancels a packet. There are no guarantees on when completion and release + * callbacks will be called. This may occur during execution of this function + * or may occur at any point later. + * + * Note that it is not guaranteed that the packet will actually be canceled if + * the packet is concurrently completed by another process. The only guarantee + * of this function is that the packet will be completed (with success, + * failure, or cancellation) and released from the transport layer in a + * reasonable time-frame. + * + * May be called before the packet has been submitted, in which case any later + * packet submission fails. + */ +void ssh_ptl_cancel(struct ssh_packet *p) +{ + if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) + return; + + /* + * Lock packet and commit with memory barrier. If this packet has + * already been locked, it's going to be removed and completed by + * another party, which should have precedence. + */ + if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) + return; + + /* + * By marking the packet as locked and employing the implicit memory + * barrier of test_and_set_bit, we have guaranteed that, at this point, + * the packet cannot be added to the queue any more. + * + * In case the packet has never been submitted, packet->ptl is NULL. If + * the packet is currently being submitted, packet->ptl may be NULL or + * non-NULL. Due marking the packet as locked above and committing with + * the memory barrier, we have guaranteed that, if packet->ptl is NULL, + * the packet will never be added to the queue. If packet->ptl is + * non-NULL, we don't have any guarantees. + */ + + if (READ_ONCE(p->ptl)) { + ssh_ptl_remove_and_complete(p, -ECANCELED); + + if (atomic_read(&p->ptl->pending.count) < SSH_PTL_MAX_PENDING) + ssh_ptl_tx_wakeup_packet(p->ptl); + + } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { + __ssh_ptl_complete(p, -ECANCELED); + } +} + +/* Must be called with pending lock held */ +static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) +{ + lockdep_assert_held(&p->ptl->pending.lock); + + if (p->timestamp != KTIME_MAX) + return ktime_add(p->timestamp, timeout); + else + return KTIME_MAX; +} + +static void ssh_ptl_timeout_reap(struct work_struct *work) +{ + struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); + struct ssh_packet *p, *n; + LIST_HEAD(claimed); + ktime_t now = ktime_get_coarse_boottime(); + ktime_t timeout = ptl->rtx_timeout.timeout; + ktime_t next = KTIME_MAX; + bool resub = false; + int status; + + /* + * Mark reaper as "not pending". This is done before checking any + * packets to avoid lost-update type problems. + */ + spin_lock(&ptl->rtx_timeout.lock); + ptl->rtx_timeout.expires = KTIME_MAX; + spin_unlock(&ptl->rtx_timeout.lock); + + spin_lock(&ptl->pending.lock); + + list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { + ktime_t expires = ssh_packet_get_expiration(p, timeout); + + /* + * Check if the timeout hasn't expired yet. Find out next + * expiration date to be handled after this run. + */ + if (ktime_after(expires, now)) { + next = ktime_before(expires, next) ? expires : next; + continue; + } + + status = __ssh_ptl_resubmit(p); + + /* + * Re-submission fails if the packet is out of tries, has been + * locked, is already queued, or the layer is being shut down. + * No need to re-schedule tx-thread in those cases. + */ + if (!status) + resub = true; + + /* Go to next packet if this packet is not out of tries. */ + if (status != -ECANCELED) + continue; + + /* No more tries left: Cancel the packet. */ + + /* + * If someone else has locked the packet already, don't use it + * and let the other party complete it. + */ + if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) + continue; + + /* + * We have now marked the packet as locked. Thus it cannot be + * added to the pending list again after we've removed it here. + * We can therefore re-use the pending_node of this packet + * temporarily. + */ + + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + atomic_dec(&ptl->pending.count); + list_del(&p->pending_node); + + list_add_tail(&p->pending_node, &claimed); + } + + spin_unlock(&ptl->pending.lock); + + /* Cancel and complete the packet. */ + list_for_each_entry_safe(p, n, &claimed, pending_node) { + if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { + ssh_ptl_queue_remove(p); + __ssh_ptl_complete(p, -ETIMEDOUT); + } + + /* + * Drop the reference we've obtained by removing it from + * the pending set. + */ + list_del(&p->pending_node); + ssh_packet_put(p); + } + + /* Ensure that reaper doesn't run again immediately. */ + next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); + if (next != KTIME_MAX) + ssh_ptl_timeout_reaper_mod(ptl, now, next); + + if (resub) + ssh_ptl_tx_wakeup_packet(ptl); +} + +static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) +{ + int i; + + /* + * Check if SEQ has been seen recently (i.e. packet was + * re-transmitted and we should ignore it). + */ + for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { + if (likely(ptl->rx.blocked.seqs[i] != seq)) + continue; + + ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); + return true; + } + + /* Update list of blocked sequence IDs. */ + ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; + ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) + % ARRAY_SIZE(ptl->rx.blocked.seqs); + + return false; +} + +static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, + const struct ssh_frame *frame, + const struct ssam_span *payload) +{ + if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) + return; + + ptl->ops.data_received(ptl, payload); +} + +static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) +{ + struct ssh_packet *packet; + struct ssam_span buf; + struct msgbuf msgb; + int status; + + status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); + if (status) { + ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); + return; + } + + ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(ACK, 0), + &ssh_ptl_ctrl_packet_ops); + + msgb_init(&msgb, buf.ptr, buf.len); + msgb_push_ack(&msgb, seq); + ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); + + ssh_ptl_submit(ptl, packet); + ssh_packet_put(packet); +} + +static void ssh_ptl_send_nak(struct ssh_ptl *ptl) +{ + struct ssh_packet *packet; + struct ssam_span buf; + struct msgbuf msgb; + int status; + + status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); + if (status) { + ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); + return; + } + + ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(NAK, 0), + &ssh_ptl_ctrl_packet_ops); + + msgb_init(&msgb, buf.ptr, buf.len); + msgb_push_nak(&msgb); + ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); + + ssh_ptl_submit(ptl, packet); + ssh_packet_put(packet); +} + +static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) +{ + struct ssh_frame *frame; + struct ssam_span payload; + struct ssam_span aligned; + bool syn_found; + int status; + + /* Find SYN. */ + syn_found = sshp_find_syn(source, &aligned); + + if (unlikely(aligned.ptr - source->ptr) > 0) { + ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); + + /* + * Notes: + * - This might send multiple NAKs in case the communication + * starts with an invalid SYN and is broken down into multiple + * pieces. This should generally be handled fine, we just + * might receive duplicate data in this case, which is + * detected when handling data frames. + * - This path will also be executed on invalid CRCs: When an + * invalid CRC is encountered, the code below will skip data + * until directly after the SYN. This causes the search for + * the next SYN, which is generally not placed directly after + * the last one. + * + * Open question: Should we send this in case of invalid + * payload CRCs if the frame-type is non-sequential (current + * implementation) or should we drop that frame without + * telling the EC? + */ + ssh_ptl_send_nak(ptl); + } + + if (unlikely(!syn_found)) + return aligned.ptr - source->ptr; + + /* Parse and validate frame. */ + status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, + SSH_PTL_RX_BUF_LEN); + if (status) /* Invalid frame: skip to next SYN. */ + return aligned.ptr - source->ptr + sizeof(u16); + if (!frame) /* Not enough data. */ + return aligned.ptr - source->ptr; + + switch (frame->type) { + case SSH_FRAME_TYPE_ACK: + ssh_ptl_acknowledge(ptl, frame->seq); + break; + + case SSH_FRAME_TYPE_NAK: + ssh_ptl_resubmit_pending(ptl); + break; + + case SSH_FRAME_TYPE_DATA_SEQ: + ssh_ptl_send_ack(ptl, frame->seq); + fallthrough; + + case SSH_FRAME_TYPE_DATA_NSQ: + ssh_ptl_rx_dataframe(ptl, frame, &payload); + break; + + default: + ptl_warn(ptl, "ptl: received frame with unknown type %#04x\n", + frame->type); + break; + } + + return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); +} + +static int ssh_ptl_rx_threadfn(void *data) +{ + struct ssh_ptl *ptl = data; + + while (true) { + struct ssam_span span; + size_t offs = 0; + size_t n; + + wait_event_interruptible(ptl->rx.wq, + !kfifo_is_empty(&ptl->rx.fifo) || + kthread_should_stop()); + if (kthread_should_stop()) + break; + + /* Copy from fifo to evaluation buffer. */ + n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); + + ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); + print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, + ptl->rx.buf.ptr + ptl->rx.buf.len - n, + n, false); + + /* Parse until we need more bytes or buffer is empty. */ + while (offs < ptl->rx.buf.len) { + sshp_buf_span_from(&ptl->rx.buf, offs, &span); + n = ssh_ptl_rx_eval(ptl, &span); + if (n == 0) + break; /* Need more bytes. */ + + offs += n; + } + + /* Throw away the evaluated parts. */ + sshp_buf_drop(&ptl->rx.buf, offs); + } + + return 0; +} + +static void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) +{ + wake_up(&ptl->rx.wq); +} + +/** + * ssh_ptl_rx_start() - Start packet transport layer receiver thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_rx_start(struct ssh_ptl *ptl) +{ + if (ptl->rx.thread) + return 0; + + ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, + "ssam_serial_hub-rx"); + if (IS_ERR(ptl->rx.thread)) + return PTR_ERR(ptl->rx.thread); + + return 0; +} + +/** + * ssh_ptl_rx_stop() - Stop packet transport layer receiver thread. + * @ptl: The packet transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_ptl_rx_stop(struct ssh_ptl *ptl) +{ + int status = 0; + + if (ptl->rx.thread) { + status = kthread_stop(ptl->rx.thread); + ptl->rx.thread = NULL; + } + + return status; +} + +/** + * ssh_ptl_rx_rcvbuf() - Push data from lower-layer transport to the packet + * layer. + * @ptl: The packet transport layer. + * @buf: Pointer to the data to push to the layer. + * @n: Size of the data to push to the layer, in bytes. + * + * Pushes data from a lower-layer transport to the receiver fifo buffer of the + * packet layer and notifies the receiver thread. Calls to this function are + * ignored once the packet layer has been shut down. + * + * Return: Returns the number of bytes transferred (positive or zero) on + * success. Returns %-ESHUTDOWN if the packet layer has been shut down. + */ +int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) +{ + int used; + + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return -ESHUTDOWN; + + used = kfifo_in(&ptl->rx.fifo, buf, n); + if (used) + ssh_ptl_rx_wakeup(ptl); + + return used; +} + +/** + * ssh_ptl_shutdown() - Shut down the packet transport layer. + * @ptl: The packet transport layer. + * + * Shuts down the packet transport layer, removing and canceling all queued + * and pending packets. Packets canceled by this operation will be completed + * with %-ESHUTDOWN as status. Receiver and transmitter threads will be + * stopped. + * + * As a result of this function, the transport layer will be marked as shut + * down. Submission of packets after the transport layer has been shut down + * will fail with %-ESHUTDOWN. + */ +void ssh_ptl_shutdown(struct ssh_ptl *ptl) +{ + LIST_HEAD(complete_q); + LIST_HEAD(complete_p); + struct ssh_packet *p, *n; + int status; + + /* Ensure that no new packets (including ACK/NAK) can be submitted. */ + set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); + /* + * Ensure that the layer gets marked as shut-down before actually + * stopping it. In combination with the check in ssh_ptl_queue_push(), + * this guarantees that no new packets can be added and all already + * queued packets are properly canceled. In combination with the check + * in ssh_ptl_rx_rcvbuf(), this guarantees that received data is + * properly cut off. + */ + smp_mb__after_atomic(); + + status = ssh_ptl_rx_stop(ptl); + if (status) + ptl_err(ptl, "ptl: failed to stop receiver thread\n"); + + status = ssh_ptl_tx_stop(ptl); + if (status) + ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); + + cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); + + /* + * At this point, all threads have been stopped. This means that the + * only references to packets from inside the system are in the queue + * and pending set. + * + * Note: We still need locks here because someone could still be + * canceling packets. + * + * Note 2: We can re-use queue_node (or pending_node) if we mark the + * packet as locked an then remove it from the queue (or pending set + * respectively). Marking the packet as locked avoids re-queuing + * (which should already be prevented by having stopped the treads...) + * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a + * new list via other threads (e.g. cancellation). + * + * Note 3: There may be overlap between complete_p and complete_q. + * This is handled via test_and_set_bit() on the "completed" flag + * (also handles cancellation). + */ + + /* Mark queued packets as locked and move them to complete_q. */ + spin_lock(&ptl->queue.lock); + list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { + set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); + /* Ensure that state does not get zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); + + list_del(&p->queue_node); + list_add_tail(&p->queue_node, &complete_q); + } + spin_unlock(&ptl->queue.lock); + + /* Mark pending packets as locked and move them to complete_p. */ + spin_lock(&ptl->pending.lock); + list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { + set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); + /* Ensure that state does not get zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + list_del(&p->pending_node); + list_add_tail(&p->pending_node, &complete_q); + } + atomic_set(&ptl->pending.count, 0); + spin_unlock(&ptl->pending.lock); + + /* Complete and drop packets on complete_q. */ + list_for_each_entry(p, &complete_q, queue_node) { + if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) + __ssh_ptl_complete(p, -ESHUTDOWN); + + ssh_packet_put(p); + } + + /* Complete and drop packets on complete_p. */ + list_for_each_entry(p, &complete_p, pending_node) { + if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) + __ssh_ptl_complete(p, -ESHUTDOWN); + + ssh_packet_put(p); + } + + /* + * At this point we have guaranteed that the system doesn't reference + * any packets any more. + */ +} + +/** + * ssh_ptl_init() - Initialize packet transport layer. + * @ptl: The packet transport layer to initialize. + * @serdev: The underlying serial device, i.e. the lower-level transport. + * @ops: Packet layer operations. + * + * Initializes the given packet transport layer. Transmitter and receiver + * threads must be started separately via ssh_ptl_tx_start() and + * ssh_ptl_rx_start(), after the packet-layer has been initialized and the + * lower-level transport layer has been set up. + * + * Return: Returns zero on success and a nonzero error code on failure. + */ +int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, + struct ssh_ptl_ops *ops) +{ + int i, status; + + ptl->serdev = serdev; + ptl->state = 0; + + spin_lock_init(&ptl->queue.lock); + INIT_LIST_HEAD(&ptl->queue.head); + + spin_lock_init(&ptl->pending.lock); + INIT_LIST_HEAD(&ptl->pending.head); + atomic_set_release(&ptl->pending.count, 0); + + ptl->tx.thread = NULL; + atomic_set(&ptl->tx.running, 0); + init_completion(&ptl->tx.thread_cplt_pkt); + init_completion(&ptl->tx.thread_cplt_tx); + init_waitqueue_head(&ptl->tx.packet_wq); + + ptl->rx.thread = NULL; + init_waitqueue_head(&ptl->rx.wq); + + spin_lock_init(&ptl->rtx_timeout.lock); + ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; + ptl->rtx_timeout.expires = KTIME_MAX; + INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); + + ptl->ops = *ops; + + /* Initialize list of recent/blocked SEQs with invalid sequence IDs. */ + for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) + ptl->rx.blocked.seqs[i] = U16_MAX; + ptl->rx.blocked.offset = 0; + + status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); + if (status) + return status; + + status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); + if (status) + kfifo_free(&ptl->rx.fifo); + + return status; +} + +/** + * ssh_ptl_destroy() - Deinitialize packet transport layer. + * @ptl: The packet transport layer to deinitialize. + * + * Deinitializes the given packet transport layer and frees resources + * associated with it. If receiver and/or transmitter threads have been + * started, the layer must first be shut down via ssh_ptl_shutdown() before + * this function can be called. + */ +void ssh_ptl_destroy(struct ssh_ptl *ptl) +{ + kfifo_free(&ptl->rx.fifo); + sshp_buf_free(&ptl->rx.buf); +} diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h new file mode 100644 index 000000000000..058f111292ca --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH packet transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H +#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ssh_parser.h" + +/** + * enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl. + * + * @SSH_PTL_SF_SHUTDOWN_BIT: + * Indicates that the packet transport layer has been shut down or is + * being shut down and should not accept any new packets/data. + */ +enum ssh_ptl_state_flags { + SSH_PTL_SF_SHUTDOWN_BIT, +}; + +/** + * struct ssh_ptl_ops - Callback operations for packet transport layer. + * @data_received: Function called when a data-packet has been received. Both, + * the packet layer on which the packet has been received and + * the packet's payload data are provided to this function. + */ +struct ssh_ptl_ops { + void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); +}; + +/** + * struct ssh_ptl - SSH packet transport layer. + * @serdev: Serial device providing the underlying data transport. + * @state: State(-flags) of the transport layer. + * @queue: Packet submission queue. + * @queue.lock: Lock for modifying the packet submission queue. + * @queue.head: List-head of the packet submission queue. + * @pending: Set/list of pending packets. + * @pending.lock: Lock for modifying the pending set. + * @pending.head: List-head of the pending set/list. + * @pending.count: Number of currently pending packets. + * @tx: Transmitter subsystem. + * @tx.running: Flag indicating (desired) transmitter thread state. + * @tx.thread: Transmitter thread. + * @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer. + * @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets. + * @tx.packet_wq: Waitqueue-head for packet transmit completion. + * @rx: Receiver subsystem. + * @rx.thread: Receiver thread. + * @rx.wq: Waitqueue-head for receiver thread. + * @rx.fifo: Buffer for receiving data/pushing data to receiver thread. + * @rx.buf: Buffer for evaluating data on receiver thread. + * @rx.blocked: List of recent/blocked sequence IDs to detect retransmission. + * @rx.blocked.seqs: Array of blocked sequence IDs. + * @rx.blocked.offset: Offset indicating where a new ID should be inserted. + * @rtx_timeout: Retransmission timeout subsystem. + * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. + * @rtx_timeout.timeout: Timeout interval for retransmission. + * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. + * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. + * @ops: Packet layer operations. + */ +struct ssh_ptl { + struct serdev_device *serdev; + unsigned long state; + + struct { + spinlock_t lock; + struct list_head head; + } queue; + + struct { + spinlock_t lock; + struct list_head head; + atomic_t count; + } pending; + + struct { + atomic_t running; + struct task_struct *thread; + struct completion thread_cplt_tx; + struct completion thread_cplt_pkt; + struct wait_queue_head packet_wq; + } tx; + + struct { + struct task_struct *thread; + struct wait_queue_head wq; + struct kfifo fifo; + struct sshp_buf buf; + + struct { + u16 seqs[8]; + u16 offset; + } blocked; + } rx; + + struct { + spinlock_t lock; + ktime_t timeout; + ktime_t expires; + struct delayed_work reaper; + } rtx_timeout; + + struct ssh_ptl_ops ops; +}; + +#define __ssam_prcond(func, p, fmt, ...) \ + do { \ + typeof(p) __p = (p); \ + \ + if (__p) \ + func(__p, fmt, ##__VA_ARGS__); \ + } while (0) + +#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) +#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) + +#define to_ssh_ptl(ptr, member) \ + container_of(ptr, struct ssh_ptl, member) + +int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, + struct ssh_ptl_ops *ops); + +void ssh_ptl_destroy(struct ssh_ptl *ptl); + +/** + * ssh_ptl_get_device() - Get device associated with packet transport layer. + * @ptl: The packet transport layer. + * + * Return: Returns the device on which the given packet transport layer builds + * upon. + */ +static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) +{ + return ptl->serdev ? &ptl->serdev->dev : NULL; +} + +int ssh_ptl_tx_start(struct ssh_ptl *ptl); +int ssh_ptl_tx_stop(struct ssh_ptl *ptl); +int ssh_ptl_rx_start(struct ssh_ptl *ptl); +int ssh_ptl_rx_stop(struct ssh_ptl *ptl); +void ssh_ptl_shutdown(struct ssh_ptl *ptl); + +int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); +void ssh_ptl_cancel(struct ssh_packet *p); + +int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); + +/** + * ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for + * transfer. + * @ptl: The packet transport layer. + * + * Wakes up the packet transmitter thread, notifying it that the underlying + * transport has more space for data to be transmitted. If the packet + * transport layer has been shut down, calls to this function will be ignored. + */ +static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) +{ + if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) + return; + + complete(&ptl->tx.thread_cplt_tx); +} + +void ssh_packet_init(struct ssh_packet *packet, unsigned long type, + u8 priority, const struct ssh_packet_ops *ops); + +#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c new file mode 100644 index 000000000000..e2dead8de94a --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_parser.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SSH message parser. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include + +#include +#include "ssh_parser.h" + +/** + * sshp_validate_crc() - Validate a CRC in raw message data. + * @src: The span of data over which the CRC should be computed. + * @crc: The pointer to the expected u16 CRC value. + * + * Computes the CRC of the provided data span (@src), compares it to the CRC + * stored at the given address (@crc), and returns the result of this + * comparison, i.e. %true if equal. This function is intended to run on raw + * input/message data. + * + * Return: Returns %true if the computed CRC matches the stored CRC, %false + * otherwise. + */ +static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) +{ + u16 actual = ssh_crc(src->ptr, src->len); + u16 expected = get_unaligned_le16(crc); + + return actual == expected; +} + +/** + * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. + * @src: The data span to check the start of. + */ +static bool sshp_starts_with_syn(const struct ssam_span *src) +{ + return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; +} + +/** + * sshp_find_syn() - Find SSH SYN bytes in the given data span. + * @src: The data span to search in. + * @rem: The span (output) indicating the remaining data, starting with SSH + * SYN bytes, if found. + * + * Search for SSH SYN bytes in the given source span. If found, set the @rem + * span to the remaining data, starting with the first SYN bytes and capped by + * the source span length, and return %true. This function does not copy any + * data, but rather only sets pointers to the respective start addresses and + * length values. + * + * If no SSH SYN bytes could be found, set the @rem span to the zero-length + * span at the end of the source span and return %false. + * + * If partial SSH SYN bytes could be found at the end of the source span, set + * the @rem span to cover these partial SYN bytes, capped by the end of the + * source span, and return %false. This function should then be re-run once + * more data is available. + * + * Return: Returns %true if a complete SSH SYN sequence could be found, + * %false otherwise. + */ +bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) +{ + size_t i; + + for (i = 0; i < src->len - 1; i++) { + if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { + rem->ptr = src->ptr + i; + rem->len = src->len - i; + return true; + } + } + + if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { + rem->ptr = src->ptr + src->len - 1; + rem->len = 1; + return false; + } + + rem->ptr = src->ptr + src->len; + rem->len = 0; + return false; +} + +/** + * sshp_parse_frame() - Parse SSH frame. + * @dev: The device used for logging. + * @source: The source to parse from. + * @frame: The parsed frame (output). + * @payload: The parsed payload (output). + * @maxlen: The maximum supported message length. + * + * Parses and validates a SSH frame, including its payload, from the given + * source. Sets the provided @frame pointer to the start of the frame and + * writes the limits of the frame payload to the provided @payload span + * pointer. + * + * This function does not copy any data, but rather only validates the message + * data and sets pointers (and length values) to indicate the respective parts. + * + * If no complete SSH frame could be found, the frame pointer will be set to + * the %NULL pointer and the payload span will be set to the null span (start + * pointer %NULL, size zero). + * + * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if + * the start of the message is invalid, %-EBADMSG if any (frame-header or + * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than + * the maximum message length specified in the @maxlen parameter. + */ +int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, + struct ssh_frame **frame, struct ssam_span *payload, + size_t maxlen) +{ + struct ssam_span sf; + struct ssam_span sp; + + /* Initialize output. */ + *frame = NULL; + payload->ptr = NULL; + payload->len = 0; + + if (!sshp_starts_with_syn(source)) { + dev_warn(dev, "rx: parser: invalid start of frame\n"); + return -ENOMSG; + } + + /* Check for minimum packet length. */ + if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { + dev_dbg(dev, "rx: parser: not enough data for frame\n"); + return 0; + } + + /* Pin down frame. */ + sf.ptr = source->ptr + sizeof(u16); + sf.len = sizeof(struct ssh_frame); + + /* Validate frame CRC. */ + if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { + dev_warn(dev, "rx: parser: invalid frame CRC\n"); + return -EBADMSG; + } + + /* Ensure packet does not exceed maximum length. */ + sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); + if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) { + dev_warn(dev, "rx: parser: frame too large: %llu bytes\n", + SSH_MESSAGE_LENGTH(sp.len)); + return -EMSGSIZE; + } + + /* Pin down payload. */ + sp.ptr = sf.ptr + sf.len + sizeof(u16); + + /* Check for frame + payload length. */ + if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { + dev_dbg(dev, "rx: parser: not enough data for payload\n"); + return 0; + } + + /* Validate payload CRC. */ + if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { + dev_warn(dev, "rx: parser: invalid payload CRC\n"); + return -EBADMSG; + } + + *frame = (struct ssh_frame *)sf.ptr; + *payload = sp; + + dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n", + (*frame)->type, (*frame)->len); + + return 0; +} + +/** + * sshp_parse_command() - Parse SSH command frame payload. + * @dev: The device used for logging. + * @source: The source to parse from. + * @command: The parsed command (output). + * @command_data: The parsed command data/payload (output). + * + * Parses and validates a SSH command frame payload. Sets the @command pointer + * to the command header and the @command_data span to the command data (i.e. + * payload of the command). This will result in a zero-length span if the + * command does not have any associated data/payload. This function does not + * check the frame-payload-type field, which should be checked by the caller + * before calling this function. + * + * The @source parameter should be the complete frame payload, e.g. returned + * by the sshp_parse_frame() command. + * + * This function does not copy any data, but rather only validates the frame + * payload data and sets pointers (and length values) to indicate the + * respective parts. + * + * Return: Returns zero on success or %-ENOMSG if @source does not represent a + * valid command-type frame payload, i.e. is too short. + */ +int sshp_parse_command(const struct device *dev, const struct ssam_span *source, + struct ssh_command **command, + struct ssam_span *command_data) +{ + /* Check for minimum length. */ + if (unlikely(source->len < sizeof(struct ssh_command))) { + *command = NULL; + command_data->ptr = NULL; + command_data->len = 0; + + dev_err(dev, "rx: parser: command payload is too short\n"); + return -ENOMSG; + } + + *command = (struct ssh_command *)source->ptr; + command_data->ptr = source->ptr + sizeof(struct ssh_command); + command_data->len = source->len - sizeof(struct ssh_command); + + dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n", + (*command)->tc, (*command)->cid); + + return 0; +} diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h new file mode 100644 index 000000000000..63c38d350988 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_parser.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH message parser. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H +#define _SURFACE_AGGREGATOR_SSH_PARSER_H + +#include +#include +#include +#include + +#include + +/** + * struct sshp_buf - Parser buffer for SSH messages. + * @ptr: Pointer to the beginning of the buffer. + * @len: Number of bytes used in the buffer. + * @cap: Maximum capacity of the buffer. + */ +struct sshp_buf { + u8 *ptr; + size_t len; + size_t cap; +}; + +/** + * sshp_buf_init() - Initialize a SSH parser buffer. + * @buf: The buffer to initialize. + * @ptr: The memory backing the buffer. + * @cap: The length of the memory backing the buffer, i.e. its capacity. + * + * Initializes the buffer with the given memory as backing and set its used + * length to zero. + */ +static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) +{ + buf->ptr = ptr; + buf->len = 0; + buf->cap = cap; +} + +/** + * sshp_buf_alloc() - Allocate and initialize a SSH parser buffer. + * @buf: The buffer to initialize/allocate to. + * @cap: The desired capacity of the buffer. + * @flags: The flags used for allocating the memory. + * + * Allocates @cap bytes and initializes the provided buffer struct with the + * allocated memory. + * + * Return: Returns zero on success and %-ENOMEM if allocation failed. + */ +static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) +{ + u8 *ptr; + + ptr = kzalloc(cap, flags); + if (!ptr) + return -ENOMEM; + + sshp_buf_init(buf, ptr, cap); + return 0; +} + +/** + * sshp_buf_free() - Free a SSH parser buffer. + * @buf: The buffer to free. + * + * Frees a SSH parser buffer by freeing the memory backing it and then + * resetting its pointer to %NULL and length and capacity to zero. Intended to + * free a buffer previously allocated with sshp_buf_alloc(). + */ +static inline void sshp_buf_free(struct sshp_buf *buf) +{ + kfree(buf->ptr); + buf->ptr = NULL; + buf->len = 0; + buf->cap = 0; +} + +/** + * sshp_buf_drop() - Drop data from the beginning of the buffer. + * @buf: The buffer to drop data from. + * @n: The number of bytes to drop. + * + * Drops the first @n bytes from the buffer. Re-aligns any remaining data to + * the beginning of the buffer. + */ +static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) +{ + memmove(buf->ptr, buf->ptr + n, buf->len - n); + buf->len -= n; +} + +/** + * sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer. + * @buf: The buffer to write the data into. + * @fifo: The fifo to read the data from. + * + * Transfers the data contained in the fifo to the buffer, removing it from + * the fifo. This function will try to transfer as much data as possible, + * limited either by the remaining space in the buffer or by the number of + * bytes available in the fifo. + * + * Return: Returns the number of bytes transferred. + */ +static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, + struct kfifo *fifo) +{ + size_t n; + + n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); + buf->len += n; + + return n; +} + +/** + * sshp_buf_span_from() - Initialize a span from the given buffer and offset. + * @buf: The buffer to create the span from. + * @offset: The offset in the buffer at which the span should start. + * @span: The span to initialize (output). + * + * Initializes the provided span to point to the memory at the given offset in + * the buffer, with the length of the span being capped by the number of bytes + * used in the buffer after the offset (i.e. bytes remaining after the + * offset). + * + * Warning: This function does not validate that @offset is less than or equal + * to the number of bytes used in the buffer or the buffer capacity. This must + * be guaranteed by the caller. + */ +static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, + struct ssam_span *span) +{ + span->ptr = buf->ptr + offset; + span->len = buf->len - offset; +} + +bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem); + +int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, + struct ssh_frame **frame, struct ssam_span *payload, + size_t maxlen); + +int sshp_parse_command(const struct device *dev, const struct ssam_span *source, + struct ssh_command **command, + struct ssam_span *command_data); + +#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c new file mode 100644 index 000000000000..66c839a995f3 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -0,0 +1,1211 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SSH request transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ssh_packet_layer.h" +#include "ssh_request_layer.h" + +/* + * SSH_RTL_REQUEST_TIMEOUT - Request timeout. + * + * Timeout as ktime_t delta for request responses. If we have not received a + * response in this time-frame after finishing the underlying packet + * transmission, the request will be completed with %-ETIMEDOUT as status + * code. + */ +#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) + +/* + * SSH_RTL_REQUEST_TIMEOUT_RESOLUTION - Request timeout granularity. + * + * Time-resolution for timeouts. Should be larger than one jiffy to avoid + * direct re-scheduling of reaper work_struct. + */ +#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) + +/* + * SSH_RTL_MAX_PENDING - Maximum number of pending requests. + * + * Maximum number of requests concurrently waiting to be completed (i.e. + * waiting for the corresponding packet transmission to finish if they don't + * have a response or waiting for a response if they have one). + */ +#define SSH_RTL_MAX_PENDING 3 + +/* + * SSH_RTL_TX_BATCH - Maximum number of requests processed per work execution. + * Used to prevent livelocking of the workqueue. Value chosen via educated + * guess, may be adjusted. + */ +#define SSH_RTL_TX_BATCH 10 + +static u16 ssh_request_get_rqid(struct ssh_request *rqst) +{ + return get_unaligned_le16(rqst->packet.data.ptr + + SSH_MSGOFFSET_COMMAND(rqid)); +} + +static u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) +{ + if (!rqst->packet.data.ptr) + return U32_MAX; + + return ssh_request_get_rqid(rqst); +} + +static void ssh_rtl_queue_remove(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + spin_lock(&rtl->queue.lock); + + if (!test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state)) { + spin_unlock(&rtl->queue.lock); + return; + } + + list_del(&rqst->node); + + spin_unlock(&rtl->queue.lock); + ssh_request_put(rqst); +} + +static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) +{ + bool empty; + + spin_lock(&rtl->queue.lock); + empty = list_empty(&rtl->queue.head); + spin_unlock(&rtl->queue.lock); + + return empty; +} + +static void ssh_rtl_pending_remove(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + spin_lock(&rtl->pending.lock); + + if (!test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { + spin_unlock(&rtl->pending.lock); + return; + } + + atomic_dec(&rtl->pending.count); + list_del(&rqst->node); + + spin_unlock(&rtl->pending.lock); + + ssh_request_put(rqst); +} + +static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + spin_lock(&rtl->pending.lock); + + if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { + spin_unlock(&rtl->pending.lock); + return -EINVAL; + } + + if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { + spin_unlock(&rtl->pending.lock); + return -EALREADY; + } + + atomic_inc(&rtl->pending.count); + list_add_tail(&ssh_request_get(rqst)->node, &rtl->pending.head); + + spin_unlock(&rtl->pending.lock); + return 0; +} + +static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + /* rtl/ptl may not be set if we're canceling before submitting. */ + rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", + ssh_request_get_rqid_safe(rqst), status); + + rqst->ops->complete(rqst, NULL, NULL, status); +} + +static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, + const struct ssh_command *cmd, + const struct ssam_span *data) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", + ssh_request_get_rqid(rqst)); + + rqst->ops->complete(rqst, cmd, data, 0); +} + +static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + + if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) + return !atomic_read(&rtl->pending.count); + + return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; +} + +static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) +{ + struct ssh_request *rqst = ERR_PTR(-ENOENT); + struct ssh_request *p, *n; + + spin_lock(&rtl->queue.lock); + + /* Find first non-locked request and remove it. */ + list_for_each_entry_safe(p, n, &rtl->queue.head, node) { + if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) + continue; + + if (!ssh_rtl_tx_can_process(p)) { + rqst = ERR_PTR(-EBUSY); + break; + } + + /* Remove from queue and mark as transmitting. */ + set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); + + list_del(&p->node); + + rqst = p; + break; + } + + spin_unlock(&rtl->queue.lock); + return rqst; +} + +static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) +{ + struct ssh_request *rqst; + int status; + + /* Get and prepare next request for transmit. */ + rqst = ssh_rtl_tx_next(rtl); + if (IS_ERR(rqst)) + return PTR_ERR(rqst); + + /* Add it to/mark it as pending. */ + status = ssh_rtl_tx_pending_push(rqst); + if (status) { + ssh_request_put(rqst); + return -EAGAIN; + } + + /* Submit packet. */ + status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); + if (status == -ESHUTDOWN) { + /* + * Packet has been refused due to the packet layer shutting + * down. Complete it here. + */ + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); + /* + * Note: A barrier is not required here, as there are only two + * references in the system at this point: The one that we have, + * and the other one that belongs to the pending set. Due to the + * request being marked as "transmitting", our process is the + * only one allowed to remove the pending node and change the + * state. Normally, the task would fall to the packet callback, + * but as this is a path where submission failed, this callback + * will never be executed. + */ + + ssh_rtl_pending_remove(rqst); + ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); + + ssh_request_put(rqst); + return -ESHUTDOWN; + + } else if (status) { + /* + * If submitting the packet failed and the packet layer isn't + * shutting down, the packet has either been submitted/queued + * before (-EALREADY, which cannot happen as we have + * guaranteed that requests cannot be re-submitted), or the + * packet was marked as locked (-EINVAL). To mark the packet + * locked at this stage, the request, and thus the packets + * itself, had to have been canceled. Simply drop the + * reference. Cancellation itself will remove it from the set + * of pending requests. + */ + + WARN_ON(status != -EINVAL); + + ssh_request_put(rqst); + return -EAGAIN; + } + + ssh_request_put(rqst); + return 0; +} + +static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) +{ + if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) + return false; + + if (ssh_rtl_queue_empty(rtl)) + return false; + + return schedule_work(&rtl->tx.work); +} + +static void ssh_rtl_tx_work_fn(struct work_struct *work) +{ + struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); + unsigned int iterations = SSH_RTL_TX_BATCH; + int status; + + /* + * Try to be nice and not block/live-lock the workqueue: Run a maximum + * of 10 tries, then re-submit if necessary. This should not be + * necessary for normal execution, but guarantee it anyway. + */ + do { + status = ssh_rtl_tx_try_process_one(rtl); + if (status == -ENOENT || status == -EBUSY) + return; /* No more requests to process. */ + + if (status == -ESHUTDOWN) { + /* + * Packet system shutting down. No new packets can be + * transmitted. Return silently, the party initiating + * the shutdown should handle the rest. + */ + return; + } + + WARN_ON(status != 0 && status != -EAGAIN); + } while (--iterations); + + /* Out of tries, reschedule. */ + ssh_rtl_tx_schedule(rtl); +} + +/** + * ssh_rtl_submit() - Submit a request to the transport layer. + * @rtl: The request transport layer. + * @rqst: The request to submit. + * + * Submits a request to the transport layer. A single request may not be + * submitted multiple times without reinitializing it. + * + * Return: Returns zero on success, %-EINVAL if the request type is invalid or + * the request has been canceled prior to submission, %-EALREADY if the + * request has already been submitted, or %-ESHUTDOWN in case the request + * transport layer has been shut down. + */ +int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) +{ + /* + * Ensure that requests expecting a response are sequenced. If this + * invariant ever changes, see the comment in ssh_rtl_complete() on what + * is required to be changed in the code. + */ + if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) + if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) + return -EINVAL; + + spin_lock(&rtl->queue.lock); + + /* + * Try to set ptl and check if this request has already been submitted. + * + * Must be inside lock as we might run into a lost update problem + * otherwise: If this were outside of the lock, cancellation in + * ssh_rtl_cancel_nonpending() may run after we've set the ptl + * reference but before we enter the lock. In that case, we'd detect + * that the request is being added to the queue and would try to remove + * it from that, but removal might fail because it hasn't actually been + * added yet. By putting this cmpxchg in the critical section, we + * ensure that the queuing detection only triggers when we are already + * in the critical section and the remove process will wait until the + * push operation has been completed (via lock) due to that. Only then, + * we can safely try to remove it. + */ + if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl)) { + spin_unlock(&rtl->queue.lock); + return -EALREADY; + } + + /* + * Ensure that we set ptl reference before we continue modifying state. + * This is required for non-pending cancellation. This barrier is paired + * with the one in ssh_rtl_cancel_nonpending(). + * + * By setting the ptl reference before we test for "locked", we can + * check if the "locked" test may have already run. See comments in + * ssh_rtl_cancel_nonpending() for more detail. + */ + smp_mb__after_atomic(); + + if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { + spin_unlock(&rtl->queue.lock); + return -ESHUTDOWN; + } + + if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { + spin_unlock(&rtl->queue.lock); + return -EINVAL; + } + + set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); + list_add_tail(&ssh_request_get(rqst)->node, &rtl->queue.head); + + spin_unlock(&rtl->queue.lock); + + ssh_rtl_tx_schedule(rtl); + return 0; +} + +static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, + ktime_t expires) +{ + unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); + ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); + + spin_lock(&rtl->rtx_timeout.lock); + + /* Re-adjust / schedule reaper only if it is above resolution delta. */ + if (ktime_before(aexp, rtl->rtx_timeout.expires)) { + rtl->rtx_timeout.expires = expires; + mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); + } + + spin_unlock(&rtl->rtx_timeout.lock); +} + +static void ssh_rtl_timeout_start(struct ssh_request *rqst) +{ + struct ssh_rtl *rtl = ssh_request_rtl(rqst); + ktime_t timestamp = ktime_get_coarse_boottime(); + ktime_t timeout = rtl->rtx_timeout.timeout; + + if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) + return; + + /* + * Note: The timestamp gets set only once. This happens on the packet + * callback. All other access to it is read-only. + */ + WRITE_ONCE(rqst->timestamp, timestamp); + /* + * Ensure timestamp is set before starting the reaper. Paired with + * implicit barrier following check on ssh_request_get_expiration() in + * ssh_rtl_timeout_reap. + */ + smp_mb__after_atomic(); + + ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); +} + +static void ssh_rtl_complete(struct ssh_rtl *rtl, + const struct ssh_command *command, + const struct ssam_span *command_data) +{ + struct ssh_request *r = NULL; + struct ssh_request *p, *n; + u16 rqid = get_unaligned_le16(&command->rqid); + + /* + * Get request from pending based on request ID and mark it as response + * received and locked. + */ + spin_lock(&rtl->pending.lock); + list_for_each_entry_safe(p, n, &rtl->pending.head, node) { + /* We generally expect requests to be processed in order. */ + if (unlikely(ssh_request_get_rqid(p) != rqid)) + continue; + + /* + * Mark as "response received" and "locked" as we're going to + * complete it. + */ + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); + set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); + + atomic_dec(&rtl->pending.count); + list_del(&p->node); + + r = p; + break; + } + spin_unlock(&rtl->pending.lock); + + if (!r) { + rtl_warn(rtl, "rtl: dropping unexpected command message (rqid = %#06x)\n", + rqid); + return; + } + + /* If the request hasn't been completed yet, we will do this now. */ + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { + ssh_request_put(r); + ssh_rtl_tx_schedule(rtl); + return; + } + + /* + * Make sure the request has been transmitted. In case of a sequenced + * request, we are guaranteed that the completion callback will run on + * the receiver thread directly when the ACK for the packet has been + * received. Similarly, this function is guaranteed to run on the + * receiver thread. Thus we are guaranteed that if the packet has been + * successfully transmitted and received an ACK, the transmitted flag + * has been set and is visible here. + * + * We are currently not handling unsequenced packets here, as those + * should never expect a response as ensured in ssh_rtl_submit. If this + * ever changes, one would have to test for + * + * (r->state & (transmitting | transmitted)) + * + * on unsequenced packets to determine if they could have been + * transmitted. There are no synchronization guarantees as in the + * sequenced case, since, in this case, the callback function will not + * run on the same thread. Thus an exact determination is impossible. + */ + if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { + rtl_err(rtl, "rtl: received response before ACK for request (rqid = %#06x)\n", + rqid); + + /* + * NB: Timeout has already been canceled, request already been + * removed from pending and marked as locked and completed. As + * we receive a "false" response, the packet might still be + * queued though. + */ + ssh_rtl_queue_remove(r); + + ssh_rtl_complete_with_status(r, -EREMOTEIO); + ssh_request_put(r); + + ssh_rtl_tx_schedule(rtl); + return; + } + + /* + * NB: Timeout has already been canceled, request already been + * removed from pending and marked as locked and completed. The request + * can also not be queued any more, as it has been marked as + * transmitting and later transmitted. Thus no need to remove it from + * anywhere. + */ + + ssh_rtl_complete_with_rsp(r, command, command_data); + ssh_request_put(r); + + ssh_rtl_tx_schedule(rtl); +} + +static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) +{ + struct ssh_rtl *rtl; + unsigned long flags, fixed; + bool remove; + + /* + * Handle unsubmitted request: Try to mark the packet as locked, + * expecting the state to be zero (i.e. unsubmitted). Note that, if + * setting the state worked, we might still be adding the packet to the + * queue in a currently executing submit call. In that case, however, + * ptl reference must have been set previously, as locked is checked + * after setting ptl. Furthermore, when the ptl reference is set, the + * submission process is guaranteed to have entered the critical + * section. Thus only if we successfully locked this request and ptl is + * NULL, we have successfully removed the request, i.e. we are + * guaranteed that, due to the "locked" check in ssh_rtl_submit(), the + * packet will never be added. Otherwise, we need to try and grab it + * from the queue, where we are now guaranteed that the packet is or has + * been due to the critical section. + * + * Note that if the cmpxchg() fails, we are guaranteed that ptl has + * been set and is non-NULL, as states can only be nonzero after this + * has been set. Also note that we need to fetch the static (type) + * flags to ensure that they don't cause the cmpxchg() to fail. + */ + fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; + flags = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); + + /* + * Force correct ordering with regards to state and ptl reference access + * to safe-guard cancellation to concurrent submission against a + * lost-update problem. First try to exchange state, then also check + * ptl if that worked. This barrier is paired with the + * one in ssh_rtl_submit(). + */ + smp_mb__after_atomic(); + + if (flags == fixed && !READ_ONCE(r->packet.ptl)) { + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_complete_with_status(r, -ECANCELED); + return true; + } + + rtl = ssh_request_rtl(r); + spin_lock(&rtl->queue.lock); + + /* + * Note: 1) Requests cannot be re-submitted. 2) If a request is + * queued, it cannot be "transmitting"/"pending" yet. Thus, if we + * successfully remove the request here, we have removed all its + * occurrences in the system. + */ + + remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); + if (!remove) { + spin_unlock(&rtl->queue.lock); + return false; + } + + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + list_del(&r->node); + + spin_unlock(&rtl->queue.lock); + + ssh_request_put(r); /* Drop reference obtained from queue. */ + + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_complete_with_status(r, -ECANCELED); + return true; +} + +static bool ssh_rtl_cancel_pending(struct ssh_request *r) +{ + /* If the packet is already locked, it's going to be removed shortly. */ + if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) + return true; + + /* + * Now that we have locked the packet, we have guaranteed that it can't + * be added to the system any more. If ptl is NULL, the locked + * check in ssh_rtl_submit() has not been run and any submission, + * currently in progress or called later, won't add the packet. Thus we + * can directly complete it. + * + * The implicit memory barrier of test_and_set_bit() should be enough + * to ensure that the correct order (first lock, then check ptl) is + * ensured. This is paired with the barrier in ssh_rtl_submit(). + */ + if (!READ_ONCE(r->packet.ptl)) { + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_complete_with_status(r, -ECANCELED); + return true; + } + + /* + * Try to cancel the packet. If the packet has not been completed yet, + * this will subsequently (and synchronously) call the completion + * callback of the packet, which will complete the request. + */ + ssh_ptl_cancel(&r->packet); + + /* + * If the packet has been completed with success, i.e. has not been + * canceled by the above call, the request may not have been completed + * yet (may be waiting for a response). Check if we need to do this + * here. + */ + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return true; + + ssh_rtl_queue_remove(r); + ssh_rtl_pending_remove(r); + ssh_rtl_complete_with_status(r, -ECANCELED); + + return true; +} + +/** + * ssh_rtl_cancel() - Cancel request. + * @rqst: The request to cancel. + * @pending: Whether to also cancel pending requests. + * + * Cancels the given request. If @pending is %false, this will not cancel + * pending requests, i.e. requests that have already been submitted to the + * packet layer but not been completed yet. If @pending is %true, this will + * cancel the given request regardless of the state it is in. + * + * If the request has been canceled by calling this function, both completion + * and release callbacks of the request will be executed in a reasonable + * time-frame. This may happen during execution of this function, however, + * there is no guarantee for this. For example, a request currently + * transmitting will be canceled/completed only after transmission has + * completed, and the respective callbacks will be executed on the transmitter + * thread, which may happen during, but also some time after execution of the + * cancel function. + * + * Return: Returns %true if the given request has been canceled or completed, + * either by this function or prior to calling this function, %false + * otherwise. If @pending is %true, this function will always return %true. + */ +bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) +{ + struct ssh_rtl *rtl; + bool canceled; + + if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) + return true; + + if (pending) + canceled = ssh_rtl_cancel_pending(rqst); + else + canceled = ssh_rtl_cancel_nonpending(rqst); + + /* Note: rtl may be NULL if request has not been submitted yet. */ + rtl = ssh_request_rtl(rqst); + if (canceled && rtl) + ssh_rtl_tx_schedule(rtl); + + return canceled; +} + +static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) +{ + struct ssh_request *r = to_ssh_request(p); + + if (unlikely(status)) { + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return; + + /* + * The packet may get canceled even though it has not been + * submitted yet. The request may still be queued. Check the + * queue and remove it if necessary. As the timeout would have + * been started in this function on success, there's no need + * to cancel it here. + */ + ssh_rtl_queue_remove(r); + ssh_rtl_pending_remove(r); + ssh_rtl_complete_with_status(r, status); + + ssh_rtl_tx_schedule(ssh_request_rtl(r)); + return; + } + + /* Update state: Mark as transmitted and clear transmitting. */ + set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); + + /* If we expect a response, we just need to start the timeout. */ + if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { + /* + * Note: This is the only place where the timestamp gets set, + * all other access to it is read-only. + */ + ssh_rtl_timeout_start(r); + return; + } + + /* + * If we don't expect a response, lock, remove, and complete the + * request. Note that, at this point, the request is guaranteed to have + * left the queue and no timeout has been started. Thus we only need to + * remove it from pending. If the request has already been completed (it + * may have been canceled) return. + */ + + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + return; + + ssh_rtl_pending_remove(r); + ssh_rtl_complete_with_status(r, 0); + + ssh_rtl_tx_schedule(ssh_request_rtl(r)); +} + +static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeout) +{ + ktime_t timestamp = READ_ONCE(r->timestamp); + + if (timestamp != KTIME_MAX) + return ktime_add(timestamp, timeout); + else + return KTIME_MAX; +} + +static void ssh_rtl_timeout_reap(struct work_struct *work) +{ + struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); + struct ssh_request *r, *n; + LIST_HEAD(claimed); + ktime_t now = ktime_get_coarse_boottime(); + ktime_t timeout = rtl->rtx_timeout.timeout; + ktime_t next = KTIME_MAX; + + /* + * Mark reaper as "not pending". This is done before checking any + * requests to avoid lost-update type problems. + */ + spin_lock(&rtl->rtx_timeout.lock); + rtl->rtx_timeout.expires = KTIME_MAX; + spin_unlock(&rtl->rtx_timeout.lock); + + spin_lock(&rtl->pending.lock); + list_for_each_entry_safe(r, n, &rtl->pending.head, node) { + ktime_t expires = ssh_request_get_expiration(r, timeout); + + /* + * Check if the timeout hasn't expired yet. Find out next + * expiration date to be handled after this run. + */ + if (ktime_after(expires, now)) { + next = ktime_before(expires, next) ? expires : next; + continue; + } + + /* Avoid further transitions if locked. */ + if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) + continue; + + /* + * We have now marked the packet as locked. Thus it cannot be + * added to the pending or queued lists again after we've + * removed it here. We can therefore re-use the node of this + * packet temporarily. + */ + + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); + + atomic_dec(&rtl->pending.count); + list_del(&r->node); + + list_add_tail(&r->node, &claimed); + } + spin_unlock(&rtl->pending.lock); + + /* Cancel and complete the request. */ + list_for_each_entry_safe(r, n, &claimed, node) { + /* + * At this point we've removed the packet from pending. This + * means that we've obtained the last (only) reference of the + * system to it. Thus we can just complete it. + */ + if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + ssh_rtl_complete_with_status(r, -ETIMEDOUT); + + /* + * Drop the reference we've obtained by removing it from the + * pending set. + */ + list_del(&r->node); + ssh_request_put(r); + } + + /* Ensure that the reaper doesn't run again immediately. */ + next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); + if (next != KTIME_MAX) + ssh_rtl_timeout_reaper_mod(rtl, now, next); + + ssh_rtl_tx_schedule(rtl); +} + +static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, + const struct ssam_span *data) +{ + rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", + get_unaligned_le16(&cmd->rqid)); + + rtl->ops.handle_event(rtl, cmd, data); +} + +static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) +{ + struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); + struct device *dev = &p->serdev->dev; + struct ssh_command *command; + struct ssam_span command_data; + + if (sshp_parse_command(dev, data, &command, &command_data)) + return; + + if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) + ssh_rtl_rx_event(rtl, command, &command_data); + else + ssh_rtl_complete(rtl, command, &command_data); +} + +static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) +{ + if (!data->len) { + ptl_err(p, "rtl: rx: no data frame payload\n"); + return; + } + + switch (data->ptr[0]) { + case SSH_PLD_TYPE_CMD: + ssh_rtl_rx_command(p, data); + break; + + default: + ptl_err(p, "rtl: rx: unknown frame payload type (type: %#04x)\n", + data->ptr[0]); + break; + } +} + +static void ssh_rtl_packet_release(struct ssh_packet *p) +{ + struct ssh_request *rqst; + + rqst = to_ssh_request(p); + rqst->ops->release(rqst); +} + +static const struct ssh_packet_ops ssh_rtl_packet_ops = { + .complete = ssh_rtl_packet_callback, + .release = ssh_rtl_packet_release, +}; + +/** + * ssh_request_init() - Initialize SSH request. + * @rqst: The request to initialize. + * @flags: Request flags, determining the type of the request. + * @ops: Request operations. + * + * Initializes the given SSH request and underlying packet. Sets the message + * buffer pointer to %NULL and the message buffer length to zero. This buffer + * has to be set separately via ssh_request_set_data() before submission and + * must contain a valid SSH request message. + * + * Return: Returns zero on success or %-EINVAL if the given flags are invalid. + */ +int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, + const struct ssh_request_ops *ops) +{ + unsigned long type = BIT(SSH_PACKET_TY_BLOCKING_BIT); + + /* Unsequenced requests cannot have a response. */ + if (flags & SSAM_REQUEST_UNSEQUENCED && flags & SSAM_REQUEST_HAS_RESPONSE) + return -EINVAL; + + if (!(flags & SSAM_REQUEST_UNSEQUENCED)) + type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); + + ssh_packet_init(&rqst->packet, type, SSH_PACKET_PRIORITY(DATA, 0), + &ssh_rtl_packet_ops); + + INIT_LIST_HEAD(&rqst->node); + + rqst->state = 0; + if (flags & SSAM_REQUEST_HAS_RESPONSE) + rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); + + rqst->timestamp = KTIME_MAX; + rqst->ops = ops; + + return 0; +} + +/** + * ssh_rtl_init() - Initialize request transport layer. + * @rtl: The request transport layer to initialize. + * @serdev: The underlying serial device, i.e. the lower-level transport. + * @ops: Request transport layer operations. + * + * Initializes the given request transport layer and associated packet + * transport layer. Transmitter and receiver threads must be started + * separately via ssh_rtl_tx_start() and ssh_rtl_rx_start(), after the + * request-layer has been initialized and the lower-level serial device layer + * has been set up. + * + * Return: Returns zero on success and a nonzero error code on failure. + */ +int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, + const struct ssh_rtl_ops *ops) +{ + struct ssh_ptl_ops ptl_ops; + int status; + + ptl_ops.data_received = ssh_rtl_rx_data; + + status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); + if (status) + return status; + + spin_lock_init(&rtl->queue.lock); + INIT_LIST_HEAD(&rtl->queue.head); + + spin_lock_init(&rtl->pending.lock); + INIT_LIST_HEAD(&rtl->pending.head); + atomic_set_release(&rtl->pending.count, 0); + + INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); + + spin_lock_init(&rtl->rtx_timeout.lock); + rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; + rtl->rtx_timeout.expires = KTIME_MAX; + INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); + + rtl->ops = *ops; + + return 0; +} + +/** + * ssh_rtl_destroy() - Deinitialize request transport layer. + * @rtl: The request transport layer to deinitialize. + * + * Deinitializes the given request transport layer and frees resources + * associated with it. If receiver and/or transmitter threads have been + * started, the layer must first be shut down via ssh_rtl_shutdown() before + * this function can be called. + */ +void ssh_rtl_destroy(struct ssh_rtl *rtl) +{ + ssh_ptl_destroy(&rtl->ptl); +} + +/** + * ssh_rtl_tx_start() - Start request transmitter and receiver. + * @rtl: The request transport layer. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssh_rtl_start(struct ssh_rtl *rtl) +{ + int status; + + status = ssh_ptl_tx_start(&rtl->ptl); + if (status) + return status; + + ssh_rtl_tx_schedule(rtl); + + status = ssh_ptl_rx_start(&rtl->ptl); + if (status) { + ssh_rtl_flush(rtl, msecs_to_jiffies(5000)); + ssh_ptl_tx_stop(&rtl->ptl); + return status; + } + + return 0; +} + +struct ssh_flush_request { + struct ssh_request base; + struct completion completion; + int status; +}; + +static void ssh_rtl_flush_request_complete(struct ssh_request *r, + const struct ssh_command *cmd, + const struct ssam_span *data, + int status) +{ + struct ssh_flush_request *rqst; + + rqst = container_of(r, struct ssh_flush_request, base); + rqst->status = status; +} + +static void ssh_rtl_flush_request_release(struct ssh_request *r) +{ + struct ssh_flush_request *rqst; + + rqst = container_of(r, struct ssh_flush_request, base); + complete_all(&rqst->completion); +} + +static const struct ssh_request_ops ssh_rtl_flush_request_ops = { + .complete = ssh_rtl_flush_request_complete, + .release = ssh_rtl_flush_request_release, +}; + +/** + * ssh_rtl_flush() - Flush the request transport layer. + * @rtl: request transport layer + * @timeout: timeout for the flush operation in jiffies + * + * Queue a special flush request and wait for its completion. This request + * will be completed after all other currently queued and pending requests + * have been completed. Instead of a normal data packet, this request submits + * a special flush packet, meaning that upon completion, also the underlying + * packet transport layer has been flushed. + * + * Flushing the request layer guarantees that all previously submitted + * requests have been fully completed before this call returns. Additionally, + * flushing blocks execution of all later submitted requests until the flush + * has been completed. + * + * If the caller ensures that no new requests are submitted after a call to + * this function, the request transport layer is guaranteed to have no + * remaining requests when this call returns. The same guarantee does not hold + * for the packet layer, on which control packets may still be queued after + * this call. + * + * Return: Returns zero on success, %-ETIMEDOUT if the flush timed out and has + * been canceled as a result of the timeout, or %-ESHUTDOWN if the packet + * and/or request transport layer has been shut down before this call. May + * also return %-EINTR if the underlying packet transmission has been + * interrupted. + */ +int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) +{ + const unsigned int init_flags = SSAM_REQUEST_UNSEQUENCED; + struct ssh_flush_request rqst; + int status; + + ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); + rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); + rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); + rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); + + init_completion(&rqst.completion); + + status = ssh_rtl_submit(rtl, &rqst.base); + if (status) + return status; + + ssh_request_put(&rqst.base); + + if (!wait_for_completion_timeout(&rqst.completion, timeout)) { + ssh_rtl_cancel(&rqst.base, true); + wait_for_completion(&rqst.completion); + } + + WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED && + rqst.status != -ESHUTDOWN && rqst.status != -EINTR); + + return rqst.status == -ECANCELED ? -ETIMEDOUT : rqst.status; +} + +/** + * ssh_rtl_shutdown() - Shut down request transport layer. + * @rtl: The request transport layer. + * + * Shuts down the request transport layer, removing and canceling all queued + * and pending requests. Requests canceled by this operation will be completed + * with %-ESHUTDOWN as status. Receiver and transmitter threads will be + * stopped, the lower-level packet layer will be shutdown. + * + * As a result of this function, the transport layer will be marked as shut + * down. Submission of requests after the transport layer has been shut down + * will fail with %-ESHUTDOWN. + */ +void ssh_rtl_shutdown(struct ssh_rtl *rtl) +{ + struct ssh_request *r, *n; + LIST_HEAD(claimed); + int pending; + + set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); + /* + * Ensure that the layer gets marked as shut-down before actually + * stopping it. In combination with the check in ssh_rtl_submit(), + * this guarantees that no new requests can be added and all already + * queued requests are properly canceled. + */ + smp_mb__after_atomic(); + + /* Remove requests from queue. */ + spin_lock(&rtl->queue.lock); + list_for_each_entry_safe(r, n, &rtl->queue.head, node) { + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); + + list_del(&r->node); + list_add_tail(&r->node, &claimed); + } + spin_unlock(&rtl->queue.lock); + + /* + * We have now guaranteed that the queue is empty and no more new + * requests can be submitted (i.e. it will stay empty). This means that + * calling ssh_rtl_tx_schedule() will not schedule tx.work any more. So + * we can simply call cancel_work_sync() on tx.work here and when that + * returns, we've locked it down. This also means that after this call, + * we don't submit any more packets to the underlying packet layer, so + * we can also shut that down. + */ + + cancel_work_sync(&rtl->tx.work); + ssh_ptl_shutdown(&rtl->ptl); + cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); + + /* + * Shutting down the packet layer should also have canceled all + * requests. Thus the pending set should be empty. Attempt to handle + * this gracefully anyways, even though this should be dead code. + */ + + pending = atomic_read(&rtl->pending.count); + if (WARN_ON(pending)) { + spin_lock(&rtl->pending.lock); + list_for_each_entry_safe(r, n, &rtl->pending.head, node) { + set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); + /* Ensure state never gets zero. */ + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); + + list_del(&r->node); + list_add_tail(&r->node, &claimed); + } + spin_unlock(&rtl->pending.lock); + } + + /* Finally, cancel and complete the requests we claimed before. */ + list_for_each_entry_safe(r, n, &claimed, node) { + /* + * We need test_and_set() because we still might compete with + * cancellation. + */ + if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) + ssh_rtl_complete_with_status(r, -ESHUTDOWN); + + /* + * Drop the reference we've obtained by removing it from the + * lists. + */ + list_del(&r->node); + ssh_request_put(r); + } +} diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h new file mode 100644 index 000000000000..cb35815858d1 --- /dev/null +++ b/drivers/platform/surface/aggregator/ssh_request_layer.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * SSH request transport layer. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H +#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "ssh_packet_layer.h" + +/** + * enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl. + * + * @SSH_RTL_SF_SHUTDOWN_BIT: + * Indicates that the request transport layer has been shut down or is + * being shut down and should not accept any new requests. + */ +enum ssh_rtl_state_flags { + SSH_RTL_SF_SHUTDOWN_BIT, +}; + +/** + * struct ssh_rtl_ops - Callback operations for request transport layer. + * @handle_event: Function called when a SSH event has been received. The + * specified function takes the request layer, received command + * struct, and corresponding payload as arguments. If the event + * has no payload, the payload span is empty (not %NULL). + */ +struct ssh_rtl_ops { + void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, + const struct ssam_span *data); +}; + +/** + * struct ssh_rtl - SSH request transport layer. + * @ptl: Underlying packet transport layer. + * @state: State(-flags) of the transport layer. + * @queue: Request submission queue. + * @queue.lock: Lock for modifying the request submission queue. + * @queue.head: List-head of the request submission queue. + * @pending: Set/list of pending requests. + * @pending.lock: Lock for modifying the request set. + * @pending.head: List-head of the pending set/list. + * @pending.count: Number of currently pending requests. + * @tx: Transmitter subsystem. + * @tx.work: Transmitter work item. + * @rtx_timeout: Retransmission timeout subsystem. + * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. + * @rtx_timeout.timeout: Timeout interval for retransmission. + * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. + * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. + * @ops: Request layer operations. + */ +struct ssh_rtl { + struct ssh_ptl ptl; + unsigned long state; + + struct { + spinlock_t lock; + struct list_head head; + } queue; + + struct { + spinlock_t lock; + struct list_head head; + atomic_t count; + } pending; + + struct { + struct work_struct work; + } tx; + + struct { + spinlock_t lock; + ktime_t timeout; + ktime_t expires; + struct delayed_work reaper; + } rtx_timeout; + + struct ssh_rtl_ops ops; +}; + +#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) +#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) +#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) +#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) +#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) + +#define to_ssh_rtl(ptr, member) \ + container_of(ptr, struct ssh_rtl, member) + +/** + * ssh_rtl_get_device() - Get device associated with request transport layer. + * @rtl: The request transport layer. + * + * Return: Returns the device on which the given request transport layer + * builds upon. + */ +static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) +{ + return ssh_ptl_get_device(&rtl->ptl); +} + +/** + * ssh_request_rtl() - Get request transport layer associated with request. + * @rqst: The request to get the request transport layer reference for. + * + * Return: Returns the &struct ssh_rtl associated with the given SSH request. + */ +static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) +{ + struct ssh_ptl *ptl; + + ptl = READ_ONCE(rqst->packet.ptl); + return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; +} + +int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst); +bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending); + +int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, + const struct ssh_rtl_ops *ops); + +int ssh_rtl_start(struct ssh_rtl *rtl); +int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout); +void ssh_rtl_shutdown(struct ssh_rtl *rtl); +void ssh_rtl_destroy(struct ssh_rtl *rtl); + +int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, + const struct ssh_request_ops *ops); + +#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ -- cgit v1.2.3 From 44b84ee7b437dd7f869341b4b671963161a34a9f Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:52 +0100 Subject: platform/surface: aggregator: Add control packet allocation caching Surface Serial Hub communication is, in its core, packet based. Each sequenced packet requires to be acknowledged, via an ACK-type control packet. In case invalid data has been received by the driver, a NAK-type (not-acknowledge/negative acknowledge) control packet is sent, triggering retransmission. Control packets are therefore a core communication primitive and used frequently enough (with every sequenced packet transmission sent by the embedded controller, including events and request responses) that it may warrant caching their allocations to reduce possible memory fragmentation. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20201221183959.1186143-3-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/core.c | 27 ++++++++++++- .../platform/surface/aggregator/ssh_packet_layer.c | 47 +++++++++++++++++----- .../platform/surface/aggregator/ssh_packet_layer.h | 3 ++ 3 files changed, 67 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index 18e0e9e34e7b..60d312f71436 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -780,7 +780,32 @@ static struct serdev_device_driver ssam_serial_hub = { .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; -module_serdev_device_driver(ssam_serial_hub); + + +/* -- Module setup. --------------------------------------------------------- */ + +static int __init ssam_core_init(void) +{ + int status; + + status = ssh_ctrl_packet_cache_init(); + if (status) + return status; + + status = serdev_device_driver_register(&ssam_serial_hub); + if (status) + ssh_ctrl_packet_cache_destroy(); + + return status; +} +module_init(ssam_core_init); + +static void __exit ssam_core_exit(void) +{ + serdev_device_driver_unregister(&ssam_serial_hub); + ssh_ctrl_packet_cache_destroy(); +} +module_exit(ssam_core_exit); MODULE_AUTHOR("Maximilian Luz "); MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 66e38fdc7963..23c2e31e7d0e 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -303,24 +303,53 @@ void ssh_packet_init(struct ssh_packet *packet, unsigned long type, packet->ops = ops; } +static struct kmem_cache *ssh_ctrl_packet_cache; + +/** + * ssh_ctrl_packet_cache_init() - Initialize the control packet cache. + */ +int ssh_ctrl_packet_cache_init(void) +{ + const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; + const unsigned int align = __alignof__(struct ssh_packet); + struct kmem_cache *cache; + + cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); + if (!cache) + return -ENOMEM; + + ssh_ctrl_packet_cache = cache; + return 0; +} + +/** + * ssh_ctrl_packet_cache_destroy() - Deinitialize the control packet cache. + */ +void ssh_ctrl_packet_cache_destroy(void) +{ + kmem_cache_destroy(ssh_ctrl_packet_cache); + ssh_ctrl_packet_cache = NULL; +} + /** - * ssh_ctrl_packet_alloc() - Allocate control packet. + * ssh_ctrl_packet_alloc() - Allocate packet from control packet cache. * @packet: Where the pointer to the newly allocated packet should be stored. * @buffer: The buffer corresponding to this packet. * @flags: Flags used for allocation. * - * Allocates a packet and corresponding transport buffer. Sets the packet's - * buffer reference to the allocated buffer. The packet must be freed via - * ssh_ctrl_packet_free(), which will also free the corresponding buffer. The - * corresponding buffer must not be freed separately. Intended to be used with - * %ssh_ptl_ctrl_packet_ops as packet operations. + * Allocates a packet and corresponding transport buffer from the control + * packet cache. Sets the packet's buffer reference to the allocated buffer. + * The packet must be freed via ssh_ctrl_packet_free(), which will also free + * the corresponding buffer. The corresponding buffer must not be freed + * separately. Intended to be used with %ssh_ptl_ctrl_packet_ops as packet + * operations. * * Return: Returns zero on success, %-ENOMEM if the allocation failed. */ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, struct ssam_span *buffer, gfp_t flags) { - *packet = kzalloc(sizeof(**packet) + SSH_MSG_LEN_CTRL, flags); + *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); if (!*packet) return -ENOMEM; @@ -331,12 +360,12 @@ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, } /** - * ssh_ctrl_packet_free() - Free control packet. + * ssh_ctrl_packet_free() - Free packet allocated from control packet cache. * @p: The packet to free. */ static void ssh_ctrl_packet_free(struct ssh_packet *p) { - kfree(p); + kmem_cache_free(ssh_ctrl_packet_cache, p); } static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h index 058f111292ca..e8757d03f279 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.h +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h @@ -184,4 +184,7 @@ static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) void ssh_packet_init(struct ssh_packet *packet, unsigned long type, u8 priority, const struct ssh_packet_ops *ops); +int ssh_ctrl_packet_cache_init(void); +void ssh_ctrl_packet_cache_destroy(void); + #endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ -- cgit v1.2.3 From 3a7081f610a0ff6385f38cf65a019383cd34bfdd Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:53 +0100 Subject: platform/surface: aggregator: Add event item allocation caching Event items are used for completing Surface Aggregator EC events, i.e. placing event command data and payload on a workqueue for later processing to avoid doing said processing directly on the receiver thread. This means that event items are allocated for each incoming event, regardless of that event being transmitted via sequenced or unsequenced packets. On the Surface Book 3 and Surface Laptop 3, touchpad HID input events (unsequenced), can constitute a larger amount of traffic, and therefore allocation of event items. This warrants caching event items to reduce memory fragmentation. The size of the cached objects is specifically tuned to accommodate keyboard and touchpad input events and their payloads on those devices. As a result, this effectively also covers most other event types. In case of a larger event payload, event item allocation will fall back to kzalloc(). Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20201221183959.1186143-4-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/controller.c | 86 +++++++++++++++++++++--- drivers/platform/surface/aggregator/controller.h | 9 +++ drivers/platform/surface/aggregator/core.c | 16 ++++- 3 files changed, 101 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 488318cf2098..775a4509bece 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -513,14 +513,74 @@ static void ssam_nf_destroy(struct ssam_nf *nf) */ #define SSAM_CPLT_WQ_BATCH 10 +/* + * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached + * &struct ssam_event_item. + * + * This length has been chosen to be accommodate standard touchpad and + * keyboard input events. Events with larger payloads will be allocated + * separately. + */ +#define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN 32 + +static struct kmem_cache *ssam_event_item_cache; + +/** + * ssam_event_item_cache_init() - Initialize the event item cache. + */ +int ssam_event_item_cache_init(void) +{ + const unsigned int size = sizeof(struct ssam_event_item) + + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; + const unsigned int align = __alignof__(struct ssam_event_item); + struct kmem_cache *cache; + + cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL); + if (!cache) + return -ENOMEM; + + ssam_event_item_cache = cache; + return 0; +} + +/** + * ssam_event_item_cache_destroy() - Deinitialize the event item cache. + */ +void ssam_event_item_cache_destroy(void) +{ + kmem_cache_destroy(ssam_event_item_cache); + ssam_event_item_cache = NULL; +} + +static void __ssam_event_item_free_cached(struct ssam_event_item *item) +{ + kmem_cache_free(ssam_event_item_cache, item); +} + +static void __ssam_event_item_free_generic(struct ssam_event_item *item) +{ + kfree(item); +} + +/** + * ssam_event_item_free() - Free the provided event item. + * @item: The event item to free. + */ +static void ssam_event_item_free(struct ssam_event_item *item) +{ + item->ops.free(item); +} + /** * ssam_event_item_alloc() - Allocate an event item with the given payload size. * @len: The event payload length. * @flags: The flags used for allocation. * - * Allocate an event item with the given payload size. Sets the item - * operations and payload length values. The item free callback (``ops.free``) - * should not be overwritten after this call. + * Allocate an event item with the given payload size, preferring allocation + * from the event item cache if the payload is small enough (i.e. smaller than + * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload + * length values. The item free callback (``ops.free``) should not be + * overwritten after this call. * * Return: Returns the newly allocated event item. */ @@ -528,9 +588,19 @@ static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) { struct ssam_event_item *item; - item = kzalloc(struct_size(item, event.data, len), flags); - if (!item) - return NULL; + if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) { + item = kmem_cache_alloc(ssam_event_item_cache, flags); + if (!item) + return NULL; + + item->ops.free = __ssam_event_item_free_cached; + } else { + item = kzalloc(struct_size(item, event.data, len), flags); + if (!item) + return NULL; + + item->ops.free = __ssam_event_item_free_generic; + } item->event.length = len; return item; @@ -692,7 +762,7 @@ static void ssam_event_queue_work_fn(struct work_struct *work) return; ssam_nf_call(nf, dev, item->rqid, &item->event); - kfree(item); + ssam_event_item_free(item); } while (--iterations); if (!ssam_event_queue_is_empty(queue)) @@ -900,7 +970,7 @@ static void ssam_handle_event(struct ssh_rtl *rtl, memcpy(&item->event.data[0], data->ptr, data->len); if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) - kfree(item); + ssam_event_item_free(item); } static const struct ssh_rtl_ops ssam_rtl_ops = { diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h index 5ee9e966f1d7..8297d34e7489 100644 --- a/drivers/platform/surface/aggregator/controller.h +++ b/drivers/platform/surface/aggregator/controller.h @@ -80,12 +80,18 @@ struct ssam_cplt; * struct ssam_event_item - Struct for event queuing and completion. * @node: The node in the queue. * @rqid: The request ID of the event. + * @ops: Instance specific functions. + * @ops.free: Callback for freeing this event item. * @event: Actual event data. */ struct ssam_event_item { struct list_head node; u16 rqid; + struct { + void (*free)(struct ssam_event_item *event); + } ops; + struct ssam_event event; /* must be last */ }; @@ -273,4 +279,7 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); int ssam_controller_suspend(struct ssam_controller *ctrl); int ssam_controller_resume(struct ssam_controller *ctrl); +int ssam_event_item_cache_init(void); +void ssam_event_item_cache_destroy(void); + #endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index 60d312f71436..37593234fb31 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -790,12 +790,23 @@ static int __init ssam_core_init(void) status = ssh_ctrl_packet_cache_init(); if (status) - return status; + goto err_cpkg; + + status = ssam_event_item_cache_init(); + if (status) + goto err_evitem; status = serdev_device_driver_register(&ssam_serial_hub); if (status) - ssh_ctrl_packet_cache_destroy(); + goto err_register; + return 0; + +err_register: + ssam_event_item_cache_destroy(); +err_evitem: + ssh_ctrl_packet_cache_destroy(); +err_cpkg: return status; } module_init(ssam_core_init); @@ -803,6 +814,7 @@ module_init(ssam_core_init); static void __exit ssam_core_exit(void) { serdev_device_driver_unregister(&ssam_serial_hub); + ssam_event_item_cache_destroy(); ssh_ctrl_packet_cache_destroy(); } module_exit(ssam_core_exit); -- cgit v1.2.3 From 0d21bb8560ef6bd09cab873120f940a939ad3aec Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:54 +0100 Subject: platform/surface: aggregator: Add trace points Add trace points to the Surface Aggregator subsystem core. These trace points can be used to track packets, requests, and allocations. They are further intended for debugging and testing/validation, specifically in combination with the error injection capabilities introduced in the subsequent commit. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Acked-by: Steven Rostedt (VMware) Link: https://lore.kernel.org/r/20201221183959.1186143-5-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/Makefile | 3 + drivers/platform/surface/aggregator/controller.c | 5 + drivers/platform/surface/aggregator/core.c | 3 + .../platform/surface/aggregator/ssh_packet_layer.c | 26 +- .../surface/aggregator/ssh_request_layer.c | 18 + drivers/platform/surface/aggregator/trace.h | 601 +++++++++++++++++++++ 6 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/surface/aggregator/trace.h (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile index faad18d4a7f2..b8b24c8ec310 100644 --- a/drivers/platform/surface/aggregator/Makefile +++ b/drivers/platform/surface/aggregator/Makefile @@ -1,6 +1,9 @@ # SPDX-License-Identifier: GPL-2.0+ # Copyright (C) 2019-2020 Maximilian Luz +# For include/trace/define_trace.h to include trace.h +CFLAGS_core.o = -I$(src) + obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o surface_aggregator-objs := core.o diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 775a4509bece..5bcb59ed579d 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -32,6 +32,8 @@ #include "ssh_msgb.h" #include "ssh_request_layer.h" +#include "trace.h" + /* -- Safe counters. -------------------------------------------------------- */ @@ -568,6 +570,7 @@ static void __ssam_event_item_free_generic(struct ssam_event_item *item) */ static void ssam_event_item_free(struct ssam_event_item *item) { + trace_ssam_event_item_free(item); item->ops.free(item); } @@ -603,6 +606,8 @@ static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) } item->event.length = len; + + trace_ssam_event_item_alloc(item, len); return item; } diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index 37593234fb31..b6a9dea53592 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -24,6 +24,9 @@ #include #include "controller.h" +#define CREATE_TRACE_POINTS +#include "trace.h" + /* -- Static controller reference. ------------------------------------------ */ diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 23c2e31e7d0e..c4f082e57372 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -26,6 +26,8 @@ #include "ssh_packet_layer.h" #include "ssh_parser.h" +#include "trace.h" + /* * To simplify reasoning about the code below, we define a few concepts. The * system below is similar to a state-machine for packets, however, there are @@ -228,6 +230,8 @@ static void __ssh_ptl_packet_release(struct kref *kref) { struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); + trace_ssam_packet_release(p); + ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); p->ops->release(p); } @@ -356,6 +360,7 @@ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, buffer->ptr = (u8 *)(*packet + 1); buffer->len = SSH_MSG_LEN_CTRL; + trace_ssam_ctrl_packet_alloc(*packet, buffer->len); return 0; } @@ -365,6 +370,7 @@ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, */ static void ssh_ctrl_packet_free(struct ssh_packet *p) { + trace_ssam_ctrl_packet_free(p); kmem_cache_free(ssh_ctrl_packet_cache, p); } @@ -398,7 +404,12 @@ static void ssh_packet_next_try(struct ssh_packet *p) lockdep_assert_held(&p->ptl->queue.lock); - p->priority = __SSH_PACKET_PRIORITY(base, try + 1); + /* + * Ensure that we write the priority in one go via WRITE_ONCE() so we + * can access it via READ_ONCE() for tracing. Note that other access + * is guarded by the queue lock, so no need to use READ_ONCE() there. + */ + WRITE_ONCE(p->priority, __SSH_PACKET_PRIORITY(base, try + 1)); } /* Must be called with queue lock held. */ @@ -560,6 +571,7 @@ static void __ssh_ptl_complete(struct ssh_packet *p, int status) { struct ssh_ptl *ptl = READ_ONCE(p->ptl); + trace_ssam_packet_complete(p, status); ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); if (p->ops->complete) @@ -1014,6 +1026,8 @@ int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) struct ssh_ptl *ptl_old; int status; + trace_ssam_packet_submit(p); + /* Validate packet fields. */ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) @@ -1065,6 +1079,8 @@ static int __ssh_ptl_resubmit(struct ssh_packet *packet) lockdep_assert_held(&packet->ptl->pending.lock); + trace_ssam_packet_resubmit(packet); + spin_lock(&packet->ptl->queue.lock); /* Check if the packet is out of tries. */ @@ -1148,6 +1164,8 @@ void ssh_ptl_cancel(struct ssh_packet *p) if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) return; + trace_ssam_packet_cancel(p); + /* * Lock packet and commit with memory barrier. If this packet has * already been locked, it's going to be removed and completed by @@ -1202,6 +1220,8 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) bool resub = false; int status; + trace_ssam_ptl_timeout_reap(atomic_read(&ptl->pending.count)); + /* * Mark reaper as "not pending". This is done before checking any * packets to avoid lost-update type problems. @@ -1224,6 +1244,8 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) continue; } + trace_ssam_packet_timeout(p); + status = __ssh_ptl_resubmit(p); /* @@ -1416,6 +1438,8 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) if (!frame) /* Not enough data. */ return aligned.ptr - source->ptr; + trace_ssam_rx_frame_received(frame); + switch (frame->type) { case SSH_FRAME_TYPE_ACK: ssh_ptl_acknowledge(ptl, frame->seq); diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index 66c839a995f3..b649d71840fd 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -22,6 +22,8 @@ #include "ssh_packet_layer.h" #include "ssh_request_layer.h" +#include "trace.h" + /* * SSH_RTL_REQUEST_TIMEOUT - Request timeout. * @@ -144,6 +146,8 @@ static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) { struct ssh_rtl *rtl = ssh_request_rtl(rqst); + trace_ssam_request_complete(rqst, status); + /* rtl/ptl may not be set if we're canceling before submitting. */ rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", ssh_request_get_rqid_safe(rqst), status); @@ -157,6 +161,8 @@ static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, { struct ssh_rtl *rtl = ssh_request_rtl(rqst); + trace_ssam_request_complete(rqst, 0); + rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", ssh_request_get_rqid(rqst)); @@ -329,6 +335,8 @@ static void ssh_rtl_tx_work_fn(struct work_struct *work) */ int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) { + trace_ssam_request_submit(rqst); + /* * Ensure that requests expecting a response are sequenced. If this * invariant ever changes, see the comment in ssh_rtl_complete() on what @@ -439,6 +447,8 @@ static void ssh_rtl_complete(struct ssh_rtl *rtl, struct ssh_request *p, *n; u16 rqid = get_unaligned_le16(&command->rqid); + trace_ssam_rx_response_received(command, command_data->len); + /* * Get request from pending based on request ID and mark it as response * received and locked. @@ -688,6 +698,8 @@ bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) return true; + trace_ssam_request_cancel(rqst); + if (pending) canceled = ssh_rtl_cancel_pending(rqst); else @@ -779,6 +791,8 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) ktime_t timeout = rtl->rtx_timeout.timeout; ktime_t next = KTIME_MAX; + trace_ssam_rtl_timeout_reap(atomic_read(&rtl->pending.count)); + /* * Mark reaper as "not pending". This is done before checking any * requests to avoid lost-update type problems. @@ -822,6 +836,8 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) /* Cancel and complete the request. */ list_for_each_entry_safe(r, n, &claimed, node) { + trace_ssam_request_timeout(r); + /* * At this point we've removed the packet from pending. This * means that we've obtained the last (only) reference of the @@ -849,6 +865,8 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, const struct ssam_span *data) { + trace_ssam_rx_event_received(cmd, data->len); + rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", get_unaligned_le16(&cmd->rqid)); diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h new file mode 100644 index 000000000000..dcca8007d876 --- /dev/null +++ b/drivers/platform/surface/aggregator/trace.h @@ -0,0 +1,601 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Trace points for SSAM/SSH. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM surface_aggregator + +#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SURFACE_AGGREGATOR_TRACE_H + +#include + +#include +#include + +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); + +TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); + +TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); +TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); + +TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); +TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); + +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); + +TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); +TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); + +TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); +TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); + +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); +TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); + +#define SSAM_PTR_UID_LEN 9 +#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) +#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) +#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) +#define SSAM_SSH_TC_NOT_APPLICABLE 0 + +#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS +#define _SURFACE_AGGREGATOR_TRACE_HELPERS + +/** + * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. + * @ptr: The pointer to convert. + * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. + * + * Converts the given pointer into a UID string that is safe to be shared + * with userspace and logs, i.e. doesn't give away the real memory location. + */ +static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) +{ + char buf[2 * sizeof(void *) + 1]; + + BUILD_BUG_ON(ARRAY_SIZE(buf) < SSAM_PTR_UID_LEN); + + snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); + memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], + SSAM_PTR_UID_LEN); +} + +/** + * ssam_trace_get_packet_seq() - Read the packet's sequence ID. + * @p: The packet. + * + * Return: Returns the packet's sequence ID (SEQ) field if present, or + * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). + */ +static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) + return SSAM_SEQ_NOT_APPLICABLE; + + return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; +} + +/** + * ssam_trace_get_request_id() - Read the packet's request ID. + * @p: The packet. + * + * Return: Returns the packet's request ID (RQID) field if the packet + * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not + * (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_RQID_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); +} + +/** + * ssam_trace_get_request_tc() - Read the packet's request target category. + * @p: The packet. + * + * Return: Returns the packet's request target category (TC) field if the + * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE + * if not (e.g. flush request, control packet). + */ +static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) +{ + if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) + return SSAM_SSH_TC_NOT_APPLICABLE; + + return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); +} + +#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */ + +#define ssam_trace_get_command_field_u8(packet, field) \ + ((!(packet) || (packet)->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ + ? 0 : (packet)->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) + +#define ssam_show_generic_u8_field(value) \ + __print_symbolic(value, \ + { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ + ) + +#define ssam_show_frame_type(ty) \ + __print_symbolic(ty, \ + { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ + { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ + { SSH_FRAME_TYPE_ACK, "ACK" }, \ + { SSH_FRAME_TYPE_NAK, "NAK" } \ + ) + +#define ssam_show_packet_type(type) \ + __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ + { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ + { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ + { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ + ) + +#define ssam_show_packet_state(state) \ + __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ + { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ + { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ + { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ + { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ + { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ + { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ + { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ + { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ + ) + +#define ssam_show_packet_seq(seq) \ + __print_symbolic(seq, \ + { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ + ) + +#define ssam_show_request_type(flags) \ + __print_flags((flags) & SSH_REQUEST_FLAGS_TY_MASK, "", \ + { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ + { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ + ) + +#define ssam_show_request_state(flags) \ + __print_flags((flags) & SSH_REQUEST_FLAGS_SF_MASK, "", \ + { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ + { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ + { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ + { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ + { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ + { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ + { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ + { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ + ) + +#define ssam_show_request_id(rqid) \ + __print_symbolic(rqid, \ + { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ + ) + +#define ssam_show_ssh_tc(rqid) \ + __print_symbolic(rqid, \ + { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ + { SSAM_SSH_TC_SAM, "SAM" }, \ + { SSAM_SSH_TC_BAT, "BAT" }, \ + { SSAM_SSH_TC_TMP, "TMP" }, \ + { SSAM_SSH_TC_PMC, "PMC" }, \ + { SSAM_SSH_TC_FAN, "FAN" }, \ + { SSAM_SSH_TC_PoM, "PoM" }, \ + { SSAM_SSH_TC_DBG, "DBG" }, \ + { SSAM_SSH_TC_KBD, "KBD" }, \ + { SSAM_SSH_TC_FWU, "FWU" }, \ + { SSAM_SSH_TC_UNI, "UNI" }, \ + { SSAM_SSH_TC_LPC, "LPC" }, \ + { SSAM_SSH_TC_TCL, "TCL" }, \ + { SSAM_SSH_TC_SFL, "SFL" }, \ + { SSAM_SSH_TC_KIP, "KIP" }, \ + { SSAM_SSH_TC_EXT, "EXT" }, \ + { SSAM_SSH_TC_BLD, "BLD" }, \ + { SSAM_SSH_TC_BAS, "BAS" }, \ + { SSAM_SSH_TC_SEN, "SEN" }, \ + { SSAM_SSH_TC_SRQ, "SRQ" }, \ + { SSAM_SSH_TC_MCU, "MCU" }, \ + { SSAM_SSH_TC_HID, "HID" }, \ + { SSAM_SSH_TC_TCH, "TCH" }, \ + { SSAM_SSH_TC_BKL, "BKL" }, \ + { SSAM_SSH_TC_TAM, "TAM" }, \ + { SSAM_SSH_TC_ACC, "ACC" }, \ + { SSAM_SSH_TC_UFI, "UFI" }, \ + { SSAM_SSH_TC_USC, "USC" }, \ + { SSAM_SSH_TC_PEN, "PEN" }, \ + { SSAM_SSH_TC_VID, "VID" }, \ + { SSAM_SSH_TC_AUD, "AUD" }, \ + { SSAM_SSH_TC_SMC, "SMC" }, \ + { SSAM_SSH_TC_KPD, "KPD" }, \ + { SSAM_SSH_TC_REG, "REG" } \ + ) + +DECLARE_EVENT_CLASS(ssam_frame_class, + TP_PROTO(const struct ssh_frame *frame), + + TP_ARGS(frame), + + TP_STRUCT__entry( + __field(u8, type) + __field(u8, seq) + __field(u16, len) + ), + + TP_fast_assign( + __entry->type = frame->type; + __entry->seq = frame->seq; + __entry->len = get_unaligned_le16(&frame->len); + ), + + TP_printk("ty=%s, seq=%#04x, len=%u", + ssam_show_frame_type(__entry->type), + __entry->seq, + __entry->len + ) +); + +#define DEFINE_SSAM_FRAME_EVENT(name) \ + DEFINE_EVENT(ssam_frame_class, ssam_##name, \ + TP_PROTO(const struct ssh_frame *frame), \ + TP_ARGS(frame) \ + ) + +DECLARE_EVENT_CLASS(ssam_command_class, + TP_PROTO(const struct ssh_command *cmd, u16 len), + + TP_ARGS(cmd, len), + + TP_STRUCT__entry( + __field(u16, rqid) + __field(u16, len) + __field(u8, tc) + __field(u8, cid) + __field(u8, iid) + ), + + TP_fast_assign( + __entry->rqid = get_unaligned_le16(&cmd->rqid); + __entry->tc = cmd->tc; + __entry->cid = cmd->cid; + __entry->iid = cmd->iid; + __entry->len = len; + ), + + TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", + __entry->rqid, + ssam_show_ssh_tc(__entry->tc), + __entry->cid, + __entry->iid, + __entry->len + ) +); + +#define DEFINE_SSAM_COMMAND_EVENT(name) \ + DEFINE_EVENT(ssam_command_class, ssam_##name, \ + TP_PROTO(const struct ssh_command *cmd, u16 len), \ + TP_ARGS(cmd, len) \ + ) + +DECLARE_EVENT_CLASS(ssam_packet_class, + TP_PROTO(const struct ssh_packet *packet), + + TP_ARGS(packet), + + TP_STRUCT__entry( + __field(unsigned long, state) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, priority) + __field(u16, length) + __field(u16, seq) + ), + + TP_fast_assign( + __entry->state = READ_ONCE(packet->state); + ssam_trace_ptr_uid(packet, __entry->uid); + __entry->priority = READ_ONCE(packet->priority); + __entry->length = packet->data.len; + __entry->seq = ssam_trace_get_packet_seq(packet); + ), + + TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s", + __entry->uid, + ssam_show_packet_seq(__entry->seq), + ssam_show_packet_type(__entry->state), + __entry->priority, + __entry->length, + ssam_show_packet_state(__entry->state) + ) +); + +#define DEFINE_SSAM_PACKET_EVENT(name) \ + DEFINE_EVENT(ssam_packet_class, ssam_##name, \ + TP_PROTO(const struct ssh_packet *packet), \ + TP_ARGS(packet) \ + ) + +DECLARE_EVENT_CLASS(ssam_packet_status_class, + TP_PROTO(const struct ssh_packet *packet, int status), + + TP_ARGS(packet, status), + + TP_STRUCT__entry( + __field(unsigned long, state) + __field(int, status) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, priority) + __field(u16, length) + __field(u16, seq) + ), + + TP_fast_assign( + __entry->state = READ_ONCE(packet->state); + __entry->status = status; + ssam_trace_ptr_uid(packet, __entry->uid); + __entry->priority = READ_ONCE(packet->priority); + __entry->length = packet->data.len; + __entry->seq = ssam_trace_get_packet_seq(packet); + ), + + TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s, status=%d", + __entry->uid, + ssam_show_packet_seq(__entry->seq), + ssam_show_packet_type(__entry->state), + __entry->priority, + __entry->length, + ssam_show_packet_state(__entry->state), + __entry->status + ) +); + +#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ + DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ + TP_PROTO(const struct ssh_packet *packet, int status), \ + TP_ARGS(packet, status) \ + ) + +DECLARE_EVENT_CLASS(ssam_request_class, + TP_PROTO(const struct ssh_request *request), + + TP_ARGS(request), + + TP_STRUCT__entry( + __field(unsigned long, state) + __field(u32, rqid) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, tc) + __field(u16, cid) + __field(u16, iid) + ), + + TP_fast_assign( + const struct ssh_packet *p = &request->packet; + + /* Use packet for UID so we can match requests to packets. */ + __entry->state = READ_ONCE(request->state); + __entry->rqid = ssam_trace_get_request_id(p); + ssam_trace_ptr_uid(p, __entry->uid); + __entry->tc = ssam_trace_get_request_tc(p); + __entry->cid = ssam_trace_get_command_field_u8(p, cid); + __entry->iid = ssam_trace_get_command_field_u8(p, iid); + ), + + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", + __entry->uid, + ssam_show_request_id(__entry->rqid), + ssam_show_request_type(__entry->state), + ssam_show_request_state(__entry->state), + ssam_show_ssh_tc(__entry->tc), + ssam_show_generic_u8_field(__entry->cid), + ssam_show_generic_u8_field(__entry->iid) + ) +); + +#define DEFINE_SSAM_REQUEST_EVENT(name) \ + DEFINE_EVENT(ssam_request_class, ssam_##name, \ + TP_PROTO(const struct ssh_request *request), \ + TP_ARGS(request) \ + ) + +DECLARE_EVENT_CLASS(ssam_request_status_class, + TP_PROTO(const struct ssh_request *request, int status), + + TP_ARGS(request, status), + + TP_STRUCT__entry( + __field(unsigned long, state) + __field(u32, rqid) + __field(int, status) + __array(char, uid, SSAM_PTR_UID_LEN) + __field(u8, tc) + __field(u16, cid) + __field(u16, iid) + ), + + TP_fast_assign( + const struct ssh_packet *p = &request->packet; + + /* Use packet for UID so we can match requests to packets. */ + __entry->state = READ_ONCE(request->state); + __entry->rqid = ssam_trace_get_request_id(p); + __entry->status = status; + ssam_trace_ptr_uid(p, __entry->uid); + __entry->tc = ssam_trace_get_request_tc(p); + __entry->cid = ssam_trace_get_command_field_u8(p, cid); + __entry->iid = ssam_trace_get_command_field_u8(p, iid); + ), + + TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", + __entry->uid, + ssam_show_request_id(__entry->rqid), + ssam_show_request_type(__entry->state), + ssam_show_request_state(__entry->state), + ssam_show_ssh_tc(__entry->tc), + ssam_show_generic_u8_field(__entry->cid), + ssam_show_generic_u8_field(__entry->iid), + __entry->status + ) +); + +#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ + DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ + TP_PROTO(const struct ssh_request *request, int status),\ + TP_ARGS(request, status) \ + ) + +DECLARE_EVENT_CLASS(ssam_alloc_class, + TP_PROTO(void *ptr, size_t len), + + TP_ARGS(ptr, len), + + TP_STRUCT__entry( + __field(size_t, len) + __array(char, uid, SSAM_PTR_UID_LEN) + ), + + TP_fast_assign( + __entry->len = len; + ssam_trace_ptr_uid(ptr, __entry->uid); + ), + + TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) +); + +#define DEFINE_SSAM_ALLOC_EVENT(name) \ + DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ + TP_PROTO(void *ptr, size_t len), \ + TP_ARGS(ptr, len) \ + ) + +DECLARE_EVENT_CLASS(ssam_free_class, + TP_PROTO(void *ptr), + + TP_ARGS(ptr), + + TP_STRUCT__entry( + __array(char, uid, SSAM_PTR_UID_LEN) + ), + + TP_fast_assign( + ssam_trace_ptr_uid(ptr, __entry->uid); + ), + + TP_printk("uid=%s", __entry->uid) +); + +#define DEFINE_SSAM_FREE_EVENT(name) \ + DEFINE_EVENT(ssam_free_class, ssam_##name, \ + TP_PROTO(void *ptr), \ + TP_ARGS(ptr) \ + ) + +DECLARE_EVENT_CLASS(ssam_pending_class, + TP_PROTO(unsigned int pending), + + TP_ARGS(pending), + + TP_STRUCT__entry( + __field(unsigned int, pending) + ), + + TP_fast_assign( + __entry->pending = pending; + ), + + TP_printk("pending=%u", __entry->pending) +); + +#define DEFINE_SSAM_PENDING_EVENT(name) \ + DEFINE_EVENT(ssam_pending_class, ssam_##name, \ + TP_PROTO(unsigned int pending), \ + TP_ARGS(pending) \ + ) + +DEFINE_SSAM_FRAME_EVENT(rx_frame_received); +DEFINE_SSAM_COMMAND_EVENT(rx_response_received); +DEFINE_SSAM_COMMAND_EVENT(rx_event_received); + +DEFINE_SSAM_PACKET_EVENT(packet_release); +DEFINE_SSAM_PACKET_EVENT(packet_submit); +DEFINE_SSAM_PACKET_EVENT(packet_resubmit); +DEFINE_SSAM_PACKET_EVENT(packet_timeout); +DEFINE_SSAM_PACKET_EVENT(packet_cancel); +DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); +DEFINE_SSAM_PENDING_EVENT(ptl_timeout_reap); + +DEFINE_SSAM_REQUEST_EVENT(request_submit); +DEFINE_SSAM_REQUEST_EVENT(request_timeout); +DEFINE_SSAM_REQUEST_EVENT(request_cancel); +DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); +DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); + +DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); +DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); + +DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); +DEFINE_SSAM_FREE_EVENT(event_item_free); + +#endif /* _SURFACE_AGGREGATOR_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE + +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE trace + +#include -- cgit v1.2.3 From 02be44f6b5a9e4ff1215d337ac4d2a6fbafc7874 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:55 +0100 Subject: platform/surface: aggregator: Add error injection capabilities This commit adds error injection hooks to the Surface Serial Hub communication protocol implementation, to: - simulate simple serial transmission errors, - drop packets, requests, and responses, simulating communication failures and potentially trigger retransmission timeouts, as well as - inject invalid data into submitted and received packets. Together with the trace points introduced in the previous commit, these facilities are intended to aid in testing, validation, and debugging of the Surface Aggregator communication layer. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Acked-by: Steven Rostedt (VMware) Link: https://lore.kernel.org/r/20201221183959.1186143-6-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/Kconfig | 14 + .../platform/surface/aggregator/ssh_packet_layer.c | 296 ++++++++++++++++++++- .../surface/aggregator/ssh_request_layer.c | 35 +++ drivers/platform/surface/aggregator/trace.h | 31 +++ 4 files changed, 375 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig index e9f4ad96e40a..e417bac67088 100644 --- a/drivers/platform/surface/aggregator/Kconfig +++ b/drivers/platform/surface/aggregator/Kconfig @@ -40,3 +40,17 @@ menuconfig SURFACE_AGGREGATOR Choose m if you want to build the SAM subsystem core and SSH driver as module, y if you want to build it into the kernel and n if you don't want it at all. + +config SURFACE_AGGREGATOR_ERROR_INJECTION + bool "Surface System Aggregator Module Error Injection Capabilities" + depends on SURFACE_AGGREGATOR + depends on FUNCTION_ERROR_INJECTION + help + Provides error-injection capabilities for the Surface System + Aggregator Module subsystem and Surface Serial Hub driver. + + Specifically, exports error injection hooks to be used with the + kernel's function error injection capabilities to simulate underlying + transport and communication problems, such as invalid data sent to or + received from the EC, dropped data, and communication timeouts. + Intended for development and debugging. diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index c4f082e57372..74f0faaa2b27 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -226,6 +227,286 @@ */ #define SSH_PTL_RX_FIFO_LEN 4096 +#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION + +/** + * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. + * + * Useful to test detection and handling of automated re-transmits by the EC. + * Specifically of packets that the EC considers not-ACKed but the driver + * already considers ACKed (due to dropped ACK). In this case, the EC + * re-transmits the packet-to-be-ACKed and the driver should detect it as + * duplicate/already handled. Note that the driver should still send an ACK + * for the re-transmitted packet. + */ +static noinline bool ssh_ptl_should_drop_ack_packet(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); + +/** + * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. + * + * Useful to test/force automated (timeout-based) re-transmit by the EC. + * Specifically, packets that have not reached the driver completely/with valid + * checksums. Only useful in combination with receival of (injected) bad data. + */ +static noinline bool ssh_ptl_should_drop_nak_packet(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); + +/** + * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced + * data packet. + * + * Useful to test re-transmit timeout of the driver. If the data packet has not + * been ACKed after a certain time, the driver should re-transmit the packet up + * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. + */ +static noinline bool ssh_ptl_should_drop_dsq_packet(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); + +/** + * ssh_ptl_should_fail_write() - Error injection hook to make + * serdev_device_write() fail. + * + * Hook to simulate errors in serdev_device_write when transmitting packets. + */ +static noinline int ssh_ptl_should_fail_write(void) +{ + return 0; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); + +/** + * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid + * data being sent to the EC. + * + * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. + * Causes the packet data to be actively corrupted by overwriting it with + * pre-defined values, such that it becomes invalid, causing the EC to respond + * with a NAK packet. Useful to test handling of NAK packets received by the + * driver. + */ +static noinline bool ssh_ptl_should_corrupt_tx_data(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); + +/** + * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid + * data being sent by the EC. + * + * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and + * test handling thereof in the driver. + */ +static noinline bool ssh_ptl_should_corrupt_rx_syn(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); + +/** + * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid + * data being sent by the EC. + * + * Hook to simulate invalid data/checksum of the message frame and test handling + * thereof in the driver. + */ +static noinline bool ssh_ptl_should_corrupt_rx_data(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); + +static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_ack_packet())) + return false; + + trace_ssam_ei_tx_drop_ack_packet(packet); + ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", + packet); + + return true; +} + +static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_nak_packet())) + return false; + + trace_ssam_ei_tx_drop_nak_packet(packet); + ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", + packet); + + return true; +} + +static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_dsq_packet())) + return false; + + trace_ssam_ei_tx_drop_dsq_packet(packet); + ptl_info(packet->ptl, + "packet error injection: dropping sequenced data packet %p\n", + packet); + + return true; +} + +static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) +{ + /* Ignore packets that don't carry any data (i.e. flush). */ + if (!packet->data.ptr || !packet->data.len) + return false; + + switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { + case SSH_FRAME_TYPE_ACK: + return __ssh_ptl_should_drop_ack_packet(packet); + + case SSH_FRAME_TYPE_NAK: + return __ssh_ptl_should_drop_nak_packet(packet); + + case SSH_FRAME_TYPE_DATA_SEQ: + return __ssh_ptl_should_drop_dsq_packet(packet); + + default: + return false; + } +} + +static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, + const unsigned char *buf, size_t count) +{ + int status; + + status = ssh_ptl_should_fail_write(); + if (unlikely(status)) { + trace_ssam_ei_tx_fail_write(packet, status); + ptl_info(packet->ptl, + "packet error injection: simulating transmit error %d, packet %p\n", + status, packet); + + return status; + } + + return serdev_device_write_buf(ptl->serdev, buf, count); +} + +static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) +{ + /* Ignore packets that don't carry any data (i.e. flush). */ + if (!packet->data.ptr || !packet->data.len) + return; + + /* Only allow sequenced data packets to be modified. */ + if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) + return; + + if (likely(!ssh_ptl_should_corrupt_tx_data())) + return; + + trace_ssam_ei_tx_corrupt_data(packet); + ptl_info(packet->ptl, + "packet error injection: simulating invalid transmit data on packet %p\n", + packet); + + /* + * NB: The value 0xb3 has been chosen more or less randomly so that it + * doesn't have any (major) overlap with the SYN bytes (aa 55) and is + * non-trivial (i.e. non-zero, non-0xff). + */ + memset(packet->data.ptr, 0xb3, packet->data.len); +} + +static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, + struct ssam_span *data) +{ + struct ssam_span frame; + + /* Check if there actually is something to corrupt. */ + if (!sshp_find_syn(data, &frame)) + return; + + if (likely(!ssh_ptl_should_corrupt_rx_syn())) + return; + + trace_ssam_ei_rx_corrupt_syn(data->len); + + data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */ +} + +static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, + struct ssam_span *frame) +{ + size_t payload_len, message_len; + struct ssh_frame *sshf; + + /* Ignore incomplete messages, will get handled once it's complete. */ + if (frame->len < SSH_MESSAGE_LENGTH(0)) + return; + + /* Ignore incomplete messages, part 2. */ + payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); + message_len = SSH_MESSAGE_LENGTH(payload_len); + if (frame->len < message_len) + return; + + if (likely(!ssh_ptl_should_corrupt_rx_data())) + return; + + sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; + trace_ssam_ei_rx_corrupt_data(sshf); + + /* + * Flip bits in first byte of payload checksum. This is basically + * equivalent to a payload/frame data error without us having to worry + * about (the, arguably pretty small, probability of) accidental + * checksum collisions. + */ + frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; +} + +#else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ + +static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) +{ + return false; +} + +static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, + struct ssh_packet *packet, + const unsigned char *buf, + size_t count) +{ + return serdev_device_write_buf(ptl->serdev, buf, count); +} + +static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) +{ +} + +static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, + struct ssam_span *data) +{ +} + +static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, + struct ssam_span *frame) +{ +} + +#endif /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ + static void __ssh_ptl_packet_release(struct kref *kref) { struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); @@ -776,6 +1057,13 @@ static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) if (unlikely(!packet->data.ptr)) return 0; + /* Error injection: drop packet to simulate transmission problem. */ + if (ssh_ptl_should_drop_packet(packet)) + return 0; + + /* Error injection: simulate invalid packet data. */ + ssh_ptl_tx_inject_invalid_data(packet); + ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, packet->data.ptr, packet->data.len, false); @@ -787,7 +1075,7 @@ static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) buf = packet->data.ptr + offset; len = packet->data.len - offset; - status = serdev_device_write_buf(ptl->serdev, buf, len); + status = ssh_ptl_write_buf(ptl, packet, buf, len); if (status < 0) return status; @@ -1400,6 +1688,9 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) bool syn_found; int status; + /* Error injection: Modify data to simulate corrupt SYN bytes. */ + ssh_ptl_rx_inject_invalid_syn(ptl, source); + /* Find SYN. */ syn_found = sshp_find_syn(source, &aligned); @@ -1430,6 +1721,9 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) if (unlikely(!syn_found)) return aligned.ptr - source->ptr; + /* Error injection: Modify data to simulate corruption. */ + ssh_ptl_rx_inject_invalid_data(ptl, &aligned); + /* Parse and validate frame. */ status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, SSH_PTL_RX_BUF_LEN); diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index b649d71840fd..bb1c862411a2 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,30 @@ */ #define SSH_RTL_TX_BATCH 10 +#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION + +/** + * ssh_rtl_should_drop_response() - Error injection hook to drop request + * responses. + * + * Useful to cause request transmission timeouts in the driver by dropping the + * response to a request. + */ +static noinline bool ssh_rtl_should_drop_response(void) +{ + return false; +} +ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); + +#else + +static inline bool ssh_rtl_should_drop_response(void) +{ + return false; +} + +#endif + static u16 ssh_request_get_rqid(struct ssh_request *rqst) { return get_unaligned_le16(rqst->packet.data.ptr @@ -459,6 +484,16 @@ static void ssh_rtl_complete(struct ssh_rtl *rtl, if (unlikely(ssh_request_get_rqid(p) != rqid)) continue; + /* Simulate response timeout. */ + if (ssh_rtl_should_drop_response()) { + spin_unlock(&rtl->pending.lock); + + trace_ssam_ei_rx_drop_response(p); + rtl_info(rtl, "request error injection: dropping response for request %p\n", + &p->packet); + return; + } + /* * Mark as "response received" and "locked" as we're going to * complete it. diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h index dcca8007d876..eb332bb53ae4 100644 --- a/drivers/platform/surface/aggregator/trace.h +++ b/drivers/platform/surface/aggregator/trace.h @@ -565,6 +565,28 @@ DECLARE_EVENT_CLASS(ssam_pending_class, TP_ARGS(pending) \ ) +DECLARE_EVENT_CLASS(ssam_data_class, + TP_PROTO(size_t length), + + TP_ARGS(length), + + TP_STRUCT__entry( + __field(size_t, length) + ), + + TP_fast_assign( + __entry->length = length; + ), + + TP_printk("length=%zu", __entry->length) +); + +#define DEFINE_SSAM_DATA_EVENT(name) \ + DEFINE_EVENT(ssam_data_class, ssam_##name, \ + TP_PROTO(size_t length), \ + TP_ARGS(length) \ + ) + DEFINE_SSAM_FRAME_EVENT(rx_frame_received); DEFINE_SSAM_COMMAND_EVENT(rx_response_received); DEFINE_SSAM_COMMAND_EVENT(rx_event_received); @@ -583,6 +605,15 @@ DEFINE_SSAM_REQUEST_EVENT(request_cancel); DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); +DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); +DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); +DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); +DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); +DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); +DEFINE_SSAM_DATA_EVENT(ei_rx_corrupt_syn); +DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); +DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); + DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); -- cgit v1.2.3 From eb0e90a82098d4a48308abb87d2087578a83987f Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:56 +0100 Subject: platform/surface: aggregator: Add dedicated bus and device type The Surface Aggregator EC provides varying functionality, depending on the Surface device. To manage this functionality, we use dedicated client devices for each subsystem or virtual device of the EC. While some of these clients are described as standard devices in ACPI and the corresponding client drivers can be implemented as platform drivers in the kernel (making use of the controller API already present), many devices, especially on newer Surface models, cannot be found there. To simplify management of these devices, we introduce a new bus and client device type for the Surface Aggregator subsystem. The new device type takes care of managing the controller reference, essentially guaranteeing its validity for as long as the client device exists, thus alleviating the need to manually establish device links for that purpose in the client driver (as has to be done with the platform devices). Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20201221183959.1186143-7-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/Kconfig | 12 + drivers/platform/surface/aggregator/Makefile | 4 + drivers/platform/surface/aggregator/bus.c | 415 ++++++++++++++++++++++++++ drivers/platform/surface/aggregator/bus.h | 27 ++ drivers/platform/surface/aggregator/core.c | 12 + include/linux/mod_devicetable.h | 18 ++ include/linux/surface_aggregator/device.h | 423 +++++++++++++++++++++++++++ scripts/mod/devicetable-offsets.c | 8 + scripts/mod/file2alias.c | 23 ++ 9 files changed, 942 insertions(+) create mode 100644 drivers/platform/surface/aggregator/bus.c create mode 100644 drivers/platform/surface/aggregator/bus.h create mode 100644 include/linux/surface_aggregator/device.h (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig index e417bac67088..3aaeea9f0433 100644 --- a/drivers/platform/surface/aggregator/Kconfig +++ b/drivers/platform/surface/aggregator/Kconfig @@ -41,6 +41,18 @@ menuconfig SURFACE_AGGREGATOR module, y if you want to build it into the kernel and n if you don't want it at all. +config SURFACE_AGGREGATOR_BUS + bool "Surface System Aggregator Module Bus" + depends on SURFACE_AGGREGATOR + default y + help + Expands the Surface System Aggregator Module (SSAM) core driver by + providing a dedicated bus and client-device type. + + This bus and device type are intended to provide and simplify support + for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are + not auto-detectable via the conventional means (e.g. ACPI). + config SURFACE_AGGREGATOR_ERROR_INJECTION bool "Surface System Aggregator Module Error Injection Capabilities" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile index b8b24c8ec310..c112e2c7112b 100644 --- a/drivers/platform/surface/aggregator/Makefile +++ b/drivers/platform/surface/aggregator/Makefile @@ -11,3 +11,7 @@ surface_aggregator-objs += ssh_parser.o surface_aggregator-objs += ssh_packet_layer.o surface_aggregator-objs += ssh_request_layer.o surface_aggregator-objs += controller.o + +ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) +surface_aggregator-objs += bus.o +endif diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c new file mode 100644 index 000000000000..a9b660af0917 --- /dev/null +++ b/drivers/platform/surface/aggregator/bus.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module bus and device integration. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include + +#include +#include + +#include "bus.h" +#include "controller.h" + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", + sdev->uid.domain, sdev->uid.category, sdev->uid.target, + sdev->uid.instance, sdev->uid.function); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *ssam_device_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ssam_device); + +static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", + sdev->uid.domain, sdev->uid.category, + sdev->uid.target, sdev->uid.instance, + sdev->uid.function); +} + +static void ssam_device_release(struct device *dev) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + ssam_controller_put(sdev->ctrl); + kfree(sdev); +} + +const struct device_type ssam_device_type = { + .name = "surface_aggregator_device", + .groups = ssam_device_groups, + .uevent = ssam_device_uevent, + .release = ssam_device_release, +}; +EXPORT_SYMBOL_GPL(ssam_device_type); + +/** + * ssam_device_alloc() - Allocate and initialize a SSAM client device. + * @ctrl: The controller under which the device should be added. + * @uid: The UID of the device to be added. + * + * Allocates and initializes a new client device. The parent of the device + * will be set to the controller device and the name will be set based on the + * UID. Note that the device still has to be added via ssam_device_add(). + * Refer to that function for more details. + * + * Return: Returns the newly allocated and initialized SSAM client device, or + * %NULL if it could not be allocated. + */ +struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, + struct ssam_device_uid uid) +{ + struct ssam_device *sdev; + + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return NULL; + + device_initialize(&sdev->dev); + sdev->dev.bus = &ssam_bus_type; + sdev->dev.type = &ssam_device_type; + sdev->dev.parent = ssam_controller_device(ctrl); + sdev->ctrl = ssam_controller_get(ctrl); + sdev->uid = uid; + + dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", + sdev->uid.domain, sdev->uid.category, sdev->uid.target, + sdev->uid.instance, sdev->uid.function); + + return sdev; +} +EXPORT_SYMBOL_GPL(ssam_device_alloc); + +/** + * ssam_device_add() - Add a SSAM client device. + * @sdev: The SSAM client device to be added. + * + * Added client devices must be guaranteed to always have a valid and active + * controller. Thus, this function will fail with %-ENODEV if the controller + * of the device has not been initialized yet, has been suspended, or has been + * shut down. + * + * The caller of this function should ensure that the corresponding call to + * ssam_device_remove() is issued before the controller is shut down. If the + * added device is a direct child of the controller device (default), it will + * be automatically removed when the controller is shut down. + * + * By default, the controller device will become the parent of the newly + * created client device. The parent may be changed before ssam_device_add is + * called, but care must be taken that a) the correct suspend/resume ordering + * is guaranteed and b) the client device does not outlive the controller, + * i.e. that the device is removed before the controller is being shut down. + * In case these guarantees have to be manually enforced, please refer to the + * ssam_client_link() and ssam_client_bind() functions, which are intended to + * set up device-links for this purpose. + * + * Return: Returns zero on success, a negative error code on failure. + */ +int ssam_device_add(struct ssam_device *sdev) +{ + int status; + + /* + * Ensure that we can only add new devices to a controller if it has + * been started and is not going away soon. This works in combination + * with ssam_controller_remove_clients to ensure driver presence for the + * controller device, i.e. it ensures that the controller (sdev->ctrl) + * is always valid and can be used for requests as long as the client + * device we add here is registered as child under it. This essentially + * guarantees that the client driver can always expect the preconditions + * for functions like ssam_request_sync (controller has to be started + * and is not suspended) to hold and thus does not have to check for + * them. + * + * Note that for this to work, the controller has to be a parent device. + * If it is not a direct parent, care has to be taken that the device is + * removed via ssam_device_remove(), as device_unregister does not + * remove child devices recursively. + */ + ssam_controller_statelock(sdev->ctrl); + + if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { + ssam_controller_stateunlock(sdev->ctrl); + return -ENODEV; + } + + status = device_add(&sdev->dev); + + ssam_controller_stateunlock(sdev->ctrl); + return status; +} +EXPORT_SYMBOL_GPL(ssam_device_add); + +/** + * ssam_device_remove() - Remove a SSAM client device. + * @sdev: The device to remove. + * + * Removes and unregisters the provided SSAM client device. + */ +void ssam_device_remove(struct ssam_device *sdev) +{ + device_unregister(&sdev->dev); +} +EXPORT_SYMBOL_GPL(ssam_device_remove); + +/** + * ssam_device_id_compatible() - Check if a device ID matches a UID. + * @id: The device ID as potential match. + * @uid: The device UID matching against. + * + * Check if the given ID is a match for the given UID, i.e. if a device with + * the provided UID is compatible to the given ID following the match rules + * described in its &ssam_device_id.match_flags member. + * + * Return: Returns %true if the given UID is compatible to the match rule + * described by the given ID, %false otherwise. + */ +static bool ssam_device_id_compatible(const struct ssam_device_id *id, + struct ssam_device_uid uid) +{ + if (id->domain != uid.domain || id->category != uid.category) + return false; + + if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) + return false; + + if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) + return false; + + if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) + return false; + + return true; +} + +/** + * ssam_device_id_is_null() - Check if a device ID is null. + * @id: The device ID to check. + * + * Check if a given device ID is null, i.e. all zeros. Used to check for the + * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. + * + * Return: Returns %true if the given ID represents a null ID, %false + * otherwise. + */ +static bool ssam_device_id_is_null(const struct ssam_device_id *id) +{ + return id->match_flags == 0 && + id->domain == 0 && + id->category == 0 && + id->target == 0 && + id->instance == 0 && + id->function == 0 && + id->driver_data == 0; +} + +/** + * ssam_device_id_match() - Find the matching ID table entry for the given UID. + * @table: The table to search in. + * @uid: The UID to matched against the individual table entries. + * + * Find the first match for the provided device UID in the provided ID table + * and return it. Returns %NULL if no match could be found. + */ +const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, + const struct ssam_device_uid uid) +{ + const struct ssam_device_id *id; + + for (id = table; !ssam_device_id_is_null(id); ++id) + if (ssam_device_id_compatible(id, uid)) + return id; + + return NULL; +} +EXPORT_SYMBOL_GPL(ssam_device_id_match); + +/** + * ssam_device_get_match() - Find and return the ID matching the device in the + * ID table of the bound driver. + * @dev: The device for which to get the matching ID table entry. + * + * Find the fist match for the UID of the device in the ID table of the + * currently bound driver and return it. Returns %NULL if the device does not + * have a driver bound to it, the driver does not have match_table (i.e. it is + * %NULL), or there is no match in the driver's match_table. + * + * This function essentially calls ssam_device_id_match() with the ID table of + * the bound device driver and the UID of the device. + * + * Return: Returns the first match for the UID of the device in the device + * driver's match table, or %NULL if no such match could be found. + */ +const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) +{ + const struct ssam_device_driver *sdrv; + + sdrv = to_ssam_device_driver(dev->dev.driver); + if (!sdrv) + return NULL; + + if (!sdrv->match_table) + return NULL; + + return ssam_device_id_match(sdrv->match_table, dev->uid); +} +EXPORT_SYMBOL_GPL(ssam_device_get_match); + +/** + * ssam_device_get_match_data() - Find the ID matching the device in the + * ID table of the bound driver and return its ``driver_data`` member. + * @dev: The device for which to get the match data. + * + * Find the fist match for the UID of the device in the ID table of the + * corresponding driver and return its driver_data. Returns %NULL if the + * device does not have a driver bound to it, the driver does not have + * match_table (i.e. it is %NULL), there is no match in the driver's + * match_table, or the match does not have any driver_data. + * + * This function essentially calls ssam_device_get_match() and, if any match + * could be found, returns its ``struct ssam_device_id.driver_data`` member. + * + * Return: Returns the driver data associated with the first match for the UID + * of the device in the device driver's match table, or %NULL if no such match + * could be found. + */ +const void *ssam_device_get_match_data(const struct ssam_device *dev) +{ + const struct ssam_device_id *id; + + id = ssam_device_get_match(dev); + if (!id) + return NULL; + + return (const void *)id->driver_data; +} +EXPORT_SYMBOL_GPL(ssam_device_get_match_data); + +static int ssam_bus_match(struct device *dev, struct device_driver *drv) +{ + struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); + struct ssam_device *sdev = to_ssam_device(dev); + + if (!is_ssam_device(dev)) + return 0; + + return !!ssam_device_id_match(sdrv->match_table, sdev->uid); +} + +static int ssam_bus_probe(struct device *dev) +{ + return to_ssam_device_driver(dev->driver) + ->probe(to_ssam_device(dev)); +} + +static int ssam_bus_remove(struct device *dev) +{ + struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); + + if (sdrv->remove) + sdrv->remove(to_ssam_device(dev)); + + return 0; +} + +struct bus_type ssam_bus_type = { + .name = "surface_aggregator", + .match = ssam_bus_match, + .probe = ssam_bus_probe, + .remove = ssam_bus_remove, +}; +EXPORT_SYMBOL_GPL(ssam_bus_type); + +/** + * __ssam_device_driver_register() - Register a SSAM client device driver. + * @sdrv: The driver to register. + * @owner: The module owning the provided driver. + * + * Please refer to the ssam_device_driver_register() macro for the normal way + * to register a driver from inside its owning module. + */ +int __ssam_device_driver_register(struct ssam_device_driver *sdrv, + struct module *owner) +{ + sdrv->driver.owner = owner; + sdrv->driver.bus = &ssam_bus_type; + + /* force drivers to async probe so I/O is possible in probe */ + sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; + + return driver_register(&sdrv->driver); +} +EXPORT_SYMBOL_GPL(__ssam_device_driver_register); + +/** + * ssam_device_driver_unregister - Unregister a SSAM device driver. + * @sdrv: The driver to unregister. + */ +void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) +{ + driver_unregister(&sdrv->driver); +} +EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); + +static int ssam_remove_device(struct device *dev, void *_data) +{ + struct ssam_device *sdev = to_ssam_device(dev); + + if (is_ssam_device(dev)) + ssam_device_remove(sdev); + + return 0; +} + +/** + * ssam_controller_remove_clients() - Remove SSAM client devices registered as + * direct children under the given controller. + * @ctrl: The controller to remove all direct clients for. + * + * Remove all SSAM client devices registered as direct children under the + * given controller. Note that this only accounts for direct children of the + * controller device. This does not take care of any client devices where the + * parent device has been manually set before calling ssam_device_add. Refer + * to ssam_device_add()/ssam_device_remove() for more details on those cases. + * + * To avoid new devices being added in parallel to this call, the main + * controller lock (not statelock) must be held during this (and if + * necessary, any subsequent deinitialization) call. + */ +void ssam_controller_remove_clients(struct ssam_controller *ctrl) +{ + struct device *dev; + + dev = ssam_controller_device(ctrl); + device_for_each_child_reverse(dev, NULL, ssam_remove_device); +} + +/** + * ssam_bus_register() - Register and set-up the SSAM client device bus. + */ +int ssam_bus_register(void) +{ + return bus_register(&ssam_bus_type); +} + +/** + * ssam_bus_unregister() - Unregister the SSAM client device bus. + */ +void ssam_bus_unregister(void) +{ + return bus_unregister(&ssam_bus_type); +} diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h new file mode 100644 index 000000000000..7712baaed6a5 --- /dev/null +++ b/drivers/platform/surface/aggregator/bus.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface System Aggregator Module bus and device integration. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _SURFACE_AGGREGATOR_BUS_H +#define _SURFACE_AGGREGATOR_BUS_H + +#include + +#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +void ssam_controller_remove_clients(struct ssam_controller *ctrl); + +int ssam_bus_register(void); +void ssam_bus_unregister(void); + +#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} +static inline int ssam_bus_register(void) { return 0; } +static inline void ssam_bus_unregister(void) {} + +#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ +#endif /* _SURFACE_AGGREGATOR_BUS_H */ diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index b6a9dea53592..8dc2c267bcd6 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -22,6 +22,8 @@ #include #include + +#include "bus.h" #include "controller.h" #define CREATE_TRACE_POINTS @@ -739,6 +741,9 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); ssam_controller_lock(ctrl); + /* Remove all client devices. */ + ssam_controller_remove_clients(ctrl); + /* Act as if suspending to silence events. */ status = ssam_ctrl_notif_display_off(ctrl); if (status) { @@ -791,6 +796,10 @@ static int __init ssam_core_init(void) { int status; + status = ssam_bus_register(); + if (status) + goto err_bus; + status = ssh_ctrl_packet_cache_init(); if (status) goto err_cpkg; @@ -810,6 +819,8 @@ err_register: err_evitem: ssh_ctrl_packet_cache_destroy(); err_cpkg: + ssam_bus_unregister(); +err_bus: return status; } module_init(ssam_core_init); @@ -819,6 +830,7 @@ static void __exit ssam_core_exit(void) serdev_device_driver_unregister(&ssam_serial_hub); ssam_event_item_cache_destroy(); ssh_ctrl_packet_cache_destroy(); + ssam_bus_unregister(); } module_exit(ssam_core_exit); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index c425290b21e2..935060955152 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -846,4 +846,22 @@ struct auxiliary_device_id { kernel_ulong_t driver_data; }; +/* Surface System Aggregator Module */ + +#define SSAM_MATCH_TARGET 0x1 +#define SSAM_MATCH_INSTANCE 0x2 +#define SSAM_MATCH_FUNCTION 0x4 + +struct ssam_device_id { + __u8 match_flags; + + __u8 domain; + __u8 category; + __u8 target; + __u8 instance; + __u8 function; + + kernel_ulong_t driver_data; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h new file mode 100644 index 000000000000..02f3e06c0a60 --- /dev/null +++ b/include/linux/surface_aggregator/device.h @@ -0,0 +1,423 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface System Aggregator Module (SSAM) bus and client-device subsystem. + * + * Main interface for the surface-aggregator bus, surface-aggregator client + * devices, and respective drivers building on top of the SSAM controller. + * Provides support for non-platform/non-ACPI SSAM clients via dedicated + * subsystem. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H +#define _LINUX_SURFACE_AGGREGATOR_DEVICE_H + +#include +#include +#include + +#include + + +/* -- Surface System Aggregator Module bus. --------------------------------- */ + +/** + * enum ssam_device_domain - SAM device domain. + * @SSAM_DOMAIN_VIRTUAL: Virtual device. + * @SSAM_DOMAIN_SERIALHUB: Physical device connected via Surface Serial Hub. + */ +enum ssam_device_domain { + SSAM_DOMAIN_VIRTUAL = 0x00, + SSAM_DOMAIN_SERIALHUB = 0x01, +}; + +/** + * enum ssam_virtual_tc - Target categories for the virtual SAM domain. + * @SSAM_VIRTUAL_TC_HUB: Device hub category. + */ +enum ssam_virtual_tc { + SSAM_VIRTUAL_TC_HUB = 0x00, +}; + +/** + * struct ssam_device_uid - Unique identifier for SSAM device. + * @domain: Domain of the device. + * @category: Target category of the device. + * @target: Target ID of the device. + * @instance: Instance ID of the device. + * @function: Sub-function of the device. This field can be used to split a + * single SAM device into multiple virtual subdevices to separate + * different functionality of that device and allow one driver per + * such functionality. + */ +struct ssam_device_uid { + u8 domain; + u8 category; + u8 target; + u8 instance; + u8 function; +}; + +/* + * Special values for device matching. + * + * These values are intended to be used with SSAM_DEVICE(), SSAM_VDEV(), and + * SSAM_SDEV() exclusively. Specifically, they are used to initialize the + * match_flags member of the device ID structure. Do not use them directly + * with struct ssam_device_id or struct ssam_device_uid. + */ +#define SSAM_ANY_TID 0xffff +#define SSAM_ANY_IID 0xffff +#define SSAM_ANY_FUN 0xffff + +/** + * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given + * parameters. + * @d: Domain of the device. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. + * @fun: Sub-function of the device. + * + * Initializes a &struct ssam_device_id with the given parameters. See &struct + * ssam_device_uid for details regarding the parameters. The special values + * %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be used to specify that + * matching should ignore target ID, instance ID, and/or sub-function, + * respectively. This macro initializes the ``match_flags`` field based on the + * given parameters. + * + * Note: The parameters @d and @cat must be valid &u8 values, the parameters + * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, + * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * allowed. + */ +#define SSAM_DEVICE(d, cat, tid, iid, fun) \ + .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ + | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ + | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ + .domain = d, \ + .category = cat, \ + .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \ + .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ + .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \ + +/** + * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with + * the given parameters. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. + * @fun: Sub-function of the device. + * + * Initializes a &struct ssam_device_id with the given parameters in the + * virtual domain. See &struct ssam_device_uid for details regarding the + * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and + * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, + * instance ID, and/or sub-function, respectively. This macro initializes the + * ``match_flags`` field based on the given parameters. + * + * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, + * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, + * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * allowed. + */ +#define SSAM_VDEV(cat, tid, iid, fun) \ + SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) + +/** + * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device + * with the given parameters. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. + * @fun: Sub-function of the device. + * + * Initializes a &struct ssam_device_id with the given parameters in the SSH + * domain. See &struct ssam_device_uid for details regarding the parameters. + * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be + * used to specify that matching should ignore target ID, instance ID, and/or + * sub-function, respectively. This macro initializes the ``match_flags`` + * field based on the given parameters. + * + * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, + * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, + * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not + * allowed. + */ +#define SSAM_SDEV(cat, tid, iid, fun) \ + SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) + +/** + * struct ssam_device - SSAM client device. + * @dev: Driver model representation of the device. + * @ctrl: SSAM controller managing this device. + * @uid: UID identifying the device. + */ +struct ssam_device { + struct device dev; + struct ssam_controller *ctrl; + + struct ssam_device_uid uid; +}; + +/** + * struct ssam_device_driver - SSAM client device driver. + * @driver: Base driver model structure. + * @match_table: Match table specifying which devices the driver should bind to. + * @probe: Called when the driver is being bound to a device. + * @remove: Called when the driver is being unbound from the device. + */ +struct ssam_device_driver { + struct device_driver driver; + + const struct ssam_device_id *match_table; + + int (*probe)(struct ssam_device *sdev); + void (*remove)(struct ssam_device *sdev); +}; + +extern struct bus_type ssam_bus_type; +extern const struct device_type ssam_device_type; + +/** + * is_ssam_device() - Check if the given device is a SSAM client device. + * @d: The device to test the type of. + * + * Return: Returns %true if the specified device is of type &struct + * ssam_device, i.e. the device type points to %ssam_device_type, and %false + * otherwise. + */ +static inline bool is_ssam_device(struct device *d) +{ + return d->type == &ssam_device_type; +} + +/** + * to_ssam_device() - Casts the given device to a SSAM client device. + * @d: The device to cast. + * + * Casts the given &struct device to a &struct ssam_device. The caller has to + * ensure that the given device is actually enclosed in a &struct ssam_device, + * e.g. by calling is_ssam_device(). + * + * Return: Returns a pointer to the &struct ssam_device wrapping the given + * device @d. + */ +static inline struct ssam_device *to_ssam_device(struct device *d) +{ + return container_of(d, struct ssam_device, dev); +} + +/** + * to_ssam_device_driver() - Casts the given device driver to a SSAM client + * device driver. + * @d: The driver to cast. + * + * Casts the given &struct device_driver to a &struct ssam_device_driver. The + * caller has to ensure that the given driver is actually enclosed in a + * &struct ssam_device_driver. + * + * Return: Returns the pointer to the &struct ssam_device_driver wrapping the + * given device driver @d. + */ +static inline +struct ssam_device_driver *to_ssam_device_driver(struct device_driver *d) +{ + return container_of(d, struct ssam_device_driver, driver); +} + +const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, + const struct ssam_device_uid uid); + +const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev); + +const void *ssam_device_get_match_data(const struct ssam_device *dev); + +struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, + struct ssam_device_uid uid); + +int ssam_device_add(struct ssam_device *sdev); +void ssam_device_remove(struct ssam_device *sdev); + +/** + * ssam_device_get() - Increment reference count of SSAM client device. + * @sdev: The device to increment the reference count of. + * + * Increments the reference count of the given SSAM client device by + * incrementing the reference count of the enclosed &struct device via + * get_device(). + * + * See ssam_device_put() for the counter-part of this function. + * + * Return: Returns the device provided as input. + */ +static inline struct ssam_device *ssam_device_get(struct ssam_device *sdev) +{ + return sdev ? to_ssam_device(get_device(&sdev->dev)) : NULL; +} + +/** + * ssam_device_put() - Decrement reference count of SSAM client device. + * @sdev: The device to decrement the reference count of. + * + * Decrements the reference count of the given SSAM client device by + * decrementing the reference count of the enclosed &struct device via + * put_device(). + * + * See ssam_device_get() for the counter-part of this function. + */ +static inline void ssam_device_put(struct ssam_device *sdev) +{ + if (sdev) + put_device(&sdev->dev); +} + +/** + * ssam_device_get_drvdata() - Get driver-data of SSAM client device. + * @sdev: The device to get the driver-data from. + * + * Return: Returns the driver-data of the given device, previously set via + * ssam_device_set_drvdata(). + */ +static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) +{ + return dev_get_drvdata(&sdev->dev); +} + +/** + * ssam_device_set_drvdata() - Set driver-data of SSAM client device. + * @sdev: The device to set the driver-data of. + * @data: The data to set the device's driver-data pointer to. + */ +static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) +{ + dev_set_drvdata(&sdev->dev, data); +} + +int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); +void ssam_device_driver_unregister(struct ssam_device_driver *d); + +/** + * ssam_device_driver_register() - Register a SSAM client device driver. + * @drv: The driver to register. + */ +#define ssam_device_driver_register(drv) \ + __ssam_device_driver_register(drv, THIS_MODULE) + +/** + * module_ssam_device_driver() - Helper macro for SSAM device driver + * registration. + * @drv: The driver managed by this module. + * + * Helper macro to register a SSAM device driver via module_init() and + * module_exit(). This macro may only be used once per module and replaces the + * aforementioned definitions. + */ +#define module_ssam_device_driver(drv) \ + module_driver(drv, ssam_device_driver_register, \ + ssam_device_driver_unregister) + + +/* -- Helpers for client-device requests. ----------------------------------- */ + +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_N() - Define synchronous client-device SAM + * request function with neither argument nor return value. + * @name: Name of the generated function. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request having neither argument nor return value. Device + * specifying parameters are not hard-coded, but instead are provided via the + * client device, specifically its UID, supplied when calling this function. + * The generated function takes care of setting up the request struct, buffer + * allocation, as well as execution of the request itself, returning once the + * request has been fully completed. The required transport buffer will be + * allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_device *sdev)``, + * returning the status of the request, which is zero on success and negative + * on failure. The ``sdev`` parameter specifies both the target device of the + * request and by association the controller via which the request is sent. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ + int name(struct ssam_device *sdev) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_W() - Define synchronous client-device SAM + * request function with argument. + * @name: Name of the generated function. + * @atype: Type of the request's argument. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking an argument of type @atype and having no + * return value. Device specifying parameters are not hard-coded, but instead + * are provided via the client device, specifically its UID, supplied when + * calling this function. The generated function takes care of setting up the + * request struct, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_device *sdev, + * const atype *arg)``, returning the status of the request, which is zero on + * success and negative on failure. The ``sdev`` parameter specifies both the + * target device of the request and by association the controller via which + * the request is sent. The request's argument is specified via the ``arg`` + * pointer. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ + int name(struct ssam_device *sdev, const atype *arg) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, arg); \ + } + +/** + * SSAM_DEFINE_SYNC_REQUEST_CL_R() - Define synchronous client-device SAM + * request function with return value. + * @name: Name of the generated function. + * @rtype: Type of the request's return value. + * @spec: Specification (&struct ssam_request_spec_md) defining the request. + * + * Defines a function executing the synchronous SAM request specified by + * @spec, with the request taking no argument but having a return value of + * type @rtype. Device specifying parameters are not hard-coded, but instead + * are provided via the client device, specifically its UID, supplied when + * calling this function. The generated function takes care of setting up the + * request struct, buffer allocation, as well as execution of the request + * itself, returning once the request has been fully completed. The required + * transport buffer will be allocated on the stack. + * + * The generated function is defined as ``int name(struct ssam_device *sdev, + * rtype *ret)``, returning the status of the request, which is zero on + * success and negative on failure. The ``sdev`` parameter specifies both the + * target device of the request and by association the controller via which + * the request is sent. The request's return value is written to the memory + * pointed to by the ``ret`` parameter. + * + * Refer to ssam_request_sync_onstack() for more details on the behavior of + * the generated function. + */ +#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ + SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ + int name(struct ssam_device *sdev, rtype *ret) \ + { \ + return __raw_##name(sdev->ctrl, sdev->uid.target, \ + sdev->uid.instance, ret); \ + } + +#endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index e377f52dbfa3..f078eeb0a961 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -246,5 +246,13 @@ int main(void) DEVID(auxiliary_device_id); DEVID_FIELD(auxiliary_device_id, name); + DEVID(ssam_device_id); + DEVID_FIELD(ssam_device_id, match_flags); + DEVID_FIELD(ssam_device_id, domain); + DEVID_FIELD(ssam_device_id, category); + DEVID_FIELD(ssam_device_id, target); + DEVID_FIELD(ssam_device_id, instance); + DEVID_FIELD(ssam_device_id, function); + return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index fb4827027536..d21d2871387b 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1375,6 +1375,28 @@ static int do_auxiliary_entry(const char *filename, void *symval, char *alias) return 1; } +/* + * Looks like: ssam:dNcNtNiNfN + * + * N is exactly 2 digits, where each is an upper-case hex digit. + */ +static int do_ssam_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, ssam_device_id, match_flags); + DEF_FIELD(symval, ssam_device_id, domain); + DEF_FIELD(symval, ssam_device_id, category); + DEF_FIELD(symval, ssam_device_id, target); + DEF_FIELD(symval, ssam_device_id, instance); + DEF_FIELD(symval, ssam_device_id, function); + + sprintf(alias, "ssam:d%02Xc%02X", domain, category); + ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); + ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); + ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); + + return 1; +} + /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { @@ -1450,6 +1472,7 @@ static const struct devtable devtable[] = { {"wmi", SIZE_wmi_device_id, do_wmi_entry}, {"mhi", SIZE_mhi_device_id, do_mhi_entry}, {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, + {"ssam", SIZE_ssam_device_id, do_ssam_entry}, }; /* Create MODULE_ALIAS() statements. -- cgit v1.2.3 From 178f6ab77e617c984d6520b92e747075a12676ff Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:58 +0100 Subject: platform/surface: Add Surface Aggregator user-space interface Add a misc-device providing user-space access to the Surface Aggregator EC, mainly intended for debugging, testing, and reverse-engineering. This interface gives user-space applications the ability to send requests to the EC and receive the corresponding responses. The device-file is managed by a pseudo platform-device and corresponding driver to avoid dependence on the dedicated bus, allowing it to be loaded in a minimal configuration. A python library and scripts to access this device can be found at [1]. [1]: https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20201221183959.1186143-9-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../driver-api/surface_aggregator/clients/cdev.rst | 87 ++++++ .../surface_aggregator/clients/index.rst | 12 +- Documentation/userspace-api/ioctl/ioctl-number.rst | 2 + MAINTAINERS | 2 + drivers/platform/surface/Kconfig | 17 ++ drivers/platform/surface/Makefile | 1 + drivers/platform/surface/surface_aggregator_cdev.c | 303 +++++++++++++++++++++ include/uapi/linux/surface_aggregator/cdev.h | 78 ++++++ 8 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 Documentation/driver-api/surface_aggregator/clients/cdev.rst create mode 100644 drivers/platform/surface/surface_aggregator_cdev.c create mode 100644 include/uapi/linux/surface_aggregator/cdev.h (limited to 'drivers') diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst new file mode 100644 index 000000000000..248c1372d879 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst @@ -0,0 +1,87 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` +.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` +.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` + +============================== +User-Space EC Interface (cdev) +============================== + +The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM +controller to allow for a (more or less) direct connection from user-space to +the SAM EC. It is intended to be used for development and debugging, and +therefore should not be used or relied upon in any other way. Note that this +module is not loaded automatically, but instead must be loaded manually. + +The provided interface is accessible through the ``/dev/surface/aggregator`` +device-file. All functionality of this interface is provided via IOCTLs. +These IOCTLs and their respective input/output parameter structs are defined in +``include/uapi/linux/surface_aggregator/cdev.h``. + +A small python library and scripts for accessing this interface can be found +at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. + + +Controller IOCTLs +================= + +The following IOCTLs are provided: + +.. flat-table:: Controller IOCTLs + :widths: 1 1 1 1 4 + :header-rows: 1 + + * - Type + - Number + - Direction + - Name + - Description + + * - ``0xA5`` + - ``1`` + - ``WR`` + - ``REQUEST`` + - Perform synchronous SAM request. + + +``REQUEST`` +----------- + +Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. + +Executes a synchronous SAM request. The request specification is passed in +as argument of type |ssam_cdev_request|, which is then written to/modified +by the IOCTL to return status and result of the request. + +Request payload data must be allocated separately and is passed in via the +``payload.data`` and ``payload.length`` members. If a response is required, +the response buffer must be allocated by the caller and passed in via the +``response.data`` member. The ``response.length`` member must be set to the +capacity of this buffer, or if no response is required, zero. Upon +completion of the request, the call will write the response to the response +buffer (if its capacity allows it) and overwrite the length field with the +actual size of the response, in bytes. + +Additionally, if the request has a response, this must be indicated via the +request flags, as is done with in-kernel requests. Request flags can be set +via the ``flags`` member and the values correspond to the values found in +|ssam_cdev_request_flags|. + +Finally, the status of the request itself is returned in the ``status`` +member (a negative errno value indicating failure). Note that failure +indication of the IOCTL is separated from failure indication of the request: +The IOCTL returns a negative status code if anything failed during setup of +the request (``-EFAULT``) or if the provided argument or any of its fields +are invalid (``-EINVAL``). In this case, the status value of the request +argument may be set, providing more detail on what went wrong (e.g. +``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL +will return with a zero status code in case the request has been set up, +submitted, and completed (i.e. handed back to user-space) successfully from +inside the IOCTL, but the request ``status`` member may still be negative in +case the actual execution of the request failed after it has been submitted. + +A full definition of the argument struct is provided below: + +.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst index 31e026d96102..ab260ec82cfb 100644 --- a/Documentation/driver-api/surface_aggregator/clients/index.rst +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -7,4 +7,14 @@ Client Driver Documentation This is the documentation for client drivers themselves. Refer to :doc:`../client` for documentation on how to write client drivers. -.. Place documentation for individual client drivers here. +.. toctree:: + :maxdepth: 1 + + cdev + +.. only:: subproject and html + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index a4c75a28c839..b5231d7f9200 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -324,6 +324,8 @@ Code Seq# Include File Comments 0xA3 90-9F linux/dtlk.h 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem 0xA4 00-1F uapi/asm/sgx.h +0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator + 0xAA 00-3F linux/uapi/linux/userfaultfd.h 0xAB 00-1F linux/nbd.h 0xAC 00-1F linux/raw.h diff --git a/MAINTAINERS b/MAINTAINERS index 41f082551942..9bd55c842898 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11820,7 +11820,9 @@ W: https://github.com/linux-surface/surface-aggregator-module C: irc://chat.freenode.net/##linux-surface F: Documentation/driver-api/surface_aggregator/ F: drivers/platform/surface/aggregator/ +F: drivers/platform/surface/surface_aggregator_cdev.c F: include/linux/surface_aggregator/ +F: include/uapi/linux/surface_aggregator/ MICROTEK X6 SCANNER M: Oliver Neukum diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 2916a47b130e..988b2de6b500 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -41,6 +41,23 @@ config SURFACE_3_POWER_OPREGION This driver provides support for ACPI operation region of the Surface 3 battery platform driver. +config SURFACE_AGGREGATOR_CDEV + tristate "Surface System Aggregator Module User-Space Interface" + depends on SURFACE_AGGREGATOR + help + Provides a misc-device interface to the Surface System Aggregator + Module (SSAM) controller. + + This option provides a module (called surface_aggregator_cdev), that, + when loaded, will add a client device (and its respective driver) to + the SSAM controller. Said client device manages a misc-device + interface (/dev/surface/aggregator), which can be used by user-space + tools to directly communicate with the SSAM EC by sending requests and + receiving the corresponding responses. + + The provided interface is intended for debugging and development only, + and should not be used otherwise. + config SURFACE_GPE tristate "Surface GPE/Lid Support Driver" depends on DMI diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 034134fe0264..161f0ad05795 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -8,5 +8,6 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ +obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c new file mode 100644 index 000000000000..340d15b148b9 --- /dev/null +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Provides user-space access to the SSAM EC via the /dev/surface/aggregator + * misc device. Intended for debugging and development. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" + +struct ssam_cdev { + struct kref kref; + struct rw_semaphore lock; + struct ssam_controller *ctrl; + struct miscdevice mdev; +}; + +static void __ssam_cdev_release(struct kref *kref) +{ + kfree(container_of(kref, struct ssam_cdev, kref)); +} + +static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) +{ + if (cdev) + kref_get(&cdev->kref); + + return cdev; +} + +static void ssam_cdev_put(struct ssam_cdev *cdev) +{ + if (cdev) + kref_put(&cdev->kref, __ssam_cdev_release); +} + +static int ssam_cdev_device_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *mdev = filp->private_data; + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); + + filp->private_data = ssam_cdev_get(cdev); + return stream_open(inode, filp); +} + +static int ssam_cdev_device_release(struct inode *inode, struct file *filp) +{ + ssam_cdev_put(filp->private_data); + return 0; +} + +static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) +{ + struct ssam_cdev_request __user *r; + struct ssam_cdev_request rqst; + struct ssam_request spec; + struct ssam_response rsp; + const void __user *plddata; + void __user *rspdata; + int status = 0, ret = 0, tmp; + + r = (struct ssam_cdev_request __user *)arg; + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); + if (ret) + goto out; + + plddata = u64_to_user_ptr(rqst.payload.data); + rspdata = u64_to_user_ptr(rqst.response.data); + + /* Setup basic request fields. */ + spec.target_category = rqst.target_category; + spec.target_id = rqst.target_id; + spec.command_id = rqst.command_id; + spec.instance_id = rqst.instance_id; + spec.flags = 0; + spec.length = rqst.payload.length; + spec.payload = NULL; + + if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) + spec.flags |= SSAM_REQUEST_HAS_RESPONSE; + + if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) + spec.flags |= SSAM_REQUEST_UNSEQUENCED; + + rsp.capacity = rqst.response.length; + rsp.length = 0; + rsp.pointer = NULL; + + /* Get request payload from user-space. */ + if (spec.length) { + if (!plddata) { + ret = -EINVAL; + goto out; + } + + spec.payload = kzalloc(spec.length, GFP_KERNEL); + if (!spec.payload) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user((void *)spec.payload, plddata, spec.length)) { + ret = -EFAULT; + goto out; + } + } + + /* Allocate response buffer. */ + if (rsp.capacity) { + if (!rspdata) { + ret = -EINVAL; + goto out; + } + + rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); + if (!rsp.pointer) { + ret = -ENOMEM; + goto out; + } + } + + /* Perform request. */ + status = ssam_request_sync(cdev->ctrl, &spec, &rsp); + if (status) + goto out; + + /* Copy response to user-space. */ + if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) + ret = -EFAULT; + +out: + /* Always try to set response-length and status. */ + tmp = put_user(rsp.length, &r->response.length); + if (tmp) + ret = tmp; + + tmp = put_user(status, &r->status); + if (tmp) + ret = tmp; + + /* Cleanup. */ + kfree(spec.payload); + kfree(rsp.pointer); + + return ret; +} + +static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case SSAM_CDEV_REQUEST: + return ssam_cdev_request(cdev, arg); + + default: + return -ENOTTY; + } +} + +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ssam_cdev *cdev = file->private_data; + long status; + + /* Ensure that controller is valid for as long as we need it. */ + if (down_read_killable(&cdev->lock)) + return -ERESTARTSYS; + + if (!cdev->ctrl) { + up_read(&cdev->lock); + return -ENODEV; + } + + status = __ssam_cdev_device_ioctl(cdev, cmd, arg); + + up_read(&cdev->lock); + return status; +} + +static const struct file_operations ssam_controller_fops = { + .owner = THIS_MODULE, + .open = ssam_cdev_device_open, + .release = ssam_cdev_device_release, + .unlocked_ioctl = ssam_cdev_device_ioctl, + .compat_ioctl = ssam_cdev_device_ioctl, + .llseek = noop_llseek, +}; + +static int ssam_dbg_device_probe(struct platform_device *pdev) +{ + struct ssam_controller *ctrl; + struct ssam_cdev *cdev; + int status; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + kref_init(&cdev->kref); + init_rwsem(&cdev->lock); + cdev->ctrl = ctrl; + + cdev->mdev.parent = &pdev->dev; + cdev->mdev.minor = MISC_DYNAMIC_MINOR; + cdev->mdev.name = "surface_aggregator"; + cdev->mdev.nodename = "surface/aggregator"; + cdev->mdev.fops = &ssam_controller_fops; + + status = misc_register(&cdev->mdev); + if (status) { + kfree(cdev); + return status; + } + + platform_set_drvdata(pdev, cdev); + return 0; +} + +static int ssam_dbg_device_remove(struct platform_device *pdev) +{ + struct ssam_cdev *cdev = platform_get_drvdata(pdev); + + misc_deregister(&cdev->mdev); + + /* + * The controller is only guaranteed to be valid for as long as the + * driver is bound. Remove controller so that any lingering open files + * cannot access it any more after we're gone. + */ + down_write(&cdev->lock); + cdev->ctrl = NULL; + up_write(&cdev->lock); + + ssam_cdev_put(cdev); + return 0; +} + +static struct platform_device *ssam_cdev_device; + +static struct platform_driver ssam_cdev_driver = { + .probe = ssam_dbg_device_probe, + .remove = ssam_dbg_device_remove, + .driver = { + .name = SSAM_CDEV_DEVICE_NAME, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init ssam_debug_init(void) +{ + int status; + + ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, + PLATFORM_DEVID_NONE); + if (!ssam_cdev_device) + return -ENOMEM; + + status = platform_device_add(ssam_cdev_device); + if (status) + goto err_device; + + status = platform_driver_register(&ssam_cdev_driver); + if (status) + goto err_driver; + + return 0; + +err_driver: + platform_device_del(ssam_cdev_device); +err_device: + platform_device_put(ssam_cdev_device); + return status; +} +module_init(ssam_debug_init); + +static void __exit ssam_debug_exit(void) +{ + platform_driver_unregister(&ssam_cdev_driver); + platform_device_unregister(ssam_cdev_device); +} +module_exit(ssam_debug_exit); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h new file mode 100644 index 000000000000..fbcce04abfe9 --- /dev/null +++ b/include/uapi/linux/surface_aggregator/cdev.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * Surface System Aggregator Module (SSAM) user-space EC interface. + * + * Definitions, structs, and IOCTLs for the /dev/surface/aggregator misc + * device. This device provides direct user-space access to the SSAM EC. + * Intended for debugging and development. + * + * Copyright (C) 2020 Maximilian Luz + */ + +#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H +#define _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H + +#include +#include + +/** + * enum ssam_cdev_request_flags - Request flags for SSAM cdev request IOCTL. + * + * @SSAM_CDEV_REQUEST_HAS_RESPONSE: + * Specifies that the request expects a response. If not set, the request + * will be directly completed after its underlying packet has been + * transmitted. If set, the request transport system waits for a response + * of the request. + * + * @SSAM_CDEV_REQUEST_UNSEQUENCED: + * Specifies that the request should be transmitted via an unsequenced + * packet. If set, the request must not have a response, meaning that this + * flag and the %SSAM_CDEV_REQUEST_HAS_RESPONSE flag are mutually + * exclusive. + */ +enum ssam_cdev_request_flags { + SSAM_CDEV_REQUEST_HAS_RESPONSE = 0x01, + SSAM_CDEV_REQUEST_UNSEQUENCED = 0x02, +}; + +/** + * struct ssam_cdev_request - Controller request IOCTL argument. + * @target_category: Target category of the SAM request. + * @target_id: Target ID of the SAM request. + * @command_id: Command ID of the SAM request. + * @instance_id: Instance ID of the SAM request. + * @flags: Request flags (see &enum ssam_cdev_request_flags). + * @status: Request status (output). + * @payload: Request payload (input data). + * @payload.data: Pointer to request payload data. + * @payload.length: Length of request payload data (in bytes). + * @response: Request response (output data). + * @response.data: Pointer to response buffer. + * @response.length: On input: Capacity of response buffer (in bytes). + * On output: Length of request response (number of bytes + * in the buffer that are actually used). + */ +struct ssam_cdev_request { + __u8 target_category; + __u8 target_id; + __u8 command_id; + __u8 instance_id; + __u16 flags; + __s16 status; + + struct { + __u64 data; + __u16 length; + __u8 __pad[6]; + } payload; + + struct { + __u64 data; + __u16 length; + __u8 __pad[6]; + } response; +} __attribute__((__packed__)); + +#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) + +#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ -- cgit v1.2.3 From fc00bc8ac1dada4085f9308f85f2d6359da0faa8 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Dec 2020 19:39:59 +0100 Subject: platform/surface: Add Surface ACPI Notify driver The Surface ACPI Notify (SAN) device provides an ACPI interface to the Surface Aggregator EC, specifically the Surface Serial Hub interface. This interface allows EC requests to be made from ACPI code and can convert a subset of EC events back to ACPI notifications. Specifically, this interface provides a GenericSerialBus operation region ACPI code can execute a request by writing the request command data and payload to this operation region and reading back the corresponding response via a write-then-read operation. Furthermore, this interface provides a _DSM method to be called when certain events from the EC have been received, essentially turning them into ACPI notifications. The driver provided in this commit essentially takes care of translating the request data written to the operation region, executing the request, waiting for it to finish, and finally writing and translating back the response (if the request has one). Furthermore, this driver takes care of enabling the events handled via ACPI _DSM calls. Lastly, this driver also exposes an interface providing discrete GPU (dGPU) power-on notifications on the Surface Book 2, which are also received via the operation region interface (but not handled by the SAN driver directly), making them accessible to other drivers (such as a dGPU hot-plug driver that may be added later on). On 5th and 6th generation Surface devices (Surface Pro 5/2017, Pro 6, Book 2, Laptop 1 and 2), the SAN interface provides full battery and thermal subsystem access, as well as other EC based functionality. On those models, battery and thermal sensor devices are implemented as standard ACPI devices of that type, however, forward ACPI calls to the corresponding Surface Aggregator EC request via the SAN interface and receive corresponding notifications (e.g. battery information change) from it. This interface is therefore required to provide said functionality on those devices. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20201221183959.1186143-10-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../surface_aggregator/clients/index.rst | 1 + .../driver-api/surface_aggregator/clients/san.rst | 44 + MAINTAINERS | 2 + drivers/platform/surface/Kconfig | 19 + drivers/platform/surface/Makefile | 1 + drivers/platform/surface/surface_acpi_notify.c | 886 +++++++++++++++++++++ include/linux/surface_acpi_notify.h | 39 + 7 files changed, 992 insertions(+) create mode 100644 Documentation/driver-api/surface_aggregator/clients/san.rst create mode 100644 drivers/platform/surface/surface_acpi_notify.c create mode 100644 include/linux/surface_acpi_notify.h (limited to 'drivers') diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst index ab260ec82cfb..3ccabce23271 100644 --- a/Documentation/driver-api/surface_aggregator/clients/index.rst +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to :maxdepth: 1 cdev + san .. only:: subproject and html diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst new file mode 100644 index 000000000000..38c2580e7758 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/san.rst @@ -0,0 +1,44 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +.. |san_client_link| replace:: :c:func:`san_client_link` +.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` +.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister` + +=================== +Surface ACPI Notify +=================== + +The Surface ACPI Notify (SAN) device provides the bridge between ACPI and +SAM controller. Specifically, ACPI code can execute requests and handle +battery and thermal events via this interface. In addition to this, events +relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from +ACPI code (note: the Surface Book 3 uses a different method for this). The +only currently known event sent via this interface is a dGPU power-on +notification. While this driver handles the former part internally, it only +relays the dGPU events to any other driver interested via its public API and +does not handle them. + +The public interface of this driver is split into two parts: Client +registration and notifier-block registration. + +A client to the SAN interface can be linked as consumer to the SAN device +via |san_client_link|. This can be used to ensure that the a client +receiving dGPU events does not miss any events due to the SAN interface not +being set up as this forces the client driver to unbind once the SAN driver +is unbound. + +Notifier-blocks can be registered by any device for as long as the module is +loaded, regardless of being linked as client or not. Registration is done +with |san_dgpu_notifier_register|. If the notifier is not needed any more, it +should be unregistered via |san_dgpu_notifier_unregister|. + +Consult the API documentation below for more details. + + +API Documentation +================= + +.. kernel-doc:: include/linux/surface_acpi_notify.h + +.. kernel-doc:: drivers/platform/surface/surface_acpi_notify.c + :export: diff --git a/MAINTAINERS b/MAINTAINERS index 9bd55c842898..cf250c1365b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11820,7 +11820,9 @@ W: https://github.com/linux-surface/surface-aggregator-module C: irc://chat.freenode.net/##linux-surface F: Documentation/driver-api/surface_aggregator/ F: drivers/platform/surface/aggregator/ +F: drivers/platform/surface/surface_acpi_notify.c F: drivers/platform/surface/surface_aggregator_cdev.c +F: include/linux/surface_acpi_notify.h F: include/linux/surface_aggregator/ F: include/uapi/linux/surface_aggregator/ diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 988b2de6b500..83b0a4c7b352 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -41,6 +41,25 @@ config SURFACE_3_POWER_OPREGION This driver provides support for ACPI operation region of the Surface 3 battery platform driver. +config SURFACE_ACPI_NOTIFY + tristate "Surface ACPI Notify Driver" + depends on SURFACE_AGGREGATOR + help + Surface ACPI Notify (SAN) driver for Microsoft Surface devices. + + This driver provides support for the ACPI interface (called SAN) of + the Surface System Aggregator Module (SSAM) EC. This interface is used + on 5th- and 6th-generation Microsoft Surface devices (including + Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in + reduced functionality on the Surface Laptop 3) to execute SSAM + requests directly from ACPI code, as well as receive SSAM events and + turn them into ACPI notifications. It essentially acts as a + translation layer between the SSAM controller and ACPI. + + Specifically, this driver may be needed for battery status reporting, + thermal sensor access, and real-time clock information, depending on + the Surface device in question. + config SURFACE_AGGREGATOR_CDEV tristate "Surface System Aggregator Module User-Space Interface" depends on SURFACE_AGGREGATOR diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 161f0ad05795..3eb971006877 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o +obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c new file mode 100644 index 000000000000..8cd67a669c86 --- /dev/null +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -0,0 +1,886 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the Surface ACPI Notify (SAN) interface/shim. + * + * Translates communication from ACPI to Surface System Aggregator Module + * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM + * events back to ACPI notifications. Allows handling of discrete GPU + * notifications sent from ACPI via the SAN interface by providing them to any + * registered external driver. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct san_data { + struct device *dev; + struct ssam_controller *ctrl; + + struct acpi_connection_info info; + + struct ssam_event_notifier nf_bat; + struct ssam_event_notifier nf_tmp; +}; + +#define to_san_data(ptr, member) \ + container_of(ptr, struct san_data, member) + + +/* -- dGPU notifier interface. ---------------------------------------------- */ + +struct san_rqsg_if { + struct rw_semaphore lock; + struct device *dev; + struct blocking_notifier_head nh; +}; + +static struct san_rqsg_if san_rqsg_if = { + .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), + .dev = NULL, + .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), +}; + +static int san_set_rqsg_interface_device(struct device *dev) +{ + int status = 0; + + down_write(&san_rqsg_if.lock); + if (!san_rqsg_if.dev && dev) + san_rqsg_if.dev = dev; + else + status = -EBUSY; + up_write(&san_rqsg_if.lock); + + return status; +} + +/** + * san_client_link() - Link client as consumer to SAN device. + * @client: The client to link. + * + * Sets up a device link between the provided client device as consumer and + * the SAN device as provider. This function can be used to ensure that the + * SAN interface has been set up and will be set up for as long as the driver + * of the client device is bound. This guarantees that, during that time, all + * dGPU events will be received by any registered notifier. + * + * The link will be automatically removed once the client device's driver is + * unbound. + * + * Return: Returns zero on success, %-ENXIO if the SAN interface has not been + * set up yet, and %-ENOMEM if device link creation failed. + */ +int san_client_link(struct device *client) +{ + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; + struct device_link *link; + + down_read(&san_rqsg_if.lock); + + if (!san_rqsg_if.dev) { + up_read(&san_rqsg_if.lock); + return -ENXIO; + } + + link = device_link_add(client, san_rqsg_if.dev, flags); + if (!link) { + up_read(&san_rqsg_if.lock); + return -ENOMEM; + } + + if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { + up_read(&san_rqsg_if.lock); + return -ENXIO; + } + + up_read(&san_rqsg_if.lock); + return 0; +} +EXPORT_SYMBOL_GPL(san_client_link); + +/** + * san_dgpu_notifier_register() - Register a SAN dGPU notifier. + * @nb: The notifier-block to register. + * + * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from + * ACPI. The registered notifier will be called with &struct san_dgpu_event + * as notifier data and the command ID of that event as notifier action. + */ +int san_dgpu_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); +} +EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); + +/** + * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. + * @nb: The notifier-block to unregister. + */ +int san_dgpu_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); +} +EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); + +static int san_dgpu_notifier_call(struct san_dgpu_event *evt) +{ + int ret; + + ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); + return notifier_to_errno(ret); +} + + +/* -- ACPI _DSM event relay. ------------------------------------------------ */ + +#define SAN_DSM_REVISION 0 + +/* 93b666c5-70c6-469f-a215-3d487c91ab3c */ +static const guid_t SAN_DSM_UUID = + GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, + 0x48, 0x7c, 0x91, 0xab, 0x3c); + +enum san_dsm_event_fn { + SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, + SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, + SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, + SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, + SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, + SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, + SAN_DSM_EVENT_FN_THERMAL = 0x09, + SAN_DSM_EVENT_FN_DPTF = 0x0a, +}; + +enum sam_event_cid_bat { + SAM_EVENT_CID_BAT_BIX = 0x15, + SAM_EVENT_CID_BAT_BST = 0x16, + SAM_EVENT_CID_BAT_ADP = 0x17, + SAM_EVENT_CID_BAT_PROT = 0x18, + SAM_EVENT_CID_BAT_DPTF = 0x4f, +}; + +enum sam_event_cid_tmp { + SAM_EVENT_CID_TMP_TRIP = 0x0b, +}; + +struct san_event_work { + struct delayed_work work; + struct device *dev; + struct ssam_event event; /* must be last */ +}; + +static int san_acpi_notify_event(struct device *dev, u64 func, + union acpi_object *param) +{ + acpi_handle san = ACPI_HANDLE(dev); + union acpi_object *obj; + int status = 0; + + if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, 1 << func)) + return 0; + + dev_dbg(dev, "notify event %#04llx\n", func); + + obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, + func, param, ACPI_TYPE_BUFFER); + if (!obj) + return -EFAULT; + + if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { + dev_err(dev, "got unexpected result from _DSM\n"); + status = -EPROTO; + } + + ACPI_FREE(obj); + return status; +} + +static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) +{ + int status; + + status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); + if (status) + return status; + + /* + * Ensure that the battery states get updated correctly. When the + * battery is fully charged and an adapter is plugged in, it sometimes + * is not updated correctly, instead showing it as charging. + * Explicitly trigger battery updates to fix this. + */ + + status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); + if (status) + return status; + + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); +} + +static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) +{ + enum san_dsm_event_fn fn; + + if (event->instance_id == 0x02) + fn = SAN_DSM_EVENT_FN_BAT2_INFO; + else + fn = SAN_DSM_EVENT_FN_BAT1_INFO; + + return san_acpi_notify_event(dev, fn, NULL); +} + +static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) +{ + enum san_dsm_event_fn fn; + + if (event->instance_id == 0x02) + fn = SAN_DSM_EVENT_FN_BAT2_STAT; + else + fn = SAN_DSM_EVENT_FN_BAT1_STAT; + + return san_acpi_notify_event(dev, fn, NULL); +} + +static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) +{ + union acpi_object payload; + + /* + * The Surface ACPI expects a buffer and not a package. It specifically + * checks for ObjectType (Arg3) == 0x03. This will cause a warning in + * acpica/nsarguments.c, but that warning can be safely ignored. + */ + payload.type = ACPI_TYPE_BUFFER; + payload.buffer.length = event->length; + payload.buffer.pointer = (u8 *)&event->data[0]; + + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); +} + +static unsigned long san_evt_bat_delay(u8 cid) +{ + switch (cid) { + case SAM_EVENT_CID_BAT_ADP: + /* + * Wait for battery state to update before signaling adapter + * change. + */ + return msecs_to_jiffies(5000); + + case SAM_EVENT_CID_BAT_BST: + /* Ensure we do not miss anything important due to caching. */ + return msecs_to_jiffies(2000); + + default: + return 0; + } +} + +static bool san_evt_bat(const struct ssam_event *event, struct device *dev) +{ + int status; + + switch (event->command_id) { + case SAM_EVENT_CID_BAT_BIX: + status = san_evt_bat_bix(dev, event); + break; + + case SAM_EVENT_CID_BAT_BST: + status = san_evt_bat_bst(dev, event); + break; + + case SAM_EVENT_CID_BAT_ADP: + status = san_evt_bat_adp(dev, event); + break; + + case SAM_EVENT_CID_BAT_PROT: + /* + * TODO: Implement support for battery protection status change + * event. + */ + return true; + + case SAM_EVENT_CID_BAT_DPTF: + status = san_evt_bat_dptf(dev, event); + break; + + default: + return false; + } + + if (status) { + dev_err(dev, "error handling power event (cid = %#04x)\n", + event->command_id); + } + + return true; +} + +static void san_evt_bat_workfn(struct work_struct *work) +{ + struct san_event_work *ev; + + ev = container_of(work, struct san_event_work, work.work); + san_evt_bat(&ev->event, ev->dev); + kfree(ev); +} + +static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, + const struct ssam_event *event) +{ + struct san_data *d = to_san_data(nf, nf_bat); + struct san_event_work *work; + unsigned long delay = san_evt_bat_delay(event->command_id); + + if (delay == 0) + return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; + + work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); + if (!work) + return ssam_notifier_from_errno(-ENOMEM); + + INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); + work->dev = d->dev; + + memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); + + schedule_delayed_work(&work->work, delay); + return SSAM_NOTIF_HANDLED; +} + +static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) +{ + union acpi_object param; + + /* + * The Surface ACPI expects an integer and not a package. This will + * cause a warning in acpica/nsarguments.c, but that warning can be + * safely ignored. + */ + param.type = ACPI_TYPE_INTEGER; + param.integer.value = event->instance_id; + + return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); +} + +static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) +{ + int status; + + switch (event->command_id) { + case SAM_EVENT_CID_TMP_TRIP: + status = san_evt_tmp_trip(dev, event); + break; + + default: + return false; + } + + if (status) { + dev_err(dev, "error handling thermal event (cid = %#04x)\n", + event->command_id); + } + + return true; +} + +static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, + const struct ssam_event *event) +{ + struct san_data *d = to_san_data(nf, nf_tmp); + + return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; +} + + +/* -- ACPI GSB OperationRegion handler -------------------------------------- */ + +struct gsb_data_in { + u8 cv; +} __packed; + +struct gsb_data_rqsx { + u8 cv; /* Command value (san_gsb_request_cv). */ + u8 tc; /* Target category. */ + u8 tid; /* Target ID. */ + u8 iid; /* Instance ID. */ + u8 snc; /* Expect-response-flag. */ + u8 cid; /* Command ID. */ + u16 cdl; /* Payload length. */ + u8 pld[]; /* Payload. */ +} __packed; + +struct gsb_data_etwl { + u8 cv; /* Command value (should be 0x02). */ + u8 etw3; /* Unknown. */ + u8 etw4; /* Unknown. */ + u8 msg[]; /* Error message (ASCIIZ). */ +} __packed; + +struct gsb_data_out { + u8 status; /* _SSH communication status. */ + u8 len; /* _SSH payload length. */ + u8 pld[]; /* _SSH payload. */ +} __packed; + +union gsb_buffer_data { + struct gsb_data_in in; /* Common input. */ + struct gsb_data_rqsx rqsx; /* RQSX input. */ + struct gsb_data_etwl etwl; /* ETWL input. */ + struct gsb_data_out out; /* Output. */ +}; + +struct gsb_buffer { + u8 status; /* GSB AttribRawProcess status. */ + u8 len; /* GSB AttribRawProcess length. */ + union gsb_buffer_data data; +} __packed; + +#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) +#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) + +#define SAN_GSB_COMMAND 0 + +enum san_gsb_request_cv { + SAN_GSB_REQUEST_CV_RQST = 0x01, + SAN_GSB_REQUEST_CV_ETWL = 0x02, + SAN_GSB_REQUEST_CV_RQSG = 0x03, +}; + +#define SAN_REQUEST_NUM_TRIES 5 + +static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) +{ + struct gsb_data_etwl *etwl = &b->data.etwl; + + if (b->len < sizeof(struct gsb_data_etwl)) { + dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); + return AE_OK; + } + + dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4, + (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), + (char *)etwl->msg); + + /* Indicate success. */ + b->status = 0x00; + b->len = 0x00; + + return AE_OK; +} + +static +struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type, + struct gsb_buffer *b) +{ + struct gsb_data_rqsx *rqsx = &b->data.rqsx; + + if (b->len < sizeof(struct gsb_data_rqsx)) { + dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); + return NULL; + } + + if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { + dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", + type, b->len, get_unaligned(&rqsx->cdl)); + return NULL; + } + + if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { + dev_err(dev, "payload for %s package too large (cdl = %d)\n", + type, get_unaligned(&rqsx->cdl)); + return NULL; + } + + return rqsx; +} + +static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) +{ + gsb->status = 0x00; + gsb->len = 0x02; + gsb->data.out.status = (u8)(-status); + gsb->data.out.len = 0x00; +} + +static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) +{ + gsb->status = 0x00; + gsb->len = len + 2; + gsb->data.out.status = 0x00; + gsb->data.out.len = len; + + if (len) + memcpy(&gsb->data.out.pld[0], ptr, len); +} + +static acpi_status san_rqst_fixup_suspended(struct san_data *d, + struct ssam_request *rqst, + struct gsb_buffer *gsb) +{ + if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { + u8 base_state = 1; + + /* Base state quirk: + * The base state may be queried from ACPI when the EC is still + * suspended. In this case it will return '-EPERM'. This query + * will only be triggered from the ACPI lid GPE interrupt, thus + * we are either in laptop or studio mode (base status 0x01 or + * 0x02). Furthermore, we will only get here if the device (and + * EC) have been suspended. + * + * We now assume that the device is in laptop mode (0x01). This + * has the drawback that it will wake the device when unfolding + * it in studio mode, but it also allows us to avoid actively + * waiting for the EC to wake up, which may incur a notable + * delay. + */ + + dev_dbg(d->dev, "rqst: fixup: base-state quirk\n"); + + gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); + return AE_OK; + } + + gsb_rqsx_response_error(gsb, -ENXIO); + return AE_OK; +} + +static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) +{ + u8 rspbuf[SAN_GSB_MAX_RESPONSE]; + struct gsb_data_rqsx *gsb_rqst; + struct ssam_request rqst; + struct ssam_response rsp; + int status = 0; + + gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); + if (!gsb_rqst) + return AE_OK; + + rqst.target_category = gsb_rqst->tc; + rqst.target_id = gsb_rqst->tid; + rqst.command_id = gsb_rqst->cid; + rqst.instance_id = gsb_rqst->iid; + rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; + rqst.length = get_unaligned(&gsb_rqst->cdl); + rqst.payload = &gsb_rqst->pld[0]; + + rsp.capacity = ARRAY_SIZE(rspbuf); + rsp.length = 0; + rsp.pointer = &rspbuf[0]; + + /* Handle suspended device. */ + if (d->dev->power.is_suspended) { + dev_warn(d->dev, "rqst: device is suspended, not executing\n"); + return san_rqst_fixup_suspended(d, &rqst, buffer); + } + + status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, + d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); + + if (!status) { + gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); + } else { + dev_err(d->dev, "rqst: failed with error %d\n", status); + gsb_rqsx_response_error(buffer, status); + } + + return AE_OK; +} + +static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) +{ + struct gsb_data_rqsx *gsb_rqsg; + struct san_dgpu_event evt; + int status; + + gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); + if (!gsb_rqsg) + return AE_OK; + + evt.category = gsb_rqsg->tc; + evt.target = gsb_rqsg->tid; + evt.command = gsb_rqsg->cid; + evt.instance = gsb_rqsg->iid; + evt.length = get_unaligned(&gsb_rqsg->cdl); + evt.payload = &gsb_rqsg->pld[0]; + + status = san_dgpu_notifier_call(&evt); + if (!status) { + gsb_rqsx_response_success(buffer, NULL, 0); + } else { + dev_err(d->dev, "rqsg: failed with error %d\n", status); + gsb_rqsx_response_error(buffer, status); + } + + return AE_OK; +} + +static acpi_status san_opreg_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, void *opreg_context, + void *region_context) +{ + struct san_data *d = to_san_data(opreg_context, info); + struct gsb_buffer *buffer = (struct gsb_buffer *)value64; + int accessor_type = (function & 0xFFFF0000) >> 16; + + if (command != SAN_GSB_COMMAND) { + dev_warn(d->dev, "unsupported command: %#04llx\n", command); + return AE_OK; + } + + if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { + dev_err(d->dev, "invalid access type: %#04x\n", accessor_type); + return AE_OK; + } + + /* Buffer must have at least contain the command-value. */ + if (buffer->len == 0) { + dev_err(d->dev, "request-package too small\n"); + return AE_OK; + } + + switch (buffer->data.in.cv) { + case SAN_GSB_REQUEST_CV_RQST: + return san_rqst(d, buffer); + + case SAN_GSB_REQUEST_CV_ETWL: + return san_etwl(d, buffer); + + case SAN_GSB_REQUEST_CV_RQSG: + return san_rqsg(d, buffer); + + default: + dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n", + buffer->data.in.cv); + return AE_OK; + } +} + + +/* -- Driver setup. --------------------------------------------------------- */ + +static int san_events_register(struct platform_device *pdev) +{ + struct san_data *d = platform_get_drvdata(pdev); + int status; + + d->nf_bat.base.priority = 1; + d->nf_bat.base.fn = san_evt_bat_nf; + d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; + d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; + d->nf_bat.event.id.instance = 0; + d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; + d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; + + d->nf_tmp.base.priority = 1; + d->nf_tmp.base.fn = san_evt_tmp_nf; + d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; + d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; + d->nf_tmp.event.id.instance = 0; + d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; + d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; + + status = ssam_notifier_register(d->ctrl, &d->nf_bat); + if (status) + return status; + + status = ssam_notifier_register(d->ctrl, &d->nf_tmp); + if (status) + ssam_notifier_unregister(d->ctrl, &d->nf_bat); + + return status; +} + +static void san_events_unregister(struct platform_device *pdev) +{ + struct san_data *d = platform_get_drvdata(pdev); + + ssam_notifier_unregister(d->ctrl, &d->nf_bat); + ssam_notifier_unregister(d->ctrl, &d->nf_tmp); +} + +#define san_consumer_printk(level, dev, handle, fmt, ...) \ +do { \ + char *path = ""; \ + struct acpi_buffer buffer = { \ + .length = ACPI_ALLOCATE_BUFFER, \ + .pointer = NULL, \ + }; \ + \ + if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer))) \ + path = buffer.pointer; \ + \ + dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__); \ + kfree(buffer.pointer); \ +} while (0) + +#define san_consumer_dbg(dev, handle, fmt, ...) \ + san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__) + +#define san_consumer_warn(dev, handle, fmt, ...) \ + san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) + +static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) +{ + struct acpi_handle_list dep_devices; + acpi_handle supplier = ACPI_HANDLE(&pdev->dev); + acpi_status status; + int i; + + if (!acpi_has_method(handle, "_DEP")) + return false; + + status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); + if (ACPI_FAILURE(status)) { + san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); + return false; + } + + for (i = 0; i < dep_devices.count; i++) { + if (dep_devices.handles[i] == supplier) + return true; + } + + return false; +} + +static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, + void *context, void **rv) +{ + const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; + struct platform_device *pdev = context; + struct acpi_device *adev; + struct device_link *link; + + if (!is_san_consumer(pdev, handle)) + return AE_OK; + + /* Ignore ACPI devices that are not present. */ + if (acpi_bus_get_device(handle, &adev) != 0) + return AE_OK; + + san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); + + /* Try to set up device links, ignore but log errors. */ + link = device_link_add(&adev->dev, &pdev->dev, flags); + if (!link) { + san_consumer_warn(&pdev->dev, handle, "failed to create device link\n"); + return AE_OK; + } + + return AE_OK; +} + +static int san_consumer_links_setup(struct platform_device *pdev) +{ + acpi_status status; + + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, san_consumer_setup, NULL, + pdev, NULL); + + return status ? -EFAULT : 0; +} + +static int san_probe(struct platform_device *pdev) +{ + acpi_handle san = ACPI_HANDLE(&pdev->dev); + struct ssam_controller *ctrl; + struct san_data *data; + acpi_status astatus; + int status; + + ctrl = ssam_client_bind(&pdev->dev); + if (IS_ERR(ctrl)) + return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); + + status = san_consumer_links_setup(pdev); + if (status) + return status; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &pdev->dev; + data->ctrl = ctrl; + + platform_set_drvdata(pdev, data); + + astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler, NULL, + &data->info); + if (ACPI_FAILURE(astatus)) + return -ENXIO; + + status = san_events_register(pdev); + if (status) + goto err_enable_events; + + status = san_set_rqsg_interface_device(&pdev->dev); + if (status) + goto err_install_dev; + + acpi_walk_dep_device_list(san); + return 0; + +err_install_dev: + san_events_unregister(pdev); +err_enable_events: + acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler); + return status; +} + +static int san_remove(struct platform_device *pdev) +{ + acpi_handle san = ACPI_HANDLE(&pdev->dev); + + san_set_rqsg_interface_device(NULL); + acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler); + san_events_unregister(pdev); + + /* + * We have unregistered our event sources. Now we need to ensure that + * all delayed works they may have spawned are run to completion. + */ + flush_scheduled_work(); + + return 0; +} + +static const struct acpi_device_id san_match[] = { + { "MSHW0091" }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, san_match); + +static struct platform_driver surface_acpi_notify = { + .probe = san_probe, + .remove = san_remove, + .driver = { + .name = "surface_acpi_notify", + .acpi_match_table = san_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(surface_acpi_notify); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h new file mode 100644 index 000000000000..8e3e86c7d78c --- /dev/null +++ b/include/linux/surface_acpi_notify.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Interface for Surface ACPI Notify (SAN) driver. + * + * Provides access to discrete GPU notifications sent from ACPI via the SAN + * driver, which are not handled by this driver directly. + * + * Copyright (C) 2019-2020 Maximilian Luz + */ + +#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H +#define _LINUX_SURFACE_ACPI_NOTIFY_H + +#include +#include + +/** + * struct san_dgpu_event - Discrete GPU ACPI event. + * @category: Category of the event. + * @target: Target ID of the event source. + * @command: Command ID of the event. + * @instance: Instance ID of the event source. + * @length: Length of the event's payload data (in bytes). + * @payload: Pointer to the event's payload data. + */ +struct san_dgpu_event { + u8 category; + u8 target; + u8 command; + u8 instance; + u16 length; + u8 *payload; +}; + +int san_client_link(struct device *client); +int san_dgpu_notifier_register(struct notifier_block *nb); +int san_dgpu_notifier_unregister(struct notifier_block *nb); + +#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ -- cgit v1.2.3 From d69cd7eea93eb59a93061beeb43e4f5e19afc4ea Mon Sep 17 00:00:00 2001 From: Jiaxun Yang Date: Thu, 7 Jan 2021 22:44:38 +0800 Subject: platform/x86: ideapad-laptop: Disable touchpad_switch for ELAN0634 Newer ideapads (e.g.: Yoga 14s, 720S 14) come with ELAN0634 touchpad do not use EC to switch touchpad. Reading VPCCMD_R_TOUCHPAD will return zero thus touchpad may be blocked unexpectedly. Writing VPCCMD_W_TOUCHPAD may cause a spurious key press. Add has_touchpad_switch to workaround these machines. Signed-off-by: Jiaxun Yang Cc: stable@vger.kernel.org # 5.4+ -- v2: Specify touchpad to ELAN0634 v3: Stupid missing ! in v2 v4: Correct acpi_dev_present usage (Hans) Link: https://lore.kernel.org/r/20210107144438.12605-1-jiaxun.yang@flygoat.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 7598cd46cf60..5b81bafa5c16 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -92,6 +92,7 @@ struct ideapad_private { struct dentry *debug; unsigned long cfg; bool has_hw_rfkill_switch; + bool has_touchpad_switch; const char *fnesc_guid; }; @@ -535,7 +536,9 @@ static umode_t ideapad_is_visible(struct kobject *kobj, } else if (attr == &dev_attr_fn_lock.attr) { supported = acpi_has_method(priv->adev->handle, "HALS") && acpi_has_method(priv->adev->handle, "SALS"); - } else + } else if (attr == &dev_attr_touchpad.attr) + supported = priv->has_touchpad_switch; + else supported = true; return supported ? attr->mode : 0; @@ -867,6 +870,9 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv) { unsigned long value; + if (!priv->has_touchpad_switch) + return; + /* Without reading from EC touchpad LED doesn't switch state */ if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { /* Some IdeaPads don't really turn off touchpad - they only @@ -989,6 +995,9 @@ static int ideapad_acpi_add(struct platform_device *pdev) priv->platform_device = pdev; priv->has_hw_rfkill_switch = dmi_check_system(hw_rfkill_list); + /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ + priv->has_touchpad_switch = !acpi_dev_present("ELAN0634", NULL, -1); + ret = ideapad_sysfs_init(priv); if (ret) return ret; @@ -1006,6 +1015,10 @@ static int ideapad_acpi_add(struct platform_device *pdev) if (!priv->has_hw_rfkill_switch) write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); + /* The same for Touchpad */ + if (!priv->has_touchpad_switch) + write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) if (test_bit(ideapad_rfk_data[i].cfgbit, &priv->cfg)) ideapad_register_rfkill(priv, i); -- cgit v1.2.3 From d26cbdd27f8c4ff2f3df227a8bc5782697ecce51 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 11 Jan 2021 14:46:48 +0000 Subject: platform/surface: fix potential integer overflow on shift of a int The left shift of int 32 bit integer constant 1 is evaluated using 32 bit arithmetic and then passed as a 64 bit function argument. In the case where func is 32 or more this can lead to an oveflow. Avoid this by shifting using the BIT_ULL macro instead. Addresses-Coverity: ("Unintentional integer overflow") Fixes: fc00bc8ac1da ("platform/surface: Add Surface ACPI Notify driver") Signed-off-by: Colin Ian King Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20210111144648.20498-1-colin.king@canonical.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_acpi_notify.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index 8cd67a669c86..ef9c1f8e8336 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -188,7 +188,7 @@ static int san_acpi_notify_event(struct device *dev, u64 func, union acpi_object *obj; int status = 0; - if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, 1 << func)) + if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func))) return 0; dev_dbg(dev, "notify event %#04llx\n", func); -- cgit v1.2.3 From a403c1dfcf9fb73a4a362f14ca1c1f04662c49e0 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 11 Jan 2021 16:48:50 +0100 Subject: platform/surface: aggregator_cdev: Fix access of uninitialized variables When copy_struct_from_user() in ssam_cdev_request() fails, we directly jump to the 'out' label. In this case, however 'spec' and 'rsp' are not initialized, but we still access fields of those variables. Fix this by initializing them at the time of their declaration. Reported-by: Colin Ian King Fixes: 178f6ab77e61 ("platform/surface: Add Surface Aggregator user-space interface") Addresses-Coverity: ("Uninitialized pointer read") Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210111154851.325404-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_cdev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index 340d15b148b9..979340cdd9de 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -66,8 +66,8 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) { struct ssam_cdev_request __user *r; struct ssam_cdev_request rqst; - struct ssam_request spec; - struct ssam_response rsp; + struct ssam_request spec = {}; + struct ssam_response rsp = {}; const void __user *plddata; void __user *rspdata; int status = 0, ret = 0, tmp; -- cgit v1.2.3 From e94a26504f41b2ad33ea1473d32506b01bf6693a Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 11 Jan 2021 16:48:51 +0100 Subject: platform/surface: aggregator_cdev: Add comments regarding unchecked allocation size CI static analysis complains about the allocation size in payload and response buffers being unchecked. In general, these allocations should be safe as the user-input is u16 and thus limited to U16_MAX, which is only slightly larger than the theoretical maximum imposed by the underlying SSH protocol. All bounds on these values required by the underlying protocol are enforced in ssam_request_sync() (or rather the functions called by it), thus bounds here are only relevant for allocation. Add comments explaining that this should be safe. Reported-by: Colin Ian King Fixes: 178f6ab77e61 ("platform/surface: Add Surface Aggregator user-space interface") Addresses-Coverity: ("Untrusted allocation size") Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210111154851.325404-3-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_cdev.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index 979340cdd9de..79e28fab7e40 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -106,6 +106,15 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) goto out; } + /* + * Note: spec.length is limited to U16_MAX bytes via struct + * ssam_cdev_request. This is slightly larger than the + * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the + * underlying protocol (note that nothing remotely this size + * should ever be allocated in any normal case). This size is + * validated later in ssam_request_sync(), for allocation the + * bound imposed by u16 should be enough. + */ spec.payload = kzalloc(spec.length, GFP_KERNEL); if (!spec.payload) { ret = -ENOMEM; @@ -125,6 +134,16 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) goto out; } + /* + * Note: rsp.capacity is limited to U16_MAX bytes via struct + * ssam_cdev_request. This is slightly larger than the + * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the + * underlying protocol (note that nothing remotely this size + * should ever be allocated in any normal case). In later use, + * this capacity does not have to be strictly bounded, as it + * is only used as an output buffer to be written to. For + * allocation the bound imposed by u16 should be enough. + */ rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); if (!rsp.pointer) { ret = -ENOMEM; -- cgit v1.2.3 From 025fe94b63dd2b781788eaff35ab2663cfef749a Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Thu, 14 Jan 2021 09:04:52 +0100 Subject: platform/surface: aggregator: fix a kernel-doc markup A function has a different name between their prototype and its kernel-doc markup: ../drivers/platform/surface/aggregator/ssh_request_layer.c:1065: warning: expecting prototype for ssh_rtl_tx_start(). Prototype was for ssh_rtl_start() instead Signed-off-by: Mauro Carvalho Chehab Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/4a6bf33cfbd06654d78294127f2b6d354d073089.1610610937.git.mchehab+huawei@kernel.org Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index bb1c862411a2..25db4d638cfa 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -1056,7 +1056,7 @@ void ssh_rtl_destroy(struct ssh_rtl *rtl) } /** - * ssh_rtl_tx_start() - Start request transmitter and receiver. + * ssh_rtl_start() - Start request transmitter and receiver. * @rtl: The request transport layer. * * Return: Returns zero on success, a negative error code on failure. -- cgit v1.2.3 From e5da18d3e67dbc326205fb179f0e5085372537dd Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 14 Jan 2021 16:08:26 +0100 Subject: platform/surface: aggregator: Fix kernel-doc references Both, ssh_rtl_rx_start() and ssh_rtl_tx_start() functions, do not exist and have been consolidated into ssh_rtl_start(). Nevertheless, kernel-doc references the former functions. Replace those references with references to ssh_rtl_start(). Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210114150826.19109-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/ssh_request_layer.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index 25db4d638cfa..52a83a8fcf82 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -1004,9 +1004,8 @@ int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, * * Initializes the given request transport layer and associated packet * transport layer. Transmitter and receiver threads must be started - * separately via ssh_rtl_tx_start() and ssh_rtl_rx_start(), after the - * request-layer has been initialized and the lower-level serial device layer - * has been set up. + * separately via ssh_rtl_start(), after the request-layer has been + * initialized and the lower-level serial device layer has been set up. * * Return: Returns zero on success and a nonzero error code on failure. */ -- cgit v1.2.3 From bbffaa981940acdfc15ba7b7621a497cd56fa3a4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 14 Jan 2021 15:34:32 +0100 Subject: platform/x86: intel-vbtn: Drop HP Stream x360 Convertible PC 11 from allow-list THe HP Stream x360 Convertible PC 11 DSDT has the following VGBS function: Method (VGBS, 0, Serialized) { If ((^^PCI0.LPCB.EC0.ROLS == Zero)) { VBDS = Zero } Else { VBDS = Zero } Return (VBDS) /* \_SB_.VGBI.VBDS */ } Which is obviously wrong, because it always returns 0 independent of the 2-in-1 being in laptop or tablet mode. This causes the intel-vbtn driver to initially report SW_TABLET_MODE = 1 to userspace, which is known to cause problems when the 2-in-1 is actually in laptop mode. During earlier testing this turned out to not be a problem because the 2-in-1 would do a Notify(..., 0xCC) or Notify(..., 0xCD) soon after the intel-vbtn driver loaded, correcting the SW_TABLET_MODE state. Further testing however has shown that this Notify() soon after the intel-vbtn driver loads, does not always happen. When the Notify does not happen, then intel-vbtn reports SW_TABLET_MODE = 1 resulting in a non-working touchpad. IOW the tablet-mode reporting is not reliable on this device, so it should be dropped from the allow-list, fixing the touchpad sometimes not working. Fixes: 8169bd3e6e19 ("platform/x86: intel-vbtn: Switch to an allow-list for SW_TABLET_MODE reporting") Link: https://lore.kernel.org/r/20210114143432.31750-1-hdegoede@redhat.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel-vbtn.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 9bbdb26d4305..30a9062d2b4b 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -204,12 +204,6 @@ static const struct dmi_system_id dmi_switches_allow_list[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Venue 11 Pro 7130"), }, }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), - DMI_MATCH(DMI_PRODUCT_NAME, "HP Stream x360 Convertible PC 11"), - }, - }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Hewlett-Packard"), -- cgit v1.2.3 From dbd7dd8f8859ac3ab556c76d9b02efb96c26df80 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Jan 2021 00:27:44 +0100 Subject: platform/x86: hp-wmi: Don't log a warning on HPWMI_RET_UNKNOWN_COMMAND errors The recently added thermal policy support makes a hp_wmi_perform_query(0x4c, ...) call on older devices which do not support thermal policies this causes the following warning to be logged (seen on a HP Stream x360 Convertible PC 11): [ 26.805305] hp_wmi: query 0x4c returned error 0x3 Error 0x3 is HPWMI_RET_UNKNOWN_COMMAND error. This commit silences the warning for unknown-command errors, silencing the new warning. Cc: Elia Devito Fixes: 81c93798ef3e ("platform/x86: hp-wmi: add support for thermal policy") Link: https://lore.kernel.org/r/20210114232744.154886-1-hdegoede@redhat.com Signed-off-by: Hans de Goede --- drivers/platform/x86/hp-wmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index ecd477964d11..18bf8aeb5f87 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -247,7 +247,8 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, ret = bios_return->return_code; if (ret) { - if (ret != HPWMI_RET_UNKNOWN_CMDTYPE) + if (ret != HPWMI_RET_UNKNOWN_COMMAND && + ret != HPWMI_RET_UNKNOWN_CMDTYPE) pr_warn("query 0x%x returned error 0x%x\n", query, ret); goto out_free; } -- cgit v1.2.3 From c47c042942d31dec2f0b4bd66b948a51a2eb4985 Mon Sep 17 00:00:00 2001 From: Jeannie Stevenson Date: Fri, 15 Jan 2021 16:06:30 +0000 Subject: platform/x86: thinkpad_acpi: Add P53/73 firmware to fan_quirk_table for dual fan control This commit enables dual fan control for the new Lenovo P53 and P73 laptop models. Signed-off-by: Jeannie Stevenson Link: https://lore.kernel.org/r/Pn_Xii4XYpQRFtgkf4PbNgieE89BAkHgLI1kWIq-zFudwh2A1DY5J_DJVHK06rMW_hGPHx_mPE33gd8mg9-8BxqJTaSC6hhPqAsfZlcNGH0=@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c102657b3eb3..f3e8eca8d86d 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8783,6 +8783,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('N', '1', 'T', TPACPI_FAN_2CTL), /* P71 */ TPACPI_Q_LNV3('N', '1', 'U', TPACPI_FAN_2CTL), /* P51 */ TPACPI_Q_LNV3('N', '2', 'C', TPACPI_FAN_2CTL), /* P52 / P72 */ + TPACPI_Q_LNV3('N', '2', 'N', TPACPI_FAN_2CTL), /* P53 / P73 */ TPACPI_Q_LNV3('N', '2', 'E', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (1st gen) */ TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */ TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */ -- cgit v1.2.3 From bfc838f8598eab49d7d3d7557e90a7a0ee9b4464 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Oct 2020 20:53:21 +0300 Subject: drm/gma500: Convert to use new SCU IPC API Convert the GMA500 driver to use the new SCU IPC API. This allows us to get rid of the duplicate PMC IPC implementation which is now covered in SCU IPC driver. Signed-off-by: Andy Shevchenko Acked-by: Patrik Jakobsson Acked-by: Linus Walleij --- drivers/gpu/drm/gma500/Kconfig | 1 + drivers/gpu/drm/gma500/mdfld_device.c | 2 -- drivers/gpu/drm/gma500/mdfld_dsi_output.c | 2 -- drivers/gpu/drm/gma500/mdfld_output.c | 8 ++++++-- drivers/gpu/drm/gma500/oaktrail_device.c | 3 --- drivers/gpu/drm/gma500/psb_drv.h | 3 +++ drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c | 12 +++++++----- 7 files changed, 17 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/gma500/Kconfig b/drivers/gpu/drm/gma500/Kconfig index 0e23c93a1094..1ef2cda5b5c2 100644 --- a/drivers/gpu/drm/gma500/Kconfig +++ b/drivers/gpu/drm/gma500/Kconfig @@ -30,6 +30,7 @@ config DRM_GMA3600 config DRM_MEDFIELD bool "Intel Medfield support (Experimental)" depends on DRM_GMA500 && X86_INTEL_MID + select INTEL_SCU_IPC help Say yes to include support for the Intel Medfield platform. diff --git a/drivers/gpu/drm/gma500/mdfld_device.c b/drivers/gpu/drm/gma500/mdfld_device.c index b83d59b21de5..12193e591327 100644 --- a/drivers/gpu/drm/gma500/mdfld_device.c +++ b/drivers/gpu/drm/gma500/mdfld_device.c @@ -8,8 +8,6 @@ #include #include -#include - #include "mdfld_dsi_output.h" #include "mdfld_output.h" #include "mid_bios.h" diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_output.c b/drivers/gpu/drm/gma500/mdfld_dsi_output.c index 4aab76613bd9..38ec49b71499 100644 --- a/drivers/gpu/drm/gma500/mdfld_dsi_output.c +++ b/drivers/gpu/drm/gma500/mdfld_dsi_output.c @@ -30,8 +30,6 @@ #include #include -#include - #include "mdfld_dsi_dpi.h" #include "mdfld_dsi_output.h" #include "mdfld_dsi_pkg_sender.h" diff --git a/drivers/gpu/drm/gma500/mdfld_output.c b/drivers/gpu/drm/gma500/mdfld_output.c index c95966bb0c96..518417f49be8 100644 --- a/drivers/gpu/drm/gma500/mdfld_output.c +++ b/drivers/gpu/drm/gma500/mdfld_output.c @@ -25,6 +25,8 @@ * Scott Rowe */ +#include + #include "mdfld_output.h" #include "mdfld_dsi_dpi.h" #include "mdfld_dsi_output.h" @@ -58,11 +60,14 @@ static void mdfld_init_panel(struct drm_device *dev, int mipi_pipe, } } - int mdfld_output_init(struct drm_device *dev) { struct drm_psb_private *dev_priv = dev->dev_private; + dev_priv->scu = devm_intel_scu_ipc_dev_get(&dev->pdev->dev); + if (!dev_priv->scu) + return -EPROBE_DEFER; + /* FIXME: hardcoded for now */ dev_priv->mdfld_panel_id = TC35876X; /* MIPI panel 1 */ @@ -71,4 +76,3 @@ int mdfld_output_init(struct drm_device *dev) mdfld_init_panel(dev, 1, HDMI); return 0; } - diff --git a/drivers/gpu/drm/gma500/oaktrail_device.c b/drivers/gpu/drm/gma500/oaktrail_device.c index 8754290b0e23..0c1a66b20f27 100644 --- a/drivers/gpu/drm/gma500/oaktrail_device.c +++ b/drivers/gpu/drm/gma500/oaktrail_device.c @@ -10,9 +10,6 @@ #include #include -#include -#include - #include #include "intel_bios.h" diff --git a/drivers/gpu/drm/gma500/psb_drv.h b/drivers/gpu/drm/gma500/psb_drv.h index 5b7f7a312d53..938efdddc27c 100644 --- a/drivers/gpu/drm/gma500/psb_drv.h +++ b/drivers/gpu/drm/gma500/psb_drv.h @@ -428,6 +428,8 @@ struct psb_ops; #define PSB_NUM_PIPE 3 +struct intel_scu_ipc_dev; + struct drm_psb_private { struct drm_device *dev; struct pci_dev *aux_pdev; /* Currently only used by mrst */ @@ -567,6 +569,7 @@ struct drm_psb_private { * Used for modifying backlight from * xrandr -- consider removing and using HAL instead */ + struct intel_scu_ipc_dev *scu; struct backlight_device *backlight_device; struct drm_property *backlight_property; bool backlight_enabled; diff --git a/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c index e5bdd99ad453..7d012db6352b 100644 --- a/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c +++ b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c @@ -444,6 +444,7 @@ static inline u16 calc_clkdiv(unsigned long baseclk, unsigned int f) static void tc35876x_brightness_init(struct drm_device *dev) { + struct drm_psb_private *dev_priv = dev->dev_private; int ret; u8 pwmctrl; u16 clkdiv; @@ -451,23 +452,23 @@ static void tc35876x_brightness_init(struct drm_device *dev) /* Make sure the PWM reference is the 19.2 MHz system clock. Read first * instead of setting directly to catch potential conflicts between PWM * users. */ - ret = intel_scu_ipc_ioread8(GPIOPWMCTRL, &pwmctrl); + ret = intel_scu_ipc_dev_ioread8(dev_priv->scu, GPIOPWMCTRL, &pwmctrl); if (ret || pwmctrl != 0x01) { if (ret) dev_err(&dev->pdev->dev, "GPIOPWMCTRL read failed\n"); else dev_warn(&dev->pdev->dev, "GPIOPWMCTRL was not set to system clock (pwmctrl = 0x%02x)\n", pwmctrl); - ret = intel_scu_ipc_iowrite8(GPIOPWMCTRL, 0x01); + ret = intel_scu_ipc_dev_iowrite8(dev_priv->scu, GPIOPWMCTRL, 0x01); if (ret) dev_err(&dev->pdev->dev, "GPIOPWMCTRL set failed\n"); } clkdiv = calc_clkdiv(SYSTEMCLK, PWM_FREQUENCY); - ret = intel_scu_ipc_iowrite8(PWM0CLKDIV1, (clkdiv >> 8) & 0xff); + ret = intel_scu_ipc_dev_iowrite8(dev_priv->scu, PWM0CLKDIV1, (clkdiv >> 8) & 0xff); if (!ret) - ret = intel_scu_ipc_iowrite8(PWM0CLKDIV0, clkdiv & 0xff); + ret = intel_scu_ipc_dev_iowrite8(dev_priv->scu, PWM0CLKDIV0, clkdiv & 0xff); if (ret) dev_err(&dev->pdev->dev, "PWM0CLKDIV set failed\n"); @@ -480,6 +481,7 @@ static void tc35876x_brightness_init(struct drm_device *dev) void tc35876x_brightness_control(struct drm_device *dev, int level) { + struct drm_psb_private *dev_priv = dev->dev_private; int ret; u8 duty_val; u8 panel_duty_val; @@ -495,7 +497,7 @@ void tc35876x_brightness_control(struct drm_device *dev, int level) panel_duty_val = (2 * level - 100) * 0xA9 / MDFLD_DSI_BRIGHTNESS_MAX_LEVEL + 0x56; - ret = intel_scu_ipc_iowrite8(PWM0DUTYCYCLE, duty_val); + ret = intel_scu_ipc_dev_iowrite8(dev_priv->scu, PWM0DUTYCYCLE, duty_val); if (ret) dev_err(&tc35876x_client->dev, "%s: ipc write fail\n", __func__); -- cgit v1.2.3 From 25ded39ad064b06757d00609c36c85ab2312a94b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Oct 2020 21:54:00 +0300 Subject: drm/gma500: Get rid of duplicate NULL checks Since GPIOs are optional we don't need to repeat checks that are done in the GPIO API. Remove them for good. Signed-off-by: Andy Shevchenko Acked-by: Patrik Jakobsson Acked-by: Linus Walleij --- drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c index 7d012db6352b..3dab94463656 100644 --- a/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c +++ b/drivers/gpu/drm/gma500/tc35876x-dsi-lvds.c @@ -518,11 +518,9 @@ void tc35876x_toshiba_bridge_panel_off(struct drm_device *dev) dev_dbg(&tc35876x_client->dev, "%s\n", __func__); - if (bridge_bl_enable) - gpiod_set_value_cansleep(bridge_bl_enable, 0); + gpiod_set_value_cansleep(bridge_bl_enable, 0); - if (backlight_voltage) - gpiod_set_value_cansleep(backlight_voltage, 0); + gpiod_set_value_cansleep(backlight_voltage, 0); } void tc35876x_toshiba_bridge_panel_on(struct drm_device *dev) @@ -567,8 +565,7 @@ void tc35876x_toshiba_bridge_panel_on(struct drm_device *dev) "i2c write failed (%d)\n", ret); } - if (bridge_bl_enable) - gpiod_set_value_cansleep(bridge_bl_enable, 1); + gpiod_set_value_cansleep(bridge_bl_enable, 1); tc35876x_brightness_control(dev, dev_priv->brightness_adjusted); } @@ -642,20 +639,17 @@ static int tc35876x_bridge_probe(struct i2c_client *client, bridge_reset = devm_gpiod_get_optional(&client->dev, "bridge-reset", GPIOD_OUT_LOW); if (IS_ERR(bridge_reset)) return PTR_ERR(bridge_reset); - if (bridge_reset) - gpiod_set_consumer_name(bridge_reset, "tc35876x bridge reset"); + gpiod_set_consumer_name(bridge_reset, "tc35876x bridge reset"); bridge_bl_enable = devm_gpiod_get_optional(&client->dev, "bl-en", GPIOD_OUT_LOW); if (IS_ERR(bridge_bl_enable)) return PTR_ERR(bridge_bl_enable); - if (bridge_bl_enable) - gpiod_set_consumer_name(bridge_bl_enable, "tc35876x panel bl en"); + gpiod_set_consumer_name(bridge_bl_enable, "tc35876x panel bl en"); backlight_voltage = devm_gpiod_get_optional(&client->dev, "vadd", GPIOD_OUT_LOW); if (IS_ERR(backlight_voltage)) return PTR_ERR(backlight_voltage); - if (backlight_voltage) - gpiod_set_consumer_name(backlight_voltage, "tc35876x panel vadd"); + gpiod_set_consumer_name(backlight_voltage, "tc35876x panel vadd"); tc35876x_client = client; -- cgit v1.2.3 From 5f7582aa2d3c2ea0a9c9be17bcb53d29c0417ae5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Nov 2019 16:57:30 +0200 Subject: gpio: intel-mid: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Moreover this code duplicates gpio-pxa since the IP has been derived from XScale implementation. If anybody wants to resurrect this it has to be part of gpio-pxa.c. Signed-off-by: Andy Shevchenko Acked-by: Linus Walleij --- MAINTAINERS | 1 - drivers/gpio/Kconfig | 7 - drivers/gpio/Makefile | 1 - drivers/gpio/TODO | 2 +- drivers/gpio/gpio-intel-mid.c | 414 ------------------------------------------ 5 files changed, 1 insertion(+), 424 deletions(-) delete mode 100644 drivers/gpio/gpio-intel-mid.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 546aa66428c9..92074cba9dcc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8940,7 +8940,6 @@ L: linux-gpio@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: drivers/gpio/gpio-ich.c -F: drivers/gpio/gpio-intel-mid.c F: drivers/gpio/gpio-merrifield.c F: drivers/gpio/gpio-ml-ioh.c F: drivers/gpio/gpio-pch.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index c70f46e80a3b..6d123cb0bd9c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1451,13 +1451,6 @@ config GPIO_BT8XX If unsure, say N. -config GPIO_INTEL_MID - bool "Intel MID GPIO support" - depends on X86_INTEL_MID - select GPIOLIB_IRQCHIP - help - Say Y here to support Intel MID GPIO. - config GPIO_MERRIFIELD tristate "Intel Merrifield GPIO support" depends on X86_INTEL_MID diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 35e3b6026665..a2106d667fb3 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -67,7 +67,6 @@ obj-$(CONFIG_GPIO_HISI) += gpio-hisi.o obj-$(CONFIG_GPIO_HLWD) += gpio-hlwd.o obj-$(CONFIG_HTC_EGPIO) += gpio-htc-egpio.o obj-$(CONFIG_GPIO_ICH) += gpio-ich.o -obj-$(CONFIG_GPIO_INTEL_MID) += gpio-intel-mid.o obj-$(CONFIG_GPIO_IOP) += gpio-iop.o obj-$(CONFIG_GPIO_IT87) += gpio-it87.o obj-$(CONFIG_GPIO_IXP4XX) += gpio-ixp4xx.o diff --git a/drivers/gpio/TODO b/drivers/gpio/TODO index 0229fa79499e..b8b1473a5b1e 100644 --- a/drivers/gpio/TODO +++ b/drivers/gpio/TODO @@ -101,7 +101,7 @@ for a few GPIOs. Those should stay where they are. At the same time it makes sense to get rid of code duplication in existing or new coming drivers. For example, gpio-ml-ioh should be incorporated into -gpio-pch. In similar way gpio-intel-mid into gpio-pxa. +gpio-pch. Generic MMIO GPIO diff --git a/drivers/gpio/gpio-intel-mid.c b/drivers/gpio/gpio-intel-mid.c deleted file mode 100644 index 86a10c808ef6..000000000000 --- a/drivers/gpio/gpio-intel-mid.c +++ /dev/null @@ -1,414 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel MID GPIO driver - * - * Copyright (c) 2008-2014,2016 Intel Corporation. - */ - -/* Supports: - * Moorestown platform Langwell chip. - * Medfield platform Penwell chip. - * Clovertrail platform Cloverview chip. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define INTEL_MID_IRQ_TYPE_EDGE (1 << 0) -#define INTEL_MID_IRQ_TYPE_LEVEL (1 << 1) - -/* - * Langwell chip has 64 pins and thus there are 2 32bit registers to control - * each feature, while Penwell chip has 96 pins for each block, and need 3 32bit - * registers to control them, so we only define the order here instead of a - * structure, to get a bit offset for a pin (use GPDR as an example): - * - * nreg = ngpio / 32; - * reg = offset / 32; - * bit = offset % 32; - * reg_addr = reg_base + GPDR * nreg * 4 + reg * 4; - * - * so the bit of reg_addr is to control pin offset's GPDR feature -*/ - -enum GPIO_REG { - GPLR = 0, /* pin level read-only */ - GPDR, /* pin direction */ - GPSR, /* pin set */ - GPCR, /* pin clear */ - GRER, /* rising edge detect */ - GFER, /* falling edge detect */ - GEDR, /* edge detect result */ - GAFR, /* alt function */ -}; - -/* intel_mid gpio driver data */ -struct intel_mid_gpio_ddata { - u16 ngpio; /* number of gpio pins */ - u32 chip_irq_type; /* chip interrupt type */ -}; - -struct intel_mid_gpio { - struct gpio_chip chip; - void __iomem *reg_base; - spinlock_t lock; - struct pci_dev *pdev; -}; - -static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned offset, - enum GPIO_REG reg_type) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - unsigned nreg = chip->ngpio / 32; - u8 reg = offset / 32; - - return priv->reg_base + reg_type * nreg * 4 + reg * 4; -} - -static void __iomem *gpio_reg_2bit(struct gpio_chip *chip, unsigned offset, - enum GPIO_REG reg_type) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - unsigned nreg = chip->ngpio / 32; - u8 reg = offset / 16; - - return priv->reg_base + reg_type * nreg * 4 + reg * 4; -} - -static int intel_gpio_request(struct gpio_chip *chip, unsigned offset) -{ - void __iomem *gafr = gpio_reg_2bit(chip, offset, GAFR); - u32 value = readl(gafr); - int shift = (offset % 16) << 1, af = (value >> shift) & 3; - - if (af) { - value &= ~(3 << shift); - writel(value, gafr); - } - return 0; -} - -static int intel_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - void __iomem *gplr = gpio_reg(chip, offset, GPLR); - - return !!(readl(gplr) & BIT(offset % 32)); -} - -static void intel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) -{ - void __iomem *gpsr, *gpcr; - - if (value) { - gpsr = gpio_reg(chip, offset, GPSR); - writel(BIT(offset % 32), gpsr); - } else { - gpcr = gpio_reg(chip, offset, GPCR); - writel(BIT(offset % 32), gpcr); - } -} - -static int intel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *gpdr = gpio_reg(chip, offset, GPDR); - u32 value; - unsigned long flags; - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - value = readl(gpdr); - value &= ~BIT(offset % 32); - writel(value, gpdr); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static int intel_gpio_direction_output(struct gpio_chip *chip, - unsigned offset, int value) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *gpdr = gpio_reg(chip, offset, GPDR); - unsigned long flags; - - intel_gpio_set(chip, offset, value); - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - value = readl(gpdr); - value |= BIT(offset % 32); - writel(value, gpdr); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static int intel_mid_irq_type(struct irq_data *d, unsigned type) -{ - struct gpio_chip *gc = irq_data_get_irq_chip_data(d); - struct intel_mid_gpio *priv = gpiochip_get_data(gc); - u32 gpio = irqd_to_hwirq(d); - unsigned long flags; - u32 value; - void __iomem *grer = gpio_reg(&priv->chip, gpio, GRER); - void __iomem *gfer = gpio_reg(&priv->chip, gpio, GFER); - - if (gpio >= priv->chip.ngpio) - return -EINVAL; - - if (priv->pdev) - pm_runtime_get(&priv->pdev->dev); - - spin_lock_irqsave(&priv->lock, flags); - if (type & IRQ_TYPE_EDGE_RISING) - value = readl(grer) | BIT(gpio % 32); - else - value = readl(grer) & (~BIT(gpio % 32)); - writel(value, grer); - - if (type & IRQ_TYPE_EDGE_FALLING) - value = readl(gfer) | BIT(gpio % 32); - else - value = readl(gfer) & (~BIT(gpio % 32)); - writel(value, gfer); - spin_unlock_irqrestore(&priv->lock, flags); - - if (priv->pdev) - pm_runtime_put(&priv->pdev->dev); - - return 0; -} - -static void intel_mid_irq_unmask(struct irq_data *d) -{ -} - -static void intel_mid_irq_mask(struct irq_data *d) -{ -} - -static struct irq_chip intel_mid_irqchip = { - .name = "INTEL_MID-GPIO", - .irq_mask = intel_mid_irq_mask, - .irq_unmask = intel_mid_irq_unmask, - .irq_set_type = intel_mid_irq_type, -}; - -static const struct intel_mid_gpio_ddata gpio_lincroft = { - .ngpio = 64, -}; - -static const struct intel_mid_gpio_ddata gpio_penwell_aon = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct intel_mid_gpio_ddata gpio_penwell_core = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct intel_mid_gpio_ddata gpio_cloverview_aon = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE | INTEL_MID_IRQ_TYPE_LEVEL, -}; - -static const struct intel_mid_gpio_ddata gpio_cloverview_core = { - .ngpio = 96, - .chip_irq_type = INTEL_MID_IRQ_TYPE_EDGE, -}; - -static const struct pci_device_id intel_gpio_ids[] = { - { - /* Lincroft */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080f), - .driver_data = (kernel_ulong_t)&gpio_lincroft, - }, - { - /* Penwell AON */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081f), - .driver_data = (kernel_ulong_t)&gpio_penwell_aon, - }, - { - /* Penwell Core */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x081a), - .driver_data = (kernel_ulong_t)&gpio_penwell_core, - }, - { - /* Cloverview Aon */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08eb), - .driver_data = (kernel_ulong_t)&gpio_cloverview_aon, - }, - { - /* Cloverview Core */ - PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x08f7), - .driver_data = (kernel_ulong_t)&gpio_cloverview_core, - }, - { } -}; - -static void intel_mid_irq_handler(struct irq_desc *desc) -{ - struct gpio_chip *gc = irq_desc_get_handler_data(desc); - struct intel_mid_gpio *priv = gpiochip_get_data(gc); - struct irq_data *data = irq_desc_get_irq_data(desc); - struct irq_chip *chip = irq_data_get_irq_chip(data); - u32 base, gpio, mask; - unsigned long pending; - void __iomem *gedr; - - /* check GPIO controller to check which pin triggered the interrupt */ - for (base = 0; base < priv->chip.ngpio; base += 32) { - gedr = gpio_reg(&priv->chip, base, GEDR); - while ((pending = readl(gedr))) { - gpio = __ffs(pending); - mask = BIT(gpio); - /* Clear before handling so we can't lose an edge */ - writel(mask, gedr); - generic_handle_irq(irq_find_mapping(gc->irq.domain, - base + gpio)); - } - } - - chip->irq_eoi(data); -} - -static int intel_mid_irq_init_hw(struct gpio_chip *chip) -{ - struct intel_mid_gpio *priv = gpiochip_get_data(chip); - void __iomem *reg; - unsigned base; - - for (base = 0; base < priv->chip.ngpio; base += 32) { - /* Clear the rising-edge detect register */ - reg = gpio_reg(&priv->chip, base, GRER); - writel(0, reg); - /* Clear the falling-edge detect register */ - reg = gpio_reg(&priv->chip, base, GFER); - writel(0, reg); - /* Clear the edge detect status register */ - reg = gpio_reg(&priv->chip, base, GEDR); - writel(~0, reg); - } - - return 0; -} - -static int __maybe_unused intel_gpio_runtime_idle(struct device *dev) -{ - int err = pm_schedule_suspend(dev, 500); - return err ?: -EBUSY; -} - -static const struct dev_pm_ops intel_gpio_pm_ops = { - SET_RUNTIME_PM_OPS(NULL, NULL, intel_gpio_runtime_idle) -}; - -static int intel_gpio_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - void __iomem *base; - struct intel_mid_gpio *priv; - u32 gpio_base; - u32 irq_base; - int retval; - struct gpio_irq_chip *girq; - struct intel_mid_gpio_ddata *ddata = - (struct intel_mid_gpio_ddata *)id->driver_data; - - retval = pcim_enable_device(pdev); - if (retval) - return retval; - - retval = pcim_iomap_regions(pdev, 1 << 0 | 1 << 1, pci_name(pdev)); - if (retval) { - dev_err(&pdev->dev, "I/O memory mapping error\n"); - return retval; - } - - base = pcim_iomap_table(pdev)[1]; - - irq_base = readl(base); - gpio_base = readl(sizeof(u32) + base); - - /* release the IO mapping, since we already get the info from bar1 */ - pcim_iounmap_regions(pdev, 1 << 1); - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->reg_base = pcim_iomap_table(pdev)[0]; - priv->chip.label = dev_name(&pdev->dev); - priv->chip.parent = &pdev->dev; - priv->chip.request = intel_gpio_request; - priv->chip.direction_input = intel_gpio_direction_input; - priv->chip.direction_output = intel_gpio_direction_output; - priv->chip.get = intel_gpio_get; - priv->chip.set = intel_gpio_set; - priv->chip.base = gpio_base; - priv->chip.ngpio = ddata->ngpio; - priv->chip.can_sleep = false; - priv->pdev = pdev; - - spin_lock_init(&priv->lock); - - girq = &priv->chip.irq; - girq->chip = &intel_mid_irqchip; - girq->init_hw = intel_mid_irq_init_hw; - girq->parent_handler = intel_mid_irq_handler; - girq->num_parents = 1; - girq->parents = devm_kcalloc(&pdev->dev, girq->num_parents, - sizeof(*girq->parents), - GFP_KERNEL); - if (!girq->parents) - return -ENOMEM; - girq->parents[0] = pdev->irq; - girq->first = irq_base; - girq->default_type = IRQ_TYPE_NONE; - girq->handler = handle_simple_irq; - - pci_set_drvdata(pdev, priv); - - retval = devm_gpiochip_add_data(&pdev->dev, &priv->chip, priv); - if (retval) { - dev_err(&pdev->dev, "gpiochip_add error %d\n", retval); - return retval; - } - - pm_runtime_put_noidle(&pdev->dev); - pm_runtime_allow(&pdev->dev); - - return 0; -} - -static struct pci_driver intel_gpio_driver = { - .name = "intel_mid_gpio", - .id_table = intel_gpio_ids, - .probe = intel_gpio_probe, - .driver = { - .pm = &intel_gpio_pm_ops, - }, -}; - -builtin_pci_driver(intel_gpio_driver); -- cgit v1.2.3 From aee25798acf00978a2d9d39ae8b2c2353757d01d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Nov 2019 16:57:30 +0200 Subject: gpio: msic: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Acked-by: Linus Walleij --- MAINTAINERS | 1 - drivers/gpio/Kconfig | 7 -- drivers/gpio/gpio-msic.c | 314 ----------------------------------------------- 3 files changed, 322 deletions(-) delete mode 100644 drivers/gpio/gpio-msic.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 92074cba9dcc..a5f161c9a059 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9098,7 +9098,6 @@ M: Andy Shevchenko S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-gpio-intel.git F: drivers/gpio/gpio-*cove.c -F: drivers/gpio/gpio-msic.c INTEL PMIC MULTIFUNCTION DEVICE DRIVERS M: Andy Shevchenko diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 6d123cb0bd9c..a6987ff28d7c 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1249,13 +1249,6 @@ config GPIO_MAX77650 GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor. These chips have a single pin that can be configured as GPIO. -config GPIO_MSIC - bool "Intel MSIC mixed signal gpio support" - depends on (X86 || COMPILE_TEST) && MFD_INTEL_MSIC - help - Enable support for GPIO on intel MSIC controllers found in - intel MID devices - config GPIO_PALMAS bool "TI PALMAS series PMICs GPIO" depends on MFD_PALMAS diff --git a/drivers/gpio/gpio-msic.c b/drivers/gpio/gpio-msic.c deleted file mode 100644 index 7e3c96e4ab2c..000000000000 --- a/drivers/gpio/gpio-msic.c +++ /dev/null @@ -1,314 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Medfield MSIC GPIO driver> - * Copyright (c) 2011, Intel Corporation. - * - * Author: Mathias Nyman - * Based on intel_pmic_gpio.c - */ - -#include -#include -#include -#include -#include -#include -#include - -/* the offset for the mapping of global gpio pin to irq */ -#define MSIC_GPIO_IRQ_OFFSET 0x100 - -#define MSIC_GPIO_DIR_IN 0 -#define MSIC_GPIO_DIR_OUT BIT(5) -#define MSIC_GPIO_TRIG_FALL BIT(1) -#define MSIC_GPIO_TRIG_RISE BIT(2) - -/* masks for msic gpio output GPIOxxxxCTLO registers */ -#define MSIC_GPIO_DIR_MASK BIT(5) -#define MSIC_GPIO_DRV_MASK BIT(4) -#define MSIC_GPIO_REN_MASK BIT(3) -#define MSIC_GPIO_RVAL_MASK (BIT(2) | BIT(1)) -#define MSIC_GPIO_DOUT_MASK BIT(0) - -/* masks for msic gpio input GPIOxxxxCTLI registers */ -#define MSIC_GPIO_GLBYP_MASK BIT(5) -#define MSIC_GPIO_DBNC_MASK (BIT(4) | BIT(3)) -#define MSIC_GPIO_INTCNT_MASK (BIT(2) | BIT(1)) -#define MSIC_GPIO_DIN_MASK BIT(0) - -#define MSIC_NUM_GPIO 24 - -struct msic_gpio { - struct platform_device *pdev; - struct mutex buslock; - struct gpio_chip chip; - int irq; - unsigned irq_base; - unsigned long trig_change_mask; - unsigned trig_type; -}; - -/* - * MSIC has 24 gpios, 16 low voltage (1.2-1.8v) and 8 high voltage (3v). - * Both the high and low voltage gpios are divided in two banks. - * GPIOs are numbered with GPIO0LV0 as gpio_base in the following order: - * GPIO0LV0..GPIO0LV7: low voltage, bank 0, gpio_base - * GPIO1LV0..GPIO1LV7: low voltage, bank 1, gpio_base + 8 - * GPIO0HV0..GPIO0HV3: high voltage, bank 0, gpio_base + 16 - * GPIO1HV0..GPIO1HV3: high voltage, bank 1, gpio_base + 20 - */ - -static int msic_gpio_to_ireg(unsigned offset) -{ - if (offset >= MSIC_NUM_GPIO) - return -EINVAL; - - if (offset < 8) - return INTEL_MSIC_GPIO0LV0CTLI - offset; - if (offset < 16) - return INTEL_MSIC_GPIO1LV0CTLI - offset + 8; - if (offset < 20) - return INTEL_MSIC_GPIO0HV0CTLI - offset + 16; - - return INTEL_MSIC_GPIO1HV0CTLI - offset + 20; -} - -static int msic_gpio_to_oreg(unsigned offset) -{ - if (offset >= MSIC_NUM_GPIO) - return -EINVAL; - - if (offset < 8) - return INTEL_MSIC_GPIO0LV0CTLO - offset; - if (offset < 16) - return INTEL_MSIC_GPIO1LV0CTLO - offset + 8; - if (offset < 20) - return INTEL_MSIC_GPIO0HV0CTLO - offset + 16; - - return INTEL_MSIC_GPIO1HV0CTLO - offset + 20; -} - -static int msic_gpio_direction_input(struct gpio_chip *chip, unsigned offset) -{ - int reg; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return reg; - - return intel_msic_reg_update(reg, MSIC_GPIO_DIR_IN, MSIC_GPIO_DIR_MASK); -} - -static int msic_gpio_direction_output(struct gpio_chip *chip, - unsigned offset, int value) -{ - int reg; - unsigned mask; - - value = (!!value) | MSIC_GPIO_DIR_OUT; - mask = MSIC_GPIO_DIR_MASK | MSIC_GPIO_DOUT_MASK; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return reg; - - return intel_msic_reg_update(reg, value, mask); -} - -static int msic_gpio_get(struct gpio_chip *chip, unsigned offset) -{ - u8 r; - int ret; - int reg; - - reg = msic_gpio_to_ireg(offset); - if (reg < 0) - return reg; - - ret = intel_msic_reg_read(reg, &r); - if (ret < 0) - return ret; - - return !!(r & MSIC_GPIO_DIN_MASK); -} - -static void msic_gpio_set(struct gpio_chip *chip, unsigned offset, int value) -{ - int reg; - - reg = msic_gpio_to_oreg(offset); - if (reg < 0) - return; - - intel_msic_reg_update(reg, !!value , MSIC_GPIO_DOUT_MASK); -} - -/* - * This is called from genirq with mg->buslock locked and - * irq_desc->lock held. We can not access the scu bus here, so we - * store the change and update in the bus_sync_unlock() function below - */ -static int msic_irq_type(struct irq_data *data, unsigned type) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - u32 gpio = data->irq - mg->irq_base; - - if (gpio >= mg->chip.ngpio) - return -EINVAL; - - /* mark for which gpio the trigger changed, protected by buslock */ - mg->trig_change_mask |= (1 << gpio); - mg->trig_type = type; - - return 0; -} - -static int msic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) -{ - struct msic_gpio *mg = gpiochip_get_data(chip); - return mg->irq_base + offset; -} - -static void msic_bus_lock(struct irq_data *data) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - mutex_lock(&mg->buslock); -} - -static void msic_bus_sync_unlock(struct irq_data *data) -{ - struct msic_gpio *mg = irq_data_get_irq_chip_data(data); - int offset; - int reg; - u8 trig = 0; - - /* We can only get one change at a time as the buslock covers the - entire transaction. The irq_desc->lock is dropped before we are - called but that is fine */ - if (mg->trig_change_mask) { - offset = __ffs(mg->trig_change_mask); - - reg = msic_gpio_to_ireg(offset); - if (reg < 0) - goto out; - - if (mg->trig_type & IRQ_TYPE_EDGE_RISING) - trig |= MSIC_GPIO_TRIG_RISE; - if (mg->trig_type & IRQ_TYPE_EDGE_FALLING) - trig |= MSIC_GPIO_TRIG_FALL; - - intel_msic_reg_update(reg, trig, MSIC_GPIO_INTCNT_MASK); - mg->trig_change_mask = 0; - } -out: - mutex_unlock(&mg->buslock); -} - -/* Firmware does all the masking and unmasking for us, no masking here. */ -static void msic_irq_unmask(struct irq_data *data) { } - -static void msic_irq_mask(struct irq_data *data) { } - -static struct irq_chip msic_irqchip = { - .name = "MSIC-GPIO", - .irq_mask = msic_irq_mask, - .irq_unmask = msic_irq_unmask, - .irq_set_type = msic_irq_type, - .irq_bus_lock = msic_bus_lock, - .irq_bus_sync_unlock = msic_bus_sync_unlock, -}; - -static void msic_gpio_irq_handler(struct irq_desc *desc) -{ - struct irq_data *data = irq_desc_get_irq_data(desc); - struct msic_gpio *mg = irq_data_get_irq_handler_data(data); - struct irq_chip *chip = irq_data_get_irq_chip(data); - struct intel_msic *msic = pdev_to_intel_msic(mg->pdev); - unsigned long pending; - int i; - int bitnr; - u8 pin; - - for (i = 0; i < (mg->chip.ngpio / BITS_PER_BYTE); i++) { - intel_msic_irq_read(msic, INTEL_MSIC_GPIO0LVIRQ + i, &pin); - pending = pin; - - for_each_set_bit(bitnr, &pending, BITS_PER_BYTE) - generic_handle_irq(mg->irq_base + i * BITS_PER_BYTE + bitnr); - } - chip->irq_eoi(data); -} - -static int platform_msic_gpio_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct intel_msic_gpio_pdata *pdata = dev_get_platdata(dev); - struct msic_gpio *mg; - int irq = platform_get_irq(pdev, 0); - int retval; - int i; - - if (irq < 0) { - dev_err(dev, "no IRQ line: %d\n", irq); - return irq; - } - - if (!pdata || !pdata->gpio_base) { - dev_err(dev, "incorrect or missing platform data\n"); - return -EINVAL; - } - - mg = kzalloc(sizeof(*mg), GFP_KERNEL); - if (!mg) - return -ENOMEM; - - dev_set_drvdata(dev, mg); - - mg->pdev = pdev; - mg->irq = irq; - mg->irq_base = pdata->gpio_base + MSIC_GPIO_IRQ_OFFSET; - mg->chip.label = "msic_gpio"; - mg->chip.direction_input = msic_gpio_direction_input; - mg->chip.direction_output = msic_gpio_direction_output; - mg->chip.get = msic_gpio_get; - mg->chip.set = msic_gpio_set; - mg->chip.to_irq = msic_gpio_to_irq; - mg->chip.base = pdata->gpio_base; - mg->chip.ngpio = MSIC_NUM_GPIO; - mg->chip.can_sleep = true; - mg->chip.parent = dev; - - mutex_init(&mg->buslock); - - retval = gpiochip_add_data(&mg->chip, mg); - if (retval) { - dev_err(dev, "Adding MSIC gpio chip failed\n"); - goto err; - } - - for (i = 0; i < mg->chip.ngpio; i++) { - irq_set_chip_data(i + mg->irq_base, mg); - irq_set_chip_and_handler(i + mg->irq_base, - &msic_irqchip, - handle_simple_irq); - } - irq_set_chained_handler_and_data(mg->irq, msic_gpio_irq_handler, mg); - - return 0; -err: - kfree(mg); - return retval; -} - -static struct platform_driver platform_msic_gpio_driver = { - .driver = { - .name = "msic_gpio", - }, - .probe = platform_msic_gpio_probe, -}; - -static int __init platform_msic_gpio_init(void) -{ - return platform_driver_register(&platform_msic_gpio_driver); -} -subsys_initcall(platform_msic_gpio_init); -- cgit v1.2.3 From bbb284c007b3be59aed94a202a20c1be3e942caf Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 17 Aug 2020 12:47:11 +0300 Subject: platform/x86: intel_mid_thermal: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Reviewed-by: Hans de Goede Acked-by: Linus Walleij --- drivers/platform/x86/Kconfig | 7 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel_mid_thermal.c | 560 ------------------------------- 3 files changed, 568 deletions(-) delete mode 100644 drivers/platform/x86/intel_mid_thermal.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 91e6176cdfbd..3ba680af3ef5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1327,13 +1327,6 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. -config INTEL_MFLD_THERMAL - tristate "Thermal driver for Intel Medfield platform" - depends on MFD_INTEL_MSIC && THERMAL - help - Say Y here to enable thermal driver support for the Intel Medfield - platform. - config INTEL_MID_POWER_BUTTON tristate "power button driver for Intel MID platforms" depends on INTEL_SCU && INPUT diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 581475f59819..6fb57502b59a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -137,7 +137,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o -obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c deleted file mode 100644 index f12f4e7bd971..000000000000 --- a/drivers/platform/x86/intel_mid_thermal.c +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel MID platform thermal driver - * - * Copyright (C) 2011 Intel Corporation - * - * Author: Durgadoss R - */ - -#define pr_fmt(fmt) "intel_mid_thermal: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Number of thermal sensors */ -#define MSIC_THERMAL_SENSORS 4 - -/* ADC1 - thermal registers */ -#define MSIC_ADC_ENBL 0x10 -#define MSIC_ADC_START 0x08 - -#define MSIC_ADCTHERM_ENBL 0x04 -#define MSIC_ADCRRDATA_ENBL 0x05 -#define MSIC_CHANL_MASK_VAL 0x0F - -#define MSIC_STOPBIT_MASK 16 -#define MSIC_ADCTHERM_MASK 4 -/* Number of ADC channels */ -#define ADC_CHANLS_MAX 15 -#define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) - -/* ADC channel code values */ -#define SKIN_SENSOR0_CODE 0x08 -#define SKIN_SENSOR1_CODE 0x09 -#define SYS_SENSOR_CODE 0x0A -#define MSIC_DIE_SENSOR_CODE 0x03 - -#define SKIN_THERM_SENSOR0 0 -#define SKIN_THERM_SENSOR1 1 -#define SYS_THERM_SENSOR2 2 -#define MSIC_DIE_THERM_SENSOR3 3 - -/* ADC code range */ -#define ADC_MAX 977 -#define ADC_MIN 162 -#define ADC_VAL0C 887 -#define ADC_VAL20C 720 -#define ADC_VAL40C 508 -#define ADC_VAL60C 315 - -/* ADC base addresses */ -#define ADC_CHNL_START_ADDR INTEL_MSIC_ADC1ADDR0 /* increments by 1 */ -#define ADC_DATA_START_ADDR INTEL_MSIC_ADC1SNS0H /* increments by 2 */ - -/* MSIC die attributes */ -#define MSIC_DIE_ADC_MIN 488 -#define MSIC_DIE_ADC_MAX 1004 - -/* This holds the address of the first free ADC channel, - * among the 15 channels - */ -static int channel_index; - -struct platform_info { - struct platform_device *pdev; - struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; -}; - -struct thermal_device_info { - unsigned int chnl_addr; - int direct; - /* This holds the current temperature in millidegree celsius */ - long curr_temp; -}; - -/** - * to_msic_die_temp - converts adc_val to msic_die temperature - * @adc_val: ADC value to be converted - * - * Can sleep - */ -static int to_msic_die_temp(uint16_t adc_val) -{ - return (368 * (adc_val) / 1000) - 220; -} - -/** - * is_valid_adc - checks whether the adc code is within the defined range - * @min: minimum value for the sensor - * @max: maximum value for the sensor - * - * Can sleep - */ -static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) -{ - return (adc_val >= min) && (adc_val <= max); -} - -/** - * adc_to_temp - converts the ADC code to temperature in C - * @direct: true if ths channel is direct index - * @adc_val: the adc_val that needs to be converted - * @tp: temperature return value - * - * Linear approximation is used to covert the skin adc value into temperature. - * This technique is used to avoid very long look-up table to get - * the appropriate temp value from ADC value. - * The adc code vs sensor temp curve is split into five parts - * to achieve very close approximate temp value with less than - * 0.5C error - */ -static int adc_to_temp(int direct, uint16_t adc_val, int *tp) -{ - int temp; - - /* Direct conversion for die temperature */ - if (direct) { - if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { - *tp = to_msic_die_temp(adc_val) * 1000; - return 0; - } - return -ERANGE; - } - - if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) - return -ERANGE; - - /* Linear approximation for skin temperature */ - if (adc_val > ADC_VAL0C) - temp = 177 - (adc_val/5); - else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) - temp = 111 - (adc_val/8); - else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) - temp = 92 - (adc_val/10); - else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) - temp = 91 - (adc_val/10); - else - temp = 112 - (adc_val/6); - - /* Convert temperature in celsius to milli degree celsius */ - *tp = temp * 1000; - return 0; -} - -/** - * mid_read_temp - read sensors for temperature - * @temp: holds the current temperature for the sensor after reading - * - * reads the adc_code from the channel and converts it to real - * temperature. The converted value is stored in temp. - * - * Can sleep - */ -static int mid_read_temp(struct thermal_zone_device *tzd, int *temp) -{ - struct thermal_device_info *td_info = tzd->devdata; - uint16_t adc_val, addr; - uint8_t data = 0; - int ret; - int curr_temp; - - addr = td_info->chnl_addr; - - /* Enable the msic for conversion before reading */ - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); - if (ret) - return ret; - - /* Re-toggle the RRDATARD bit (temporary workaround) */ - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL); - if (ret) - return ret; - - /* Read the higher bits of data */ - ret = intel_msic_reg_read(addr, &data); - if (ret) - return ret; - - /* Shift bits to accommodate the lower two data bits */ - adc_val = (data << 2); - addr++; - - ret = intel_msic_reg_read(addr, &data);/* Read lower bits */ - if (ret) - return ret; - - /* Adding lower two bits to the higher bits */ - data &= 03; - adc_val += data; - - /* Convert ADC value to temperature */ - ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); - if (ret == 0) - *temp = td_info->curr_temp = curr_temp; - return ret; -} - -/** - * configure_adc - enables/disables the ADC for conversion - * @val: zero: disables the ADC non-zero:enables the ADC - * - * Enable/Disable the ADC depending on the argument - * - * Can sleep - */ -static int configure_adc(int val) -{ - int ret; - uint8_t data; - - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); - if (ret) - return ret; - - if (val) { - /* Enable and start the ADC */ - data |= (MSIC_ADC_ENBL | MSIC_ADC_START); - } else { - /* Just stop the ADC */ - data &= (~MSIC_ADC_START); - } - return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data); -} - -/** - * set_up_therm_channel - enable thermal channel for conversion - * @base_addr: index of free msic ADC channel - * - * Enable all the three channels for conversion - * - * Can sleep - */ -static int set_up_therm_channel(u16 base_addr) -{ - int ret; - - /* Enable all the sensor channels */ - ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE); - if (ret) - return ret; - - ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE); - if (ret) - return ret; - - ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE); - if (ret) - return ret; - - /* Since this is the last channel, set the stop bit - * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ - ret = intel_msic_reg_write(base_addr + 3, - (MSIC_DIE_SENSOR_CODE | 0x10)); - if (ret) - return ret; - - /* Enable ADC and start it */ - return configure_adc(1); -} - -/** - * reset_stopbit - sets the stop bit to 0 on the given channel - * @addr: address of the channel - * - * Can sleep - */ -static int reset_stopbit(uint16_t addr) -{ - int ret; - uint8_t data; - ret = intel_msic_reg_read(addr, &data); - if (ret) - return ret; - /* Set the stop bit to zero */ - return intel_msic_reg_write(addr, (data & 0xEF)); -} - -/** - * find_free_channel - finds an empty channel for conversion - * - * If the ADC is not enabled then start using 0th channel - * itself. Otherwise find an empty channel by looking for a - * channel in which the stopbit is set to 1. returns the index - * of the first free channel if succeeds or an error code. - * - * Context: can sleep - * - * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc - * code. - */ -static int find_free_channel(void) -{ - int ret; - int i; - uint8_t data; - - /* check whether ADC is enabled */ - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); - if (ret) - return ret; - - if ((data & MSIC_ADC_ENBL) == 0) - return 0; - - /* ADC is already enabled; Looking for an empty channel */ - for (i = 0; i < ADC_CHANLS_MAX; i++) { - ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data); - if (ret) - return ret; - - if (data & MSIC_STOPBIT_MASK) { - ret = i; - break; - } - } - return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; -} - -/** - * mid_initialize_adc - initializing the ADC - * @dev: our device structure - * - * Initialize the ADC for reading thermistor values. Can sleep. - */ -static int mid_initialize_adc(struct device *dev) -{ - u8 data; - u16 base_addr; - int ret; - - /* - * Ensure that adctherm is disabled before we - * initialize the ADC - */ - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data); - if (ret) - return ret; - - data &= ~MSIC_ADCTHERM_MASK; - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data); - if (ret) - return ret; - - /* Index of the first channel in which the stop bit is set */ - channel_index = find_free_channel(); - if (channel_index < 0) { - dev_err(dev, "No free ADC channels"); - return channel_index; - } - - base_addr = ADC_CHNL_START_ADDR + channel_index; - - if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { - /* Reset stop bit for channels other than 0 and 12 */ - ret = reset_stopbit(base_addr); - if (ret) - return ret; - - /* Index of the first free channel */ - base_addr++; - channel_index++; - } - - ret = set_up_therm_channel(base_addr); - if (ret) { - dev_err(dev, "unable to enable ADC"); - return ret; - } - dev_dbg(dev, "ADC initialization successful"); - return ret; -} - -/** - * initialize_sensor - sets default temp and timer ranges - * @index: index of the sensor - * - * Context: can sleep - */ -static struct thermal_device_info *initialize_sensor(int index) -{ - struct thermal_device_info *td_info = - kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); - - if (!td_info) - return NULL; - - /* Set the base addr of the channel for this sensor */ - td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); - /* Sensor 3 is direct conversion */ - if (index == 3) - td_info->direct = 1; - return td_info; -} - -#ifdef CONFIG_PM_SLEEP -/** - * mid_thermal_resume - resume routine - * @dev: device structure - * - * mid thermal resume: re-initializes the adc. Can sleep. - */ -static int mid_thermal_resume(struct device *dev) -{ - return mid_initialize_adc(dev); -} - -/** - * mid_thermal_suspend - suspend routine - * @dev: device structure - * - * mid thermal suspend implements the suspend functionality - * by stopping the ADC. Can sleep. - */ -static int mid_thermal_suspend(struct device *dev) -{ - /* - * This just stops the ADC and does not disable it. - * temporary workaround until we have a generic ADC driver. - * If 0 is passed, it disables the ADC. - */ - return configure_adc(0); -} -#endif - -static SIMPLE_DEV_PM_OPS(mid_thermal_pm, - mid_thermal_suspend, mid_thermal_resume); - -/** - * read_curr_temp - reads the current temperature and stores in temp - * @temp: holds the current temperature value after reading - * - * Can sleep - */ -static int read_curr_temp(struct thermal_zone_device *tzd, int *temp) -{ - WARN_ON(tzd == NULL); - return mid_read_temp(tzd, temp); -} - -/* Can't be const */ -static struct thermal_zone_device_ops tzd_ops = { - .get_temp = read_curr_temp, -}; - -/** - * mid_thermal_probe - mfld thermal initialize - * @pdev: platform device structure - * - * mid thermal probe initializes the hardware and registers - * all the sensors with the generic thermal framework. Can sleep. - */ -static int mid_thermal_probe(struct platform_device *pdev) -{ - static char *name[MSIC_THERMAL_SENSORS] = { - "skin0", "skin1", "sys", "msicdie" - }; - - int ret; - int i; - struct platform_info *pinfo; - - pinfo = devm_kzalloc(&pdev->dev, sizeof(struct platform_info), - GFP_KERNEL); - if (!pinfo) - return -ENOMEM; - - /* Initializing the hardware */ - ret = mid_initialize_adc(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "ADC init failed"); - return ret; - } - - /* Register each sensor with the generic thermal framework*/ - for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { - struct thermal_device_info *td_info = initialize_sensor(i); - - if (!td_info) { - ret = -ENOMEM; - goto err; - } - pinfo->tzd[i] = thermal_zone_device_register(name[i], - 0, 0, td_info, &tzd_ops, NULL, 0, 0); - if (IS_ERR(pinfo->tzd[i])) { - kfree(td_info); - ret = PTR_ERR(pinfo->tzd[i]); - goto err; - } - ret = thermal_zone_device_enable(pinfo->tzd[i]); - if (ret) { - kfree(td_info); - thermal_zone_device_unregister(pinfo->tzd[i]); - goto err; - } - } - - pinfo->pdev = pdev; - platform_set_drvdata(pdev, pinfo); - return 0; - -err: - while (--i >= 0) { - kfree(pinfo->tzd[i]->devdata); - thermal_zone_device_unregister(pinfo->tzd[i]); - } - configure_adc(0); - return ret; -} - -/** - * mid_thermal_remove - mfld thermal finalize - * @dev: platform device structure - * - * MLFD thermal remove unregisters all the sensors from the generic - * thermal framework. Can sleep. - */ -static int mid_thermal_remove(struct platform_device *pdev) -{ - int i; - struct platform_info *pinfo = platform_get_drvdata(pdev); - - for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { - kfree(pinfo->tzd[i]->devdata); - thermal_zone_device_unregister(pinfo->tzd[i]); - } - - /* Stop the ADC */ - return configure_adc(0); -} - -#define DRIVER_NAME "msic_thermal" - -static const struct platform_device_id therm_id_table[] = { - { DRIVER_NAME, 1 }, - { } -}; -MODULE_DEVICE_TABLE(platform, therm_id_table); - -static struct platform_driver mid_thermal_driver = { - .driver = { - .name = DRIVER_NAME, - .pm = &mid_thermal_pm, - }, - .probe = mid_thermal_probe, - .remove = mid_thermal_remove, - .id_table = therm_id_table, -}; - -module_platform_driver(mid_thermal_driver); - -MODULE_AUTHOR("Durgadoss R "); -MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From ae1527948f67d4b8a61f586f792d0971ea44bc92 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 28 Jul 2020 16:42:10 +0300 Subject: platform/x86: intel_mid_powerbtn: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Reviewed-by: Hans de Goede Acked-by: Linus Walleij --- drivers/platform/x86/Kconfig | 8 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel_mid_powerbtn.c | 233 ------------------------------ 3 files changed, 242 deletions(-) delete mode 100644 drivers/platform/x86/intel_mid_powerbtn.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 3ba680af3ef5..4a5798a0ce0c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1327,14 +1327,6 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. -config INTEL_MID_POWER_BUTTON - tristate "power button driver for Intel MID platforms" - depends on INTEL_SCU && INPUT - help - This driver handles the power button on the Intel MID platforms. - - If unsure, say N. - config INTEL_MRFLD_PWRBTN tristate "Intel Merrifield Basin Cove power button driver" depends on INTEL_SOC_PMIC_MRFLD diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 6fb57502b59a..728ccc226a29 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -137,7 +137,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o -obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c deleted file mode 100644 index df434abbb66f..000000000000 --- a/drivers/platform/x86/intel_mid_powerbtn.c +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Power button driver for Intel MID platforms. - * - * Copyright (C) 2010,2017 Intel Corp - * - * Author: Hong Liu - * Author: Andy Shevchenko - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define DRIVER_NAME "msic_power_btn" - -#define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */ - -/* - * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask - * power button interrupt - */ -#define MSIC_PWRBTNM (1 << 0) - -/* Intel Tangier */ -#define BCOVE_PB_LEVEL (1 << 4) /* 1 - release, 0 - press */ - -/* Basin Cove PMIC */ -#define BCOVE_PBIRQ 0x02 -#define BCOVE_IRQLVL1MSK 0x0c -#define BCOVE_PBIRQMASK 0x0d -#define BCOVE_PBSTATUS 0x27 - -struct mid_pb_ddata { - struct device *dev; - int irq; - struct input_dev *input; - unsigned short mirqlvl1_addr; - unsigned short pbstat_addr; - u8 pbstat_mask; - struct intel_scu_ipc_dev *scu; - int (*setup)(struct mid_pb_ddata *ddata); -}; - -static int mid_pbstat(struct mid_pb_ddata *ddata, int *value) -{ - struct input_dev *input = ddata->input; - int ret; - u8 pbstat; - - ret = intel_scu_ipc_dev_ioread8(ddata->scu, ddata->pbstat_addr, - &pbstat); - if (ret) - return ret; - - dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat); - - *value = !(pbstat & ddata->pbstat_mask); - return 0; -} - -static int mid_irq_ack(struct mid_pb_ddata *ddata) -{ - return intel_scu_ipc_dev_update(ddata->scu, ddata->mirqlvl1_addr, 0, - MSIC_PWRBTNM); -} - -static int mrfld_setup(struct mid_pb_ddata *ddata) -{ - /* Unmask the PBIRQ and MPBIRQ on Tangier */ - intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQ, 0, MSIC_PWRBTNM); - intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM); - - return 0; -} - -static irqreturn_t mid_pb_isr(int irq, void *dev_id) -{ - struct mid_pb_ddata *ddata = dev_id; - struct input_dev *input = ddata->input; - int value = 0; - int ret; - - ret = mid_pbstat(ddata, &value); - if (ret < 0) { - dev_err(input->dev.parent, - "Read error %d while reading MSIC_PB_STATUS\n", ret); - } else { - input_event(input, EV_KEY, KEY_POWER, value); - input_sync(input); - } - - mid_irq_ack(ddata); - return IRQ_HANDLED; -} - -static const struct mid_pb_ddata mfld_ddata = { - .mirqlvl1_addr = INTEL_MSIC_IRQLVL1MSK, - .pbstat_addr = INTEL_MSIC_PBSTATUS, - .pbstat_mask = MSIC_PB_LEVEL, -}; - -static const struct mid_pb_ddata mrfld_ddata = { - .mirqlvl1_addr = BCOVE_IRQLVL1MSK, - .pbstat_addr = BCOVE_PBSTATUS, - .pbstat_mask = BCOVE_PB_LEVEL, - .setup = mrfld_setup, -}; - -static const struct x86_cpu_id mid_pb_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_MID, &mfld_ddata), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, &mrfld_ddata), - {} -}; - -static int mid_pb_probe(struct platform_device *pdev) -{ - const struct x86_cpu_id *id; - struct mid_pb_ddata *ddata; - struct input_dev *input; - int irq = platform_get_irq(pdev, 0); - int error; - - id = x86_match_cpu(mid_pb_cpu_ids); - if (!id) - return -ENODEV; - - if (irq < 0) { - dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); - return irq; - } - - input = devm_input_allocate_device(&pdev->dev); - if (!input) - return -ENOMEM; - - input->name = pdev->name; - input->phys = "power-button/input0"; - input->id.bustype = BUS_HOST; - input->dev.parent = &pdev->dev; - - input_set_capability(input, EV_KEY, KEY_POWER); - - ddata = devm_kmemdup(&pdev->dev, (void *)id->driver_data, - sizeof(*ddata), GFP_KERNEL); - if (!ddata) - return -ENOMEM; - - ddata->dev = &pdev->dev; - ddata->irq = irq; - ddata->input = input; - - if (ddata->setup) { - error = ddata->setup(ddata); - if (error) - return error; - } - - ddata->scu = devm_intel_scu_ipc_dev_get(&pdev->dev); - if (!ddata->scu) - return -EPROBE_DEFER; - - error = devm_request_threaded_irq(&pdev->dev, irq, NULL, mid_pb_isr, - IRQF_ONESHOT, DRIVER_NAME, ddata); - if (error) { - dev_err(&pdev->dev, - "Unable to request irq %d for MID power button\n", irq); - return error; - } - - error = input_register_device(input); - if (error) { - dev_err(&pdev->dev, - "Unable to register input dev, error %d\n", error); - return error; - } - - platform_set_drvdata(pdev, ddata); - - /* - * SCU firmware might send power button interrupts to IA core before - * kernel boots and doesn't get EOI from IA core. The first bit of - * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new - * power interrupt to Android kernel. Unmask the bit when probing - * power button in kernel. - * There is a very narrow race between irq handler and power button - * initialization. The race happens rarely. So we needn't worry - * about it. - */ - error = mid_irq_ack(ddata); - if (error) { - dev_err(&pdev->dev, - "Unable to clear power button interrupt, error: %d\n", - error); - return error; - } - - device_init_wakeup(&pdev->dev, true); - dev_pm_set_wake_irq(&pdev->dev, irq); - - return 0; -} - -static int mid_pb_remove(struct platform_device *pdev) -{ - dev_pm_clear_wake_irq(&pdev->dev); - device_init_wakeup(&pdev->dev, false); - - return 0; -} - -static struct platform_driver mid_pb_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = mid_pb_probe, - .remove = mid_pb_remove, -}; - -module_platform_driver(mid_pb_driver); - -MODULE_AUTHOR("Hong Liu "); -MODULE_DESCRIPTION("Intel MID Power Button Driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRIVER_NAME); -- cgit v1.2.3 From c5158358dffc8c7962f412c2c89fcce4e5fff96f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 18 Nov 2020 12:46:44 +0200 Subject: rtc: mrst: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Acked-by: Alexandre Belloni Acked-by: Linus Walleij --- drivers/rtc/Kconfig | 12 -- drivers/rtc/Makefile | 1 - drivers/rtc/rtc-mrst.c | 521 ------------------------------------------------- 3 files changed, 534 deletions(-) delete mode 100644 drivers/rtc/rtc-mrst.c (limited to 'drivers') diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 6123f9f4fbc9..2a402c10f8f1 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -973,18 +973,6 @@ config RTC_DRV_ALPHA Direct support for the real-time clock found on every Alpha system, specifically MC146818 compatibles. If in doubt, say Y. -config RTC_DRV_VRTC - tristate "Virtual RTC for Intel MID platforms" - depends on X86_INTEL_MID - default y if X86_INTEL_MID - - help - Say "yes" here to get direct support for the real time clock - found on Moorestown platforms. The VRTC is a emulated RTC that - derives its clock source from a real RTC in the PMIC. The MC146818 - style programming interface is mostly conserved, but any - updates are done via IPC calls to the system controller FW. - config RTC_DRV_DS1216 tristate "Dallas DS1216" depends on SNI_RM diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index bb8f319b09fb..f8ac4f574522 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -174,7 +174,6 @@ obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl.o obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o -obj-$(CONFIG_RTC_DRV_VRTC) += rtc-mrst.o obj-$(CONFIG_RTC_DRV_VT8500) += rtc-vt8500.o obj-$(CONFIG_RTC_DRV_WILCO_EC) += rtc-wilco-ec.o obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o diff --git a/drivers/rtc/rtc-mrst.c b/drivers/rtc/rtc-mrst.c deleted file mode 100644 index 421b3b6071b6..000000000000 --- a/drivers/rtc/rtc-mrst.c +++ /dev/null @@ -1,521 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * rtc-mrst.c: Driver for Moorestown virtual RTC - * - * (C) Copyright 2009 Intel Corporation - * Author: Jacob Pan (jacob.jun.pan@intel.com) - * Feng Tang (feng.tang@intel.com) - * - * Note: - * VRTC is emulated by system controller firmware, the real HW - * RTC is located in the PMIC device. SCU FW shadows PMIC RTC - * in a memory mapped IO space that is visible to the host IA - * processor. - * - * This driver is based upon drivers/rtc/rtc-cmos.c - */ - -/* - * Note: - * * vRTC only supports binary mode and 24H mode - * * vRTC only support PIE and AIE, no UIE, and its PIE only happens - * at 23:59:59pm everyday, no support for adjustable frequency - * * Alarm function is also limited to hr/min/sec. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -struct mrst_rtc { - struct rtc_device *rtc; - struct device *dev; - int irq; - - u8 enabled_wake; - u8 suspend_ctrl; -}; - -static const char driver_name[] = "rtc_mrst"; - -#define RTC_IRQMASK (RTC_PF | RTC_AF) - -static inline int is_intr(u8 rtc_intr) -{ - if (!(rtc_intr & RTC_IRQF)) - return 0; - return rtc_intr & RTC_IRQMASK; -} - -static inline unsigned char vrtc_is_updating(void) -{ - unsigned char uip; - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - uip = (vrtc_cmos_read(RTC_FREQ_SELECT) & RTC_UIP); - spin_unlock_irqrestore(&rtc_lock, flags); - return uip; -} - -/* - * rtc_time's year contains the increment over 1900, but vRTC's YEAR - * register can't be programmed to value larger than 0x64, so vRTC - * driver chose to use 1972 (1970 is UNIX time start point) as the base, - * and does the translation at read/write time. - * - * Why not just use 1970 as the offset? it's because using 1972 will - * make it consistent in leap year setting for both vrtc and low-level - * physical rtc devices. Then why not use 1960 as the offset? If we use - * 1960, for a device's first use, its YEAR register is 0 and the system - * year will be parsed as 1960 which is not a valid UNIX time and will - * cause many applications to fail mysteriously. - */ -static int mrst_read_time(struct device *dev, struct rtc_time *time) -{ - unsigned long flags; - - if (vrtc_is_updating()) - msleep(20); - - spin_lock_irqsave(&rtc_lock, flags); - time->tm_sec = vrtc_cmos_read(RTC_SECONDS); - time->tm_min = vrtc_cmos_read(RTC_MINUTES); - time->tm_hour = vrtc_cmos_read(RTC_HOURS); - time->tm_mday = vrtc_cmos_read(RTC_DAY_OF_MONTH); - time->tm_mon = vrtc_cmos_read(RTC_MONTH); - time->tm_year = vrtc_cmos_read(RTC_YEAR); - spin_unlock_irqrestore(&rtc_lock, flags); - - /* Adjust for the 1972/1900 */ - time->tm_year += 72; - time->tm_mon--; - return 0; -} - -static int mrst_set_time(struct device *dev, struct rtc_time *time) -{ - int ret; - unsigned long flags; - unsigned char mon, day, hrs, min, sec; - unsigned int yrs; - - yrs = time->tm_year; - mon = time->tm_mon + 1; /* tm_mon starts at zero */ - day = time->tm_mday; - hrs = time->tm_hour; - min = time->tm_min; - sec = time->tm_sec; - - if (yrs < 72 || yrs > 172) - return -EINVAL; - yrs -= 72; - - spin_lock_irqsave(&rtc_lock, flags); - - vrtc_cmos_write(yrs, RTC_YEAR); - vrtc_cmos_write(mon, RTC_MONTH); - vrtc_cmos_write(day, RTC_DAY_OF_MONTH); - vrtc_cmos_write(hrs, RTC_HOURS); - vrtc_cmos_write(min, RTC_MINUTES); - vrtc_cmos_write(sec, RTC_SECONDS); - - spin_unlock_irqrestore(&rtc_lock, flags); - - ret = intel_scu_ipc_simple_command(IPCMSG_VRTC, IPC_CMD_VRTC_SETTIME); - return ret; -} - -static int mrst_read_alarm(struct device *dev, struct rtc_wkalrm *t) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char rtc_control; - - if (mrst->irq <= 0) - return -EIO; - - /* vRTC only supports binary mode */ - spin_lock_irq(&rtc_lock); - t->time.tm_sec = vrtc_cmos_read(RTC_SECONDS_ALARM); - t->time.tm_min = vrtc_cmos_read(RTC_MINUTES_ALARM); - t->time.tm_hour = vrtc_cmos_read(RTC_HOURS_ALARM); - - rtc_control = vrtc_cmos_read(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - t->enabled = !!(rtc_control & RTC_AIE); - t->pending = 0; - - return 0; -} - -static void mrst_checkintr(struct mrst_rtc *mrst, unsigned char rtc_control) -{ - unsigned char rtc_intr; - - /* - * NOTE after changing RTC_xIE bits we always read INTR_FLAGS; - * allegedly some older rtcs need that to handle irqs properly - */ - rtc_intr = vrtc_cmos_read(RTC_INTR_FLAGS); - rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF; - if (is_intr(rtc_intr)) - rtc_update_irq(mrst->rtc, 1, rtc_intr); -} - -static void mrst_irq_enable(struct mrst_rtc *mrst, unsigned char mask) -{ - unsigned char rtc_control; - - /* - * Flush any pending IRQ status, notably for update irqs, - * before we enable new IRQs - */ - rtc_control = vrtc_cmos_read(RTC_CONTROL); - mrst_checkintr(mrst, rtc_control); - - rtc_control |= mask; - vrtc_cmos_write(rtc_control, RTC_CONTROL); - - mrst_checkintr(mrst, rtc_control); -} - -static void mrst_irq_disable(struct mrst_rtc *mrst, unsigned char mask) -{ - unsigned char rtc_control; - - rtc_control = vrtc_cmos_read(RTC_CONTROL); - rtc_control &= ~mask; - vrtc_cmos_write(rtc_control, RTC_CONTROL); - mrst_checkintr(mrst, rtc_control); -} - -static int mrst_set_alarm(struct device *dev, struct rtc_wkalrm *t) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char hrs, min, sec; - int ret = 0; - - if (!mrst->irq) - return -EIO; - - hrs = t->time.tm_hour; - min = t->time.tm_min; - sec = t->time.tm_sec; - - spin_lock_irq(&rtc_lock); - /* Next rtc irq must not be from previous alarm setting */ - mrst_irq_disable(mrst, RTC_AIE); - - /* Update alarm */ - vrtc_cmos_write(hrs, RTC_HOURS_ALARM); - vrtc_cmos_write(min, RTC_MINUTES_ALARM); - vrtc_cmos_write(sec, RTC_SECONDS_ALARM); - - spin_unlock_irq(&rtc_lock); - - ret = intel_scu_ipc_simple_command(IPCMSG_VRTC, IPC_CMD_VRTC_SETALARM); - if (ret) - return ret; - - spin_lock_irq(&rtc_lock); - if (t->enabled) - mrst_irq_enable(mrst, RTC_AIE); - - spin_unlock_irq(&rtc_lock); - - return 0; -} - -/* Currently, the vRTC doesn't support UIE ON/OFF */ -static int mrst_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned long flags; - - spin_lock_irqsave(&rtc_lock, flags); - if (enabled) - mrst_irq_enable(mrst, RTC_AIE); - else - mrst_irq_disable(mrst, RTC_AIE); - spin_unlock_irqrestore(&rtc_lock, flags); - return 0; -} - - -#if IS_ENABLED(CONFIG_RTC_INTF_PROC) - -static int mrst_procfs(struct device *dev, struct seq_file *seq) -{ - unsigned char rtc_control; - - spin_lock_irq(&rtc_lock); - rtc_control = vrtc_cmos_read(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - seq_printf(seq, - "periodic_IRQ\t: %s\n" - "alarm\t\t: %s\n" - "BCD\t\t: no\n" - "periodic_freq\t: daily (not adjustable)\n", - (rtc_control & RTC_PIE) ? "on" : "off", - (rtc_control & RTC_AIE) ? "on" : "off"); - - return 0; -} - -#else -#define mrst_procfs NULL -#endif - -static const struct rtc_class_ops mrst_rtc_ops = { - .read_time = mrst_read_time, - .set_time = mrst_set_time, - .read_alarm = mrst_read_alarm, - .set_alarm = mrst_set_alarm, - .proc = mrst_procfs, - .alarm_irq_enable = mrst_rtc_alarm_irq_enable, -}; - -static struct mrst_rtc mrst_rtc; - -/* - * When vRTC IRQ is captured by SCU FW, FW will clear the AIE bit in - * Reg B, so no need for this driver to clear it - */ -static irqreturn_t mrst_rtc_irq(int irq, void *p) -{ - u8 irqstat; - - spin_lock(&rtc_lock); - /* This read will clear all IRQ flags inside Reg C */ - irqstat = vrtc_cmos_read(RTC_INTR_FLAGS); - spin_unlock(&rtc_lock); - - irqstat &= RTC_IRQMASK | RTC_IRQF; - if (is_intr(irqstat)) { - rtc_update_irq(p, 1, irqstat); - return IRQ_HANDLED; - } - return IRQ_NONE; -} - -static int vrtc_mrst_do_probe(struct device *dev, struct resource *iomem, - int rtc_irq) -{ - int retval = 0; - unsigned char rtc_control; - - /* There can be only one ... */ - if (mrst_rtc.dev) - return -EBUSY; - - if (!iomem) - return -ENODEV; - - iomem = devm_request_mem_region(dev, iomem->start, resource_size(iomem), - driver_name); - if (!iomem) { - dev_dbg(dev, "i/o mem already in use.\n"); - return -EBUSY; - } - - mrst_rtc.irq = rtc_irq; - mrst_rtc.dev = dev; - dev_set_drvdata(dev, &mrst_rtc); - - mrst_rtc.rtc = devm_rtc_allocate_device(dev); - if (IS_ERR(mrst_rtc.rtc)) - return PTR_ERR(mrst_rtc.rtc); - - mrst_rtc.rtc->ops = &mrst_rtc_ops; - - rename_region(iomem, dev_name(&mrst_rtc.rtc->dev)); - - spin_lock_irq(&rtc_lock); - mrst_irq_disable(&mrst_rtc, RTC_PIE | RTC_AIE); - rtc_control = vrtc_cmos_read(RTC_CONTROL); - spin_unlock_irq(&rtc_lock); - - if (!(rtc_control & RTC_24H) || (rtc_control & (RTC_DM_BINARY))) - dev_dbg(dev, "TODO: support more than 24-hr BCD mode\n"); - - if (rtc_irq) { - retval = devm_request_irq(dev, rtc_irq, mrst_rtc_irq, - 0, dev_name(&mrst_rtc.rtc->dev), - mrst_rtc.rtc); - if (retval < 0) { - dev_dbg(dev, "IRQ %d is already in use, err %d\n", - rtc_irq, retval); - goto cleanup0; - } - } - - retval = devm_rtc_register_device(mrst_rtc.rtc); - if (retval) - goto cleanup0; - - dev_dbg(dev, "initialised\n"); - return 0; - -cleanup0: - mrst_rtc.dev = NULL; - dev_err(dev, "rtc-mrst: unable to initialise\n"); - return retval; -} - -static void rtc_mrst_do_shutdown(void) -{ - spin_lock_irq(&rtc_lock); - mrst_irq_disable(&mrst_rtc, RTC_IRQMASK); - spin_unlock_irq(&rtc_lock); -} - -static void rtc_mrst_do_remove(struct device *dev) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - - rtc_mrst_do_shutdown(); - - mrst->rtc = NULL; - mrst->dev = NULL; -} - -#ifdef CONFIG_PM_SLEEP -static int mrst_suspend(struct device *dev) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char tmp; - - /* Only the alarm might be a wakeup event source */ - spin_lock_irq(&rtc_lock); - mrst->suspend_ctrl = tmp = vrtc_cmos_read(RTC_CONTROL); - if (tmp & (RTC_PIE | RTC_AIE)) { - unsigned char mask; - - if (device_may_wakeup(dev)) - mask = RTC_IRQMASK & ~RTC_AIE; - else - mask = RTC_IRQMASK; - tmp &= ~mask; - vrtc_cmos_write(tmp, RTC_CONTROL); - - mrst_checkintr(mrst, tmp); - } - spin_unlock_irq(&rtc_lock); - - if (tmp & RTC_AIE) { - mrst->enabled_wake = 1; - enable_irq_wake(mrst->irq); - } - - dev_dbg(&mrst_rtc.rtc->dev, "suspend%s, ctrl %02x\n", - (tmp & RTC_AIE) ? ", alarm may wake" : "", - tmp); - - return 0; -} - -/* - * We want RTC alarms to wake us from the deep power saving state - */ -static inline int mrst_poweroff(struct device *dev) -{ - return mrst_suspend(dev); -} - -static int mrst_resume(struct device *dev) -{ - struct mrst_rtc *mrst = dev_get_drvdata(dev); - unsigned char tmp = mrst->suspend_ctrl; - - /* Re-enable any irqs previously active */ - if (tmp & RTC_IRQMASK) { - unsigned char mask; - - if (mrst->enabled_wake) { - disable_irq_wake(mrst->irq); - mrst->enabled_wake = 0; - } - - spin_lock_irq(&rtc_lock); - do { - vrtc_cmos_write(tmp, RTC_CONTROL); - - mask = vrtc_cmos_read(RTC_INTR_FLAGS); - mask &= (tmp & RTC_IRQMASK) | RTC_IRQF; - if (!is_intr(mask)) - break; - - rtc_update_irq(mrst->rtc, 1, mask); - tmp &= ~RTC_AIE; - } while (mask & RTC_AIE); - spin_unlock_irq(&rtc_lock); - } - - dev_dbg(&mrst_rtc.rtc->dev, "resume, ctrl %02x\n", tmp); - - return 0; -} - -static SIMPLE_DEV_PM_OPS(mrst_pm_ops, mrst_suspend, mrst_resume); -#define MRST_PM_OPS (&mrst_pm_ops) - -#else -#define MRST_PM_OPS NULL - -static inline int mrst_poweroff(struct device *dev) -{ - return -ENOSYS; -} - -#endif - -static int vrtc_mrst_platform_probe(struct platform_device *pdev) -{ - return vrtc_mrst_do_probe(&pdev->dev, - platform_get_resource(pdev, IORESOURCE_MEM, 0), - platform_get_irq(pdev, 0)); -} - -static int vrtc_mrst_platform_remove(struct platform_device *pdev) -{ - rtc_mrst_do_remove(&pdev->dev); - return 0; -} - -static void vrtc_mrst_platform_shutdown(struct platform_device *pdev) -{ - if (system_state == SYSTEM_POWER_OFF && !mrst_poweroff(&pdev->dev)) - return; - - rtc_mrst_do_shutdown(); -} - -MODULE_ALIAS("platform:vrtc_mrst"); - -static struct platform_driver vrtc_mrst_platform_driver = { - .probe = vrtc_mrst_platform_probe, - .remove = vrtc_mrst_platform_remove, - .shutdown = vrtc_mrst_platform_shutdown, - .driver = { - .name = driver_name, - .pm = MRST_PM_OPS, - } -}; - -module_platform_driver(vrtc_mrst_platform_driver); - -MODULE_AUTHOR("Jacob Pan; Feng Tang"); -MODULE_DESCRIPTION("Driver for Moorestown virtual RTC"); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 1b5b5b4eb52216af05ae4eebbe2efebed4f15a1c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Nov 2019 16:57:30 +0200 Subject: watchdog: intel_scu_watchdog: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run more or less fresh kernel on it. The commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") also in align with this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Reviewed-by: Guenter Roeck Acked-by: Linus Walleij --- drivers/watchdog/Kconfig | 9 - drivers/watchdog/Makefile | 1 - drivers/watchdog/intel_scu_watchdog.c | 533 ---------------------------------- drivers/watchdog/intel_scu_watchdog.h | 50 ---- 4 files changed, 593 deletions(-) delete mode 100644 drivers/watchdog/intel_scu_watchdog.c delete mode 100644 drivers/watchdog/intel_scu_watchdog.h (limited to 'drivers') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 7ff941e71b79..6b9e93d8532b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1219,15 +1219,6 @@ config IE6XX_WDT To compile this driver as a module, choose M here: the module will be called ie6xx_wdt. -config INTEL_SCU_WATCHDOG - bool "Intel SCU Watchdog for Mobile Platforms" - depends on X86_INTEL_MID - help - Hardware driver for the watchdog time built into the Intel SCU - for Intel Mobile Platforms. - - To compile this driver as a module, choose M here. - config INTEL_MID_WATCHDOG tristate "Intel MID Watchdog Timer" depends on X86_INTEL_MID diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5c74ee19d441..74f61e4105d8 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -140,7 +140,6 @@ obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o -obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o diff --git a/drivers/watchdog/intel_scu_watchdog.c b/drivers/watchdog/intel_scu_watchdog.c deleted file mode 100644 index 804e35940983..000000000000 --- a/drivers/watchdog/intel_scu_watchdog.c +++ /dev/null @@ -1,533 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device - * for Intel part #(s): - * - AF82MP20 PCH - * - * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "intel_scu_watchdog.h" - -/* Bounds number of times we will retry loading time count */ -/* This retry is a work around for a silicon bug. */ -#define MAX_RETRY 16 - -#define IPC_SET_WATCHDOG_TIMER 0xF8 - -static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN; -module_param(timer_margin, int, 0); -MODULE_PARM_DESC(timer_margin, - "Watchdog timer margin" - "Time between interrupt and resetting the system" - "The range is from 1 to 160" - "This is the time for all keep alives to arrive"); - -static int timer_set = DEFAULT_TIME; -module_param(timer_set, int, 0); -MODULE_PARM_DESC(timer_set, - "Default Watchdog timer setting" - "Complete cycle time" - "The range is from 1 to 170" - "This is the time for all keep alives to arrive"); - -/* After watchdog device is closed, check force_boot. If: - * force_boot == 0, then force boot on next watchdog interrupt after close, - * force_boot == 1, then force boot immediately when device is closed. - */ -static int force_boot; -module_param(force_boot, int, 0); -MODULE_PARM_DESC(force_boot, - "A value of 1 means that the driver will reboot" - "the system immediately if the /dev/watchdog device is closed" - "A value of 0 means that when /dev/watchdog device is closed" - "the watchdog timer will be refreshed for one more interval" - "of length: timer_set. At the end of this interval, the" - "watchdog timer will reset the system." - ); - -/* there is only one device in the system now; this can be made into - * an array in the future if we have more than one device */ - -static struct intel_scu_watchdog_dev watchdog_device; - -/* Forces restart, if force_reboot is set */ -static void watchdog_fire(void) -{ - if (force_boot) { - pr_crit("Initiating system reboot\n"); - emergency_restart(); - pr_crit("Reboot didn't ?????\n"); - } - - else { - pr_crit("Immediate Reboot Disabled\n"); - pr_crit("System will reset when watchdog timer times out!\n"); - } -} - -static int check_timer_margin(int new_margin) -{ - if ((new_margin < MIN_TIME_CYCLE) || - (new_margin > MAX_TIME - timer_set)) { - pr_debug("value of new_margin %d is out of the range %d to %d\n", - new_margin, MIN_TIME_CYCLE, MAX_TIME - timer_set); - return -EINVAL; - } - return 0; -} - -/* - * IPC operations - */ -static int watchdog_set_ipc(int soft_threshold, int threshold) -{ - u32 *ipc_wbuf; - u8 cbuf[16] = { '\0' }; - int ipc_ret = 0; - - ipc_wbuf = (u32 *)&cbuf; - ipc_wbuf[0] = soft_threshold; - ipc_wbuf[1] = threshold; - - ipc_ret = intel_scu_ipc_command( - IPC_SET_WATCHDOG_TIMER, - 0, - ipc_wbuf, - 2, - NULL, - 0); - - if (ipc_ret != 0) - pr_err("Error setting SCU watchdog timer: %x\n", ipc_ret); - - return ipc_ret; -}; - -/* - * Intel_SCU operations - */ - -/* timer interrupt handler */ -static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id) -{ - int int_status; - int_status = ioread32(watchdog_device.timer_interrupt_status_addr); - - pr_debug("irq, int_status: %x\n", int_status); - - if (int_status != 0) - return IRQ_NONE; - - /* has the timer been started? If not, then this is spurious */ - if (watchdog_device.timer_started == 0) { - pr_debug("spurious interrupt received\n"); - return IRQ_HANDLED; - } - - /* temporarily disable the timer */ - iowrite32(0x00000002, watchdog_device.timer_control_addr); - - /* set the timer to the threshold */ - iowrite32(watchdog_device.threshold, - watchdog_device.timer_load_count_addr); - - /* allow the timer to run */ - iowrite32(0x00000003, watchdog_device.timer_control_addr); - - return IRQ_HANDLED; -} - -static int intel_scu_keepalive(void) -{ - - /* read eoi register - clears interrupt */ - ioread32(watchdog_device.timer_clear_interrupt_addr); - - /* temporarily disable the timer */ - iowrite32(0x00000002, watchdog_device.timer_control_addr); - - /* set the timer to the soft_threshold */ - iowrite32(watchdog_device.soft_threshold, - watchdog_device.timer_load_count_addr); - - /* allow the timer to run */ - iowrite32(0x00000003, watchdog_device.timer_control_addr); - - return 0; -} - -static int intel_scu_stop(void) -{ - iowrite32(0, watchdog_device.timer_control_addr); - return 0; -} - -static int intel_scu_set_heartbeat(u32 t) -{ - int ipc_ret; - int retry_count; - u32 soft_value; - u32 hw_value; - - watchdog_device.timer_set = t; - watchdog_device.threshold = - timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; - watchdog_device.soft_threshold = - (watchdog_device.timer_set - timer_margin) - * watchdog_device.timer_tbl_ptr->freq_hz; - - pr_debug("set_heartbeat: timer freq is %d\n", - watchdog_device.timer_tbl_ptr->freq_hz); - pr_debug("set_heartbeat: timer_set is %x (hex)\n", - watchdog_device.timer_set); - pr_debug("set_heartbeat: timer_margin is %x (hex)\n", timer_margin); - pr_debug("set_heartbeat: threshold is %x (hex)\n", - watchdog_device.threshold); - pr_debug("set_heartbeat: soft_threshold is %x (hex)\n", - watchdog_device.soft_threshold); - - /* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */ - /* watchdog timing come out right. */ - watchdog_device.threshold = - watchdog_device.threshold / FREQ_ADJUSTMENT; - watchdog_device.soft_threshold = - watchdog_device.soft_threshold / FREQ_ADJUSTMENT; - - /* temporarily disable the timer */ - iowrite32(0x00000002, watchdog_device.timer_control_addr); - - /* send the threshold and soft_threshold via IPC to the processor */ - ipc_ret = watchdog_set_ipc(watchdog_device.soft_threshold, - watchdog_device.threshold); - - if (ipc_ret != 0) { - /* Make sure the watchdog timer is stopped */ - intel_scu_stop(); - return ipc_ret; - } - - /* Soft Threshold set loop. Early versions of silicon did */ - /* not always set this count correctly. This loop checks */ - /* the value and retries if it was not set correctly. */ - - retry_count = 0; - soft_value = watchdog_device.soft_threshold & 0xFFFF0000; - do { - - /* Make sure timer is stopped */ - intel_scu_stop(); - - if (MAX_RETRY < retry_count++) { - /* Unable to set timer value */ - pr_err("Unable to set timer\n"); - return -ENODEV; - } - - /* set the timer to the soft threshold */ - iowrite32(watchdog_device.soft_threshold, - watchdog_device.timer_load_count_addr); - - /* read count value before starting timer */ - ioread32(watchdog_device.timer_load_count_addr); - - /* Start the timer */ - iowrite32(0x00000003, watchdog_device.timer_control_addr); - - /* read the value the time loaded into its count reg */ - hw_value = ioread32(watchdog_device.timer_load_count_addr); - hw_value = hw_value & 0xFFFF0000; - - - } while (soft_value != hw_value); - - watchdog_device.timer_started = 1; - - return 0; -} - -/* - * /dev/watchdog handling - */ - -static int intel_scu_open(struct inode *inode, struct file *file) -{ - - /* Set flag to indicate that watchdog device is open */ - if (test_and_set_bit(0, &watchdog_device.driver_open)) - return -EBUSY; - - /* Check for reopen of driver. Reopens are not allowed */ - if (watchdog_device.driver_closed) - return -EPERM; - - return stream_open(inode, file); -} - -static int intel_scu_release(struct inode *inode, struct file *file) -{ - /* - * This watchdog should not be closed, after the timer - * is started with the WDIPC_SETTIMEOUT ioctl - * If force_boot is set watchdog_fire() will cause an - * immediate reset. If force_boot is not set, the watchdog - * timer is refreshed for one more interval. At the end - * of that interval, the watchdog timer will reset the system. - */ - - if (!test_and_clear_bit(0, &watchdog_device.driver_open)) { - pr_debug("intel_scu_release, without open\n"); - return -ENOTTY; - } - - if (!watchdog_device.timer_started) { - /* Just close, since timer has not been started */ - pr_debug("closed, without starting timer\n"); - return 0; - } - - pr_crit("Unexpected close of /dev/watchdog!\n"); - - /* Since the timer was started, prevent future reopens */ - watchdog_device.driver_closed = 1; - - /* Refresh the timer for one more interval */ - intel_scu_keepalive(); - - /* Reboot system (if force_boot is set) */ - watchdog_fire(); - - /* We should only reach this point if force_boot is not set */ - return 0; -} - -static ssize_t intel_scu_write(struct file *file, - char const *data, - size_t len, - loff_t *ppos) -{ - - if (watchdog_device.timer_started) - /* Watchdog already started, keep it alive */ - intel_scu_keepalive(); - else - /* Start watchdog with timer value set by init */ - intel_scu_set_heartbeat(watchdog_device.timer_set); - - return len; -} - -static long intel_scu_ioctl(struct file *file, - unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - u32 __user *p = argp; - u32 new_margin; - - - static const struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT - | WDIOF_KEEPALIVEPING, - .firmware_version = 0, /* @todo Get from SCU via - ipc_get_scu_fw_version()? */ - .identity = "Intel_SCU IOH Watchdog" /* len < 32 */ - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, - &ident, - sizeof(ident)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_KEEPALIVE: - intel_scu_keepalive(); - - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_margin, p)) - return -EFAULT; - - if (check_timer_margin(new_margin)) - return -EINVAL; - - if (intel_scu_set_heartbeat(new_margin)) - return -EINVAL; - return 0; - case WDIOC_GETTIMEOUT: - return put_user(watchdog_device.soft_threshold, p); - - default: - return -ENOTTY; - } -} - -/* - * Notifier for system down - */ -static int intel_scu_notify_sys(struct notifier_block *this, - unsigned long code, - void *another_unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - /* Turn off the watchdog timer. */ - intel_scu_stop(); - return NOTIFY_DONE; -} - -/* - * Kernel Interfaces - */ -static const struct file_operations intel_scu_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = intel_scu_write, - .unlocked_ioctl = intel_scu_ioctl, - .compat_ioctl = compat_ptr_ioctl, - .open = intel_scu_open, - .release = intel_scu_release, -}; - -static int __init intel_scu_watchdog_init(void) -{ - int ret; - u32 __iomem *tmp_addr; - - /* - * We don't really need to check this as the SFI timer get will fail - * but if we do so we can exit with a clearer reason and no noise. - * - * If it isn't an intel MID device then it doesn't have this watchdog - */ - if (!intel_mid_identify_cpu()) - return -ENODEV; - - /* Check boot parameters to verify that their initial values */ - /* are in range. */ - /* Check value of timer_set boot parameter */ - if ((timer_set < MIN_TIME_CYCLE) || - (timer_set > MAX_TIME - MIN_TIME_CYCLE)) { - pr_err("value of timer_set %x (hex) is out of range from %x to %x (hex)\n", - timer_set, MIN_TIME_CYCLE, MAX_TIME - MIN_TIME_CYCLE); - return -EINVAL; - } - - /* Check value of timer_margin boot parameter */ - if (check_timer_margin(timer_margin)) - return -EINVAL; - - watchdog_device.timer_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1); - - if (watchdog_device.timer_tbl_ptr == NULL) { - pr_debug("timer is not available\n"); - return -ENODEV; - } - /* make sure the timer exists */ - if (watchdog_device.timer_tbl_ptr->phys_addr == 0) { - pr_debug("timer %d does not have valid physical memory\n", - sfi_mtimer_num); - return -ENODEV; - } - - if (watchdog_device.timer_tbl_ptr->irq == 0) { - pr_debug("timer %d invalid irq\n", sfi_mtimer_num); - return -ENODEV; - } - - tmp_addr = ioremap(watchdog_device.timer_tbl_ptr->phys_addr, - 20); - - if (tmp_addr == NULL) { - pr_debug("timer unable to ioremap\n"); - return -ENOMEM; - } - - watchdog_device.timer_load_count_addr = tmp_addr++; - watchdog_device.timer_current_value_addr = tmp_addr++; - watchdog_device.timer_control_addr = tmp_addr++; - watchdog_device.timer_clear_interrupt_addr = tmp_addr++; - watchdog_device.timer_interrupt_status_addr = tmp_addr++; - - /* Set the default time values in device structure */ - - watchdog_device.timer_set = timer_set; - watchdog_device.threshold = - timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; - watchdog_device.soft_threshold = - (watchdog_device.timer_set - timer_margin) - * watchdog_device.timer_tbl_ptr->freq_hz; - - - watchdog_device.intel_scu_notifier.notifier_call = - intel_scu_notify_sys; - - ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier); - if (ret) { - pr_err("cannot register notifier %d)\n", ret); - goto register_reboot_error; - } - - watchdog_device.miscdev.minor = WATCHDOG_MINOR; - watchdog_device.miscdev.name = "watchdog"; - watchdog_device.miscdev.fops = &intel_scu_fops; - - ret = misc_register(&watchdog_device.miscdev); - if (ret) { - pr_err("cannot register miscdev %d err =%d\n", - WATCHDOG_MINOR, ret); - goto misc_register_error; - } - - ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq, - watchdog_timer_interrupt, - IRQF_SHARED, "watchdog", - &watchdog_device.timer_load_count_addr); - if (ret) { - pr_err("error requesting irq %d\n", ret); - goto request_irq_error; - } - /* Make sure timer is disabled before returning */ - intel_scu_stop(); - return 0; - -/* error cleanup */ - -request_irq_error: - misc_deregister(&watchdog_device.miscdev); -misc_register_error: - unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); -register_reboot_error: - intel_scu_stop(); - iounmap(watchdog_device.timer_load_count_addr); - return ret; -} -late_initcall(intel_scu_watchdog_init); diff --git a/drivers/watchdog/intel_scu_watchdog.h b/drivers/watchdog/intel_scu_watchdog.h deleted file mode 100644 index fb12a25ee417..000000000000 --- a/drivers/watchdog/intel_scu_watchdog.h +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device - * for Intel part #(s): - * - AF82MP20 PCH - * - * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. - */ - -#ifndef __INTEL_SCU_WATCHDOG_H -#define __INTEL_SCU_WATCHDOG_H - -#define WDT_VER "0.3" - -/* minimum time between interrupts */ -#define MIN_TIME_CYCLE 1 - -/* Time from warning to reboot is 2 seconds */ -#define DEFAULT_SOFT_TO_HARD_MARGIN 2 - -#define MAX_TIME 170 - -#define DEFAULT_TIME 5 - -#define MAX_SOFT_TO_HARD_MARGIN (MAX_TIME-MIN_TIME_CYCLE) - -/* Ajustment to clock tick frequency to make timing come out right */ -#define FREQ_ADJUSTMENT 8 - -struct intel_scu_watchdog_dev { - ulong driver_open; - ulong driver_closed; - u32 timer_started; - u32 timer_set; - u32 threshold; - u32 soft_threshold; - u32 __iomem *timer_load_count_addr; - u32 __iomem *timer_current_value_addr; - u32 __iomem *timer_control_addr; - u32 __iomem *timer_clear_interrupt_addr; - u32 __iomem *timer_interrupt_status_addr; - struct sfi_timer_table_entry *timer_tbl_ptr; - struct notifier_block intel_scu_notifier; - struct miscdevice miscdev; -}; - -extern int sfi_mtimer_num; - -/* extern struct sfi_timer_table_entry *sfi_get_mtmr(int hint); */ -#endif /* __INTEL_SCU_WATCHDOG_H */ -- cgit v1.2.3 From f285c9532b5bd3de7e37a6203318437cab79bd9a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Oct 2020 19:33:02 +0300 Subject: watchdog: intel-mid_wdt: Postpone IRQ handler registration till SCU is ready When SCU is not ready and CONFIG_DEBUG_SHIRQ=y we got deferred probe followed by fired test IRQ which immediately makes kernel panic. Fix this by delaying IRQ handler registration till SCU is ready. Fixes: 80ae679b8f86 ("watchdog: intel-mid_wdt: Convert to use new SCU IPC API") Signed-off-by: Andy Shevchenko Reviewed-by: Guenter Roeck Acked-by: Linus Walleij Reviewed-by: Mika Westerberg --- drivers/watchdog/intel-mid_wdt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/intel-mid_wdt.c b/drivers/watchdog/intel-mid_wdt.c index 1ae03b64ef8b..9b2173f765c8 100644 --- a/drivers/watchdog/intel-mid_wdt.c +++ b/drivers/watchdog/intel-mid_wdt.c @@ -154,6 +154,10 @@ static int mid_wdt_probe(struct platform_device *pdev) watchdog_set_nowayout(wdt_dev, WATCHDOG_NOWAYOUT); watchdog_set_drvdata(wdt_dev, mid); + mid->scu = devm_intel_scu_ipc_dev_get(dev); + if (!mid->scu) + return -EPROBE_DEFER; + ret = devm_request_irq(dev, pdata->irq, mid_wdt_irq, IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog", wdt_dev); @@ -162,10 +166,6 @@ static int mid_wdt_probe(struct platform_device *pdev) return ret; } - mid->scu = devm_intel_scu_ipc_dev_get(dev); - if (!mid->scu) - return -EPROBE_DEFER; - /* * The firmware followed by U-Boot leaves the watchdog running * with the default threshold which may vary. When we get here -- cgit v1.2.3 From 18365d686e1ee953983e04b7beca4362bff56297 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Oct 2020 19:03:19 +0300 Subject: platform/x86: intel_scu_wdt: Move driver from arch/x86 The ACPI-enabled Intel MID platforms neither have WDAT table nor proper IDs to instantiate watchdog device. In order to keep them working move the board code from arch/x86 to drivers/platform/x86. Note, the complete SFI support is going to be removed, that's why PDx86 has been chosen as a new home for it. This is the only device which needs additional code so far. Signed-off-by: Andy Shevchenko Reviewed-by: Guenter Roeck Reviewed-by: Hans de Goede Acked-by: Linus Walleij --- arch/x86/platform/intel-mid/device_libs/Makefile | 1 - .../intel-mid/device_libs/platform_mrfld_wdt.c | 82 ---------------------- drivers/platform/x86/Kconfig | 8 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/intel_scu_wdt.c | 82 ++++++++++++++++++++++ 5 files changed, 91 insertions(+), 83 deletions(-) delete mode 100644 arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c create mode 100644 drivers/platform/x86/intel_scu_wdt.c (limited to 'drivers') diff --git a/arch/x86/platform/intel-mid/device_libs/Makefile b/arch/x86/platform/intel-mid/device_libs/Makefile index 480fed21cc7d..918edac9ab9a 100644 --- a/arch/x86/platform/intel-mid/device_libs/Makefile +++ b/arch/x86/platform/intel-mid/device_libs/Makefile @@ -30,4 +30,3 @@ obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_tca6416.o obj-$(subst m,y,$(CONFIG_KEYBOARD_GPIO)) += platform_gpio_keys.o obj-$(subst m,y,$(CONFIG_INTEL_MID_POWER_BUTTON)) += platform_mrfld_power_btn.o obj-$(subst m,y,$(CONFIG_RTC_DRV_CMOS)) += platform_mrfld_rtc.o -obj-$(subst m,y,$(CONFIG_INTEL_MID_WATCHDOG)) += platform_mrfld_wdt.o diff --git a/arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c b/arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c deleted file mode 100644 index 227218a8f98e..000000000000 --- a/arch/x86/platform/intel-mid/device_libs/platform_mrfld_wdt.c +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Intel Merrifield watchdog platform device library file - * - * (C) Copyright 2014 Intel Corporation - * Author: David Cohen - */ - -#include -#include -#include -#include - -#include -#include -#include -#include - -#define TANGIER_EXT_TIMER0_MSI 12 - -static struct platform_device wdt_dev = { - .name = "intel_mid_wdt", - .id = -1, -}; - -static int tangier_probe(struct platform_device *pdev) -{ - struct irq_alloc_info info; - struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data; - int gsi = TANGIER_EXT_TIMER0_MSI; - int irq; - - if (!pdata) - return -EINVAL; - - /* IOAPIC builds identity mapping between GSI and IRQ on MID */ - ioapic_set_alloc_attr(&info, cpu_to_node(0), 1, 0); - irq = mp_map_gsi_to_irq(gsi, IOAPIC_MAP_ALLOC, &info); - if (irq < 0) { - dev_warn(&pdev->dev, "cannot find interrupt %d in ioapic\n", gsi); - return irq; - } - - pdata->irq = irq; - return 0; -} - -static struct intel_mid_wdt_pdata tangier_pdata = { - .probe = tangier_probe, -}; - -static int wdt_scu_status_change(struct notifier_block *nb, - unsigned long code, void *data) -{ - if (code == SCU_DOWN) { - platform_device_unregister(&wdt_dev); - return 0; - } - - return platform_device_register(&wdt_dev); -} - -static struct notifier_block wdt_scu_notifier = { - .notifier_call = wdt_scu_status_change, -}; - -static int __init register_mid_wdt(void) -{ - if (intel_mid_identify_cpu() != INTEL_MID_CPU_CHIP_TANGIER) - return -ENODEV; - - wdt_dev.dev.platform_data = &tangier_pdata; - - /* - * We need to be sure that the SCU IPC is ready before watchdog device - * can be registered: - */ - intel_scu_notifier_add(&wdt_scu_notifier); - - return 0; -} -arch_initcall(register_mid_wdt); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4a5798a0ce0c..0bb85eabace1 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1424,6 +1424,14 @@ config INTEL_SCU_PLATFORM and SCU (sometimes called PMC as well). The driver currently supports Intel Elkhart Lake and compatible platforms. +config INTEL_SCU_WDT + bool + default INTEL_SCU_PCI + depends on INTEL_MID_WATCHDOG + help + This is a specific platform code to instantiate watchdog device + on ACPI-based Intel MID platforms. + config INTEL_SCU_IPC_UTIL tristate "Intel SCU IPC utility driver" depends on INTEL_SCU diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 728ccc226a29..19306450d791 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -146,6 +146,7 @@ obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o +obj-$(CONFIG_INTEL_SCU_WDT) += intel_scu_wdt.o obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ intel_telemetry_pltdrv.o \ diff --git a/drivers/platform/x86/intel_scu_wdt.c b/drivers/platform/x86/intel_scu_wdt.c new file mode 100644 index 000000000000..227218a8f98e --- /dev/null +++ b/drivers/platform/x86/intel_scu_wdt.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Merrifield watchdog platform device library file + * + * (C) Copyright 2014 Intel Corporation + * Author: David Cohen + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define TANGIER_EXT_TIMER0_MSI 12 + +static struct platform_device wdt_dev = { + .name = "intel_mid_wdt", + .id = -1, +}; + +static int tangier_probe(struct platform_device *pdev) +{ + struct irq_alloc_info info; + struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data; + int gsi = TANGIER_EXT_TIMER0_MSI; + int irq; + + if (!pdata) + return -EINVAL; + + /* IOAPIC builds identity mapping between GSI and IRQ on MID */ + ioapic_set_alloc_attr(&info, cpu_to_node(0), 1, 0); + irq = mp_map_gsi_to_irq(gsi, IOAPIC_MAP_ALLOC, &info); + if (irq < 0) { + dev_warn(&pdev->dev, "cannot find interrupt %d in ioapic\n", gsi); + return irq; + } + + pdata->irq = irq; + return 0; +} + +static struct intel_mid_wdt_pdata tangier_pdata = { + .probe = tangier_probe, +}; + +static int wdt_scu_status_change(struct notifier_block *nb, + unsigned long code, void *data) +{ + if (code == SCU_DOWN) { + platform_device_unregister(&wdt_dev); + return 0; + } + + return platform_device_register(&wdt_dev); +} + +static struct notifier_block wdt_scu_notifier = { + .notifier_call = wdt_scu_status_change, +}; + +static int __init register_mid_wdt(void) +{ + if (intel_mid_identify_cpu() != INTEL_MID_CPU_CHIP_TANGIER) + return -ENODEV; + + wdt_dev.dev.platform_data = &tangier_pdata; + + /* + * We need to be sure that the SCU IPC is ready before watchdog device + * can be registered: + */ + intel_scu_notifier_add(&wdt_scu_notifier); + + return 0; +} +arch_initcall(register_mid_wdt); -- cgit v1.2.3 From 55627c70db6ad41371ed07a64c6e58d258ab0ae9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 23 Oct 2020 19:14:15 +0300 Subject: platform/x86: intel_scu_wdt: Drop SCU notification Since SCU code along with the Intel MID watchdog driver has been refactored in a way that latter will be probed only after the former has been come to live, the notification code is bogus and not needed. Remove it for good. Signed-off-by: Andy Shevchenko Reviewed-by: Guenter Roeck Reviewed-by: Hans de Goede Acked-by: Linus Walleij --- drivers/platform/x86/intel_scu_wdt.c | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel_scu_wdt.c b/drivers/platform/x86/intel_scu_wdt.c index 227218a8f98e..19f7686a3c19 100644 --- a/drivers/platform/x86/intel_scu_wdt.c +++ b/drivers/platform/x86/intel_scu_wdt.c @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -49,34 +48,18 @@ static struct intel_mid_wdt_pdata tangier_pdata = { .probe = tangier_probe, }; -static int wdt_scu_status_change(struct notifier_block *nb, - unsigned long code, void *data) -{ - if (code == SCU_DOWN) { - platform_device_unregister(&wdt_dev); - return 0; - } - - return platform_device_register(&wdt_dev); -} - -static struct notifier_block wdt_scu_notifier = { - .notifier_call = wdt_scu_status_change, -}; - static int __init register_mid_wdt(void) { if (intel_mid_identify_cpu() != INTEL_MID_CPU_CHIP_TANGIER) return -ENODEV; wdt_dev.dev.platform_data = &tangier_pdata; - - /* - * We need to be sure that the SCU IPC is ready before watchdog device - * can be registered: - */ - intel_scu_notifier_add(&wdt_scu_notifier); - - return 0; + return platform_device_register(&wdt_dev); } arch_initcall(register_mid_wdt); + +static void __exit unregister_mid_wdt(void) +{ + platform_device_unregister(&wdt_dev); +} +__exitcall(unregister_mid_wdt); -- cgit v1.2.3 From a507e5d90f3d6846a02d9c2c79e6f6395982db92 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Jul 2018 20:45:36 +0300 Subject: platform/x86: intel_scu_wdt: Get rid of custom x86 model comparison Switch the platform code to use x86_id_table and accompanying API instead of custom comparison against x86 CPU model. This is one of the last users of custom API for that and following changes will remove it for the good. Signed-off-by: Andy Shevchenko Reviewed-by: Guenter Roeck --- drivers/platform/x86/intel_scu_wdt.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel_scu_wdt.c b/drivers/platform/x86/intel_scu_wdt.c index 19f7686a3c19..85ee85ca2215 100644 --- a/drivers/platform/x86/intel_scu_wdt.c +++ b/drivers/platform/x86/intel_scu_wdt.c @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -48,12 +50,20 @@ static struct intel_mid_wdt_pdata tangier_pdata = { .probe = tangier_probe, }; +static const struct x86_cpu_id intel_mid_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, &tangier_pdata), + {} +}; + static int __init register_mid_wdt(void) { - if (intel_mid_identify_cpu() != INTEL_MID_CPU_CHIP_TANGIER) + const struct x86_cpu_id *id; + + id = x86_match_cpu(intel_mid_cpu_ids); + if (!id) return -ENODEV; - wdt_dev.dev.platform_data = &tangier_pdata; + wdt_dev.dev.platform_data = (const struct intel_mid_wdt_pdata *)id->driver_data; return platform_device_register(&wdt_dev); } arch_initcall(register_mid_wdt); -- cgit v1.2.3 From 5862b4df6681c4bc4051b71099f616a41ac696c2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Jan 2021 17:18:47 +0100 Subject: platform/x86: intel-vbtn: Rework wakeup handling in notify_handler() Rework the wakeup path inside notify_handler() to special case the buttons (KE_KEY) case instead of the switches case. In case of a button wake event we want to skip reporting this, mirroring how the drivers/acpi/button.c code skips the reporting in the wakeup case (suspended flag set) too. The reason to skip reporting in this case is that some Linux desktop-environments will immediately resuspend if we report an evdev event for the power-button press on wakeup. Before this commit the skipping of the button-press was done in a round-about way: In case of a wakeup the regular sparse_keymap_report_event() would always be skipped by an early return, and then to avoid not reporting switch changes on wakeup there was a special KE_SW path with a duplicate sparse_keymap_report_event() call. This commit refactors the wakeup handling to explicitly skip the reporting for button wake events, while using the regular reporting path for non button (switches) wakeup events. No intentional functional impact. Cc: Elia Devito Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210115161850.117614-1-hdegoede@redhat.com --- drivers/platform/x86/intel-vbtn.c | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 30a9062d2b4b..e1bb37a03ba3 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -131,22 +131,17 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) if (priv->wakeup_mode) { ke = sparse_keymap_entry_from_scancode(priv->input_dev, event); - if (ke) { - pm_wakeup_hard_event(&device->dev); - - /* - * Switch events like tablet mode will wake the device - * and report the new switch position to the input - * subsystem. - */ - if (ke->type == KE_SW) - sparse_keymap_report_event(priv->input_dev, - event, - val, - 0); + if (!ke) + goto out_unknown; + + pm_wakeup_hard_event(&device->dev); + + /* + * Skip reporting an evdev event for button wake events, + * mirroring how the drivers/acpi/button.c code skips this too. + */ + if (ke->type == KE_KEY) return; - } - goto out_unknown; } /* -- cgit v1.2.3 From 034b8c2e7b06777775c55cd2db2b6a98f4791b5f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Jan 2021 17:18:48 +0100 Subject: platform/x86: intel-vbtn: Create 2 separate input-devs for buttons and switches Create 2 separate input-devs for buttons and switches, this is a preparation for dynamically registering the switches-input device for devices which are not on the switches allow-list, but do make Notify() calls with an event value from the switches sparse-keymap. This also brings the intel-vbtn driver inline with the intel-hid driver which is doing the same thing. Cc: Elia Devito Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210115161850.117614-2-hdegoede@redhat.com --- drivers/platform/x86/intel-vbtn.c | 98 +++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 36 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index e1bb37a03ba3..04725173d087 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -44,6 +44,7 @@ static const struct key_entry intel_vbtn_keymap[] = { { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ + { KE_END } }; static const struct key_entry intel_vbtn_switchmap[] = { @@ -51,14 +52,15 @@ static const struct key_entry intel_vbtn_switchmap[] = { { KE_SW, 0xCB, { .sw = { SW_DOCK, 0 } } }, /* Undocked */ { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ + { KE_END } }; #define KEYMAP_LEN \ (ARRAY_SIZE(intel_vbtn_keymap) + ARRAY_SIZE(intel_vbtn_switchmap) + 1) struct intel_vbtn_priv { - struct key_entry keymap[KEYMAP_LEN]; - struct input_dev *input_dev; + struct input_dev *buttons_dev; + struct input_dev *switches_dev; bool has_buttons; bool has_switches; bool wakeup_mode; @@ -77,48 +79,62 @@ static void detect_tablet_mode(struct platform_device *device) return; m = !(vgbs & VGBS_TABLET_MODE_FLAGS); - input_report_switch(priv->input_dev, SW_TABLET_MODE, m); + input_report_switch(priv->switches_dev, SW_TABLET_MODE, m); m = (vgbs & VGBS_DOCK_MODE_FLAG) ? 1 : 0; - input_report_switch(priv->input_dev, SW_DOCK, m); + input_report_switch(priv->switches_dev, SW_DOCK, m); } +/* + * Note this unconditionally creates the 2 input_dev-s and sets up + * the sparse-keymaps. Only the registration is conditional on + * have_buttons / have_switches. This is done so that the notify + * handler can always call sparse_keymap_entry_from_scancode() + * on the input_dev-s do determine the event type. + */ static int intel_vbtn_input_setup(struct platform_device *device) { struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); - int ret, keymap_len = 0; + int ret; - if (priv->has_buttons) { - memcpy(&priv->keymap[keymap_len], intel_vbtn_keymap, - ARRAY_SIZE(intel_vbtn_keymap) * - sizeof(struct key_entry)); - keymap_len += ARRAY_SIZE(intel_vbtn_keymap); - } + priv->buttons_dev = devm_input_allocate_device(&device->dev); + if (!priv->buttons_dev) + return -ENOMEM; - if (priv->has_switches) { - memcpy(&priv->keymap[keymap_len], intel_vbtn_switchmap, - ARRAY_SIZE(intel_vbtn_switchmap) * - sizeof(struct key_entry)); - keymap_len += ARRAY_SIZE(intel_vbtn_switchmap); - } + ret = sparse_keymap_setup(priv->buttons_dev, intel_vbtn_keymap, NULL); + if (ret) + return ret; - priv->keymap[keymap_len].type = KE_END; + priv->buttons_dev->dev.parent = &device->dev; + priv->buttons_dev->name = "Intel Virtual Buttons"; + priv->buttons_dev->id.bustype = BUS_HOST; + + if (priv->has_buttons) { + ret = input_register_device(priv->buttons_dev); + if (ret) + return ret; + } - priv->input_dev = devm_input_allocate_device(&device->dev); - if (!priv->input_dev) + priv->switches_dev = devm_input_allocate_device(&device->dev); + if (!priv->switches_dev) return -ENOMEM; - ret = sparse_keymap_setup(priv->input_dev, priv->keymap, NULL); + ret = sparse_keymap_setup(priv->switches_dev, intel_vbtn_switchmap, NULL); if (ret) return ret; - priv->input_dev->dev.parent = &device->dev; - priv->input_dev->name = "Intel Virtual Button driver"; - priv->input_dev->id.bustype = BUS_HOST; + priv->switches_dev->dev.parent = &device->dev; + priv->switches_dev->name = "Intel Virtual Switches"; + priv->switches_dev->id.bustype = BUS_HOST; - if (priv->has_switches) + if (priv->has_switches) { detect_tablet_mode(device); - return input_register_device(priv->input_dev); + ret = input_register_device(priv->switches_dev); + if (ret) + return ret; + } + + return 0; } static void notify_handler(acpi_handle handle, u32 event, void *context) @@ -127,13 +143,27 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); unsigned int val = !(event & 1); /* Even=press, Odd=release */ const struct key_entry *ke, *ke_rel; + struct input_dev *input_dev; bool autorelease; - if (priv->wakeup_mode) { - ke = sparse_keymap_entry_from_scancode(priv->input_dev, event); - if (!ke) - goto out_unknown; + if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) { + if (!priv->has_buttons) { + dev_warn(&device->dev, "Warning: received a button event on a device without buttons, please report this.\n"); + return; + } + input_dev = priv->buttons_dev; + } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) { + if (!priv->has_switches) { + dev_warn(&device->dev, "Warning: received a switches event on a device without switchess, please report this.\n"); + return; + } + input_dev = priv->switches_dev; + } else { + dev_dbg(&device->dev, "unknown event index 0x%x\n", event); + return; + } + if (priv->wakeup_mode) { pm_wakeup_hard_event(&device->dev); /* @@ -148,14 +178,10 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) * Even press events are autorelease if there is no corresponding odd * release event, or if the odd event is KE_IGNORE. */ - ke_rel = sparse_keymap_entry_from_scancode(priv->input_dev, event | 1); + ke_rel = sparse_keymap_entry_from_scancode(input_dev, event | 1); autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); - if (sparse_keymap_report_event(priv->input_dev, event, val, autorelease)) - return; - -out_unknown: - dev_dbg(&device->dev, "unknown event index 0x%x\n", event); + sparse_keymap_report_event(input_dev, event, val, autorelease); } static bool intel_vbtn_has_buttons(acpi_handle handle) -- cgit v1.2.3 From 3a2f53cd03101f6a7cc34c558b0dbfbaca798165 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Jan 2021 17:18:49 +0100 Subject: platform/x86: intel-vbtn: Add alternative method to enable switches Some 2-in-1s have a broken VGBS method, so we cannot get an initial state for the switches from them. Reporting the wrong initial state for SW_TABLET_MODE causes serious problems (touchpad and/or keyboard events being ignored by userspace when reporting SW_TABLET_MODE=1), so on these devices we cannot register an input-dev for the switches at probe time. We can however register an input-dev for the switches as soon as we receive the first switches event, because then we will know the state. Note this mirrors the behavior of recent changs to the intel-hid driver which also registers a separate switches input-dev on receiving the first event on machines with a broken VGBS method. Cc: Elia Devito Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210115161850.117614-3-hdegoede@redhat.com --- drivers/platform/x86/intel-vbtn.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 04725173d087..852cb07c3dfd 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -145,6 +145,7 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) const struct key_entry *ke, *ke_rel; struct input_dev *input_dev; bool autorelease; + int ret; if ((ke = sparse_keymap_entry_from_scancode(priv->buttons_dev, event))) { if (!priv->has_buttons) { @@ -154,8 +155,12 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) input_dev = priv->buttons_dev; } else if ((ke = sparse_keymap_entry_from_scancode(priv->switches_dev, event))) { if (!priv->has_switches) { - dev_warn(&device->dev, "Warning: received a switches event on a device without switchess, please report this.\n"); - return; + dev_info(&device->dev, "Registering Intel Virtual Switches input-dev after receiving a switch event\n"); + ret = input_register_device(priv->switches_dev); + if (ret) + return; + + priv->has_switches = true; } input_dev = priv->switches_dev; } else { -- cgit v1.2.3 From 26173179fae1b1ff16ed07853fe50457828a6c87 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 15 Jan 2021 17:18:50 +0100 Subject: platform/x86: intel-vbtn: Eval VBDL after registering our notifier The VBDL ACPI method enables button/switch reporting through the intel-vbtn device. In some cases the embedded-controller (EC) might call Notify() on the intel-vbtn device immediately after the the VBDL call to make sure that the OS is synced with the EC's button and switch state. If we register our notify_handler after evaluating VBDL this means that we might miss the Notify() calls made by the EC to sync the state. E.g. the HP Stream x360 Convertible PC 11 has a VGBS method which always returns 0, independent of the actual SW_TABLET_MODE state of the device; and immediately after the VBDL call it calls Notify(0xCD) or Notify(0xCC) to report the actual state. Move the evaluation of VBDL to after registering our notify_handler so that we don't miss any events. Cc: Elia Devito Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210115161850.117614-4-hdegoede@redhat.com --- drivers/platform/x86/intel-vbtn.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 852cb07c3dfd..8a8017f9ca91 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -189,14 +189,6 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) sparse_keymap_report_event(input_dev, event, val, autorelease); } -static bool intel_vbtn_has_buttons(acpi_handle handle) -{ - acpi_status status; - - status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); - return ACPI_SUCCESS(status); -} - /* * There are several laptops (non 2-in-1) models out there which support VGBS, * but simply always return 0, which we translate to SW_TABLET_MODE=1. This in @@ -271,7 +263,7 @@ static int intel_vbtn_probe(struct platform_device *device) acpi_status status; int err; - has_buttons = intel_vbtn_has_buttons(handle); + has_buttons = acpi_has_method(handle, "VBDL"); has_switches = intel_vbtn_has_switches(handle); if (!has_buttons && !has_switches) { @@ -300,6 +292,12 @@ static int intel_vbtn_probe(struct platform_device *device) if (ACPI_FAILURE(status)) return -EBUSY; + if (has_buttons) { + status = acpi_evaluate_object(handle, "VBDL", NULL, NULL); + if (ACPI_FAILURE(status)) + dev_err(&device->dev, "Error VBDL failed with ACPI status %d\n", status); + } + device_init_wakeup(&device->dev, true); /* * In order for system wakeup to work, the EC GPE has to be marked as -- cgit v1.2.3 From 2ebe01e25b28465b6e85c32c45125a97221b972f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 20 Jan 2021 13:49:41 +0100 Subject: platform/x86: hp-wmi: Disable tablet-mode reporting by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recently userspace has started making more use of SW_TABLET_MODE (when an input-dev reports this). Specifically recent GNOME3 versions will: 1. When SW_TABLET_MODE is reported and is reporting 0: 1.1 Disable accelerometer-based screen auto-rotation 1.2 Disable automatically showing the on-screen keyboard when a text-input field is focussed 2. When SW_TABLET_MODE is reported and is reporting 1: 2.1 Ignore input-events from the builtin keyboard and touchpad (this is for 360° hinges style 2-in-1s where the keyboard and touchpads are accessible on the back of the tablet when folded into tablet-mode) This means that claiming to support SW_TABLET_MODE when it does not actually work / reports correct values has bad side-effects. The check in the hp-wmi code which is used to decide if the input-dev should claim SW_TABLET_MODE support, only checks if the HPWMI_HARDWARE_QUERY is supported. It does *not* check if the hardware actually is capable of reporting SW_TABLET_MODE. This leads to the hp-wmi input-dev claiming SW_TABLET_MODE support, while in reality it will always report 0 as SW_TABLET_MODE value. This has been seen on a "HP ENVY x360 Convertible 15-cp0xxx" and this likely is the case on a whole lot of other HP models. This problem causes both auto-rotation and on-screen keyboard support to not work on affected x360 models. There is no easy fix for this, but since userspace expects SW_TABLET_MODE reporting to be reliable when advertised it is better to not claim/report SW_TABLET_MODE support at all, then to claim to support it while it does not work. To avoid the mentioned problems, add a new enable_tablet_mode_sw module-parameter which defaults to false. Note I've made this an int using the standard -1=auto, 0=off, 1=on triplett, with the hope that in the future we can come up with a better way to detect SW_TABLET_MODE support. ATM the default auto option just does the same as off. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=1918255 Cc: Stefan Brüns Signed-off-by: Hans de Goede Acked-by: Mark Gross Link: https://lore.kernel.org/r/20210120124941.73409-1-hdegoede@redhat.com --- drivers/platform/x86/hp-wmi.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 18bf8aeb5f87..e94e59283ecb 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -32,6 +32,10 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); +static int enable_tablet_mode_sw = -1; +module_param(enable_tablet_mode_sw, int, 0444); +MODULE_PARM_DESC(enable_tablet_mode_sw, "Enable SW_TABLET_MODE reporting (-1=auto, 0=no, 1=yes)"); + #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" #define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" @@ -654,10 +658,12 @@ static int __init hp_wmi_input_setup(void) } /* Tablet mode */ - val = hp_wmi_hw_state(HPWMI_TABLET_MASK); - if (!(val < 0)) { - __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); - input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); + if (enable_tablet_mode_sw > 0) { + val = hp_wmi_hw_state(HPWMI_TABLET_MASK); + if (val >= 0) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); + } } err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); -- cgit v1.2.3 From d073d867e98977996df64a4383b9880c975bba7b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Jan 2021 13:42:27 +0200 Subject: platform/x86: intel_mid_thermal: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run a more or less fresh kernel on it. Commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") which has been upstream for a while now confirms this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Reviewed-by: Hans de Goede Acked-by: Linus Walleij Link: https://lore.kernel.org/r/20210122114227.39102-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 7 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel_mid_thermal.c | 560 ------------------------------- 3 files changed, 568 deletions(-) delete mode 100644 drivers/platform/x86/intel_mid_thermal.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 91e6176cdfbd..3ba680af3ef5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1327,13 +1327,6 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. -config INTEL_MFLD_THERMAL - tristate "Thermal driver for Intel Medfield platform" - depends on MFD_INTEL_MSIC && THERMAL - help - Say Y here to enable thermal driver support for the Intel Medfield - platform. - config INTEL_MID_POWER_BUTTON tristate "power button driver for Intel MID platforms" depends on INTEL_SCU && INPUT diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 581475f59819..6fb57502b59a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -137,7 +137,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o -obj-$(CONFIG_INTEL_MFLD_THERMAL) += intel_mid_thermal.o obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o diff --git a/drivers/platform/x86/intel_mid_thermal.c b/drivers/platform/x86/intel_mid_thermal.c deleted file mode 100644 index f12f4e7bd971..000000000000 --- a/drivers/platform/x86/intel_mid_thermal.c +++ /dev/null @@ -1,560 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel MID platform thermal driver - * - * Copyright (C) 2011 Intel Corporation - * - * Author: Durgadoss R - */ - -#define pr_fmt(fmt) "intel_mid_thermal: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Number of thermal sensors */ -#define MSIC_THERMAL_SENSORS 4 - -/* ADC1 - thermal registers */ -#define MSIC_ADC_ENBL 0x10 -#define MSIC_ADC_START 0x08 - -#define MSIC_ADCTHERM_ENBL 0x04 -#define MSIC_ADCRRDATA_ENBL 0x05 -#define MSIC_CHANL_MASK_VAL 0x0F - -#define MSIC_STOPBIT_MASK 16 -#define MSIC_ADCTHERM_MASK 4 -/* Number of ADC channels */ -#define ADC_CHANLS_MAX 15 -#define ADC_LOOP_MAX (ADC_CHANLS_MAX - MSIC_THERMAL_SENSORS) - -/* ADC channel code values */ -#define SKIN_SENSOR0_CODE 0x08 -#define SKIN_SENSOR1_CODE 0x09 -#define SYS_SENSOR_CODE 0x0A -#define MSIC_DIE_SENSOR_CODE 0x03 - -#define SKIN_THERM_SENSOR0 0 -#define SKIN_THERM_SENSOR1 1 -#define SYS_THERM_SENSOR2 2 -#define MSIC_DIE_THERM_SENSOR3 3 - -/* ADC code range */ -#define ADC_MAX 977 -#define ADC_MIN 162 -#define ADC_VAL0C 887 -#define ADC_VAL20C 720 -#define ADC_VAL40C 508 -#define ADC_VAL60C 315 - -/* ADC base addresses */ -#define ADC_CHNL_START_ADDR INTEL_MSIC_ADC1ADDR0 /* increments by 1 */ -#define ADC_DATA_START_ADDR INTEL_MSIC_ADC1SNS0H /* increments by 2 */ - -/* MSIC die attributes */ -#define MSIC_DIE_ADC_MIN 488 -#define MSIC_DIE_ADC_MAX 1004 - -/* This holds the address of the first free ADC channel, - * among the 15 channels - */ -static int channel_index; - -struct platform_info { - struct platform_device *pdev; - struct thermal_zone_device *tzd[MSIC_THERMAL_SENSORS]; -}; - -struct thermal_device_info { - unsigned int chnl_addr; - int direct; - /* This holds the current temperature in millidegree celsius */ - long curr_temp; -}; - -/** - * to_msic_die_temp - converts adc_val to msic_die temperature - * @adc_val: ADC value to be converted - * - * Can sleep - */ -static int to_msic_die_temp(uint16_t adc_val) -{ - return (368 * (adc_val) / 1000) - 220; -} - -/** - * is_valid_adc - checks whether the adc code is within the defined range - * @min: minimum value for the sensor - * @max: maximum value for the sensor - * - * Can sleep - */ -static int is_valid_adc(uint16_t adc_val, uint16_t min, uint16_t max) -{ - return (adc_val >= min) && (adc_val <= max); -} - -/** - * adc_to_temp - converts the ADC code to temperature in C - * @direct: true if ths channel is direct index - * @adc_val: the adc_val that needs to be converted - * @tp: temperature return value - * - * Linear approximation is used to covert the skin adc value into temperature. - * This technique is used to avoid very long look-up table to get - * the appropriate temp value from ADC value. - * The adc code vs sensor temp curve is split into five parts - * to achieve very close approximate temp value with less than - * 0.5C error - */ -static int adc_to_temp(int direct, uint16_t adc_val, int *tp) -{ - int temp; - - /* Direct conversion for die temperature */ - if (direct) { - if (is_valid_adc(adc_val, MSIC_DIE_ADC_MIN, MSIC_DIE_ADC_MAX)) { - *tp = to_msic_die_temp(adc_val) * 1000; - return 0; - } - return -ERANGE; - } - - if (!is_valid_adc(adc_val, ADC_MIN, ADC_MAX)) - return -ERANGE; - - /* Linear approximation for skin temperature */ - if (adc_val > ADC_VAL0C) - temp = 177 - (adc_val/5); - else if ((adc_val <= ADC_VAL0C) && (adc_val > ADC_VAL20C)) - temp = 111 - (adc_val/8); - else if ((adc_val <= ADC_VAL20C) && (adc_val > ADC_VAL40C)) - temp = 92 - (adc_val/10); - else if ((adc_val <= ADC_VAL40C) && (adc_val > ADC_VAL60C)) - temp = 91 - (adc_val/10); - else - temp = 112 - (adc_val/6); - - /* Convert temperature in celsius to milli degree celsius */ - *tp = temp * 1000; - return 0; -} - -/** - * mid_read_temp - read sensors for temperature - * @temp: holds the current temperature for the sensor after reading - * - * reads the adc_code from the channel and converts it to real - * temperature. The converted value is stored in temp. - * - * Can sleep - */ -static int mid_read_temp(struct thermal_zone_device *tzd, int *temp) -{ - struct thermal_device_info *td_info = tzd->devdata; - uint16_t adc_val, addr; - uint8_t data = 0; - int ret; - int curr_temp; - - addr = td_info->chnl_addr; - - /* Enable the msic for conversion before reading */ - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCRRDATA_ENBL); - if (ret) - return ret; - - /* Re-toggle the RRDATARD bit (temporary workaround) */ - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, MSIC_ADCTHERM_ENBL); - if (ret) - return ret; - - /* Read the higher bits of data */ - ret = intel_msic_reg_read(addr, &data); - if (ret) - return ret; - - /* Shift bits to accommodate the lower two data bits */ - adc_val = (data << 2); - addr++; - - ret = intel_msic_reg_read(addr, &data);/* Read lower bits */ - if (ret) - return ret; - - /* Adding lower two bits to the higher bits */ - data &= 03; - adc_val += data; - - /* Convert ADC value to temperature */ - ret = adc_to_temp(td_info->direct, adc_val, &curr_temp); - if (ret == 0) - *temp = td_info->curr_temp = curr_temp; - return ret; -} - -/** - * configure_adc - enables/disables the ADC for conversion - * @val: zero: disables the ADC non-zero:enables the ADC - * - * Enable/Disable the ADC depending on the argument - * - * Can sleep - */ -static int configure_adc(int val) -{ - int ret; - uint8_t data; - - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); - if (ret) - return ret; - - if (val) { - /* Enable and start the ADC */ - data |= (MSIC_ADC_ENBL | MSIC_ADC_START); - } else { - /* Just stop the ADC */ - data &= (~MSIC_ADC_START); - } - return intel_msic_reg_write(INTEL_MSIC_ADC1CNTL1, data); -} - -/** - * set_up_therm_channel - enable thermal channel for conversion - * @base_addr: index of free msic ADC channel - * - * Enable all the three channels for conversion - * - * Can sleep - */ -static int set_up_therm_channel(u16 base_addr) -{ - int ret; - - /* Enable all the sensor channels */ - ret = intel_msic_reg_write(base_addr, SKIN_SENSOR0_CODE); - if (ret) - return ret; - - ret = intel_msic_reg_write(base_addr + 1, SKIN_SENSOR1_CODE); - if (ret) - return ret; - - ret = intel_msic_reg_write(base_addr + 2, SYS_SENSOR_CODE); - if (ret) - return ret; - - /* Since this is the last channel, set the stop bit - * to 1 by ORing the DIE_SENSOR_CODE with 0x10 */ - ret = intel_msic_reg_write(base_addr + 3, - (MSIC_DIE_SENSOR_CODE | 0x10)); - if (ret) - return ret; - - /* Enable ADC and start it */ - return configure_adc(1); -} - -/** - * reset_stopbit - sets the stop bit to 0 on the given channel - * @addr: address of the channel - * - * Can sleep - */ -static int reset_stopbit(uint16_t addr) -{ - int ret; - uint8_t data; - ret = intel_msic_reg_read(addr, &data); - if (ret) - return ret; - /* Set the stop bit to zero */ - return intel_msic_reg_write(addr, (data & 0xEF)); -} - -/** - * find_free_channel - finds an empty channel for conversion - * - * If the ADC is not enabled then start using 0th channel - * itself. Otherwise find an empty channel by looking for a - * channel in which the stopbit is set to 1. returns the index - * of the first free channel if succeeds or an error code. - * - * Context: can sleep - * - * FIXME: Ultimately the channel allocator will move into the intel_scu_ipc - * code. - */ -static int find_free_channel(void) -{ - int ret; - int i; - uint8_t data; - - /* check whether ADC is enabled */ - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL1, &data); - if (ret) - return ret; - - if ((data & MSIC_ADC_ENBL) == 0) - return 0; - - /* ADC is already enabled; Looking for an empty channel */ - for (i = 0; i < ADC_CHANLS_MAX; i++) { - ret = intel_msic_reg_read(ADC_CHNL_START_ADDR + i, &data); - if (ret) - return ret; - - if (data & MSIC_STOPBIT_MASK) { - ret = i; - break; - } - } - return (ret > ADC_LOOP_MAX) ? (-EINVAL) : ret; -} - -/** - * mid_initialize_adc - initializing the ADC - * @dev: our device structure - * - * Initialize the ADC for reading thermistor values. Can sleep. - */ -static int mid_initialize_adc(struct device *dev) -{ - u8 data; - u16 base_addr; - int ret; - - /* - * Ensure that adctherm is disabled before we - * initialize the ADC - */ - ret = intel_msic_reg_read(INTEL_MSIC_ADC1CNTL3, &data); - if (ret) - return ret; - - data &= ~MSIC_ADCTHERM_MASK; - ret = intel_msic_reg_write(INTEL_MSIC_ADC1CNTL3, data); - if (ret) - return ret; - - /* Index of the first channel in which the stop bit is set */ - channel_index = find_free_channel(); - if (channel_index < 0) { - dev_err(dev, "No free ADC channels"); - return channel_index; - } - - base_addr = ADC_CHNL_START_ADDR + channel_index; - - if (!(channel_index == 0 || channel_index == ADC_LOOP_MAX)) { - /* Reset stop bit for channels other than 0 and 12 */ - ret = reset_stopbit(base_addr); - if (ret) - return ret; - - /* Index of the first free channel */ - base_addr++; - channel_index++; - } - - ret = set_up_therm_channel(base_addr); - if (ret) { - dev_err(dev, "unable to enable ADC"); - return ret; - } - dev_dbg(dev, "ADC initialization successful"); - return ret; -} - -/** - * initialize_sensor - sets default temp and timer ranges - * @index: index of the sensor - * - * Context: can sleep - */ -static struct thermal_device_info *initialize_sensor(int index) -{ - struct thermal_device_info *td_info = - kzalloc(sizeof(struct thermal_device_info), GFP_KERNEL); - - if (!td_info) - return NULL; - - /* Set the base addr of the channel for this sensor */ - td_info->chnl_addr = ADC_DATA_START_ADDR + 2 * (channel_index + index); - /* Sensor 3 is direct conversion */ - if (index == 3) - td_info->direct = 1; - return td_info; -} - -#ifdef CONFIG_PM_SLEEP -/** - * mid_thermal_resume - resume routine - * @dev: device structure - * - * mid thermal resume: re-initializes the adc. Can sleep. - */ -static int mid_thermal_resume(struct device *dev) -{ - return mid_initialize_adc(dev); -} - -/** - * mid_thermal_suspend - suspend routine - * @dev: device structure - * - * mid thermal suspend implements the suspend functionality - * by stopping the ADC. Can sleep. - */ -static int mid_thermal_suspend(struct device *dev) -{ - /* - * This just stops the ADC and does not disable it. - * temporary workaround until we have a generic ADC driver. - * If 0 is passed, it disables the ADC. - */ - return configure_adc(0); -} -#endif - -static SIMPLE_DEV_PM_OPS(mid_thermal_pm, - mid_thermal_suspend, mid_thermal_resume); - -/** - * read_curr_temp - reads the current temperature and stores in temp - * @temp: holds the current temperature value after reading - * - * Can sleep - */ -static int read_curr_temp(struct thermal_zone_device *tzd, int *temp) -{ - WARN_ON(tzd == NULL); - return mid_read_temp(tzd, temp); -} - -/* Can't be const */ -static struct thermal_zone_device_ops tzd_ops = { - .get_temp = read_curr_temp, -}; - -/** - * mid_thermal_probe - mfld thermal initialize - * @pdev: platform device structure - * - * mid thermal probe initializes the hardware and registers - * all the sensors with the generic thermal framework. Can sleep. - */ -static int mid_thermal_probe(struct platform_device *pdev) -{ - static char *name[MSIC_THERMAL_SENSORS] = { - "skin0", "skin1", "sys", "msicdie" - }; - - int ret; - int i; - struct platform_info *pinfo; - - pinfo = devm_kzalloc(&pdev->dev, sizeof(struct platform_info), - GFP_KERNEL); - if (!pinfo) - return -ENOMEM; - - /* Initializing the hardware */ - ret = mid_initialize_adc(&pdev->dev); - if (ret) { - dev_err(&pdev->dev, "ADC init failed"); - return ret; - } - - /* Register each sensor with the generic thermal framework*/ - for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { - struct thermal_device_info *td_info = initialize_sensor(i); - - if (!td_info) { - ret = -ENOMEM; - goto err; - } - pinfo->tzd[i] = thermal_zone_device_register(name[i], - 0, 0, td_info, &tzd_ops, NULL, 0, 0); - if (IS_ERR(pinfo->tzd[i])) { - kfree(td_info); - ret = PTR_ERR(pinfo->tzd[i]); - goto err; - } - ret = thermal_zone_device_enable(pinfo->tzd[i]); - if (ret) { - kfree(td_info); - thermal_zone_device_unregister(pinfo->tzd[i]); - goto err; - } - } - - pinfo->pdev = pdev; - platform_set_drvdata(pdev, pinfo); - return 0; - -err: - while (--i >= 0) { - kfree(pinfo->tzd[i]->devdata); - thermal_zone_device_unregister(pinfo->tzd[i]); - } - configure_adc(0); - return ret; -} - -/** - * mid_thermal_remove - mfld thermal finalize - * @dev: platform device structure - * - * MLFD thermal remove unregisters all the sensors from the generic - * thermal framework. Can sleep. - */ -static int mid_thermal_remove(struct platform_device *pdev) -{ - int i; - struct platform_info *pinfo = platform_get_drvdata(pdev); - - for (i = 0; i < MSIC_THERMAL_SENSORS; i++) { - kfree(pinfo->tzd[i]->devdata); - thermal_zone_device_unregister(pinfo->tzd[i]); - } - - /* Stop the ADC */ - return configure_adc(0); -} - -#define DRIVER_NAME "msic_thermal" - -static const struct platform_device_id therm_id_table[] = { - { DRIVER_NAME, 1 }, - { } -}; -MODULE_DEVICE_TABLE(platform, therm_id_table); - -static struct platform_driver mid_thermal_driver = { - .driver = { - .name = DRIVER_NAME, - .pm = &mid_thermal_pm, - }, - .probe = mid_thermal_probe, - .remove = mid_thermal_remove, - .id_table = therm_id_table, -}; - -module_platform_driver(mid_thermal_driver); - -MODULE_AUTHOR("Durgadoss R "); -MODULE_DESCRIPTION("Intel Medfield Platform Thermal Driver"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From aecb925db7085265595e79fc3feccd184d14464b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 22 Jan 2021 13:41:45 +0200 Subject: platform/x86: intel_mid_powerbtn: Remove driver for deprecated platform Intel Moorestown and Medfield are quite old Intel Atom based 32-bit platforms, which were in limited use in some Android phones, tablets and consumer electronics more than eight years ago. There are no bugs or problems ever reported outside from Intel for breaking any of that platforms for years. It seems no real users exists who run a more or less fresh kernel on it. Commit 05f4434bc130 ("ASoC: Intel: remove mfld_machine") which has been upstream for a while now confirms this theory. Due to above and to reduce a burden of supporting outdated drivers we remove the support of outdated platforms completely. Signed-off-by: Andy Shevchenko Reviewed-by: Hans de Goede Acked-by: Linus Walleij Link: https://lore.kernel.org/r/20210122114145.38813-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 8 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/intel_mid_powerbtn.c | 233 ------------------------------ 3 files changed, 242 deletions(-) delete mode 100644 drivers/platform/x86/intel_mid_powerbtn.c (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 3ba680af3ef5..4a5798a0ce0c 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1327,14 +1327,6 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. -config INTEL_MID_POWER_BUTTON - tristate "power button driver for Intel MID platforms" - depends on INTEL_SCU && INPUT - help - This driver handles the power button on the Intel MID platforms. - - If unsure, say N. - config INTEL_MRFLD_PWRBTN tristate "Intel Merrifield Basin Cove power button driver" depends on INTEL_SOC_PMIC_MRFLD diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 6fb57502b59a..728ccc226a29 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -137,7 +137,6 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o # Intel PMIC / PMC / P-Unit devices obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o -obj-$(CONFIG_INTEL_MID_POWER_BUTTON) += intel_mid_powerbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_INTEL_PMT_CLASS) += intel_pmt_class.o diff --git a/drivers/platform/x86/intel_mid_powerbtn.c b/drivers/platform/x86/intel_mid_powerbtn.c deleted file mode 100644 index df434abbb66f..000000000000 --- a/drivers/platform/x86/intel_mid_powerbtn.c +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Power button driver for Intel MID platforms. - * - * Copyright (C) 2010,2017 Intel Corp - * - * Author: Hong Liu - * Author: Andy Shevchenko - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#define DRIVER_NAME "msic_power_btn" - -#define MSIC_PB_LEVEL (1 << 3) /* 1 - release, 0 - press */ - -/* - * MSIC document ti_datasheet defines the 1st bit reg 0x21 is used to mask - * power button interrupt - */ -#define MSIC_PWRBTNM (1 << 0) - -/* Intel Tangier */ -#define BCOVE_PB_LEVEL (1 << 4) /* 1 - release, 0 - press */ - -/* Basin Cove PMIC */ -#define BCOVE_PBIRQ 0x02 -#define BCOVE_IRQLVL1MSK 0x0c -#define BCOVE_PBIRQMASK 0x0d -#define BCOVE_PBSTATUS 0x27 - -struct mid_pb_ddata { - struct device *dev; - int irq; - struct input_dev *input; - unsigned short mirqlvl1_addr; - unsigned short pbstat_addr; - u8 pbstat_mask; - struct intel_scu_ipc_dev *scu; - int (*setup)(struct mid_pb_ddata *ddata); -}; - -static int mid_pbstat(struct mid_pb_ddata *ddata, int *value) -{ - struct input_dev *input = ddata->input; - int ret; - u8 pbstat; - - ret = intel_scu_ipc_dev_ioread8(ddata->scu, ddata->pbstat_addr, - &pbstat); - if (ret) - return ret; - - dev_dbg(input->dev.parent, "PB_INT status= %d\n", pbstat); - - *value = !(pbstat & ddata->pbstat_mask); - return 0; -} - -static int mid_irq_ack(struct mid_pb_ddata *ddata) -{ - return intel_scu_ipc_dev_update(ddata->scu, ddata->mirqlvl1_addr, 0, - MSIC_PWRBTNM); -} - -static int mrfld_setup(struct mid_pb_ddata *ddata) -{ - /* Unmask the PBIRQ and MPBIRQ on Tangier */ - intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQ, 0, MSIC_PWRBTNM); - intel_scu_ipc_dev_update(ddata->scu, BCOVE_PBIRQMASK, 0, MSIC_PWRBTNM); - - return 0; -} - -static irqreturn_t mid_pb_isr(int irq, void *dev_id) -{ - struct mid_pb_ddata *ddata = dev_id; - struct input_dev *input = ddata->input; - int value = 0; - int ret; - - ret = mid_pbstat(ddata, &value); - if (ret < 0) { - dev_err(input->dev.parent, - "Read error %d while reading MSIC_PB_STATUS\n", ret); - } else { - input_event(input, EV_KEY, KEY_POWER, value); - input_sync(input); - } - - mid_irq_ack(ddata); - return IRQ_HANDLED; -} - -static const struct mid_pb_ddata mfld_ddata = { - .mirqlvl1_addr = INTEL_MSIC_IRQLVL1MSK, - .pbstat_addr = INTEL_MSIC_PBSTATUS, - .pbstat_mask = MSIC_PB_LEVEL, -}; - -static const struct mid_pb_ddata mrfld_ddata = { - .mirqlvl1_addr = BCOVE_IRQLVL1MSK, - .pbstat_addr = BCOVE_PBSTATUS, - .pbstat_mask = BCOVE_PB_LEVEL, - .setup = mrfld_setup, -}; - -static const struct x86_cpu_id mid_pb_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(ATOM_SALTWELL_MID, &mfld_ddata), - X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_MID, &mrfld_ddata), - {} -}; - -static int mid_pb_probe(struct platform_device *pdev) -{ - const struct x86_cpu_id *id; - struct mid_pb_ddata *ddata; - struct input_dev *input; - int irq = platform_get_irq(pdev, 0); - int error; - - id = x86_match_cpu(mid_pb_cpu_ids); - if (!id) - return -ENODEV; - - if (irq < 0) { - dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq); - return irq; - } - - input = devm_input_allocate_device(&pdev->dev); - if (!input) - return -ENOMEM; - - input->name = pdev->name; - input->phys = "power-button/input0"; - input->id.bustype = BUS_HOST; - input->dev.parent = &pdev->dev; - - input_set_capability(input, EV_KEY, KEY_POWER); - - ddata = devm_kmemdup(&pdev->dev, (void *)id->driver_data, - sizeof(*ddata), GFP_KERNEL); - if (!ddata) - return -ENOMEM; - - ddata->dev = &pdev->dev; - ddata->irq = irq; - ddata->input = input; - - if (ddata->setup) { - error = ddata->setup(ddata); - if (error) - return error; - } - - ddata->scu = devm_intel_scu_ipc_dev_get(&pdev->dev); - if (!ddata->scu) - return -EPROBE_DEFER; - - error = devm_request_threaded_irq(&pdev->dev, irq, NULL, mid_pb_isr, - IRQF_ONESHOT, DRIVER_NAME, ddata); - if (error) { - dev_err(&pdev->dev, - "Unable to request irq %d for MID power button\n", irq); - return error; - } - - error = input_register_device(input); - if (error) { - dev_err(&pdev->dev, - "Unable to register input dev, error %d\n", error); - return error; - } - - platform_set_drvdata(pdev, ddata); - - /* - * SCU firmware might send power button interrupts to IA core before - * kernel boots and doesn't get EOI from IA core. The first bit of - * MSIC reg 0x21 is kept masked, and SCU firmware doesn't send new - * power interrupt to Android kernel. Unmask the bit when probing - * power button in kernel. - * There is a very narrow race between irq handler and power button - * initialization. The race happens rarely. So we needn't worry - * about it. - */ - error = mid_irq_ack(ddata); - if (error) { - dev_err(&pdev->dev, - "Unable to clear power button interrupt, error: %d\n", - error); - return error; - } - - device_init_wakeup(&pdev->dev, true); - dev_pm_set_wake_irq(&pdev->dev, irq); - - return 0; -} - -static int mid_pb_remove(struct platform_device *pdev) -{ - dev_pm_clear_wake_irq(&pdev->dev); - device_init_wakeup(&pdev->dev, false); - - return 0; -} - -static struct platform_driver mid_pb_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = mid_pb_probe, - .remove = mid_pb_remove, -}; - -module_platform_driver(mid_pb_driver); - -MODULE_AUTHOR("Hong Liu "); -MODULE_DESCRIPTION("Intel MID Power Button Driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" DRIVER_NAME); -- cgit v1.2.3 From d7cbe2773aed0b636d48bb6795637eb486ecba6d Mon Sep 17 00:00:00 2001 From: Nitin Joshi Date: Mon, 25 Jan 2021 11:59:16 +0900 Subject: platform/x86: thinkpad_acpi: set keyboard language This patch is to create sysfs entry for setting keyboard language using ASL method. Some thinkpads models like T580 , T590 , T15 Gen 1 etc. has "=", "(',")" numeric keys, which are not displaying correctly, when keyboard language is other than "english". This patch fixes this issue by setting keyboard language to ECFW. Signed-off-by: Nitin Joshi Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210125025916.180831-1-nitjoshi@gmail.com Signed-off-by: Hans de Goede --- .../admin-guide/laptops/thinkpad-acpi.rst | 24 +++ drivers/platform/x86/thinkpad_acpi.c | 181 +++++++++++++++++++++ 2 files changed, 205 insertions(+) (limited to 'drivers') diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst index 5fe1ade88c17..b1188f05a99a 100644 --- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst +++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst @@ -51,6 +51,7 @@ detailed description): - UWB enable and disable - LCD Shadow (PrivacyGuard) enable and disable - Lap mode sensor + - Setting keyboard language A compatibility table by model and feature is maintained on the web site, http://ibm-acpi.sf.net/. I appreciate any success or failure @@ -1466,6 +1467,29 @@ Sysfs notes rfkill controller switch "tpacpi_uwb_sw": refer to Documentation/driver-api/rfkill.rst for details. + +Setting keyboard language +------------------- + +sysfs: keyboard_lang + +This feature is used to set keyboard language to ECFW using ASL interface. +Fewer thinkpads models like T580 , T590 , T15 Gen 1 etc.. has "=", "(', +")" numeric keys, which are not displaying correctly, when keyboard language +is other than "english". This is because of default keyboard language in ECFW +is set as "english". Hence using this sysfs, user can set correct keyboard +language to ECFW and then these key's will work correctly . + +Example of command to set keyboard language is mentioned below:: + + echo jp > /sys/devices/platform/thinkpad_acpi/keyboard_lang + +Text corresponding to keyboard layout to be set in sysfs are : jp (Japan), be(Belgian), +cz(Czech), en(English), da(Danish), de(German), es(Spain) , et(Estonian), +fr(French) , fr-ch (French(Switzerland)), pl(Polish), sl(Slovenian), hu +(Hungarian), nl(Dutch), tr(Turkey), it(Italy), sv(Sweden), pt(portugese) + + Adaptive keyboard ----------------- diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index f3e8eca8d86d..81f8ad6bb688 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -9983,6 +9983,183 @@ static struct ibm_struct proxsensor_driver_data = { .exit = proxsensor_exit, }; +/************************************************************************* + * Keyboard language interface + */ + +struct keyboard_lang_data { + const char *lang_str; + int lang_code; +}; + +/* + * When adding new entries to keyboard_lang_data, please check that + * the select_lang[] buffer in keyboard_lang_show() is still large enough. + */ +struct keyboard_lang_data keyboard_lang_data[] = { + {"en", 0}, + {"be", 0x080c}, + {"cz", 0x0405}, + {"da", 0x0406}, + {"de", 0x0c07}, + {"es", 0x2c0a}, + {"et", 0x0425}, + {"fr", 0x040c}, + {"fr-ch", 0x100c}, + {"hu", 0x040e}, + {"it", 0x0410}, + {"jp", 0x0411}, + {"nl", 0x0413}, + {"nn", 0x0414}, + {"pl", 0x0415}, + {"pt", 0x0816}, + {"sl", 0x041b}, + {"sv", 0x081d}, + {"tr", 0x041f}, +}; + +static int set_keyboard_lang_command(int command) +{ + acpi_handle sskl_handle; + int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "SSKL", &sskl_handle))) { + /* Platform doesn't support SSKL */ + return -ENODEV; + } + + if (!acpi_evalf(sskl_handle, &output, NULL, "dd", command)) + return -EIO; + + return 0; +} + +static int get_keyboard_lang(int *output) +{ + acpi_handle gskl_handle; + int kbd_lang; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GSKL", &gskl_handle))) { + /* Platform doesn't support GSKL */ + return -ENODEV; + } + + if (!acpi_evalf(gskl_handle, &kbd_lang, NULL, "dd", 0x02000000)) + return -EIO; + + *output = kbd_lang; + + return 0; +} + +/* sysfs keyboard language entry */ +static ssize_t keyboard_lang_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int output, err, i; + char select_lang[80] = ""; + char lang[8] = ""; + + err = get_keyboard_lang(&output); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(keyboard_lang_data); i++) { + if (i) + strcat(select_lang, " "); + + if (output == keyboard_lang_data[i].lang_code) { + strcat(lang, "["); + strcat(lang, keyboard_lang_data[i].lang_str); + strcat(lang, "]"); + strcat(select_lang, lang); + } else { + strcat(select_lang, keyboard_lang_data[i].lang_str); + } + } + + return sysfs_emit(buf, "%s\n", select_lang); +} + +static ssize_t keyboard_lang_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, i; + bool lang_found = false; + int lang_code = 0; + + for (i = 0; i < ARRAY_SIZE(keyboard_lang_data); i++) { + if (sysfs_streq(buf, keyboard_lang_data[i].lang_str)) { + lang_code = keyboard_lang_data[i].lang_code; + lang_found = true; + break; + } + } + + if (lang_found) { + lang_code = lang_code | 1 << 24; + + /* Set language code */ + err = set_keyboard_lang_command(lang_code); + if (err) + return err; + } else { + pr_err("Unknown Keyboard language. Ignoring\n"); + return -EINVAL; + } + + tpacpi_disclose_usertask(attr->attr.name, + "keyboard language is set to %s\n", buf); + + sysfs_notify(&tpacpi_pdev->dev.kobj, NULL, "keyboard_lang"); + + return count; +} + +static DEVICE_ATTR_RW(keyboard_lang); + +static struct attribute *kbdlang_attributes[] = { + &dev_attr_keyboard_lang.attr, + NULL +}; + +static const struct attribute_group kbdlang_attr_group = { + .attrs = kbdlang_attributes, +}; + +static int tpacpi_kbdlang_init(struct ibm_init_struct *iibm) +{ + int err, output; + + err = get_keyboard_lang(&output); + /* + * If support isn't available (ENODEV) then don't return an error + * just don't create the sysfs group + */ + if (err == -ENODEV) + return 0; + + if (err) + return err; + + /* Platform supports this feature - create the sysfs file */ + err = sysfs_create_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group); + + return err; +} + +static void kbdlang_exit(void) +{ + sysfs_remove_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group); +} + +static struct ibm_struct kbdlang_driver_data = { + .name = "kbdlang", + .exit = kbdlang_exit, +}; + /**************************************************************************** **************************************************************************** * @@ -10475,6 +10652,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_proxsensor_init, .data = &proxsensor_driver_data, }, + { + .init = tpacpi_kbdlang_init, + .data = &kbdlang_driver_data, + }, }; static int __init set_ibm_param(const char *val, const struct kernel_param *kp) -- cgit v1.2.3 From 64b0efa18f8c3b1baac369b8d74d0fdae02bc4bc Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 29 Jan 2021 11:26:54 -0600 Subject: platform/x86: dell-wmi-sysman: fix a NULL pointer dereference An upcoming Dell platform is causing a NULL pointer dereference in dell-wmi-sysman initialization. Validate that the input from BIOS matches correct ACPI types and abort module initialization if it fails. Signed-off-by: Mario Limonciello Tested-by: Perry Yuan Link: https://lore.kernel.org/r/20210129172654.2326751-1-mario.limonciello@dell.com [hdegoede@redhat.com: Drop redundant release_attributes_data() call] Signed-off-by: Hans de Goede --- drivers/platform/x86/dell-wmi-sysman/sysman.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell-wmi-sysman/sysman.c index dc6dd531c996..cb81010ba1a2 100644 --- a/drivers/platform/x86/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell-wmi-sysman/sysman.c @@ -419,13 +419,17 @@ static int init_bios_attributes(int attr_type, const char *guid) return retval; /* need to use specific instance_id and guid combination to get right data */ obj = get_wmiobj_pointer(instance_id, guid); - if (!obj) + if (!obj || obj->type != ACPI_TYPE_PACKAGE) return -ENODEV; elements = obj->package.elements; mutex_lock(&wmi_priv.mutex); while (elements) { /* sanity checking */ + if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) { + pr_debug("incorrect element type\n"); + goto nextobj; + } if (strlen(elements[ATTR_NAME].string.pointer) == 0) { pr_debug("empty attribute found\n"); goto nextobj; -- cgit v1.2.3 From 9e9c64131f47b0016911b76cebfcda6b1bb3b7b7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 25 Jan 2021 21:52:58 +0100 Subject: platform/x86: thinkpad_acpi: Don't register keyboard_lang unnecessarily All recent ThinkPad BIOS-es support the GSKL method used to query the keyboard-layout used by the ECFW for the SHIFT + other-key key-press emulation for special keys such as e.g. the '=', '(' and ')' keys above the numpad on 15" models. So just checking for the method is not a good indicator of the model supporting getting/setting the keyboard_lang. On models where this is not supported GSKL succeeds, but it returns METHOD_ERR in the returned integer to indicate that this is not supported on this model. Add a check for METHOD_ERR and return -ENODEV if it is set to avoid registering a non-working keyboard_lang sysfs-attr on models where this is not supported. Cc: Nitin Joshi Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210125205258.135664-1-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 81f8ad6bb688..4a1dba3099a6 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -10047,6 +10047,13 @@ static int get_keyboard_lang(int *output) if (!acpi_evalf(gskl_handle, &kbd_lang, NULL, "dd", 0x02000000)) return -EIO; + /* + * METHOD_ERR gets returned on devices where there are no special (e.g. '=', + * '(' and ')') keys which use layout dependent key-press emulation. + */ + if (kbd_lang & METHOD_ERR) + return -ENODEV; + *output = kbd_lang; return 0; -- cgit v1.2.3 From cfa75cca618ef35cbbc05ff74ca9af6c7ff274ea Mon Sep 17 00:00:00 2001 From: Nitin Joshi Date: Tue, 2 Feb 2021 09:32:10 +0900 Subject: platform/x86: thinkpad_acpi: fixed warning and incorporated review comments The previous commit adding new sysfs for keyboard language has warning and few code correction has to be done as per new review comments. Below changes has been addressed in this version: - corrected warning. Many thanks to kernel test robot for reporting and determining this warning. - used sysfs_emit_at() API instead of strcat. - sorted keyboard language array. - removed unwanted space and corrected sentences. Reported-by: kernel test robot Signed-off-by: Nitin Joshi Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210202003210.91773-1-njoshi1@lenovo.com Signed-off-by: Hans de Goede --- .../admin-guide/laptops/thinkpad-acpi.rst | 15 +++++----- drivers/platform/x86/thinkpad_acpi.c | 33 ++++++++-------------- 2 files changed, 19 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/Documentation/admin-guide/laptops/thinkpad-acpi.rst b/Documentation/admin-guide/laptops/thinkpad-acpi.rst index 0e4c5bb7fb70..91fd6846ce17 100644 --- a/Documentation/admin-guide/laptops/thinkpad-acpi.rst +++ b/Documentation/admin-guide/laptops/thinkpad-acpi.rst @@ -1476,18 +1476,19 @@ sysfs: keyboard_lang This feature is used to set keyboard language to ECFW using ASL interface. Fewer thinkpads models like T580 , T590 , T15 Gen 1 etc.. has "=", "(', ")" numeric keys, which are not displaying correctly, when keyboard language -is other than "english". This is because of default keyboard language in ECFW -is set as "english". Hence using this sysfs, user can set correct keyboard -language to ECFW and then these key's will work correctly . +is other than "english". This is because the default keyboard language in ECFW +is set as "english". Hence using this sysfs, user can set the correct keyboard +language to ECFW and then these key's will work correctly. Example of command to set keyboard language is mentioned below:: echo jp > /sys/devices/platform/thinkpad_acpi/keyboard_lang -Text corresponding to keyboard layout to be set in sysfs are : jp (Japan), be(Belgian), -cz(Czech), en(English), da(Danish), de(German), es(Spain) , et(Estonian), -fr(French) , fr-ch (French(Switzerland)), pl(Polish), sl(Slovenian), hu -(Hungarian), nl(Dutch), tr(Turkey), it(Italy), sv(Sweden), pt(portugese) +Text corresponding to keyboard layout to be set in sysfs are: be(Belgian), +cz(Czech), da(Danish), de(German), en(English), es(Spain), et(Estonian), +fr(French), fr-ch(French(Switzerland)), hu(Hungarian), it(Italy), jp (Japan), +nl(Dutch), nn(Norway), pl(Polish), pt(portugese), sl(Slovenian), sv(Sweden), +tr(Turkey) Adaptive keyboard diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 4a1dba3099a6..48575efb5ae8 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -9992,16 +9992,12 @@ struct keyboard_lang_data { int lang_code; }; -/* - * When adding new entries to keyboard_lang_data, please check that - * the select_lang[] buffer in keyboard_lang_show() is still large enough. - */ -struct keyboard_lang_data keyboard_lang_data[] = { - {"en", 0}, +static const struct keyboard_lang_data keyboard_lang_data[] = { {"be", 0x080c}, {"cz", 0x0405}, {"da", 0x0406}, {"de", 0x0c07}, + {"en", 0x0000}, {"es", 0x2c0a}, {"et", 0x0425}, {"fr", 0x040c}, @@ -10064,9 +10060,7 @@ static ssize_t keyboard_lang_show(struct device *dev, struct device_attribute *attr, char *buf) { - int output, err, i; - char select_lang[80] = ""; - char lang[8] = ""; + int output, err, i, len = 0; err = get_keyboard_lang(&output); if (err) @@ -10074,19 +10068,17 @@ static ssize_t keyboard_lang_show(struct device *dev, for (i = 0; i < ARRAY_SIZE(keyboard_lang_data); i++) { if (i) - strcat(select_lang, " "); + len += sysfs_emit_at(buf, len, "%s", " "); if (output == keyboard_lang_data[i].lang_code) { - strcat(lang, "["); - strcat(lang, keyboard_lang_data[i].lang_str); - strcat(lang, "]"); - strcat(select_lang, lang); + len += sysfs_emit_at(buf, len, "[%s]", keyboard_lang_data[i].lang_str); } else { - strcat(select_lang, keyboard_lang_data[i].lang_str); + len += sysfs_emit_at(buf, len, "%s", keyboard_lang_data[i].lang_str); } } + len += sysfs_emit_at(buf, len, "\n"); - return sysfs_emit(buf, "%s\n", select_lang); + return len; } static ssize_t keyboard_lang_store(struct device *dev, @@ -10113,7 +10105,7 @@ static ssize_t keyboard_lang_store(struct device *dev, if (err) return err; } else { - pr_err("Unknown Keyboard language. Ignoring\n"); + dev_err(&tpacpi_pdev->dev, "Unknown Keyboard language. Ignoring\n"); return -EINVAL; } @@ -10124,7 +10116,6 @@ static ssize_t keyboard_lang_store(struct device *dev, return count; } - static DEVICE_ATTR_RW(keyboard_lang); static struct attribute *kbdlang_attributes[] = { @@ -10143,7 +10134,7 @@ static int tpacpi_kbdlang_init(struct ibm_init_struct *iibm) err = get_keyboard_lang(&output); /* * If support isn't available (ENODEV) then don't return an error - * just don't create the sysfs group + * just don't create the sysfs group. */ if (err == -ENODEV) return 0; @@ -10152,9 +10143,7 @@ static int tpacpi_kbdlang_init(struct ibm_init_struct *iibm) return err; /* Platform supports this feature - create the sysfs file */ - err = sysfs_create_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group); - - return err; + return sysfs_create_group(&tpacpi_pdev->dev.kobj, &kbdlang_attr_group); } static void kbdlang_exit(void) -- cgit v1.2.3 From c3bfcd4c676238e198d5a798b50e5d424bf05497 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Mon, 11 Jan 2021 11:22:37 -0500 Subject: platform/x86: thinkpad_acpi: Add platform profile support Add support to thinkpad_acpi for Lenovo platforms that have DYTC version 5 support or newer to use the platform profile feature. This will allow users to determine and control the platform modes between low-power, balanced operation and performance modes. Signed-off-by: Mark Pearson Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210111162237.3469-1-markpearson@lenovo.com Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 294 ++++++++++++++++++++++++++++++++++- 1 file changed, 288 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 48575efb5ae8..18b390153e7f 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -66,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -9855,16 +9856,27 @@ static bool has_lapsensor; static bool palm_state; static bool lap_state; -static int lapsensor_get(bool *present, bool *state) +static int dytc_command(int command, int *output) { acpi_handle dytc_handle; - int output; - *present = false; - if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "DYTC", &dytc_handle))) { + /* Platform doesn't support DYTC */ return -ENODEV; - if (!acpi_evalf(dytc_handle, &output, NULL, "dd", DYTC_CMD_GET)) + } + if (!acpi_evalf(dytc_handle, output, NULL, "dd", command)) return -EIO; + return 0; +} + +static int lapsensor_get(bool *present, bool *state) +{ + int output, err; + + *present = false; + err = dytc_command(DYTC_CMD_GET, &output); + if (err) + return err; *present = true; /*If we get his far, we have lapmode support*/ *state = output & BIT(DYTC_GET_LAPMODE_BIT) ? true : false; @@ -9983,6 +9995,264 @@ static struct ibm_struct proxsensor_driver_data = { .exit = proxsensor_exit, }; +#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE) + +/************************************************************************* + * DYTC Platform Profile interface + */ + +#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ +#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ +#define DYTC_CMD_RESET 0x1ff /* To reset back to default */ + +#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ +#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ + +#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ +#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ + +#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ +#define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ +#define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ + +#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ +#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ + +#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ + +#define DYTC_SET_COMMAND(function, mode, on) \ + (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ + (mode) << DYTC_SET_MODE_BIT | \ + (on) << DYTC_SET_VALID_BIT) + +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) + +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) + +static bool dytc_profile_available; +static enum platform_profile_option dytc_current_profile; +static atomic_t dytc_ignore_event = ATOMIC_INIT(0); +static DEFINE_MUTEX(dytc_mutex); + +static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) +{ + switch (dytcmode) { + case DYTC_MODE_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + return 0; +} + +static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) +{ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + *perfmode = DYTC_MODE_LOWPOWER; + break; + case PLATFORM_PROFILE_BALANCED: + *perfmode = DYTC_MODE_BALANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + *perfmode = DYTC_MODE_PERFORM; + break; + default: /* Unknown profile */ + return -EOPNOTSUPP; + } + return 0; +} + +/* + * dytc_profile_get: Function to register with platform_profile + * handler. Returns current platform profile. + */ +int dytc_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + *profile = dytc_current_profile; + return 0; +} + +/* + * Helper function - check if we are in CQL mode and if we are + * - disable CQL, + * - run the command + * - enable CQL + * If not in CQL mode, just run the command + */ +int dytc_cql_command(int command, int *output) +{ + int err, cmd_err, dummy; + int cur_funcmode; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_command(DYTC_CMD_GET, output); + if (err) + return err; + + cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; + /* Check if we're OK to return immediately */ + if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL)) + return 0; + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + atomic_inc(&dytc_ignore_event); + err = dytc_command(DYTC_DISABLE_CQL, &dummy); + if (err) + return err; + } + + cmd_err = dytc_command(command, output); + /* Check return condition after we've restored CQL state */ + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + err = dytc_command(DYTC_ENABLE_CQL, &dummy); + if (err) + return err; + } + + return cmd_err; +} + +/* + * dytc_profile_set: Function to register with platform_profile + * handler. Sets current platform profile. + */ +int dytc_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + int output; + int err; + + if (!dytc_profile_available) + return -ENODEV; + + err = mutex_lock_interruptible(&dytc_mutex); + if (err) + return err; + + if (profile == PLATFORM_PROFILE_BALANCED) { + /* To get back to balanced mode we just issue a reset command */ + err = dytc_command(DYTC_CMD_RESET, &output); + if (err) + goto unlock; + } else { + int perfmode; + + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); + if (err) + goto unlock; + } + /* Success - update current profile */ + dytc_current_profile = profile; +unlock: + mutex_unlock(&dytc_mutex); + return err; +} + +static void dytc_profile_refresh(void) +{ + enum platform_profile_option profile; + int output, err; + int perfmode; + + mutex_lock(&dytc_mutex); + err = dytc_cql_command(DYTC_CMD_GET, &output); + mutex_unlock(&dytc_mutex); + if (err) + return; + + perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; + convert_dytc_to_profile(perfmode, &profile); + if (profile != dytc_current_profile) { + dytc_current_profile = profile; + platform_profile_notify(); + } +} + +static struct platform_profile_handler dytc_profile = { + .profile_get = dytc_profile_get, + .profile_set = dytc_profile_set, +}; + +static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) +{ + int err, output; + + /* Setup supported modes */ + set_bit(PLATFORM_PROFILE_LOW_POWER, dytc_profile.choices); + set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices); + + dytc_profile_available = false; + err = dytc_command(DYTC_CMD_QUERY, &output); + /* + * If support isn't available (ENODEV) then don't return an error + * and don't create the sysfs group + */ + if (err == -ENODEV) + return 0; + /* For all other errors we can flag the failure */ + if (err) + return err; + + /* Check DYTC is enabled and supports mode setting */ + if (output & BIT(DYTC_QUERY_ENABLE_BIT)) { + /* Only DYTC v5.0 and later has this feature. */ + int dytc_version; + + dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; + if (dytc_version >= 5) { + dbg_printk(TPACPI_DBG_INIT, + "DYTC version %d: thermal mode available\n", dytc_version); + /* Create platform_profile structure and register */ + err = platform_profile_register(&dytc_profile); + /* + * If for some reason platform_profiles aren't enabled + * don't quit terminally. + */ + if (err) + return 0; + + dytc_profile_available = true; + /* Ensure initial values are correct */ + dytc_profile_refresh(); + } + } + return 0; +} + +static void dytc_profile_exit(void) +{ + if (dytc_profile_available) { + dytc_profile_available = false; + platform_profile_remove(); + } +} + +static struct ibm_struct dytc_profile_driver_data = { + .name = "dytc-profile", + .exit = dytc_profile_exit, +}; +#endif /* CONFIG_ACPI_PLATFORM_PROFILE */ + /************************************************************************* * Keyboard language interface */ @@ -10204,8 +10474,14 @@ static void tpacpi_driver_event(const unsigned int hkey_event) mutex_unlock(&kbdlight_mutex); } - if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) + if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) { lapsensor_refresh(); +#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE) + /* If we are already accessing DYTC then skip dytc update */ + if (!atomic_add_unless(&dytc_ignore_event, -1, 0)) + dytc_profile_refresh(); +#endif + } } static void hotkey_driver_event(const unsigned int scancode) @@ -10648,6 +10924,12 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_proxsensor_init, .data = &proxsensor_driver_data, }, +#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE) + { + .init = tpacpi_dytc_profile_init, + .data = &dytc_profile_driver_data, + }, +#endif { .init = tpacpi_kbdlang_init, .data = &kbdlang_driver_data, -- cgit v1.2.3 From eabe533904cbcb6c7df530fd807cf2a3c3567d35 Mon Sep 17 00:00:00 2001 From: Jiaxun Yang Date: Tue, 5 Jan 2021 21:14:43 +0800 Subject: platform/x86: ideapad-laptop: DYTC Platform profile support Add support to ideapad-laptop for Lenovo platforms that have DYTC version 5 support or newer to use the platform profile feature. Mostly based on Mark Pearson 's thinkpad-acpi work but massaged to fit ideapad driver. Note that different from ThinkPads, IdeaPads's Thermal Hotkey won't trigger profile switch itself, we'll leave it for userspace programs. Tested on Lenovo Yoga-14S ARE Chinese Edition. Signed-off-by: Jiaxun Yang Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210105131447.38036-3-jiaxun.yang@flygoat.com [hdegoede@redhat.com s/QUIET/LOW_POWER/] Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/ideapad-laptop.c | 289 ++++++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4a5798a0ce0c..6909e4a0cfff 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -624,6 +624,7 @@ config IDEAPAD_LAPTOP depends on BACKLIGHT_CLASS_DEVICE depends on ACPI_VIDEO || ACPI_VIDEO = n depends on ACPI_WMI || ACPI_WMI = n + depends on ACPI_PLATFORM_PROFILE select INPUT_SPARSEKMAP help This is a driver for Lenovo IdeaPad netbooks contains drivers for diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 5b81bafa5c16..cc42af2a0a98 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,13 @@ enum { VPCCMD_W_BL_POWER = 0x33, }; +struct ideapad_dytc_priv { + enum platform_profile_option current_profile; + struct platform_profile_handler pprof; + struct mutex mutex; + struct ideapad_private *priv; +}; + struct ideapad_rfk_priv { int dev; struct ideapad_private *priv; @@ -89,6 +97,7 @@ struct ideapad_private { struct platform_device *platform_device; struct input_dev *inputdev; struct backlight_device *blightdev; + struct ideapad_dytc_priv *dytc; struct dentry *debug; unsigned long cfg; bool has_hw_rfkill_switch; @@ -137,6 +146,28 @@ static int method_int1(acpi_handle handle, char *method, int cmd) return ACPI_FAILURE(status) ? -1 : 0; } +static int method_dytc(acpi_handle handle, int cmd, int *ret) +{ + acpi_status status; + unsigned long long result; + struct acpi_object_list params; + union acpi_object in_obj; + + params.count = 1; + params.pointer = &in_obj; + in_obj.type = ACPI_TYPE_INTEGER; + in_obj.integer.value = cmd; + + status = acpi_evaluate_integer(handle, "DYTC", ¶ms, &result); + + if (ACPI_FAILURE(status)) { + *ret = -1; + return -1; + } + *ret = result; + return 0; +} + static int method_vpcr(acpi_handle handle, int cmd, int *ret) { acpi_status status; @@ -549,6 +580,257 @@ static const struct attribute_group ideapad_attribute_group = { .attrs = ideapad_attributes }; +/* + * DYTC Platform profile + */ +#define DYTC_CMD_QUERY 0 /* To get DYTC status - enable/revision */ +#define DYTC_CMD_SET 1 /* To enable/disable IC function mode */ +#define DYTC_CMD_GET 2 /* To get current IC function and mode */ +#define DYTC_CMD_RESET 0x1ff /* To reset back to default */ + +#define DYTC_QUERY_ENABLE_BIT 8 /* Bit 8 - 0 = disabled, 1 = enabled */ +#define DYTC_QUERY_SUBREV_BIT 16 /* Bits 16 - 27 - sub revision */ +#define DYTC_QUERY_REV_BIT 28 /* Bits 28 - 31 - revision */ + +#define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ +#define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ + +#define DYTC_SET_FUNCTION_BIT 12 /* Bits 12-15 - function setting */ +#define DYTC_SET_MODE_BIT 16 /* Bits 16-19 - mode setting */ +#define DYTC_SET_VALID_BIT 20 /* Bit 20 - 1 = on, 0 = off */ + +#define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ +#define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ + +#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_LOW_POWER 3 /* Low power mode aka quiet */ +#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ + +#define DYTC_SET_COMMAND(function, mode, on) \ + (DYTC_CMD_SET | (function) << DYTC_SET_FUNCTION_BIT | \ + (mode) << DYTC_SET_MODE_BIT | \ + (on) << DYTC_SET_VALID_BIT) + +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) + +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) + +static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) +{ + switch (dytcmode) { + case DYTC_MODE_LOW_POWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + return 0; +} + +static int convert_profile_to_dytc(enum platform_profile_option profile, int *perfmode) +{ + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + *perfmode = DYTC_MODE_LOW_POWER; + break; + case PLATFORM_PROFILE_BALANCED: + *perfmode = DYTC_MODE_BALANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + *perfmode = DYTC_MODE_PERFORM; + break; + default: /* Unknown profile */ + return -EOPNOTSUPP; + } + return 0; +} + +/* + * dytc_profile_get: Function to register with platform_profile + * handler. Returns current platform profile. + */ +int dytc_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) +{ + struct ideapad_dytc_priv *dytc; + + dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); + *profile = dytc->current_profile; + return 0; +} + +/* + * Helper function - check if we are in CQL mode and if we are + * - disable CQL, + * - run the command + * - enable CQL + * If not in CQL mode, just run the command + */ +int dytc_cql_command(struct ideapad_private *priv, int command, int *output) +{ + int err, cmd_err, dummy; + int cur_funcmode; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = method_dytc(priv->adev->handle, DYTC_CMD_GET, output); + if (err) + return err; + + cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; + /* Check if we're OK to return immediately */ + if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL)) + return 0; + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + err = method_dytc(priv->adev->handle, DYTC_DISABLE_CQL, &dummy); + if (err) + return err; + } + + cmd_err = method_dytc(priv->adev->handle, command, output); + /* Check return condition after we've restored CQL state */ + + if (cur_funcmode == DYTC_FUNCTION_CQL) { + err = method_dytc(priv->adev->handle, DYTC_ENABLE_CQL, &dummy); + if (err) + return err; + } + + return cmd_err; +} + +/* + * dytc_profile_set: Function to register with platform_profile + * handler. Sets current platform profile. + */ +int dytc_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) +{ + struct ideapad_dytc_priv *dytc; + struct ideapad_private *priv; + int output; + int err; + + dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); + priv = dytc->priv; + + err = mutex_lock_interruptible(&dytc->mutex); + if (err) + return err; + + if (profile == PLATFORM_PROFILE_BALANCED) { + /* To get back to balanced mode we just issue a reset command */ + err = method_dytc(priv->adev->handle, DYTC_CMD_RESET, &output); + if (err) + goto unlock; + } else { + int perfmode; + + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(priv, + DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), + &output); + if (err) + goto unlock; + } + /* Success - update current profile */ + dytc->current_profile = profile; +unlock: + mutex_unlock(&dytc->mutex); + return err; +} + +static void dytc_profile_refresh(struct ideapad_private *priv) +{ + enum platform_profile_option profile; + int output, err; + int perfmode; + + mutex_lock(&priv->dytc->mutex); + err = dytc_cql_command(priv, DYTC_CMD_GET, &output); + mutex_unlock(&priv->dytc->mutex); + if (err) + return; + + perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; + convert_dytc_to_profile(perfmode, &profile); + if (profile != priv->dytc->current_profile) { + priv->dytc->current_profile = profile; + platform_profile_notify(); + } +} + +static int ideapad_dytc_profile_init(struct ideapad_private *priv) +{ + int err, output, dytc_version; + + err = method_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); + /* For all other errors we can flag the failure */ + if (err) + return err; + + /* Check DYTC is enabled and supports mode setting */ + if (!(output & BIT(DYTC_QUERY_ENABLE_BIT))) + return -ENODEV; + + dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; + if (dytc_version < 5) + return -ENODEV; + + priv->dytc = kzalloc(sizeof(struct ideapad_dytc_priv), GFP_KERNEL); + if (!priv->dytc) + return -ENOMEM; + + mutex_init(&priv->dytc->mutex); + + priv->dytc->priv = priv; + priv->dytc->pprof.profile_get = dytc_profile_get; + priv->dytc->pprof.profile_set = dytc_profile_set; + + /* Setup supported modes */ + set_bit(PLATFORM_PROFILE_LOW_POWER, priv->dytc->pprof.choices); + set_bit(PLATFORM_PROFILE_BALANCED, priv->dytc->pprof.choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, priv->dytc->pprof.choices); + + /* Create platform_profile structure and register */ + err = platform_profile_register(&priv->dytc->pprof); + if (err) + goto mutex_destroy; + + /* Ensure initial values are correct */ + dytc_profile_refresh(priv); + + return 0; + +mutex_destroy: + mutex_destroy(&priv->dytc->mutex); + kfree(priv->dytc); + priv->dytc = NULL; + return err; +} + +static void ideapad_dytc_profile_exit(struct ideapad_private *priv) +{ + if (!priv->dytc) + return; + + platform_profile_remove(); + mutex_destroy(&priv->dytc->mutex); + kfree(priv->dytc); + priv->dytc = NULL; +} + /* * Rfkill */ @@ -1026,6 +1308,8 @@ static int ideapad_acpi_add(struct platform_device *pdev) ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); + ideapad_dytc_profile_init(priv); + if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { ret = ideapad_backlight_init(priv); if (ret && ret != -ENODEV) @@ -1079,6 +1363,7 @@ static int ideapad_acpi_remove(struct platform_device *pdev) acpi_remove_notify_handler(priv->adev->handle, ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); ideapad_backlight_exit(priv); + ideapad_dytc_profile_exit(priv); for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); ideapad_input_exit(priv); @@ -1100,6 +1385,10 @@ static int ideapad_acpi_resume(struct device *device) ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); + + if (priv->dytc) + dytc_profile_refresh(priv); + return 0; } #endif -- cgit v1.2.3 From 745ed17a04f966406c8c27c8f992544336c06013 Mon Sep 17 00:00:00 2001 From: Pan Bian Date: Wed, 20 Jan 2021 20:50:05 -0800 Subject: platform/x86: amd-pmc: put device on error paths Put the PCI device rdev on error paths to fix potential reference count leaks. Signed-off-by: Pan Bian Link: https://lore.kernel.org/r/20210121045005.73342-1-bianpan2016@163.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index ef8342572463..b9da58ee9b1e 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -210,31 +210,39 @@ static int amd_pmc_probe(struct platform_device *pdev) dev->dev = &pdev->dev; rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); - if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) + if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) { + pci_dev_put(rdev); return -ENODEV; + } dev->cpu_id = rdev->device; err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_LO); if (err) { dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS); + pci_dev_put(rdev); return pcibios_err_to_errno(err); } err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val); - if (err) + if (err) { + pci_dev_put(rdev); return pcibios_err_to_errno(err); + } base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK; err = pci_write_config_dword(rdev, AMD_PMC_SMU_INDEX_ADDRESS, AMD_PMC_BASE_ADDR_HI); if (err) { dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMC_SMU_INDEX_ADDRESS); + pci_dev_put(rdev); return pcibios_err_to_errno(err); } err = pci_read_config_dword(rdev, AMD_PMC_SMU_INDEX_DATA, &val); - if (err) + if (err) { + pci_dev_put(rdev); return pcibios_err_to_errno(err); + } base_addr_hi = val & AMD_PMC_BASE_ADDR_LO_MASK; pci_dev_put(rdev); -- cgit v1.2.3 From cec551ea0d41c679ed11d758e1a386e20285b29d Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Tue, 26 Jan 2021 08:37:38 +0100 Subject: Platform: OLPC: Fix probe error handling Reset ec_priv if probe ends unsuccessfully. Signed-off-by: Lubomir Rintel Link: https://lore.kernel.org/r/20210126073740.10232-2-lkundrak@v3.sk Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/olpc/olpc-ec.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index f64b82824db2..2db7113383fd 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -426,11 +426,8 @@ static int olpc_ec_probe(struct platform_device *pdev) /* get the EC revision */ err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1); - if (err) { - ec_priv = NULL; - kfree(ec); - return err; - } + if (err) + goto error; config.dev = pdev->dev.parent; config.driver_data = ec; @@ -440,12 +437,16 @@ static int olpc_ec_probe(struct platform_device *pdev) if (IS_ERR(ec->dcon_rdev)) { dev_err(&pdev->dev, "failed to register DCON regulator\n"); err = PTR_ERR(ec->dcon_rdev); - kfree(ec); - return err; + goto error; } ec->dbgfs_dir = olpc_ec_setup_debugfs(); + return 0; + +error: + ec_priv = NULL; + kfree(ec); return err; } -- cgit v1.2.3 From 23f8b0a154630ab5e8f6ba09560ef46b8c8b77a4 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Tue, 26 Jan 2021 08:37:39 +0100 Subject: Platform: OLPC: Remove dcon_rdev from olpc_ec_priv It is not needed. Use a local variable instead. Signed-off-by: Lubomir Rintel Link: https://lore.kernel.org/r/20210126073740.10232-3-lkundrak@v3.sk Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/olpc/olpc-ec.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 2db7113383fd..3c852d573e9b 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -37,7 +37,6 @@ struct olpc_ec_priv { struct mutex cmd_lock; /* DCON regulator */ - struct regulator_dev *dcon_rdev; bool dcon_enabled; /* Pending EC commands */ @@ -405,6 +404,7 @@ static int olpc_ec_probe(struct platform_device *pdev) { struct olpc_ec_priv *ec; struct regulator_config config = { }; + struct regulator_dev *regulator; int err; if (!ec_driver) @@ -432,11 +432,10 @@ static int olpc_ec_probe(struct platform_device *pdev) config.dev = pdev->dev.parent; config.driver_data = ec; ec->dcon_enabled = true; - ec->dcon_rdev = devm_regulator_register(&pdev->dev, &dcon_desc, - &config); - if (IS_ERR(ec->dcon_rdev)) { + regulator = devm_regulator_register(&pdev->dev, &dcon_desc, &config); + if (IS_ERR(regulator)) { dev_err(&pdev->dev, "failed to register DCON regulator\n"); - err = PTR_ERR(ec->dcon_rdev); + err = PTR_ERR(regulator); goto error; } -- cgit v1.2.3 From fa707a580e77765b968925e4135f8d8c887eb38b Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Tue, 26 Jan 2021 08:37:40 +0100 Subject: Platform: OLPC: Specify the enable time Determined empirically. Signed-off-by: Lubomir Rintel Link: https://lore.kernel.org/r/20210126073740.10232-4-lkundrak@v3.sk Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/olpc/olpc-ec.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 3c852d573e9b..72dbbea0005c 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -393,11 +393,12 @@ static struct regulator_ops dcon_regulator_ops = { }; static const struct regulator_desc dcon_desc = { - .name = "dcon", - .id = 0, - .ops = &dcon_regulator_ops, - .type = REGULATOR_VOLTAGE, - .owner = THIS_MODULE, + .name = "dcon", + .id = 0, + .ops = &dcon_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .enable_time = 25000, }; static int olpc_ec_probe(struct platform_device *pdev) -- cgit v1.2.3 From 2691d0ae668ab9d9f3f275ac6ed6029862780084 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Tue, 26 Jan 2021 18:22:02 +0100 Subject: platform/surface: aggregator: Fix braces in if condition with unlikely() macro The braces of the unlikely() macro inside the if condition only cover the subtraction part, not the whole statement. This causes the result of the subtraction to be converted to zero or one. While that still works in this context, it causes static analysis tools to complain (and is just plain wrong). Fix the bracket placement and, while at it, simplify the if-condition. Also add a comment to the if-condition explaining what we expect the result to be and what happens on the failure path, as it seems to have caused a bit of confusion. This commit should not cause any difference in behavior or generated code. Reported-by: Dan Carpenter Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210126172202.1428367-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../platform/surface/aggregator/ssh_packet_layer.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 74f0faaa2b27..583315db8b02 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -1694,7 +1694,24 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) /* Find SYN. */ syn_found = sshp_find_syn(source, &aligned); - if (unlikely(aligned.ptr - source->ptr) > 0) { + if (unlikely(aligned.ptr != source->ptr)) { + /* + * We expect aligned.ptr == source->ptr. If this is not the + * case, then aligned.ptr > source->ptr and we've encountered + * some unexpected data where we'd expect the start of a new + * message (i.e. the SYN sequence). + * + * This can happen when a CRC check for the previous message + * failed and we start actively searching for the next one + * (via the call to sshp_find_syn() above), or the first bytes + * of a message got dropped or corrupted. + * + * In any case, we issue a warning, send a NAK to the EC to + * request re-transmission of any data we haven't acknowledged + * yet, and finally, skip everything up to the next SYN + * sequence. + */ + ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); /* -- cgit v1.2.3 From 35d8a973fe4d38afee944db636c3d2b1df3741a7 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Tue, 26 Jan 2021 12:55:06 -0800 Subject: platform/x86: intel_pmt: Make INTEL_PMT_CLASS non-user-selectable Fix error in Kconfig that exposed INTEL_PMT_CLASS as a user selectable option. It is already selected by INTEL_PMT_TELEMETRY and INTEL_PMT_CRASHLOG which are user selectable. Fixes: e2729113ce66 ("platform/x86: Intel PMT class driver") Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20210126205508.30907-1-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a6290c32cfda..64131ef44747 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1355,7 +1355,7 @@ config INTEL_PMC_CORE - MPHY/PLL gating status (Sunrisepoint PCH only) config INTEL_PMT_CLASS - tristate "Intel Platform Monitoring Technology (PMT) Class driver" + tristate help The Intel Platform Monitoring Technology (PMT) class driver provides the basic sysfs interface and file hierarchy uses by PMT devices. -- cgit v1.2.3 From f3f6da5014dea3cc005b36948abe3664b5d1f7d3 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Tue, 26 Jan 2021 12:55:07 -0800 Subject: platform/x86: intel_pmt_telemetry: Add dependency on MFD_INTEL_PMT All devices that expose Intel Platform Monitoring Technology (PMT) telemetry are currently owned by the intel_pmt MFD driver. Therefore make the telemetry driver depend on the MFD driver for build. Fixes: 68fe8e6e2c4b ("platform/x86: Intel PMT Telemetry capability driver") Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20210126205508.30907-2-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 64131ef44747..c23040af770d 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1368,6 +1368,7 @@ config INTEL_PMT_CLASS config INTEL_PMT_TELEMETRY tristate "Intel Platform Monitoring Technology (PMT) Telemetry driver" + depends on MFD_INTEL_PMT select INTEL_PMT_CLASS help The Intel Platform Monitory Technology (PMT) Telemetry driver provides -- cgit v1.2.3 From fdd3feb37e36bec2ad75d76f8ac4d0273c5c0a91 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Tue, 26 Jan 2021 12:55:08 -0800 Subject: platform/x86: intel_pmt_crashlog: Add dependency on MFD_INTEL_PMT All devices that expose Intel Platform Monitoring Technology (PMT) crashlog are currently owned by the intel_pmt MFD driver. Therefore make the crashlog driver depend on the MFD driver for build. Fixes: 5ef9998c96b0 ("platform/x86: Intel PMT Crashlog capability driver") Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20210126205508.30907-3-david.e.box@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index c23040af770d..175ae4ae0028 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1380,6 +1380,7 @@ config INTEL_PMT_TELEMETRY config INTEL_PMT_CRASHLOG tristate "Intel Platform Monitoring Technology (PMT) Crashlog driver" + depends on MFD_INTEL_PMT select INTEL_PMT_CLASS help The Intel Platform Monitoring Technology (PMT) crashlog driver provides -- cgit v1.2.3 From ae5919d349408e1eeca5dbb5dafe3511464b7e74 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 28 Jan 2021 12:36:53 +0100 Subject: platform/x86: touchscreen_dmi: Add info for the Jumper EZpad 7 tablet Add touchscreen info for the Jumper EZpad 7 tablet. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210128113653.5442-1-hdegoede@redhat.com --- drivers/platform/x86/touchscreen_dmi.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index c4de932302d6..c44a6e8dceb8 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -382,6 +382,23 @@ static const struct ts_dmi_data jumper_ezpad_6_m4_data = { .properties = jumper_ezpad_6_m4_props, }; +static const struct property_entry jumper_ezpad_7_props[] = { + PROPERTY_ENTRY_U32("touchscreen-min-x", 4), + PROPERTY_ENTRY_U32("touchscreen-min-y", 10), + PROPERTY_ENTRY_U32("touchscreen-size-x", 2044), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1526), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3680-jumper-ezpad-7.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,stuck-controller-bug"), + { } +}; + +static const struct ts_dmi_data jumper_ezpad_7_data = { + .acpi_name = "MSSL1680:00", + .properties = jumper_ezpad_7_props, +}; + static const struct property_entry jumper_ezpad_mini3_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 23), PROPERTY_ENTRY_U32("touchscreen-min-y", 16), @@ -1034,6 +1051,16 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_BIOS_VERSION, "Jumper8.S106x"), }, }, + { + /* Jumper EZpad 7 */ + .driver_data = (void *)&jumper_ezpad_7_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Jumper"), + DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"), + /* Jumper12x.WJ2012.bsBKRCP05 with the version dropped */ + DMI_MATCH(DMI_BIOS_VERSION, "Jumper12x.WJ2012.bsBKRCP"), + }, + }, { /* Jumper EZpad mini3 */ .driver_data = (void *)&jumper_ezpad_mini3_data, -- cgit v1.2.3 From 60accc011af0ff869875b1ded81cbd0948267f05 Mon Sep 17 00:00:00 2001 From: Artem Bityutskiy Date: Wed, 3 Feb 2021 13:43:20 +0200 Subject: platform/x86/intel-uncore-freq: Add Sapphire Rapids server support Sapphire Rapids uncore frequency control is the same as Skylake and Ice Lake. Add the Sapphire Rapids CPU model number to the match array. Signed-off-by: Artem Bityutskiy Reviewed-by: Tony Luck Link: https://lore.kernel.org/r/20210203114320.1398801-1-dedekind1@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel-uncore-frequency.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/intel-uncore-frequency.c b/drivers/platform/x86/intel-uncore-frequency.c index 12d5ab7e1f5d..3ee4c5c8a64f 100644 --- a/drivers/platform/x86/intel-uncore-frequency.c +++ b/drivers/platform/x86/intel-uncore-frequency.c @@ -377,6 +377,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = { X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), {} }; -- cgit v1.2.3 From a14b3c83ab435e0a06f83a2c519ad27baf805cba Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:54:16 +0000 Subject: platform/x86: ideapad-laptop: remove unnecessary dev_set_drvdata() call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver core already sets the driver specific data on bind failure or removal. Thus the call is unnecessary. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-2-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index cc42af2a0a98..18c71e28ebc8 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1369,7 +1369,6 @@ static int ideapad_acpi_remove(struct platform_device *pdev) ideapad_input_exit(priv); ideapad_debugfs_exit(priv); ideapad_sysfs_exit(priv); - dev_set_drvdata(&pdev->dev, NULL); return 0; } -- cgit v1.2.3 From e1a39a4460c17fa397020cd064744a908e2eac71 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:54:23 +0000 Subject: platform/x86: ideapad-laptop: remove unnecessary NULL checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checks that are removed test pointers which should not be NULL. If they are NULL, that indicates a bug in a different part of the kernel. Instead of silently bailing out, let it fail loudly. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-3-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 78 ++++++++++++++--------------------- 1 file changed, 31 insertions(+), 47 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 18c71e28ebc8..415d362a642a 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -264,9 +264,6 @@ static int debugfs_status_show(struct seq_file *s, void *data) struct ideapad_private *priv = s->private; unsigned long value; - if (!priv) - return -EINVAL; - if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) seq_printf(s, "Backlight max:\t%lu\n", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) @@ -311,39 +308,36 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) { struct ideapad_private *priv = s->private; - if (!priv) { - seq_printf(s, "cfg: N/A\n"); - } else { - seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", - priv->cfg); - if (test_bit(CFG_BT_BIT, &priv->cfg)) - seq_printf(s, "Bluetooth "); - if (test_bit(CFG_3G_BIT, &priv->cfg)) - seq_printf(s, "3G "); - if (test_bit(CFG_WIFI_BIT, &priv->cfg)) - seq_printf(s, "Wireless "); - if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) - seq_printf(s, "Camera "); - seq_printf(s, "\nGraphic: "); - switch ((priv->cfg)&0x700) { - case 0x100: - seq_printf(s, "Intel"); - break; - case 0x200: - seq_printf(s, "ATI"); - break; - case 0x300: - seq_printf(s, "Nvidia"); - break; - case 0x400: - seq_printf(s, "Intel and ATI"); - break; - case 0x500: - seq_printf(s, "Intel and Nvidia"); - break; - } - seq_printf(s, "\n"); + seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", + priv->cfg); + if (test_bit(CFG_BT_BIT, &priv->cfg)) + seq_printf(s, "Bluetooth "); + if (test_bit(CFG_3G_BIT, &priv->cfg)) + seq_printf(s, "3G "); + if (test_bit(CFG_WIFI_BIT, &priv->cfg)) + seq_printf(s, "Wireless "); + if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) + seq_printf(s, "Camera "); + seq_printf(s, "\nGraphic: "); + switch ((priv->cfg)&0x700) { + case 0x100: + seq_printf(s, "Intel"); + break; + case 0x200: + seq_printf(s, "ATI"); + break; + case 0x300: + seq_printf(s, "Nvidia"); + break; + case 0x400: + seq_printf(s, "Intel and ATI"); + break; + case 0x500: + seq_printf(s, "Intel and Nvidia"); + break; } + seq_printf(s, "\n"); + return 0; } DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); @@ -1050,9 +1044,6 @@ static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) struct ideapad_private *priv = bl_get_data(blightdev); unsigned long now; - if (!priv) - return -EINVAL; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) return -EIO; return now; @@ -1062,9 +1053,6 @@ static int ideapad_backlight_update_status(struct backlight_device *blightdev) { struct ideapad_private *priv = bl_get_data(blightdev); - if (!priv) - return -EINVAL; - if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, blightdev->props.brightness)) return -EIO; @@ -1374,13 +1362,9 @@ static int ideapad_acpi_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP -static int ideapad_acpi_resume(struct device *device) +static int ideapad_acpi_resume(struct device *dev) { - struct ideapad_private *priv; - - if (!device) - return -EINVAL; - priv = dev_get_drvdata(device); + struct ideapad_private *priv = dev_get_drvdata(dev); ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); -- cgit v1.2.3 From 803be832ac5698f54afa0c10458f59ce4104aa0f Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:54:30 +0000 Subject: platform/x86: ideapad-laptop: use appropriately typed variable to store the return value of ACPI methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a variable with type `acpi_status` to store the return value of ACPI methods instead of a plain `int`. And use ACPI_{SUCCESS,FAILURE} macros where possible instead of direct comparison. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-4-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 415d362a642a..6c1ed5153a37 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1247,6 +1247,7 @@ static int ideapad_acpi_add(struct platform_device *pdev) int cfg; struct ideapad_private *priv; struct acpi_device *adev; + acpi_status status; ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); if (ret) @@ -1303,22 +1304,27 @@ static int ideapad_acpi_add(struct platform_device *pdev) if (ret && ret != -ENODEV) goto backlight_failed; } - ret = acpi_install_notify_handler(adev->handle, - ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); - if (ret) + status = acpi_install_notify_handler(adev->handle, + ACPI_DEVICE_NOTIFY, + ideapad_acpi_notify, priv); + if (ACPI_FAILURE(status)) { + ret = -EIO; goto notification_failed; + } #if IS_ENABLED(CONFIG_ACPI_WMI) for (i = 0; i < ARRAY_SIZE(ideapad_wmi_fnesc_events); i++) { - ret = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], - ideapad_wmi_notify, priv); - if (ret == AE_OK) { + status = wmi_install_notify_handler(ideapad_wmi_fnesc_events[i], + ideapad_wmi_notify, priv); + if (ACPI_SUCCESS(status)) { priv->fnesc_guid = ideapad_wmi_fnesc_events[i]; break; } } - if (ret != AE_OK && ret != AE_NOT_EXIST) + if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { + ret = -EIO; goto notification_failed_wmi; + } #endif return 0; -- cgit v1.2.3 From 7d38f034e7b2d6eae3b0e29efb3fd968d156a797 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:54:36 +0000 Subject: platform/x86: ideapad-laptop: sort includes lexicographically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Managing includes is easier when they are sorted, so sort them lexicographically. Signed-off-by: Barnabás Pőcze Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-5-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 6c1ed5153a37..e3016c18e88e 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -8,23 +8,24 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include #include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include +#include + #include #define IDEAPAD_RFKILL_DEV_NUM (3) -- cgit v1.2.3 From caa315b8de372890aedfa612b91e649168a31187 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:54:43 +0000 Subject: platform/x86: ideapad-laptop: add missing call to submodule destructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ideapad_dytc_profile_exit() is not called in ideapad_acpi_add() in the error path. Add the missing call. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-6-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index e3016c18e88e..7ee5ac662f80 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1337,6 +1337,7 @@ notification_failed_wmi: notification_failed: ideapad_backlight_exit(priv); backlight_failed: + ideapad_dytc_profile_exit(priv); for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); ideapad_input_exit(priv); -- cgit v1.2.3 From d6b508896afedc0c4197cd5a6c4b9a24e64d05c9 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:54:56 +0000 Subject: platform/x86: ideapad-laptop: use sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sysfs_emit() has been introduced to make it less ambiguous which function is preferred when writing to the output buffer in a device attribute's show() callback. Convert the ideapad-laptop module to utilize this new helper function. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-7-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 7ee5ac662f80..1a91588399f5 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -371,8 +372,8 @@ static ssize_t show_ideapad_cam(struct device *dev, struct ideapad_private *priv = dev_get_drvdata(dev); if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%lu\n", result); + return sysfs_emit(buf, "-1\n"); + return sysfs_emit(buf, "%lu\n", result); } static ssize_t store_ideapad_cam(struct device *dev, @@ -402,8 +403,8 @@ static ssize_t show_ideapad_fan(struct device *dev, struct ideapad_private *priv = dev_get_drvdata(dev); if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%lu\n", result); + return sysfs_emit(buf, "-1\n"); + return sysfs_emit(buf, "%lu\n", result); } static ssize_t store_ideapad_fan(struct device *dev, @@ -435,8 +436,8 @@ static ssize_t touchpad_show(struct device *dev, unsigned long result; if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%lu\n", result); + return sysfs_emit(buf, "-1\n"); + return sysfs_emit(buf, "%lu\n", result); } /* Switch to RO for now: It might be revisited in the future */ @@ -468,8 +469,8 @@ static ssize_t conservation_mode_show(struct device *dev, unsigned long result; if (method_gbmd(priv->adev->handle, &result)) - return sprintf(buf, "-1\n"); - return sprintf(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); + return sysfs_emit(buf, "-1\n"); + return sysfs_emit(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); } static ssize_t conservation_mode_store(struct device *dev, @@ -504,10 +505,10 @@ static ssize_t fn_lock_show(struct device *dev, int fail = read_method_int(priv->adev->handle, "HALS", &hals); if (fail) - return sprintf(buf, "-1\n"); + return sysfs_emit(buf, "-1\n"); result = hals; - return sprintf(buf, "%u\n", test_bit(HA_FNLOCK_BIT, &result)); + return sysfs_emit(buf, "%u\n", test_bit(HA_FNLOCK_BIT, &result)); } static ssize_t fn_lock_store(struct device *dev, -- cgit v1.2.3 From 8782d8d7e8433924b2608ace57c778902c68ecec Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:05 +0000 Subject: platform/x86: ideapad-laptop: use device_{add,remove}_group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use device_{add,remove}_group instead of sysfs_{add,remove}_group. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-8-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 1a91588399f5..2153688012c3 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -924,14 +924,14 @@ static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) */ static int ideapad_sysfs_init(struct ideapad_private *priv) { - return sysfs_create_group(&priv->platform_device->dev.kobj, - &ideapad_attribute_group); + return device_add_group(&priv->platform_device->dev, + &ideapad_attribute_group); } static void ideapad_sysfs_exit(struct ideapad_private *priv) { - sysfs_remove_group(&priv->platform_device->dev.kobj, - &ideapad_attribute_group); + device_remove_group(&priv->platform_device->dev, + &ideapad_attribute_group); } /* -- cgit v1.2.3 From 708086b2365bca758f652bb6bff4e56e5bbf9d45 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:15 +0000 Subject: platform/x86: ideapad-laptop: use kobj_to_dev() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use kobj_to_dev() instead of open-coding the container_of() macro. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-9-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 2153688012c3..30ea97143567 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -547,7 +547,7 @@ static umode_t ideapad_is_visible(struct kobject *kobj, struct attribute *attr, int idx) { - struct device *dev = container_of(kobj, struct device, kobj); + struct device *dev = kobj_to_dev(kobj); struct ideapad_private *priv = dev_get_drvdata(dev); bool supported; -- cgit v1.2.3 From 0c4915b6ad823b2e6ae9d97f6da64f1612254d6e Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:21 +0000 Subject: platform/x86: ideapad-laptop: use for_each_set_bit() helper to simplify event processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current code used the combination of a for loop + test_bit, which can be simplified using for_each_set_bit(), so utilize that. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-10-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 108 +++++++++++++++++----------------- 1 file changed, 53 insertions(+), 55 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 30ea97143567..5978770bac2a 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -1018,22 +1019,20 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); - for (bit = 0; bit < 16; bit++) { - if (test_bit(bit, &value)) { - switch (bit) { - case 0: /* Z580 */ - case 6: /* Z570 */ - /* Thermal Management button */ - ideapad_input_report(priv, 65); - break; - case 1: - /* OneKey Theater button */ - ideapad_input_report(priv, 64); - break; - default: - pr_info("Unknown special button: %lu\n", bit); - break; - } + for_each_set_bit (bit, &value, 16) { + switch (bit) { + case 0: /* Z580 */ + case 6: /* Z570 */ + /* Thermal Management button */ + ideapad_input_report(priv, 65); + break; + case 1: + /* OneKey Theater button */ + ideapad_input_report(priv, 64); + break; + default: + pr_info("Unknown special button: %lu\n", bit); + break; } } } @@ -1161,7 +1160,7 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv) static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) { struct ideapad_private *priv = data; - unsigned long vpc1, vpc2, vpc_bit; + unsigned long vpc1, vpc2, bit; if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) return; @@ -1169,44 +1168,43 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) return; vpc1 = (vpc2 << 8) | vpc1; - for (vpc_bit = 0; vpc_bit < 16; vpc_bit++) { - if (test_bit(vpc_bit, &vpc1)) { - switch (vpc_bit) { - case 9: - ideapad_sync_rfk_state(priv); - break; - case 13: - case 11: - case 8: - case 7: - case 6: - ideapad_input_report(priv, vpc_bit); - break; - case 5: - ideapad_sync_touchpad_state(priv); - break; - case 4: - ideapad_backlight_notify_brightness(priv); - break; - case 3: - ideapad_input_novokey(priv); - break; - case 2: - ideapad_backlight_notify_power(priv); - break; - case 0: - ideapad_check_special_buttons(priv); - break; - case 1: - /* Some IdeaPads report event 1 every ~20 - * seconds while on battery power; some - * report this when changing to/from tablet - * mode. Squelch this event. - */ - break; - default: - pr_info("Unknown event: %lu\n", vpc_bit); - } + + for_each_set_bit (bit, &vpc1, 16) { + switch (bit) { + case 9: + ideapad_sync_rfk_state(priv); + break; + case 13: + case 11: + case 8: + case 7: + case 6: + ideapad_input_report(priv, bit); + break; + case 5: + ideapad_sync_touchpad_state(priv); + break; + case 4: + ideapad_backlight_notify_brightness(priv); + break; + case 3: + ideapad_input_novokey(priv); + break; + case 2: + ideapad_backlight_notify_power(priv); + break; + case 0: + ideapad_check_special_buttons(priv); + break; + case 1: + /* Some IdeaPads report event 1 every ~20 + * seconds while on battery power; some + * report this when changing to/from tablet + * mode. Squelch this event. + */ + break; + default: + pr_info("Unknown event: %lu\n", bit); } } } -- cgit v1.2.3 From 40e0447d6f8052e241a1082bd97f8f3e40ed499d Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:27 +0000 Subject: platform/x86: ideapad-laptop: use msecs_to_jiffies() helper instead of hand-crafted formula MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current code used a hand-crafted formula to convert milliseconds to jiffies, replace it with the msecs_to_jiffies() function. Furthermore, use a while loop instead of for loop for shorter lines and simplicity. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-11-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 5978770bac2a..bb7eb9c1f0ec 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -221,8 +222,9 @@ static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) if (method_vpcw(handle, 1, cmd)) return -1; - for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; - time_before(jiffies, end_jiffies);) { + end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; + + while (time_before(jiffies, end_jiffies)) { schedule(); if (method_vpcr(handle, 1, &val)) return -1; @@ -247,8 +249,9 @@ static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) if (method_vpcw(handle, 1, cmd)) return -1; - for (end_jiffies = jiffies+(HZ)*IDEAPAD_EC_TIMEOUT/1000+1; - time_before(jiffies, end_jiffies);) { + end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; + + while (time_before(jiffies, end_jiffies)) { schedule(); if (method_vpcr(handle, 1, &val)) return -1; -- cgit v1.2.3 From 654324c45d8efb405466124fe954d2661bf33f69 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:32 +0000 Subject: platform/x86: ideapad-laptop: use dev_{err,warn} or appropriate variant to display log messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having the device name in the log message makes it easier to determine in the context of which device the message was printed, so utilize the appropriate variants of dev_{err,warn,...} when printing log messages. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-12-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index bb7eb9c1f0ec..1aa3a05c3360 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -235,7 +235,7 @@ static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) return 0; } } - pr_err("timeout in %s\n", __func__); + acpi_handle_err(handle, "timeout in %s\n", __func__); return -1; } @@ -258,7 +258,7 @@ static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) if (val == 0) return 0; } - pr_err("timeout in %s\n", __func__); + acpi_handle_err(handle, "timeout in %s\n", __func__); return -1; } @@ -974,13 +974,15 @@ static int ideapad_input_init(struct ideapad_private *priv) error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); if (error) { - pr_err("Unable to setup input device keymap\n"); + dev_err(&priv->platform_device->dev, + "Unable to setup input device keymap\n"); goto err_free_dev; } error = input_register_device(inputdev); if (error) { - pr_err("Unable to register input device\n"); + dev_err(&priv->platform_device->dev, + "Unable to register input device\n"); goto err_free_dev; } @@ -1034,7 +1036,8 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) ideapad_input_report(priv, 64); break; default: - pr_info("Unknown special button: %lu\n", bit); + dev_info(&priv->platform_device->dev, + "Unknown special button: %lu\n", bit); break; } } @@ -1094,7 +1097,8 @@ static int ideapad_backlight_init(struct ideapad_private *priv) &ideapad_backlight_ops, &props); if (IS_ERR(blightdev)) { - pr_err("Could not register backlight device\n"); + dev_err(&priv->platform_device->dev, + "Could not register backlight device\n"); return PTR_ERR(blightdev); } @@ -1207,7 +1211,8 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) */ break; default: - pr_info("Unknown event: %lu\n", bit); + dev_info(&priv->platform_device->dev, + "Unknown event: %lu\n", bit); } } } @@ -1215,12 +1220,15 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) #if IS_ENABLED(CONFIG_ACPI_WMI) static void ideapad_wmi_notify(u32 value, void *context) { + struct ideapad_private *priv = context; + switch (value) { case 128: - ideapad_input_report(context, value); + ideapad_input_report(priv, value); break; default: - pr_info("Unknown WMI event %u\n", value); + dev_info(&priv->platform_device->dev, + "Unknown WMI event: %u\n", value); } } #endif -- cgit v1.2.3 From 7be193e368d0933208c47895d37566b4f30e458b Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:43 +0000 Subject: platform/x86: ideapad-laptop: convert ACPI helpers to return -EIO in case of failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACPI helpers returned -1 in case of failure. Convert these functions to return appropriate error codes, and convert their users to propagate these error codes accordingly. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-14-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 108 +++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 46 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 1aa3a05c3360..c7c939f18e38 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -126,7 +126,7 @@ static int read_method_int(acpi_handle handle, const char *method, int *val) status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); if (ACPI_FAILURE(status)) { *val = -1; - return -1; + return -EIO; } *val = result; return 0; @@ -147,7 +147,7 @@ static int method_int1(acpi_handle handle, char *method, int cmd) acpi_status status; status = acpi_execute_simple_method(handle, method, cmd); - return ACPI_FAILURE(status) ? -1 : 0; + return ACPI_FAILURE(status) ? -EIO : 0; } static int method_dytc(acpi_handle handle, int cmd, int *ret) @@ -166,7 +166,7 @@ static int method_dytc(acpi_handle handle, int cmd, int *ret) if (ACPI_FAILURE(status)) { *ret = -1; - return -1; + return -EIO; } *ret = result; return 0; @@ -188,7 +188,7 @@ static int method_vpcr(acpi_handle handle, int cmd, int *ret) if (ACPI_FAILURE(status)) { *ret = -1; - return -1; + return -EIO; } *ret = result; return 0; @@ -210,56 +210,62 @@ static int method_vpcw(acpi_handle handle, int cmd, int data) status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); if (status != AE_OK) - return -1; + return -EIO; return 0; } static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) { - int val; unsigned long int end_jiffies; + int val, err; - if (method_vpcw(handle, 1, cmd)) - return -1; + err = method_vpcw(handle, 1, cmd); + if (err) + return err; end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; while (time_before(jiffies, end_jiffies)) { schedule(); - if (method_vpcr(handle, 1, &val)) - return -1; + err = method_vpcr(handle, 1, &val); + if (err) + return err; if (val == 0) { - if (method_vpcr(handle, 0, &val)) - return -1; + err = method_vpcr(handle, 0, &val); + if (err) + return err; *data = val; return 0; } } acpi_handle_err(handle, "timeout in %s\n", __func__); - return -1; + return -ETIMEDOUT; } static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) { - int val; unsigned long int end_jiffies; + int val, err; - if (method_vpcw(handle, 0, data)) - return -1; - if (method_vpcw(handle, 1, cmd)) - return -1; + err = method_vpcw(handle, 0, data); + if (err) + return err; + err = method_vpcw(handle, 1, cmd); + if (err) + return err; end_jiffies = jiffies + msecs_to_jiffies(IDEAPAD_EC_TIMEOUT) + 1; while (time_before(jiffies, end_jiffies)) { schedule(); - if (method_vpcr(handle, 1, &val)) - return -1; + err = method_vpcr(handle, 1, &val); + if (err) + return err; if (val == 0) return 0; } acpi_handle_err(handle, "timeout in %s\n", __func__); - return -1; + return -ETIMEDOUT; } /* @@ -392,8 +398,8 @@ static ssize_t store_ideapad_cam(struct device *dev, if (sscanf(buf, "%i", &state) != 1) return -EINVAL; ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); - if (ret < 0) - return -EIO; + if (ret) + return ret; return count; } @@ -425,8 +431,8 @@ static ssize_t store_ideapad_fan(struct device *dev, if (state < 0 || state > 4 || state == 3) return -EINVAL; ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); - if (ret < 0) - return -EIO; + if (ret) + return ret; return count; } @@ -458,8 +464,8 @@ static ssize_t __maybe_unused touchpad_store(struct device *dev, return ret; ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); - if (ret < 0) - return -EIO; + if (ret) + return ret; return count; } @@ -492,8 +498,8 @@ static ssize_t conservation_mode_store(struct device *dev, ret = method_int1(priv->adev->handle, "SBMC", state ? BMCMD_CONSERVATION_ON : BMCMD_CONSERVATION_OFF); - if (ret < 0) - return -EIO; + if (ret) + return ret; return count; } @@ -530,8 +536,8 @@ static ssize_t fn_lock_store(struct device *dev, ret = method_int1(priv->adev->handle, "SALS", state ? HACMD_FNLOCK_ON : HACMD_FNLOCK_OFF); - if (ret < 0) - return -EIO; + if (ret) + return ret; return count; } @@ -1022,7 +1028,8 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) { unsigned long bit, value; - read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value); + if (read_ec_data(priv->adev->handle, VPCCMD_R_SPECIAL_BUTTONS, &value)) + return; for_each_set_bit (bit, &value, 16) { switch (bit) { @@ -1050,22 +1057,27 @@ static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) { struct ideapad_private *priv = bl_get_data(blightdev); unsigned long now; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) - return -EIO; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); + if (err) + return err; return now; } static int ideapad_backlight_update_status(struct backlight_device *blightdev) { struct ideapad_private *priv = bl_get_data(blightdev); + int err; - if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, - blightdev->props.brightness)) - return -EIO; - if (write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, - blightdev->props.power == FB_BLANK_POWERDOWN ? 0 : 1)) - return -EIO; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL, + blightdev->props.brightness); + if (err) + return err; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, + blightdev->props.power != FB_BLANK_POWERDOWN); + if (err) + return err; return 0; } @@ -1080,13 +1092,17 @@ static int ideapad_backlight_init(struct ideapad_private *priv) struct backlight_device *blightdev; struct backlight_properties props; unsigned long max, now, power; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max)) - return -EIO; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now)) - return -EIO; - if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) - return -EIO; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); + if (err) + return err; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); + if (err) + return err; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); + if (err) + return err; memset(&props, 0, sizeof(struct backlight_properties)); props.max_brightness = max; -- cgit v1.2.3 From c81f241081b8dd6796d9f29fb4f264aa997311cb Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:48 +0000 Subject: platform/x86: ideapad-laptop: always propagate error codes from device attributes' show() callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumers can differentiate an error from a successful read much more easily if the read() call fails with an appropriate errno instead of returning a magic string like "-1". This introduces an ABI change, but not many users are expected to be relying on the previous behavior, and this change makes this module conforming to the standard behavior that sysfs attribute show/store callbacks return an appropriate errno in case of failure. Thus the ABI breakage is deemed justified. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-15-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index c7c939f18e38..d9e2d0cbe1a3 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -380,9 +380,11 @@ static ssize_t show_ideapad_cam(struct device *dev, { unsigned long result; struct ideapad_private *priv = dev_get_drvdata(dev); + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result)) - return sysfs_emit(buf, "-1\n"); + err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); + if (err) + return err; return sysfs_emit(buf, "%lu\n", result); } @@ -411,9 +413,11 @@ static ssize_t show_ideapad_fan(struct device *dev, { unsigned long result; struct ideapad_private *priv = dev_get_drvdata(dev); + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result)) - return sysfs_emit(buf, "-1\n"); + err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); + if (err) + return err; return sysfs_emit(buf, "%lu\n", result); } @@ -444,9 +448,11 @@ static ssize_t touchpad_show(struct device *dev, { struct ideapad_private *priv = dev_get_drvdata(dev); unsigned long result; + int err; - if (read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result)) - return sysfs_emit(buf, "-1\n"); + err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); + if (err) + return err; return sysfs_emit(buf, "%lu\n", result); } @@ -477,9 +483,11 @@ static ssize_t conservation_mode_show(struct device *dev, { struct ideapad_private *priv = dev_get_drvdata(dev); unsigned long result; + int err; - if (method_gbmd(priv->adev->handle, &result)) - return sysfs_emit(buf, "-1\n"); + err = method_gbmd(priv->adev->handle, &result); + if (err) + return err; return sysfs_emit(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); } @@ -515,7 +523,7 @@ static ssize_t fn_lock_show(struct device *dev, int fail = read_method_int(priv->adev->handle, "HALS", &hals); if (fail) - return sysfs_emit(buf, "-1\n"); + return fail; result = hals; return sysfs_emit(buf, "%u\n", test_bit(HA_FNLOCK_BIT, &result)); -- cgit v1.2.3 From 00641c086d2d929a770afcd8d637655625664eae Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:53 +0000 Subject: platform/x86: ideapad-laptop: misc. device attribute changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not handle zero length buffer separately. Use kstrtouint() instead of sscanf(). Use kstrtobool() in store_ideapad_cam(). These introduce minor ABI changes, but it is expected that no users rely on the previous behavior. Thus the change is deemed justifed. Additionally, use `!!` to convert to `int` and use the "%d" format specifier in sysfs_emit() for boolean-like attributes. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-16-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index d9e2d0cbe1a3..65cf1be53e4e 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -385,20 +385,20 @@ static ssize_t show_ideapad_cam(struct device *dev, err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); if (err) return err; - return sysfs_emit(buf, "%lu\n", result); + return sysfs_emit(buf, "%d\n", !!result); } static ssize_t store_ideapad_cam(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - int ret, state; struct ideapad_private *priv = dev_get_drvdata(dev); + bool state; + int ret; - if (!count) - return 0; - if (sscanf(buf, "%i", &state) != 1) - return -EINVAL; + ret = kstrtobool(buf, &state); + if (ret) + return ret; ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); if (ret) return ret; @@ -425,14 +425,14 @@ static ssize_t store_ideapad_fan(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - int ret, state; struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned int state; + int ret; - if (!count) - return 0; - if (sscanf(buf, "%i", &state) != 1) - return -EINVAL; - if (state < 0 || state > 4 || state == 3) + ret = kstrtouint(buf, 0, &state); + if (ret) + return ret; + if (state > 4 || state == 3) return -EINVAL; ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); if (ret) @@ -453,7 +453,7 @@ static ssize_t touchpad_show(struct device *dev, err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); if (err) return err; - return sysfs_emit(buf, "%lu\n", result); + return sysfs_emit(buf, "%d\n", !!result); } /* Switch to RO for now: It might be revisited in the future */ @@ -488,7 +488,7 @@ static ssize_t conservation_mode_show(struct device *dev, err = method_gbmd(priv->adev->handle, &result); if (err) return err; - return sysfs_emit(buf, "%u\n", test_bit(BM_CONSERVATION_BIT, &result)); + return sysfs_emit(buf, "%d\n", !!test_bit(BM_CONSERVATION_BIT, &result)); } static ssize_t conservation_mode_store(struct device *dev, @@ -526,7 +526,7 @@ static ssize_t fn_lock_show(struct device *dev, return fail; result = hals; - return sysfs_emit(buf, "%u\n", test_bit(HA_FNLOCK_BIT, &result)); + return sysfs_emit(buf, "%d\n", !!test_bit(HA_FNLOCK_BIT, &result)); } static ssize_t fn_lock_store(struct device *dev, -- cgit v1.2.3 From 0b765671cb80abc74c8d125f80c830dfc2f7d22e Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:55:59 +0000 Subject: platform/x86: ideapad-laptop: group and separate (un)related constants into enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group and rename constants depending on which ACPI interface they pertain to, and rename CFG_X constants to CFG_CAP_X. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-17-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 64 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 27 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 65cf1be53e4e..c9ee06b060f7 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -33,14 +33,6 @@ #define IDEAPAD_RFKILL_DEV_NUM (3) -#define BM_CONSERVATION_BIT (5) -#define HA_FNLOCK_BIT (10) - -#define CFG_BT_BIT (16) -#define CFG_3G_BIT (17) -#define CFG_WIFI_BIT (18) -#define CFG_CAMERA_BIT (19) - #if IS_ENABLED(CONFIG_ACPI_WMI) static const char *const ideapad_wmi_fnesc_events[] = { "26CAB2E5-5CF1-46AE-AAC3-4A12B6BA50E6", /* Yoga 3 */ @@ -49,10 +41,28 @@ static const char *const ideapad_wmi_fnesc_events[] = { #endif enum { - BMCMD_CONSERVATION_ON = 3, - BMCMD_CONSERVATION_OFF = 5, - HACMD_FNLOCK_ON = 0xe, - HACMD_FNLOCK_OFF = 0xf, + CFG_CAP_BT_BIT = 16, + CFG_CAP_3G_BIT = 17, + CFG_CAP_WIFI_BIT = 18, + CFG_CAP_CAM_BIT = 19, +}; + +enum { + GBMD_CONSERVATION_STATE_BIT = 5, +}; + +enum { + SMBC_CONSERVATION_ON = 3, + SMBC_CONSERVATION_OFF = 5, +}; + +enum { + HALS_FNLOCK_STATE_BIT = 10, +}; + +enum { + SALS_FNLOCK_ON = 0xe, + SALS_FNLOCK_OFF = 0xf, }; enum { @@ -308,7 +318,7 @@ static int debugfs_status_show(struct seq_file *s, void *data) if (!method_gbmd(priv->adev->handle, &value)) { seq_printf(s, "Conservation mode:\t%s(%lu)\n", - test_bit(BM_CONSERVATION_BIT, &value) ? "On" : "Off", + test_bit(GBMD_CONSERVATION_STATE_BIT, &value) ? "On" : "Off", value); } @@ -322,13 +332,13 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", priv->cfg); - if (test_bit(CFG_BT_BIT, &priv->cfg)) + if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) seq_printf(s, "Bluetooth "); - if (test_bit(CFG_3G_BIT, &priv->cfg)) + if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) seq_printf(s, "3G "); - if (test_bit(CFG_WIFI_BIT, &priv->cfg)) + if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) seq_printf(s, "Wireless "); - if (test_bit(CFG_CAMERA_BIT, &priv->cfg)) + if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) seq_printf(s, "Camera "); seq_printf(s, "\nGraphic: "); switch ((priv->cfg)&0x700) { @@ -488,7 +498,7 @@ static ssize_t conservation_mode_show(struct device *dev, err = method_gbmd(priv->adev->handle, &result); if (err) return err; - return sysfs_emit(buf, "%d\n", !!test_bit(BM_CONSERVATION_BIT, &result)); + return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); } static ssize_t conservation_mode_store(struct device *dev, @@ -504,8 +514,8 @@ static ssize_t conservation_mode_store(struct device *dev, return ret; ret = method_int1(priv->adev->handle, "SBMC", state ? - BMCMD_CONSERVATION_ON : - BMCMD_CONSERVATION_OFF); + SMBC_CONSERVATION_ON : + SMBC_CONSERVATION_OFF); if (ret) return ret; return count; @@ -526,7 +536,7 @@ static ssize_t fn_lock_show(struct device *dev, return fail; result = hals; - return sysfs_emit(buf, "%d\n", !!test_bit(HA_FNLOCK_BIT, &result)); + return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &result)); } static ssize_t fn_lock_store(struct device *dev, @@ -542,8 +552,8 @@ static ssize_t fn_lock_store(struct device *dev, return ret; ret = method_int1(priv->adev->handle, "SALS", state ? - HACMD_FNLOCK_ON : - HACMD_FNLOCK_OFF); + SALS_FNLOCK_ON : + SALS_FNLOCK_OFF); if (ret) return ret; return count; @@ -570,7 +580,7 @@ static umode_t ideapad_is_visible(struct kobject *kobj, bool supported; if (attr == &dev_attr_camera_power.attr) - supported = test_bit(CFG_CAMERA_BIT, &(priv->cfg)); + supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); else if (attr == &dev_attr_fan_mode.attr) { unsigned long value; supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, @@ -856,9 +866,9 @@ struct ideapad_rfk_data { }; static const struct ideapad_rfk_data ideapad_rfk_data[] = { - { "ideapad_wlan", CFG_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, - { "ideapad_bluetooth", CFG_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, - { "ideapad_3g", CFG_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, + { "ideapad_wlan", CFG_CAP_WIFI_BIT, VPCCMD_W_WIFI, RFKILL_TYPE_WLAN }, + { "ideapad_bluetooth", CFG_CAP_BT_BIT, VPCCMD_W_BT, RFKILL_TYPE_BLUETOOTH }, + { "ideapad_3g", CFG_CAP_3G_BIT, VPCCMD_W_3G, RFKILL_TYPE_WWAN }, }; static int ideapad_rfk_set(void *data, bool blocked) -- cgit v1.2.3 From ff36b0d953dc4cbc40a72945920ff8e805f1b0da Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:05 +0000 Subject: platform/x86: ideapad-laptop: rework and create new ACPI helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create dedicated helper functions for accessing the main ACPI methods: GBMD, SMBC, HALS, SALS; and utilize them. Use `unsigned long` consistently in every ACPI helper wherever possible. Change names to better express purpose. Do not assign values to output parameters in case of failure. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-18-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 172 +++++++++++++++------------------- 1 file changed, 77 insertions(+), 95 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index c9ee06b060f7..c95aad42f1e3 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -128,84 +128,78 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); */ #define IDEAPAD_EC_TIMEOUT (200) /* in ms */ -static int read_method_int(acpi_handle handle, const char *method, int *val) +static int eval_int(acpi_handle handle, const char *name, unsigned long *res) { - acpi_status status; unsigned long long result; + acpi_status status; - status = acpi_evaluate_integer(handle, (char *)method, NULL, &result); - if (ACPI_FAILURE(status)) { - *val = -1; + status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); + if (ACPI_FAILURE(status)) return -EIO; - } - *val = result; + *res = result; return 0; - } -static int method_gbmd(acpi_handle handle, unsigned long *ret) +static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg) { - int result, val; + acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg); - result = read_method_int(handle, "GBMD", &val); - *ret = val; - return result; + return ACPI_FAILURE(status) ? -EIO : 0; } -static int method_int1(acpi_handle handle, char *method, int cmd) +static int eval_gbmd(acpi_handle handle, unsigned long *res) { - acpi_status status; - - status = acpi_execute_simple_method(handle, method, cmd); - return ACPI_FAILURE(status) ? -EIO : 0; + return eval_int(handle, "GBMD", res); } -static int method_dytc(acpi_handle handle, int cmd, int *ret) +static int exec_smbc(acpi_handle handle, unsigned long arg) { - acpi_status status; - unsigned long long result; - struct acpi_object_list params; - union acpi_object in_obj; - - params.count = 1; - params.pointer = &in_obj; - in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = cmd; + return exec_simple_method(handle, "SMBC", arg); +} - status = acpi_evaluate_integer(handle, "DYTC", ¶ms, &result); +static int eval_hals(acpi_handle handle, unsigned long *res) +{ + return eval_int(handle, "HALS", res); +} - if (ACPI_FAILURE(status)) { - *ret = -1; - return -EIO; - } - *ret = result; - return 0; +static int exec_sals(acpi_handle handle, unsigned long arg) +{ + return exec_simple_method(handle, "SALS", arg); } -static int method_vpcr(acpi_handle handle, int cmd, int *ret) +static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long arg, unsigned long *res) { - acpi_status status; - unsigned long long result; struct acpi_object_list params; + unsigned long long result; union acpi_object in_obj; + acpi_status status; params.count = 1; params.pointer = &in_obj; in_obj.type = ACPI_TYPE_INTEGER; - in_obj.integer.value = cmd; + in_obj.integer.value = arg; - status = acpi_evaluate_integer(handle, "VPCR", ¶ms, &result); - - if (ACPI_FAILURE(status)) { - *ret = -1; + status = acpi_evaluate_integer(handle, (char *)name, ¶ms, &result); + if (ACPI_FAILURE(status)) return -EIO; - } - *ret = result; + + if (res) + *res = result; + return 0; +} +static int eval_dytc(acpi_handle handle, unsigned long cmd, unsigned long *res) +{ + return eval_int_with_arg(handle, "DYTC", cmd, res); +} + +static int eval_vpcr(acpi_handle handle, unsigned long cmd, unsigned long *res) +{ + return eval_int_with_arg(handle, "VPCR", cmd, res); } -static int method_vpcw(acpi_handle handle, int cmd, int data) +static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) { struct acpi_object_list params; union acpi_object in_obj[2]; @@ -219,17 +213,17 @@ static int method_vpcw(acpi_handle handle, int cmd, int data) in_obj[1].integer.value = data; status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); - if (status != AE_OK) + if (ACPI_FAILURE(status)) return -EIO; return 0; } -static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) +static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *data) { - unsigned long int end_jiffies; - int val, err; + unsigned long end_jiffies, val; + int err; - err = method_vpcw(handle, 1, cmd); + err = eval_vpcw(handle, 1, cmd); if (err) return err; @@ -237,30 +231,25 @@ static int read_ec_data(acpi_handle handle, int cmd, unsigned long *data) while (time_before(jiffies, end_jiffies)) { schedule(); - err = method_vpcr(handle, 1, &val); + err = eval_vpcr(handle, 1, &val); if (err) return err; - if (val == 0) { - err = method_vpcr(handle, 0, &val); - if (err) - return err; - *data = val; - return 0; - } + if (val == 0) + return eval_vpcr(handle, 0, data); } acpi_handle_err(handle, "timeout in %s\n", __func__); return -ETIMEDOUT; } -static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) +static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long data) { - unsigned long int end_jiffies; - int val, err; + unsigned long end_jiffies, val; + int err; - err = method_vpcw(handle, 0, data); + err = eval_vpcw(handle, 0, data); if (err) return err; - err = method_vpcw(handle, 1, cmd); + err = eval_vpcw(handle, 1, cmd); if (err) return err; @@ -268,7 +257,7 @@ static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) while (time_before(jiffies, end_jiffies)) { schedule(); - err = method_vpcr(handle, 1, &val); + err = eval_vpcr(handle, 1, &val); if (err) return err; if (val == 0) @@ -316,7 +305,7 @@ static int debugfs_status_show(struct seq_file *s, void *data) value ? "On" : "Off", value); seq_puts(s, "=====================\n"); - if (!method_gbmd(priv->adev->handle, &value)) { + if (!eval_gbmd(priv->adev->handle, &value)) { seq_printf(s, "Conservation mode:\t%s(%lu)\n", test_bit(GBMD_CONSERVATION_STATE_BIT, &value) ? "On" : "Off", value); @@ -495,7 +484,7 @@ static ssize_t conservation_mode_show(struct device *dev, unsigned long result; int err; - err = method_gbmd(priv->adev->handle, &result); + err = eval_gbmd(priv->adev->handle, &result); if (err) return err; return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); @@ -513,9 +502,7 @@ static ssize_t conservation_mode_store(struct device *dev, if (ret) return ret; - ret = method_int1(priv->adev->handle, "SBMC", state ? - SMBC_CONSERVATION_ON : - SMBC_CONSERVATION_OFF); + ret = exec_smbc(priv->adev->handle, state ? SMBC_CONSERVATION_ON : SMBC_CONSERVATION_OFF); if (ret) return ret; return count; @@ -528,15 +515,13 @@ static ssize_t fn_lock_show(struct device *dev, char *buf) { struct ideapad_private *priv = dev_get_drvdata(dev); - unsigned long result; - int hals; - int fail = read_method_int(priv->adev->handle, "HALS", &hals); + unsigned long hals; + int fail = eval_hals(priv->adev->handle, &hals); if (fail) return fail; - result = hals; - return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &result)); + return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); } static ssize_t fn_lock_store(struct device *dev, @@ -551,9 +536,7 @@ static ssize_t fn_lock_store(struct device *dev, if (ret) return ret; - ret = method_int1(priv->adev->handle, "SALS", state ? - SALS_FNLOCK_ON : - SALS_FNLOCK_OFF); + ret = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); if (ret) return ret; return count; @@ -697,32 +680,31 @@ int dytc_profile_get(struct platform_profile_handler *pprof, * - enable CQL * If not in CQL mode, just run the command */ -int dytc_cql_command(struct ideapad_private *priv, int command, int *output) +int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, unsigned long *output) { - int err, cmd_err, dummy; - int cur_funcmode; + int err, cmd_err, cur_funcmode; /* Determine if we are in CQL mode. This alters the commands we do */ - err = method_dytc(priv->adev->handle, DYTC_CMD_GET, output); + err = eval_dytc(priv->adev->handle, DYTC_CMD_GET, output); if (err) return err; cur_funcmode = (*output >> DYTC_GET_FUNCTION_BIT) & 0xF; /* Check if we're OK to return immediately */ - if ((command == DYTC_CMD_GET) && (cur_funcmode != DYTC_FUNCTION_CQL)) + if (cmd == DYTC_CMD_GET && cur_funcmode != DYTC_FUNCTION_CQL) return 0; if (cur_funcmode == DYTC_FUNCTION_CQL) { - err = method_dytc(priv->adev->handle, DYTC_DISABLE_CQL, &dummy); + err = eval_dytc(priv->adev->handle, DYTC_DISABLE_CQL, NULL); if (err) return err; } - cmd_err = method_dytc(priv->adev->handle, command, output); + cmd_err = eval_dytc(priv->adev->handle, cmd, output); /* Check return condition after we've restored CQL state */ if (cur_funcmode == DYTC_FUNCTION_CQL) { - err = method_dytc(priv->adev->handle, DYTC_ENABLE_CQL, &dummy); + err = eval_dytc(priv->adev->handle, DYTC_ENABLE_CQL, NULL); if (err) return err; } @@ -739,7 +721,6 @@ int dytc_profile_set(struct platform_profile_handler *pprof, { struct ideapad_dytc_priv *dytc; struct ideapad_private *priv; - int output; int err; dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); @@ -751,7 +732,7 @@ int dytc_profile_set(struct platform_profile_handler *pprof, if (profile == PLATFORM_PROFILE_BALANCED) { /* To get back to balanced mode we just issue a reset command */ - err = method_dytc(priv->adev->handle, DYTC_CMD_RESET, &output); + err = eval_dytc(priv->adev->handle, DYTC_CMD_RESET, NULL); if (err) goto unlock; } else { @@ -764,7 +745,7 @@ int dytc_profile_set(struct platform_profile_handler *pprof, /* Determine if we are in CQL mode. This alters the commands we do */ err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), - &output); + NULL); if (err) goto unlock; } @@ -778,8 +759,8 @@ unlock: static void dytc_profile_refresh(struct ideapad_private *priv) { enum platform_profile_option profile; - int output, err; - int perfmode; + unsigned long output; + int err, perfmode; mutex_lock(&priv->dytc->mutex); err = dytc_cql_command(priv, DYTC_CMD_GET, &output); @@ -797,9 +778,10 @@ static void dytc_profile_refresh(struct ideapad_private *priv) static int ideapad_dytc_profile_init(struct ideapad_private *priv) { - int err, output, dytc_version; + int err, dytc_version; + unsigned long output; - err = method_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); + err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); /* For all other errors we can flag the failure */ if (err) return err; @@ -1289,16 +1271,16 @@ static const struct dmi_system_id hw_rfkill_list[] = { static int ideapad_acpi_add(struct platform_device *pdev) { int ret, i; - int cfg; struct ideapad_private *priv; struct acpi_device *adev; acpi_status status; + unsigned long cfg; ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); if (ret) return -ENODEV; - if (read_method_int(adev->handle, "_CFG", &cfg)) + if (eval_int(adev->handle, "_CFG", &cfg)) return -ENODEV; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); -- cgit v1.2.3 From 1c59de4ad24b6024b5d5b78d25486848f2d96c5d Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:10 +0000 Subject: platform/x86: ideapad-laptop: rework is_visible() logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store the supported features in the driver private data, and modify the is_visible() callback to use it, and create ideapad_check_features() to populate it. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-19-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 80 ++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index c95aad42f1e3..509f2528e1af 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -114,9 +114,15 @@ struct ideapad_private { struct ideapad_dytc_priv *dytc; struct dentry *debug; unsigned long cfg; - bool has_hw_rfkill_switch; - bool has_touchpad_switch; const char *fnesc_guid; + struct { + bool conservation_mode : 1; + bool dytc : 1; + bool fan_mode : 1; + bool fn_lock : 1; + bool hw_rfkill_switch : 1; + bool touchpad_ctrl_via_ec : 1; + } features; }; static bool no_bt_rfkill; @@ -560,24 +566,18 @@ static umode_t ideapad_is_visible(struct kobject *kobj, { struct device *dev = kobj_to_dev(kobj); struct ideapad_private *priv = dev_get_drvdata(dev); - bool supported; + bool supported = true; if (attr == &dev_attr_camera_power.attr) supported = test_bit(CFG_CAP_CAM_BIT, &priv->cfg); - else if (attr == &dev_attr_fan_mode.attr) { - unsigned long value; - supported = !read_ec_data(priv->adev->handle, VPCCMD_R_FAN, - &value); - } else if (attr == &dev_attr_conservation_mode.attr) { - supported = acpi_has_method(priv->adev->handle, "GBMD") && - acpi_has_method(priv->adev->handle, "SBMC"); - } else if (attr == &dev_attr_fn_lock.attr) { - supported = acpi_has_method(priv->adev->handle, "HALS") && - acpi_has_method(priv->adev->handle, "SALS"); - } else if (attr == &dev_attr_touchpad.attr) - supported = priv->has_touchpad_switch; - else - supported = true; + else if (attr == &dev_attr_conservation_mode.attr) + supported = priv->features.conservation_mode; + else if (attr == &dev_attr_fan_mode.attr) + supported = priv->features.fan_mode; + else if (attr == &dev_attr_fn_lock.attr) + supported = priv->features.fn_lock; + else if (attr == &dev_attr_touchpad.attr) + supported = priv->features.touchpad_ctrl_via_ec; return supported ? attr->mode : 0; } @@ -781,6 +781,9 @@ static int ideapad_dytc_profile_init(struct ideapad_private *priv) int err, dytc_version; unsigned long output; + if (!priv->features.dytc) + return -ENODEV; + err = eval_dytc(priv->adev->handle, DYTC_CMD_QUERY, &output); /* For all other errors we can flag the failure */ if (err) @@ -870,7 +873,7 @@ static void ideapad_sync_rfk_state(struct ideapad_private *priv) unsigned long hw_blocked = 0; int i; - if (priv->has_hw_rfkill_switch) { + if (priv->features.hw_rfkill_switch) { if (read_ec_data(priv->adev->handle, VPCCMD_R_RF, &hw_blocked)) return; hw_blocked = !hw_blocked; @@ -1164,7 +1167,7 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv) { unsigned long value; - if (!priv->has_touchpad_switch) + if (!priv->features.touchpad_ctrl_via_ec) return; /* Without reading from EC touchpad LED doesn't switch state */ @@ -1268,6 +1271,29 @@ static const struct dmi_system_id hw_rfkill_list[] = { {} }; +static void ideapad_check_features(struct ideapad_private *priv) +{ + acpi_handle handle = priv->adev->handle; + unsigned long val; + + priv->features.hw_rfkill_switch = dmi_check_system(hw_rfkill_list); + + /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ + priv->features.touchpad_ctrl_via_ec = !acpi_dev_present("ELAN0634", NULL, -1); + + if (!read_ec_data(handle, VPCCMD_R_FAN, &val)) + priv->features.fan_mode = true; + + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) + priv->features.conservation_mode = true; + + if (acpi_has_method(handle, "DYTC")) + priv->features.dytc = true; + + if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) + priv->features.fn_lock = true; +} + static int ideapad_acpi_add(struct platform_device *pdev) { int ret, i; @@ -1291,10 +1317,8 @@ static int ideapad_acpi_add(struct platform_device *pdev) priv->cfg = cfg; priv->adev = adev; priv->platform_device = pdev; - priv->has_hw_rfkill_switch = dmi_check_system(hw_rfkill_list); - /* Most ideapads with ELAN0634 touchpad don't use EC touchpad switch */ - priv->has_touchpad_switch = !acpi_dev_present("ELAN0634", NULL, -1); + ideapad_check_features(priv); ret = ideapad_sysfs_init(priv); if (ret) @@ -1310,11 +1334,11 @@ static int ideapad_acpi_add(struct platform_device *pdev) * On some models without a hw-switch (the yoga 2 13 at least) * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. */ - if (!priv->has_hw_rfkill_switch) + if (!priv->features.hw_rfkill_switch) write_ec_cmd(priv->adev->handle, VPCCMD_W_RF, 1); /* The same for Touchpad */ - if (!priv->has_touchpad_switch) + if (!priv->features.touchpad_ctrl_via_ec) write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, 1); for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) @@ -1324,7 +1348,13 @@ static int ideapad_acpi_add(struct platform_device *pdev) ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); - ideapad_dytc_profile_init(priv); + ret = ideapad_dytc_profile_init(priv); + if (ret) { + if (ret != -ENODEV) + dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", ret); + else + dev_info(&pdev->dev, "DYTC interface is not available\n"); + } if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { ret = ideapad_backlight_init(priv); -- cgit v1.2.3 From 392cbf0a42777bb08153c76dfd0cb8c575bd6f10 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:16 +0000 Subject: platform/x86: ideapad-laptop: check for Fn-lock support in HALS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bit 9 in the return value of the HALS ACPI method is set if Fn-lock is supported. Change ideapad_check_features() to check it. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-20-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 509f2528e1af..d63c19fb5569 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -57,7 +57,9 @@ enum { }; enum { - HALS_FNLOCK_STATE_BIT = 10, + HALS_FNLOCK_SUPPORT_BIT = 9, + HALS_FNLOCK_STATE_BIT = 10, + HALS_HOTKEYS_PRIMARY_BIT = 11, }; enum { @@ -1290,8 +1292,12 @@ static void ideapad_check_features(struct ideapad_private *priv) if (acpi_has_method(handle, "DYTC")) priv->features.dytc = true; - if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) - priv->features.fn_lock = true; + if (acpi_has_method(handle, "HALS") && acpi_has_method(handle, "SALS")) { + if (!eval_hals(handle, &val)) { + if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) + priv->features.fn_lock = true; + } + } } static int ideapad_acpi_add(struct platform_device *pdev) -- cgit v1.2.3 From b3ed1b7fe3786c8fe795c16ca07cf3bda67b652f Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:22 +0000 Subject: platform/x86: ideapad-laptop: check for touchpad support in _CFG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bit 30 of _CFG is set if the device has a touchpad, check that in is_visible() for the touchpad attribute. Show 'touchpad', if supported, in the list of capabilities in the 'cfg' debugfs file. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-21-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index d63c19fb5569..f3d950c7fc01 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -41,10 +41,11 @@ static const char *const ideapad_wmi_fnesc_events[] = { #endif enum { - CFG_CAP_BT_BIT = 16, - CFG_CAP_3G_BIT = 17, - CFG_CAP_WIFI_BIT = 18, - CFG_CAP_CAM_BIT = 19, + CFG_CAP_BT_BIT = 16, + CFG_CAP_3G_BIT = 17, + CFG_CAP_WIFI_BIT = 18, + CFG_CAP_CAM_BIT = 19, + CFG_CAP_TOUCHPAD_BIT = 30, }; enum { @@ -337,6 +338,8 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) seq_printf(s, "Wireless "); if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) seq_printf(s, "Camera "); + if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) + seq_printf(s, "Touchpad "); seq_printf(s, "\nGraphic: "); switch ((priv->cfg)&0x700) { case 0x100: @@ -579,7 +582,8 @@ static umode_t ideapad_is_visible(struct kobject *kobj, else if (attr == &dev_attr_fn_lock.attr) supported = priv->features.fn_lock; else if (attr == &dev_attr_touchpad.attr) - supported = priv->features.touchpad_ctrl_via_ec; + supported = priv->features.touchpad_ctrl_via_ec && + test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); return supported ? attr->mode : 0; } -- cgit v1.2.3 From 7553390d4b7e636d9be7913b16f4b9ae8b75df4c Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:34 +0000 Subject: platform/x86: ideapad-laptop: change 'status' debugfs file format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove conservation mode reporting since it is already reported via the appropriate device attribute, and its state can be deduced from the value of GBMD. Add the return value of the GBMD and HALS ACPI methods to the output. Use seq_puts() where possible. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-22-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 37 ++++++++++++++--------------------- 1 file changed, 15 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index f3d950c7fc01..c26941cb8f9d 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -285,40 +285,33 @@ static int debugfs_status_show(struct seq_file *s, void *data) unsigned long value; if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &value)) - seq_printf(s, "Backlight max:\t%lu\n", value); + seq_printf(s, "Backlight max: %lu\n", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL, &value)) - seq_printf(s, "Backlight now:\t%lu\n", value); + seq_printf(s, "Backlight now: %lu\n", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) - seq_printf(s, "BL power value:\t%s\n", value ? "On" : "Off"); - seq_printf(s, "=====================\n"); + seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); + seq_puts(s, "=====================\n"); if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) - seq_printf(s, "Radio status:\t%s(%lu)\n", - value ? "On" : "Off", value); + seq_printf(s, "Radio status: %s (%lu)\n", value ? "on" : "off", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_WIFI, &value)) - seq_printf(s, "Wifi status:\t%s(%lu)\n", - value ? "On" : "Off", value); + seq_printf(s, "Wifi status: %s (%lu)\n", value ? "on" : "off", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BT, &value)) - seq_printf(s, "BT status:\t%s(%lu)\n", - value ? "On" : "Off", value); + seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) - seq_printf(s, "3G status:\t%s(%lu)\n", - value ? "On" : "Off", value); - seq_printf(s, "=====================\n"); + seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); + seq_puts(s, "=====================\n"); if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) - seq_printf(s, "Touchpad status:%s(%lu)\n", - value ? "On" : "Off", value); + seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) - seq_printf(s, "Camera status:\t%s(%lu)\n", - value ? "On" : "Off", value); + seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); seq_puts(s, "=====================\n"); - if (!eval_gbmd(priv->adev->handle, &value)) { - seq_printf(s, "Conservation mode:\t%s(%lu)\n", - test_bit(GBMD_CONSERVATION_STATE_BIT, &value) ? "On" : "Off", - value); - } + if (!eval_gbmd(priv->adev->handle, &value)) + seq_printf(s, "GBMD: %#010lx\n", value); + if (!eval_hals(priv->adev->handle, &value)) + seq_printf(s, "HALS: %#010lx\n", value); return 0; } -- cgit v1.2.3 From 18227424549cfc1b3f7c88ec04be2c6a8ac3b887 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:39 +0000 Subject: platform/x86: ideapad-laptop: change 'cfg' debugfs file format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor formatting changes. Use seq_puts() where possible. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-23-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index c26941cb8f9d..a89588f3dc51 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -321,37 +321,40 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) { struct ideapad_private *priv = s->private; - seq_printf(s, "cfg: 0x%.8lX\n\nCapability: ", - priv->cfg); + seq_printf(s, "_CFG: %#010lx\n\n", priv->cfg); + + seq_puts(s, "Capabilities:"); if (test_bit(CFG_CAP_BT_BIT, &priv->cfg)) - seq_printf(s, "Bluetooth "); + seq_puts(s, " bluetooth"); if (test_bit(CFG_CAP_3G_BIT, &priv->cfg)) - seq_printf(s, "3G "); + seq_puts(s, " 3G"); if (test_bit(CFG_CAP_WIFI_BIT, &priv->cfg)) - seq_printf(s, "Wireless "); + seq_puts(s, " wifi"); if (test_bit(CFG_CAP_CAM_BIT, &priv->cfg)) - seq_printf(s, "Camera "); + seq_puts(s, " camera"); if (test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg)) - seq_printf(s, "Touchpad "); - seq_printf(s, "\nGraphic: "); - switch ((priv->cfg)&0x700) { + seq_puts(s, " touchpad"); + seq_puts(s, "\n"); + + seq_puts(s, "Graphics: "); + switch (priv->cfg & 0x700) { case 0x100: - seq_printf(s, "Intel"); + seq_puts(s, "Intel"); break; case 0x200: - seq_printf(s, "ATI"); + seq_puts(s, "ATI"); break; case 0x300: - seq_printf(s, "Nvidia"); + seq_puts(s, "Nvidia"); break; case 0x400: - seq_printf(s, "Intel and ATI"); + seq_puts(s, "Intel and ATI"); break; case 0x500: - seq_printf(s, "Intel and Nvidia"); + seq_puts(s, "Intel and Nvidia"); break; } - seq_printf(s, "\n"); + seq_puts(s, "\n"); return 0; } -- cgit v1.2.3 From 921f70ffe8901f98f7552194cc0458c4a145145e Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:44 +0000 Subject: Revert "platform/x86: ideapad-laptop: Switch touchpad attribute to be RO" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The touchpad can be enabled/disabled via this attribute on a Lenovo Yoga 520-14IKB. Allow writing as it provides legitimate functionality. This reverts commit 7f363145992cebf4ea760447f1cfdf6f81459683. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-24-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index a89588f3dc51..4cecf3b4e7df 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -462,10 +462,9 @@ static ssize_t touchpad_show(struct device *dev, return sysfs_emit(buf, "%d\n", !!result); } -/* Switch to RO for now: It might be revisited in the future */ -static ssize_t __maybe_unused touchpad_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t touchpad_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); bool state; @@ -481,7 +480,7 @@ static ssize_t __maybe_unused touchpad_store(struct device *dev, return count; } -static DEVICE_ATTR_RO(touchpad); +static DEVICE_ATTR_RW(touchpad); static ssize_t conservation_mode_show(struct device *dev, struct device_attribute *attr, -- cgit v1.2.3 From 65c7713a5079278eee3146092fc4df2627b42604 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:56:56 +0000 Subject: platform/x86: ideapad-laptop: fix checkpatch warnings, more consistent style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all checkpatch warnings. Reorder variable definitions from longest to shortest. Add more whitespaces for better readability. Rename variables named `ret` to `err` where appropriate. Reorder sysfs attributes show/store callbacks and the `ideapad_attributes` array in lexicographic order. And other minor formatting changes. No significant functional changes are intended. Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-25-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 454 +++++++++++++++++++--------------- 1 file changed, 256 insertions(+), 198 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 4cecf3b4e7df..90fb071ccbc0 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -31,7 +31,7 @@ #include -#define IDEAPAD_RFKILL_DEV_NUM (3) +#define IDEAPAD_RFKILL_DEV_NUM 3 #if IS_ENABLED(CONFIG_ACPI_WMI) static const char *const ideapad_wmi_fnesc_events[] = { @@ -98,7 +98,7 @@ enum { struct ideapad_dytc_priv { enum platform_profile_option current_profile; struct platform_profile_handler pprof; - struct mutex mutex; + struct mutex mutex; /* protects the DYTC interface */ struct ideapad_private *priv; }; @@ -135,7 +135,7 @@ MODULE_PARM_DESC(no_bt_rfkill, "No rfkill for bluetooth."); /* * ACPI Helpers */ -#define IDEAPAD_EC_TIMEOUT (200) /* in ms */ +#define IDEAPAD_EC_TIMEOUT 200 /* in ms */ static int eval_int(acpi_handle handle, const char *name, unsigned long *res) { @@ -145,7 +145,9 @@ static int eval_int(acpi_handle handle, const char *name, unsigned long *res) status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); if (ACPI_FAILURE(status)) return -EIO; + *res = result; + return 0; } @@ -224,6 +226,7 @@ static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data) status = acpi_evaluate_object(handle, "VPCW", ¶ms, NULL); if (ACPI_FAILURE(status)) return -EIO; + return 0; } @@ -240,13 +243,17 @@ static int read_ec_data(acpi_handle handle, unsigned long cmd, unsigned long *da while (time_before(jiffies, end_jiffies)) { schedule(); + err = eval_vpcr(handle, 1, &val); if (err) return err; + if (val == 0) return eval_vpcr(handle, 0, data); } + acpi_handle_err(handle, "timeout in %s\n", __func__); + return -ETIMEDOUT; } @@ -258,6 +265,7 @@ static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long dat err = eval_vpcw(handle, 0, data); if (err) return err; + err = eval_vpcw(handle, 1, cmd); if (err) return err; @@ -266,13 +274,17 @@ static int write_ec_cmd(acpi_handle handle, unsigned long cmd, unsigned long dat while (time_before(jiffies, end_jiffies)) { schedule(); + err = eval_vpcr(handle, 1, &val); if (err) return err; + if (val == 0) return 0; } + acpi_handle_err(handle, "timeout in %s\n", __func__); + return -ETIMEDOUT; } @@ -290,6 +302,7 @@ static int debugfs_status_show(struct seq_file *s, void *data) seq_printf(s, "Backlight now: %lu\n", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &value)) seq_printf(s, "BL power value: %s (%lu)\n", value ? "on" : "off", value); + seq_puts(s, "=====================\n"); if (!read_ec_data(priv->adev->handle, VPCCMD_R_RF, &value)) @@ -300,12 +313,14 @@ static int debugfs_status_show(struct seq_file *s, void *data) seq_printf(s, "BT status: %s (%lu)\n", value ? "on" : "off", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_3G, &value)) seq_printf(s, "3G status: %s (%lu)\n", value ? "on" : "off", value); + seq_puts(s, "=====================\n"); if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) seq_printf(s, "Touchpad status: %s (%lu)\n", value ? "on" : "off", value); if (!read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &value)) seq_printf(s, "Camera status: %s (%lu)\n", value ? "on" : "off", value); + seq_puts(s, "=====================\n"); if (!eval_gbmd(priv->adev->handle, &value)) @@ -367,8 +382,8 @@ static void ideapad_debugfs_init(struct ideapad_private *priv) dir = debugfs_create_dir("ideapad", NULL); priv->debug = dir; - debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops); - debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops); + debugfs_create_file("cfg", 0444, dir, priv, &debugfs_cfg_fops); + debugfs_create_file("status", 0444, dir, priv, &debugfs_status_fops); } static void ideapad_debugfs_exit(struct ideapad_private *priv) @@ -380,75 +395,79 @@ static void ideapad_debugfs_exit(struct ideapad_private *priv) /* * sysfs */ -static ssize_t show_ideapad_cam(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t camera_power_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - unsigned long result; struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long result; int err; err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result); if (err) return err; + return sysfs_emit(buf, "%d\n", !!result); } -static ssize_t store_ideapad_cam(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t camera_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); bool state; - int ret; - - ret = kstrtobool(buf, &state); - if (ret) - return ret; - ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); - if (ret) - return ret; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state); + if (err) + return err; + return count; } -static DEVICE_ATTR(camera_power, 0644, show_ideapad_cam, store_ideapad_cam); +static DEVICE_ATTR_RW(camera_power); -static ssize_t show_ideapad_fan(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t conservation_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) { - unsigned long result; struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long result; int err; - err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); + err = eval_gbmd(priv->adev->handle, &result); if (err) return err; - return sysfs_emit(buf, "%lu\n", result); + + return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); } -static ssize_t store_ideapad_fan(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t conservation_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); - unsigned int state; - int ret; + bool state; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = exec_smbc(priv->adev->handle, state ? SMBC_CONSERVATION_ON : SMBC_CONSERVATION_OFF); + if (err) + return err; - ret = kstrtouint(buf, 0, &state); - if (ret) - return ret; - if (state > 4 || state == 3) - return -EINVAL; - ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); - if (ret) - return ret; return count; } -static DEVICE_ATTR(fan_mode, 0644, show_ideapad_fan, store_ideapad_fan); +static DEVICE_ATTR_RW(conservation_mode); -static ssize_t touchpad_show(struct device *dev, +static ssize_t fan_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -456,113 +475,121 @@ static ssize_t touchpad_show(struct device *dev, unsigned long result; int err; - err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); + err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result); if (err) return err; - return sysfs_emit(buf, "%d\n", !!result); + + return sysfs_emit(buf, "%lu\n", result); } -static ssize_t touchpad_store(struct device *dev, +static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); - bool state; - int ret; + unsigned int state; + int err; + + err = kstrtouint(buf, 0, &state); + if (err) + return err; + + if (state > 4 || state == 3) + return -EINVAL; - ret = kstrtobool(buf, &state); - if (ret) - return ret; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state); + if (err) + return err; - ret = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); - if (ret) - return ret; return count; } -static DEVICE_ATTR_RW(touchpad); +static DEVICE_ATTR_RW(fan_mode); -static ssize_t conservation_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t fn_lock_show(struct device *dev, + struct device_attribute *attr, + char *buf) { struct ideapad_private *priv = dev_get_drvdata(dev); - unsigned long result; + unsigned long hals; int err; - err = eval_gbmd(priv->adev->handle, &result); + err = eval_hals(priv->adev->handle, &hals); if (err) return err; - return sysfs_emit(buf, "%d\n", !!test_bit(GBMD_CONSERVATION_STATE_BIT, &result)); + + return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); } -static ssize_t conservation_mode_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t fn_lock_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); bool state; - int ret; + int err; - ret = kstrtobool(buf, &state); - if (ret) - return ret; + err = kstrtobool(buf, &state); + if (err) + return err; + + err = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); + if (err) + return err; - ret = exec_smbc(priv->adev->handle, state ? SMBC_CONSERVATION_ON : SMBC_CONSERVATION_OFF); - if (ret) - return ret; return count; } -static DEVICE_ATTR_RW(conservation_mode); +static DEVICE_ATTR_RW(fn_lock); -static ssize_t fn_lock_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static ssize_t touchpad_show(struct device *dev, + struct device_attribute *attr, + char *buf) { struct ideapad_private *priv = dev_get_drvdata(dev); - unsigned long hals; - int fail = eval_hals(priv->adev->handle, &hals); + unsigned long result; + int err; - if (fail) - return fail; + err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result); + if (err) + return err; - return sysfs_emit(buf, "%d\n", !!test_bit(HALS_FNLOCK_STATE_BIT, &hals)); + return sysfs_emit(buf, "%d\n", !!result); } -static ssize_t fn_lock_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t touchpad_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct ideapad_private *priv = dev_get_drvdata(dev); bool state; - int ret; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; - ret = kstrtobool(buf, &state); - if (ret) - return ret; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state); + if (err) + return err; - ret = exec_sals(priv->adev->handle, state ? SALS_FNLOCK_ON : SALS_FNLOCK_OFF); - if (ret) - return ret; return count; } -static DEVICE_ATTR_RW(fn_lock); - +static DEVICE_ATTR_RW(touchpad); static struct attribute *ideapad_attributes[] = { &dev_attr_camera_power.attr, - &dev_attr_fan_mode.attr, - &dev_attr_touchpad.attr, &dev_attr_conservation_mode.attr, + &dev_attr_fan_mode.attr, &dev_attr_fn_lock.attr, + &dev_attr_touchpad.attr, NULL }; static umode_t ideapad_is_visible(struct kobject *kobj, - struct attribute *attr, - int idx) + struct attribute *attr, + int idx) { struct device *dev = kobj_to_dev(kobj); struct ideapad_private *priv = dev_get_drvdata(dev); @@ -639,6 +666,7 @@ static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *p default: /* Unknown mode */ return -EINVAL; } + return 0; } @@ -657,6 +685,7 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe default: /* Unknown profile */ return -EOPNOTSUPP; } + return 0; } @@ -664,24 +693,24 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe * dytc_profile_get: Function to register with platform_profile * handler. Returns current platform profile. */ -int dytc_profile_get(struct platform_profile_handler *pprof, - enum platform_profile_option *profile) +static int dytc_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) { - struct ideapad_dytc_priv *dytc; + struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); - dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); *profile = dytc->current_profile; return 0; } /* * Helper function - check if we are in CQL mode and if we are - * - disable CQL, + * - disable CQL, * - run the command * - enable CQL * If not in CQL mode, just run the command */ -int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, unsigned long *output) +static int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, + unsigned long *output) { int err, cmd_err, cur_funcmode; @@ -717,16 +746,13 @@ int dytc_cql_command(struct ideapad_private *priv, unsigned long cmd, unsigned l * dytc_profile_set: Function to register with platform_profile * handler. Sets current platform profile. */ -int dytc_profile_set(struct platform_profile_handler *pprof, - enum platform_profile_option profile) +static int dytc_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) { - struct ideapad_dytc_priv *dytc; - struct ideapad_private *priv; + struct ideapad_dytc_priv *dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); + struct ideapad_private *priv = dytc->priv; int err; - dytc = container_of(pprof, struct ideapad_dytc_priv, pprof); - priv = dytc->priv; - err = mutex_lock_interruptible(&dytc->mutex); if (err) return err; @@ -744,16 +770,18 @@ int dytc_profile_set(struct platform_profile_handler *pprof, goto unlock; /* Determine if we are in CQL mode. This alters the commands we do */ - err = dytc_cql_command(priv, - DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), - NULL); + err = dytc_cql_command(priv, DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), + NULL); if (err) goto unlock; } + /* Success - update current profile */ dytc->current_profile = profile; + unlock: mutex_unlock(&dytc->mutex); + return err; } @@ -770,7 +798,10 @@ static void dytc_profile_refresh(struct ideapad_private *priv) return; perfmode = (output >> DYTC_GET_MODE_BIT) & 0xF; - convert_dytc_to_profile(perfmode, &profile); + + if (convert_dytc_to_profile(perfmode, &profile)) + return; + if (profile != priv->dytc->current_profile) { priv->dytc->current_profile = profile; platform_profile_notify(); @@ -791,14 +822,14 @@ static int ideapad_dytc_profile_init(struct ideapad_private *priv) return err; /* Check DYTC is enabled and supports mode setting */ - if (!(output & BIT(DYTC_QUERY_ENABLE_BIT))) + if (!test_bit(DYTC_QUERY_ENABLE_BIT, &output)) return -ENODEV; dytc_version = (output >> DYTC_QUERY_REV_BIT) & 0xF; if (dytc_version < 5) return -ENODEV; - priv->dytc = kzalloc(sizeof(struct ideapad_dytc_priv), GFP_KERNEL); + priv->dytc = kzalloc(sizeof(*priv->dytc), GFP_KERNEL); if (!priv->dytc) return -ENOMEM; @@ -816,17 +847,18 @@ static int ideapad_dytc_profile_init(struct ideapad_private *priv) /* Create platform_profile structure and register */ err = platform_profile_register(&priv->dytc->pprof); if (err) - goto mutex_destroy; + goto pp_reg_failed; /* Ensure initial values are correct */ dytc_profile_refresh(priv); return 0; -mutex_destroy: +pp_reg_failed: mutex_destroy(&priv->dytc->mutex); kfree(priv->dytc); priv->dytc = NULL; + return err; } @@ -838,6 +870,7 @@ static void ideapad_dytc_profile_exit(struct ideapad_private *priv) platform_profile_remove(); mutex_destroy(&priv->dytc->mutex); kfree(priv->dytc); + priv->dytc = NULL; } @@ -887,16 +920,15 @@ static void ideapad_sync_rfk_state(struct ideapad_private *priv) static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) { - int ret; - unsigned long sw_blocked; + unsigned long rf_enabled; + int err; - if (no_bt_rfkill && - (ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH)) { + if (no_bt_rfkill && ideapad_rfk_data[dev].type == RFKILL_TYPE_BLUETOOTH) { /* Force to enable bluetooth when no_bt_rfkill=1 */ - write_ec_cmd(priv->adev->handle, - ideapad_rfk_data[dev].opcode, 1); + write_ec_cmd(priv->adev->handle, ideapad_rfk_data[dev].opcode, 1); return 0; } + priv->rfk_priv[dev].dev = dev; priv->rfk_priv[dev].priv = priv; @@ -908,20 +940,17 @@ static int ideapad_register_rfkill(struct ideapad_private *priv, int dev) if (!priv->rfk[dev]) return -ENOMEM; - if (read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode-1, - &sw_blocked)) { - rfkill_init_sw_state(priv->rfk[dev], 0); - } else { - sw_blocked = !sw_blocked; - rfkill_init_sw_state(priv->rfk[dev], sw_blocked); - } + err = read_ec_data(priv->adev->handle, ideapad_rfk_data[dev].opcode - 1, &rf_enabled); + if (err) + rf_enabled = 1; + + rfkill_init_sw_state(priv->rfk[dev], !rf_enabled); - ret = rfkill_register(priv->rfk[dev]); - if (ret) { + err = rfkill_register(priv->rfk[dev]); + if (err) rfkill_destroy(priv->rfk[dev]); - return ret; - } - return 0; + + return err; } static void ideapad_unregister_rfkill(struct ideapad_private *priv, int dev) @@ -952,26 +981,25 @@ static void ideapad_sysfs_exit(struct ideapad_private *priv) * input device */ static const struct key_entry ideapad_keymap[] = { - { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, - { KE_KEY, 7, { KEY_CAMERA } }, - { KE_KEY, 8, { KEY_MICMUTE } }, - { KE_KEY, 11, { KEY_F16 } }, - { KE_KEY, 13, { KEY_WLAN } }, - { KE_KEY, 16, { KEY_PROG1 } }, - { KE_KEY, 17, { KEY_PROG2 } }, - { KE_KEY, 64, { KEY_PROG3 } }, - { KE_KEY, 65, { KEY_PROG4 } }, - { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, - { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, + { KE_KEY, 6, { KEY_SWITCHVIDEOMODE } }, + { KE_KEY, 7, { KEY_CAMERA } }, + { KE_KEY, 8, { KEY_MICMUTE } }, + { KE_KEY, 11, { KEY_F16 } }, + { KE_KEY, 13, { KEY_WLAN } }, + { KE_KEY, 16, { KEY_PROG1 } }, + { KE_KEY, 17, { KEY_PROG2 } }, + { KE_KEY, 64, { KEY_PROG3 } }, + { KE_KEY, 65, { KEY_PROG4 } }, + { KE_KEY, 66, { KEY_TOUCHPAD_OFF } }, + { KE_KEY, 67, { KEY_TOUCHPAD_ON } }, { KE_KEY, 128, { KEY_ESC } }, - - { KE_END, 0 }, + { KE_END }, }; static int ideapad_input_init(struct ideapad_private *priv) { struct input_dev *inputdev; - int error; + int err; inputdev = input_allocate_device(); if (!inputdev) @@ -982,26 +1010,28 @@ static int ideapad_input_init(struct ideapad_private *priv) inputdev->id.bustype = BUS_HOST; inputdev->dev.parent = &priv->platform_device->dev; - error = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); - if (error) { + err = sparse_keymap_setup(inputdev, ideapad_keymap, NULL); + if (err) { dev_err(&priv->platform_device->dev, - "Unable to setup input device keymap\n"); + "Could not set up input device keymap: %d\n", err); goto err_free_dev; } - error = input_register_device(inputdev); - if (error) { + err = input_register_device(inputdev); + if (err) { dev_err(&priv->platform_device->dev, - "Unable to register input device\n"); + "Could not register input device: %d\n", err); goto err_free_dev; } priv->inputdev = inputdev; + return 0; err_free_dev: input_free_device(inputdev); - return error; + + return err; } static void ideapad_input_exit(struct ideapad_private *priv) @@ -1022,6 +1052,7 @@ static void ideapad_input_novokey(struct ideapad_private *priv) if (read_ec_data(priv->adev->handle, VPCCMD_R_NOVO, &long_pressed)) return; + if (long_pressed) ideapad_input_report(priv, 17); else @@ -1037,8 +1068,8 @@ static void ideapad_check_special_buttons(struct ideapad_private *priv) for_each_set_bit (bit, &value, 16) { switch (bit) { - case 0: /* Z580 */ case 6: /* Z570 */ + case 0: /* Z580 */ /* Thermal Management button */ ideapad_input_report(priv, 65); break; @@ -1066,6 +1097,7 @@ static int ideapad_backlight_get_brightness(struct backlight_device *blightdev) err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); if (err) return err; + return now; } @@ -1078,6 +1110,7 @@ static int ideapad_backlight_update_status(struct backlight_device *blightdev) blightdev->props.brightness); if (err) return err; + err = write_ec_cmd(priv->adev->handle, VPCCMD_W_BL_POWER, blightdev->props.power != FB_BLANK_POWERDOWN); if (err) @@ -1101,30 +1134,36 @@ static int ideapad_backlight_init(struct ideapad_private *priv) err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_MAX, &max); if (err) return err; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); if (err) return err; + err = read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power); if (err) return err; - memset(&props, 0, sizeof(struct backlight_properties)); + memset(&props, 0, sizeof(props)); + props.max_brightness = max; props.type = BACKLIGHT_PLATFORM; + blightdev = backlight_device_register("ideapad", &priv->platform_device->dev, priv, &ideapad_backlight_ops, &props); if (IS_ERR(blightdev)) { + err = PTR_ERR(blightdev); dev_err(&priv->platform_device->dev, - "Could not register backlight device\n"); - return PTR_ERR(blightdev); + "Could not register backlight device: %d\n", err); + return err; } priv->blightdev = blightdev; blightdev->props.brightness = now; blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; + backlight_update_status(blightdev); return 0; @@ -1138,13 +1177,15 @@ static void ideapad_backlight_exit(struct ideapad_private *priv) static void ideapad_backlight_notify_power(struct ideapad_private *priv) { - unsigned long power; struct backlight_device *blightdev = priv->blightdev; + unsigned long power; if (!blightdev) return; + if (read_ec_data(priv->adev->handle, VPCCMD_R_BL_POWER, &power)) return; + blightdev->props.power = power ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN; } @@ -1153,12 +1194,10 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) unsigned long now; /* if we control brightness via acpi video driver */ - if (priv->blightdev == NULL) { + if (!priv->blightdev) read_ec_data(priv->adev->handle, VPCCMD_R_BL, &now); - return; - } - - backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); + else + backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); } /* @@ -1173,13 +1212,14 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv) /* Without reading from EC touchpad LED doesn't switch state */ if (!read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &value)) { - /* Some IdeaPads don't really turn off touchpad - they only + unsigned char param; + /* + * Some IdeaPads don't really turn off touchpad - they only * switch the LED state. We (de)activate KBC AUX port to turn * touchpad off and on. We send KEY_TOUCHPAD_OFF and - * KEY_TOUCHPAD_ON to not to get out of sync with LED */ - unsigned char param; - i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : - I8042_CMD_AUX_DISABLE); + * KEY_TOUCHPAD_ON to not to get out of sync with LED + */ + i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); ideapad_input_report(priv, value ? 67 : 66); } } @@ -1191,6 +1231,7 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1)) return; + if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2)) return; @@ -1198,9 +1239,6 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) for_each_set_bit (bit, &vpc1, 16) { switch (bit) { - case 9: - ideapad_sync_rfk_state(priv); - break; case 13: case 11: case 8: @@ -1208,6 +1246,9 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) case 6: ideapad_input_report(priv, bit); break; + case 9: + ideapad_sync_rfk_state(priv); + break; case 5: ideapad_sync_touchpad_state(priv); break; @@ -1220,16 +1261,17 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) case 2: ideapad_backlight_notify_power(priv); break; - case 0: - ideapad_check_special_buttons(priv); - break; case 1: - /* Some IdeaPads report event 1 every ~20 + /* + * Some IdeaPads report event 1 every ~20 * seconds while on battery power; some * report this when changing to/from tablet * mode. Squelch this event. */ break; + case 0: + ideapad_check_special_buttons(priv); + break; default: dev_info(&priv->platform_device->dev, "Unknown event: %lu\n", bit); @@ -1301,14 +1343,14 @@ static void ideapad_check_features(struct ideapad_private *priv) static int ideapad_acpi_add(struct platform_device *pdev) { - int ret, i; struct ideapad_private *priv; struct acpi_device *adev; acpi_status status; unsigned long cfg; + int err, i; - ret = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); - if (ret) + err = acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev); + if (err) return -ENODEV; if (eval_int(adev->handle, "_CFG", &cfg)) @@ -1319,20 +1361,21 @@ static int ideapad_acpi_add(struct platform_device *pdev) return -ENOMEM; dev_set_drvdata(&pdev->dev, priv); + priv->cfg = cfg; priv->adev = adev; priv->platform_device = pdev; ideapad_check_features(priv); - ret = ideapad_sysfs_init(priv); - if (ret) - return ret; + err = ideapad_sysfs_init(priv); + if (err) + return err; ideapad_debugfs_init(priv); - ret = ideapad_input_init(priv); - if (ret) + err = ideapad_input_init(priv); + if (err) goto input_failed; /* @@ -1353,24 +1396,25 @@ static int ideapad_acpi_add(struct platform_device *pdev) ideapad_sync_rfk_state(priv); ideapad_sync_touchpad_state(priv); - ret = ideapad_dytc_profile_init(priv); - if (ret) { - if (ret != -ENODEV) - dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", ret); + err = ideapad_dytc_profile_init(priv); + if (err) { + if (err != -ENODEV) + dev_warn(&pdev->dev, "Could not set up DYTC interface: %d\n", err); else dev_info(&pdev->dev, "DYTC interface is not available\n"); } if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { - ret = ideapad_backlight_init(priv); - if (ret && ret != -ENODEV) + err = ideapad_backlight_init(priv); + if (err && err != -ENODEV) goto backlight_failed; } + status = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, ideapad_acpi_notify, priv); if (ACPI_FAILURE(status)) { - ret = -EIO; + err = -EIO; goto notification_failed; } @@ -1383,29 +1427,38 @@ static int ideapad_acpi_add(struct platform_device *pdev) break; } } + if (ACPI_FAILURE(status) && status != AE_NOT_EXIST) { - ret = -EIO; + err = -EIO; goto notification_failed_wmi; } #endif return 0; + #if IS_ENABLED(CONFIG_ACPI_WMI) notification_failed_wmi: acpi_remove_notify_handler(priv->adev->handle, - ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); + ACPI_DEVICE_NOTIFY, + ideapad_acpi_notify); #endif + notification_failed: ideapad_backlight_exit(priv); + backlight_failed: ideapad_dytc_profile_exit(priv); + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + ideapad_input_exit(priv); + input_failed: ideapad_debugfs_exit(priv); ideapad_sysfs_exit(priv); - return ret; + + return err; } static int ideapad_acpi_remove(struct platform_device *pdev) @@ -1417,12 +1470,17 @@ static int ideapad_acpi_remove(struct platform_device *pdev) if (priv->fnesc_guid) wmi_remove_notify_handler(priv->fnesc_guid); #endif + acpi_remove_notify_handler(priv->adev->handle, - ACPI_DEVICE_NOTIFY, ideapad_acpi_notify); + ACPI_DEVICE_NOTIFY, + ideapad_acpi_notify); + ideapad_backlight_exit(priv); ideapad_dytc_profile_exit(priv); + for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + ideapad_input_exit(priv); ideapad_debugfs_exit(priv); ideapad_sysfs_exit(priv); @@ -1447,8 +1505,8 @@ static int ideapad_acpi_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(ideapad_pm, NULL, ideapad_acpi_resume); static const struct acpi_device_id ideapad_device_ids[] = { - { "VPC2004", 0}, - { "", 0}, + {"VPC2004", 0}, + {"", 0}, }; MODULE_DEVICE_TABLE(acpi, ideapad_device_ids); -- cgit v1.2.3 From c67957464e1e4934588d2672ef6189f5d790fb67 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:57:01 +0000 Subject: platform/x86: ideapad-laptop: send notification about touchpad state change to sysfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consumers can determine if the value of an attribute changed much more easily if changes are broadcast using sysfs_notify(), so utilize it. Signed-off-by: Barnabás Pőcze Reviewed-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210203215403.290792-26-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 90fb071ccbc0..18f920c29809 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1221,6 +1221,7 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv) */ i8042_command(¶m, value ? I8042_CMD_AUX_ENABLE : I8042_CMD_AUX_DISABLE); ideapad_input_report(priv, value ? 67 : 66); + sysfs_notify(&priv->platform_device->dev.kobj, NULL, "touchpad"); } } -- cgit v1.2.3 From 503325f84bc0ee3a07b0831ee59d6eae84cfa695 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:57:07 +0000 Subject: platform/x86: ideapad-laptop: add keyboard backlight control support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On certain models it is possible to control/query the keyboard backlight via the SALS/HALS ACPI methods. Add support for that, and register an LED class device to expose this functionality. Tested on: Lenovo YOGA 520-14IKB 80X8 Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-27-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/ideapad-laptop.c | 133 +++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 18f920c29809..24dcb4ad75ef 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +33,8 @@ #include +#include + #define IDEAPAD_RFKILL_DEV_NUM 3 #if IS_ENABLED(CONFIG_ACPI_WMI) @@ -58,12 +62,16 @@ enum { }; enum { + HALS_KBD_BL_SUPPORT_BIT = 4, + HALS_KBD_BL_STATE_BIT = 5, HALS_FNLOCK_SUPPORT_BIT = 9, HALS_FNLOCK_STATE_BIT = 10, HALS_HOTKEYS_PRIMARY_BIT = 11, }; enum { + SALS_KBD_BL_ON = 0x8, + SALS_KBD_BL_OFF = 0x9, SALS_FNLOCK_ON = 0xe, SALS_FNLOCK_OFF = 0xf, }; @@ -124,8 +132,14 @@ struct ideapad_private { bool fan_mode : 1; bool fn_lock : 1; bool hw_rfkill_switch : 1; + bool kbd_bl : 1; bool touchpad_ctrl_via_ec : 1; } features; + struct { + bool initialized; + struct led_classdev led; + unsigned int last_brightness; + } kbd_bl; }; static bool no_bt_rfkill; @@ -1200,6 +1214,108 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv) backlight_force_update(priv->blightdev, BACKLIGHT_UPDATE_HOTKEY); } +/* + * keyboard backlight + */ +static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv) +{ + unsigned long hals; + int err; + + err = eval_hals(priv->adev->handle, &hals); + if (err) + return err; + + return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals); +} + +static enum led_brightness ideapad_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) +{ + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); + + return ideapad_kbd_bl_brightness_get(priv); +} + +static int ideapad_kbd_bl_brightness_set(struct ideapad_private *priv, unsigned int brightness) +{ + int err = exec_sals(priv->adev->handle, brightness ? SALS_KBD_BL_ON : SALS_KBD_BL_OFF); + + if (err) + return err; + + priv->kbd_bl.last_brightness = brightness; + + return 0; +} + +static int ideapad_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ideapad_private *priv = container_of(led_cdev, struct ideapad_private, kbd_bl.led); + + return ideapad_kbd_bl_brightness_set(priv, brightness); +} + +static void ideapad_kbd_bl_notify(struct ideapad_private *priv) +{ + int brightness; + + if (!priv->kbd_bl.initialized) + return; + + brightness = ideapad_kbd_bl_brightness_get(priv); + if (brightness < 0) + return; + + if (brightness == priv->kbd_bl.last_brightness) + return; + + priv->kbd_bl.last_brightness = brightness; + + led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness); +} + +static int ideapad_kbd_bl_init(struct ideapad_private *priv) +{ + int brightness, err; + + if (!priv->features.kbd_bl) + return -ENODEV; + + if (WARN_ON(priv->kbd_bl.initialized)) + return -EEXIST; + + brightness = ideapad_kbd_bl_brightness_get(priv); + if (brightness < 0) + return brightness; + + priv->kbd_bl.last_brightness = brightness; + + priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; + priv->kbd_bl.led.max_brightness = 1; + priv->kbd_bl.led.brightness_get = ideapad_kbd_bl_led_cdev_brightness_get; + priv->kbd_bl.led.brightness_set_blocking = ideapad_kbd_bl_led_cdev_brightness_set; + priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; + + err = led_classdev_register(&priv->platform_device->dev, &priv->kbd_bl.led); + if (err) + return err; + + priv->kbd_bl.initialized = true; + + return 0; +} + +static void ideapad_kbd_bl_exit(struct ideapad_private *priv) +{ + if (!priv->kbd_bl.initialized) + return; + + priv->kbd_bl.initialized = false; + + led_classdev_unregister(&priv->kbd_bl.led); +} + /* * module init/exit */ @@ -1267,8 +1383,10 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) * Some IdeaPads report event 1 every ~20 * seconds while on battery power; some * report this when changing to/from tablet - * mode. Squelch this event. + * mode; some report this when the keyboard + * backlight has changed. */ + ideapad_kbd_bl_notify(priv); break; case 0: ideapad_check_special_buttons(priv); @@ -1338,6 +1456,9 @@ static void ideapad_check_features(struct ideapad_private *priv) if (!eval_hals(handle, &val)) { if (test_bit(HALS_FNLOCK_SUPPORT_BIT, &val)) priv->features.fn_lock = true; + + if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) + priv->features.kbd_bl = true; } } } @@ -1379,6 +1500,14 @@ static int ideapad_acpi_add(struct platform_device *pdev) if (err) goto input_failed; + err = ideapad_kbd_bl_init(priv); + if (err) { + if (err != -ENODEV) + dev_warn(&pdev->dev, "Could not set up keyboard backlight LED: %d\n", err); + else + dev_info(&pdev->dev, "Keyboard backlight control not available\n"); + } + /* * On some models without a hw-switch (the yoga 2 13 at least) * VPCCMD_W_RF must be explicitly set to 1 for the wifi to work. @@ -1453,6 +1582,7 @@ backlight_failed: for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + ideapad_kbd_bl_exit(priv); ideapad_input_exit(priv); input_failed: @@ -1482,6 +1612,7 @@ static int ideapad_acpi_remove(struct platform_device *pdev) for (i = 0; i < IDEAPAD_RFKILL_DEV_NUM; i++) ideapad_unregister_rfkill(priv, i); + ideapad_kbd_bl_exit(priv); ideapad_input_exit(priv); ideapad_debugfs_exit(priv); ideapad_sysfs_exit(priv); -- cgit v1.2.3 From 6b49dea4fd9c539f5fea61f6a203ec1349292a26 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Wed, 3 Feb 2021 21:57:13 +0000 Subject: platform/x86: ideapad-laptop: add "always on USB charging" control support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Certain models have a so-called "always on USB charging" feature, which enables USB charging even when the computer is turned off or suspended, and which may be controlled/queried using the SALS/HALS ACPI methods. Expose this functionality via a new device attribute (usb_charging). Tested on: Lenovo YOGA 520-14IKB 80X8 Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210203215403.290792-28-pobrn@protonmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-platform-ideapad-laptop | 9 +++ drivers/platform/x86/ideapad-laptop.c | 65 +++++++++++++++++++--- 2 files changed, 65 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/Documentation/ABI/testing/sysfs-platform-ideapad-laptop b/Documentation/ABI/testing/sysfs-platform-ideapad-laptop index fd2ac02bc5bd..b17688d73922 100644 --- a/Documentation/ABI/testing/sysfs-platform-ideapad-laptop +++ b/Documentation/ABI/testing/sysfs-platform-ideapad-laptop @@ -41,3 +41,12 @@ Description: # echo "0" > \ /sys/bus/pci/devices/0000:00:1f.0/PNP0C09:00/VPC2004:00/fn_lock + +What: /sys/bus/platform/devices/VPC2004:*/usb_charging +Date: Feb 2021 +KernelVersion: 5.12 +Contact: platform-driver-x86@vger.kernel.org +Description: + Controls whether the "always on USB charging" feature is + enabled or not. This feature enables charging USB devices + even if the computer is not turned on. diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 24dcb4ad75ef..6cb5ad4be231 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -62,18 +62,22 @@ enum { }; enum { - HALS_KBD_BL_SUPPORT_BIT = 4, - HALS_KBD_BL_STATE_BIT = 5, - HALS_FNLOCK_SUPPORT_BIT = 9, - HALS_FNLOCK_STATE_BIT = 10, - HALS_HOTKEYS_PRIMARY_BIT = 11, + HALS_KBD_BL_SUPPORT_BIT = 4, + HALS_KBD_BL_STATE_BIT = 5, + HALS_USB_CHARGING_SUPPORT_BIT = 6, + HALS_USB_CHARGING_STATE_BIT = 7, + HALS_FNLOCK_SUPPORT_BIT = 9, + HALS_FNLOCK_STATE_BIT = 10, + HALS_HOTKEYS_PRIMARY_BIT = 11, }; enum { - SALS_KBD_BL_ON = 0x8, - SALS_KBD_BL_OFF = 0x9, - SALS_FNLOCK_ON = 0xe, - SALS_FNLOCK_OFF = 0xf, + SALS_KBD_BL_ON = 0x8, + SALS_KBD_BL_OFF = 0x9, + SALS_USB_CHARGING_ON = 0xa, + SALS_USB_CHARGING_OFF = 0xb, + SALS_FNLOCK_ON = 0xe, + SALS_FNLOCK_OFF = 0xf, }; enum { @@ -134,6 +138,7 @@ struct ideapad_private { bool hw_rfkill_switch : 1; bool kbd_bl : 1; bool touchpad_ctrl_via_ec : 1; + bool usb_charging : 1; } features; struct { bool initialized; @@ -592,12 +597,49 @@ static ssize_t touchpad_store(struct device *dev, static DEVICE_ATTR_RW(touchpad); +static ssize_t usb_charging_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + unsigned long hals; + int err; + + err = eval_hals(priv->adev->handle, &hals); + if (err) + return err; + + return sysfs_emit(buf, "%d\n", !!test_bit(HALS_USB_CHARGING_STATE_BIT, &hals)); +} + +static ssize_t usb_charging_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ideapad_private *priv = dev_get_drvdata(dev); + bool state; + int err; + + err = kstrtobool(buf, &state); + if (err) + return err; + + err = exec_sals(priv->adev->handle, state ? SALS_USB_CHARGING_ON : SALS_USB_CHARGING_OFF); + if (err) + return err; + + return count; +} + +static DEVICE_ATTR_RW(usb_charging); + static struct attribute *ideapad_attributes[] = { &dev_attr_camera_power.attr, &dev_attr_conservation_mode.attr, &dev_attr_fan_mode.attr, &dev_attr_fn_lock.attr, &dev_attr_touchpad.attr, + &dev_attr_usb_charging.attr, NULL }; @@ -620,6 +662,8 @@ static umode_t ideapad_is_visible(struct kobject *kobj, else if (attr == &dev_attr_touchpad.attr) supported = priv->features.touchpad_ctrl_via_ec && test_bit(CFG_CAP_TOUCHPAD_BIT, &priv->cfg); + else if (attr == &dev_attr_usb_charging.attr) + supported = priv->features.usb_charging; return supported ? attr->mode : 0; } @@ -1459,6 +1503,9 @@ static void ideapad_check_features(struct ideapad_private *priv) if (test_bit(HALS_KBD_BL_SUPPORT_BIT, &val)) priv->features.kbd_bl = true; + + if (test_bit(HALS_USB_CHARGING_SUPPORT_BIT, &val)) + priv->features.usb_charging = true; } } } -- cgit v1.2.3 From f1e1ea516721d1ea0b21327ff9e6cb2c2bb86e28 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 3 Feb 2021 13:58:32 -0600 Subject: platform/x86: Move all dell drivers to their own subdirectory A user without a Dell system doesn't need to pick any of these drivers. Users with a Dell system can enable this submenu and all drivers behind it will be enabled. Suggested-by: Andy Shevchenko Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20210203195832.2950605-1-mario.limonciello@dell.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- MAINTAINERS | 22 +- drivers/platform/x86/Kconfig | 176 +- drivers/platform/x86/Makefile | 16 +- drivers/platform/x86/alienware-wmi.c | 853 -------- drivers/platform/x86/dcdbas.c | 770 ------- drivers/platform/x86/dcdbas.h | 109 - drivers/platform/x86/dell-laptop.c | 2303 -------------------- drivers/platform/x86/dell-rbtn.c | 499 ----- drivers/platform/x86/dell-rbtn.h | 16 - drivers/platform/x86/dell-smbios-base.c | 652 ------ drivers/platform/x86/dell-smbios-smm.c | 183 -- drivers/platform/x86/dell-smbios-wmi.c | 277 --- drivers/platform/x86/dell-smbios.h | 100 - drivers/platform/x86/dell-smo8800.c | 232 -- drivers/platform/x86/dell-wmi-aio.c | 197 -- drivers/platform/x86/dell-wmi-descriptor.c | 206 -- drivers/platform/x86/dell-wmi-descriptor.h | 25 - drivers/platform/x86/dell-wmi-led.c | 186 -- drivers/platform/x86/dell-wmi-sysman/Makefile | 8 - .../x86/dell-wmi-sysman/biosattr-interface.c | 186 -- .../platform/x86/dell-wmi-sysman/dell-wmi-sysman.h | 191 -- .../platform/x86/dell-wmi-sysman/enum-attributes.c | 189 -- .../platform/x86/dell-wmi-sysman/int-attributes.c | 179 -- .../x86/dell-wmi-sysman/passobj-attributes.c | 187 -- .../x86/dell-wmi-sysman/passwordattr-interface.c | 153 -- .../x86/dell-wmi-sysman/string-attributes.c | 159 -- drivers/platform/x86/dell-wmi-sysman/sysman.c | 631 ------ drivers/platform/x86/dell-wmi.c | 764 ------- drivers/platform/x86/dell/Kconfig | 207 ++ drivers/platform/x86/dell/Makefile | 21 + drivers/platform/x86/dell/alienware-wmi.c | 853 ++++++++ drivers/platform/x86/dell/dcdbas.c | 770 +++++++ drivers/platform/x86/dell/dcdbas.h | 109 + drivers/platform/x86/dell/dell-laptop.c | 2303 ++++++++++++++++++++ drivers/platform/x86/dell/dell-rbtn.c | 499 +++++ drivers/platform/x86/dell/dell-rbtn.h | 16 + drivers/platform/x86/dell/dell-smbios-base.c | 652 ++++++ drivers/platform/x86/dell/dell-smbios-smm.c | 183 ++ drivers/platform/x86/dell/dell-smbios-wmi.c | 277 +++ drivers/platform/x86/dell/dell-smbios.h | 100 + drivers/platform/x86/dell/dell-smo8800.c | 232 ++ drivers/platform/x86/dell/dell-wmi-aio.c | 197 ++ drivers/platform/x86/dell/dell-wmi-descriptor.c | 206 ++ drivers/platform/x86/dell/dell-wmi-descriptor.h | 25 + drivers/platform/x86/dell/dell-wmi-led.c | 186 ++ drivers/platform/x86/dell/dell-wmi-sysman/Makefile | 8 + .../x86/dell/dell-wmi-sysman/biosattr-interface.c | 186 ++ .../x86/dell/dell-wmi-sysman/dell-wmi-sysman.h | 191 ++ .../x86/dell/dell-wmi-sysman/enum-attributes.c | 189 ++ .../x86/dell/dell-wmi-sysman/int-attributes.c | 179 ++ .../x86/dell/dell-wmi-sysman/passobj-attributes.c | 187 ++ .../dell/dell-wmi-sysman/passwordattr-interface.c | 153 ++ .../x86/dell/dell-wmi-sysman/string-attributes.c | 159 ++ drivers/platform/x86/dell/dell-wmi-sysman/sysman.c | 631 ++++++ drivers/platform/x86/dell/dell-wmi.c | 764 +++++++ drivers/platform/x86/dell/dell_rbu.c | 680 ++++++ drivers/platform/x86/dell_rbu.c | 680 ------ 57 files changed, 10176 insertions(+), 10136 deletions(-) delete mode 100644 drivers/platform/x86/alienware-wmi.c delete mode 100644 drivers/platform/x86/dcdbas.c delete mode 100644 drivers/platform/x86/dcdbas.h delete mode 100644 drivers/platform/x86/dell-laptop.c delete mode 100644 drivers/platform/x86/dell-rbtn.c delete mode 100644 drivers/platform/x86/dell-rbtn.h delete mode 100644 drivers/platform/x86/dell-smbios-base.c delete mode 100644 drivers/platform/x86/dell-smbios-smm.c delete mode 100644 drivers/platform/x86/dell-smbios-wmi.c delete mode 100644 drivers/platform/x86/dell-smbios.h delete mode 100644 drivers/platform/x86/dell-smo8800.c delete mode 100644 drivers/platform/x86/dell-wmi-aio.c delete mode 100644 drivers/platform/x86/dell-wmi-descriptor.c delete mode 100644 drivers/platform/x86/dell-wmi-descriptor.h delete mode 100644 drivers/platform/x86/dell-wmi-led.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/Makefile delete mode 100644 drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h delete mode 100644 drivers/platform/x86/dell-wmi-sysman/enum-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/int-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/string-attributes.c delete mode 100644 drivers/platform/x86/dell-wmi-sysman/sysman.c delete mode 100644 drivers/platform/x86/dell-wmi.c create mode 100644 drivers/platform/x86/dell/Kconfig create mode 100644 drivers/platform/x86/dell/Makefile create mode 100644 drivers/platform/x86/dell/alienware-wmi.c create mode 100644 drivers/platform/x86/dell/dcdbas.c create mode 100644 drivers/platform/x86/dell/dcdbas.h create mode 100644 drivers/platform/x86/dell/dell-laptop.c create mode 100644 drivers/platform/x86/dell/dell-rbtn.c create mode 100644 drivers/platform/x86/dell/dell-rbtn.h create mode 100644 drivers/platform/x86/dell/dell-smbios-base.c create mode 100644 drivers/platform/x86/dell/dell-smbios-smm.c create mode 100644 drivers/platform/x86/dell/dell-smbios-wmi.c create mode 100644 drivers/platform/x86/dell/dell-smbios.h create mode 100644 drivers/platform/x86/dell/dell-smo8800.c create mode 100644 drivers/platform/x86/dell/dell-wmi-aio.c create mode 100644 drivers/platform/x86/dell/dell-wmi-descriptor.c create mode 100644 drivers/platform/x86/dell/dell-wmi-descriptor.h create mode 100644 drivers/platform/x86/dell/dell-wmi-led.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/Makefile create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c create mode 100644 drivers/platform/x86/dell/dell-wmi-sysman/sysman.c create mode 100644 drivers/platform/x86/dell/dell-wmi.c create mode 100644 drivers/platform/x86/dell/dell_rbu.c delete mode 100644 drivers/platform/x86/dell_rbu.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 25de2d7aaa46..34bfa5c1aec5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4970,17 +4970,17 @@ M: Matthew Garrett M: Pali Rohár L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-laptop.c +F: drivers/platform/x86/dell/dell-laptop.c DELL LAPTOP FREEFALL DRIVER M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-smo8800.c +F: drivers/platform/x86/dell/dell-smo8800.c DELL LAPTOP RBTN DRIVER M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-rbtn.* +F: drivers/platform/x86/dell/dell-rbtn.* DELL LAPTOP SMM DRIVER M: Pali Rohár @@ -4992,26 +4992,26 @@ DELL REMOTE BIOS UPDATE DRIVER M: Stuart Hayes L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell_rbu.c +F: drivers/platform/x86/dell/dell_rbu.c DELL SMBIOS DRIVER M: Pali Rohár M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios.* +F: drivers/platform/x86/dell/dell-smbios.* DELL SMBIOS SMM DRIVER M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios-smm.c +F: drivers/platform/x86/dell/dell-smbios-smm.c DELL SMBIOS WMI DRIVER M: Mario Limonciello L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/dell-smbios-wmi.c +F: drivers/platform/x86/dell/dell-smbios-wmi.c F: tools/wmi/dell-smbios-example.c DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas) @@ -5019,12 +5019,12 @@ M: Stuart Hayes L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/driver-api/dcdbas.rst -F: drivers/platform/x86/dcdbas.* +F: drivers/platform/x86/dell/dcdbas.* DELL WMI DESCRIPTOR DRIVER M: Mario Limonciello S: Maintained -F: drivers/platform/x86/dell-wmi-descriptor.c +F: drivers/platform/x86/dell/dell-wmi-descriptor.c DELL WMI SYSMAN DRIVER M: Divya Bharathi @@ -5033,13 +5033,13 @@ M: Prasanth Ksr L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-class-firmware-attributes -F: drivers/platform/x86/dell-wmi-sysman/ +F: drivers/platform/x86/dell/dell-wmi-sysman/ DELL WMI NOTIFICATIONS DRIVER M: Matthew Garrett M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell-wmi.c +F: drivers/platform/x86/dell/dell-wmi.c DELTA ST MEDIA DRIVER M: Hugues Fruchet diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 175ae4ae0028..cf76e724e8c3 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -49,18 +49,6 @@ config WMI_BMOF To compile this driver as a module, choose M here: the module will be called wmi-bmof. -config ALIENWARE_WMI - tristate "Alienware Special feature control" - depends on ACPI - depends on LEDS_CLASS - depends on NEW_LEDS - depends on ACPI_WMI - help - This is a driver for controlling Alienware BIOS driven - features. It exposes an interface for controlling the AlienFX - zones on Alienware machines that don't contain a dedicated AlienFX - USB MCU such as the X51 and X51-R2. - config HUAWEI_WMI tristate "Huawei WMI laptop extras driver" depends on ACPI_BATTERY @@ -327,169 +315,7 @@ config EEEPC_WMI If you have an ACPI-WMI compatible Eee PC laptop (>= 1000), say Y or M here. -config DCDBAS - tristate "Dell Systems Management Base Driver" - depends on X86 - help - The Dell Systems Management Base Driver provides a sysfs interface - for systems management software to perform System Management - Interrupts (SMIs) and Host Control Actions (system power cycle or - power off after OS shutdown) on certain Dell systems. - - See for more details on the driver - and the Dell systems on which Dell systems management software makes - use of this driver. - - Say Y or M here to enable the driver for use by Dell systems - management software such as Dell OpenManage. - -# -# The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those -# backends are selected. The "depends" line prevents a configuration -# where DELL_SMBIOS=y while either of those dependencies =m. -# -config DELL_SMBIOS - tristate "Dell SMBIOS driver" - depends on DCDBAS || DCDBAS=n - depends on ACPI_WMI || ACPI_WMI=n - help - This provides support for the Dell SMBIOS calling interface. - If you have a Dell computer you should enable this option. - - Be sure to select at least one backend for it to work properly. - -config DELL_SMBIOS_WMI - bool "Dell SMBIOS driver WMI backend" - default y - depends on ACPI_WMI - select DELL_WMI_DESCRIPTOR - depends on DELL_SMBIOS - help - This provides an implementation for the Dell SMBIOS calling interface - communicated over ACPI-WMI. - - If you have a Dell computer from >2007 you should say Y here. - If you aren't sure and this module doesn't work for your computer - it just won't load. - -config DELL_SMBIOS_SMM - bool "Dell SMBIOS driver SMM backend" - default y - depends on DCDBAS - depends on DELL_SMBIOS - help - This provides an implementation for the Dell SMBIOS calling interface - communicated over SMI/SMM. - - If you have a Dell computer from <=2017 you should say Y here. - If you aren't sure and this module doesn't work for your computer - it just won't load. - -config DELL_LAPTOP - tristate "Dell Laptop Extras" - depends on DMI - depends on BACKLIGHT_CLASS_DEVICE - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on RFKILL || RFKILL = n - depends on SERIO_I8042 - depends on DELL_SMBIOS - select POWER_SUPPLY - select LEDS_CLASS - select NEW_LEDS - select LEDS_TRIGGERS - select LEDS_TRIGGER_AUDIO - help - This driver adds support for rfkill and backlight control to Dell - laptops (except for some models covered by the Compal driver). - -config DELL_RBTN - tristate "Dell Airplane Mode Switch driver" - depends on ACPI - depends on INPUT - depends on RFKILL - help - Say Y here if you want to support Dell Airplane Mode Switch ACPI - device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. - This driver register rfkill device or input hotkey device depending - on hardware type (hw switch slider or keyboard toggle button). For - rfkill devices it receive HW switch events and set correct hard - rfkill state. - - To compile this driver as a module, choose M here: the module will - be called dell-rbtn. - -config DELL_RBU - tristate "BIOS update support for DELL systems via sysfs" - depends on X86 - select FW_LOADER - select FW_LOADER_USER_HELPER - help - Say m if you want to have the option of updating the BIOS for your - DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) - supporting application to communicate with the BIOS regarding the new - image for the image update to take effect. - See for more details on the driver. - -config DELL_SMO8800 - tristate "Dell Latitude freefall driver (ACPI SMO88XX)" - depends on ACPI - help - Say Y here if you want to support SMO88XX freefall devices - on Dell Latitude laptops. - - To compile this driver as a module, choose M here: the module will - be called dell-smo8800. - -config DELL_WMI - tristate "Dell WMI notifications" - depends on ACPI_WMI - depends on DMI - depends on INPUT - depends on ACPI_VIDEO || ACPI_VIDEO = n - depends on DELL_SMBIOS - select DELL_WMI_DESCRIPTOR - select INPUT_SPARSEKMAP - help - Say Y here if you want to support WMI-based hotkeys on Dell laptops. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi. - -config DELL_WMI_SYSMAN - tristate "Dell WMI-based Systems management driver" - depends on ACPI_WMI - depends on DMI - select NLS - help - This driver allows changing BIOS settings on many Dell machines from - 2018 and newer without the use of any additional software. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi-sysman. - -config DELL_WMI_DESCRIPTOR - tristate - depends on ACPI_WMI - -config DELL_WMI_AIO - tristate "WMI Hotkeys for Dell All-In-One series" - depends on ACPI_WMI - depends on INPUT - select INPUT_SPARSEKMAP - help - Say Y here if you want to support WMI-based hotkeys on Dell - All-In-One machines. - - To compile this driver as a module, choose M here: the module will - be called dell-wmi-aio. - -config DELL_WMI_LED - tristate "External LED on Dell Business Netbooks" - depends on LEDS_CLASS - depends on ACPI_WMI - help - This adds support for the Latitude 2100 and similar - notebooks that have an external LED. +source "drivers/platform/x86/dell/Kconfig" config AMILO_RFKILL tristate "Fujitsu-Siemens Amilo rfkill support" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 19306450d791..60d554073749 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -9,7 +9,6 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o # WMI drivers -obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_INTEL_WMI_SBL_FW_UPDATE) += intel-wmi-sbl-fw-update.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o @@ -37,20 +36,7 @@ obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o obj-$(CONFIG_EEEPC_WMI) += eeepc-wmi.o # Dell -obj-$(CONFIG_DCDBAS) += dcdbas.o -obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o -dell-smbios-objs := dell-smbios-base.o -dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o -dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o -obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o -obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o -obj-$(CONFIG_DELL_RBU) += dell_rbu.o -obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o -obj-$(CONFIG_DELL_WMI) += dell-wmi.o -obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o -obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o -obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o -obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ +obj-$(CONFIG_X86_PLATFORM_DRIVERS_DELL) += dell/ # Fujitsu obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c deleted file mode 100644 index 5bb2859c8285..000000000000 --- a/drivers/platform/x86/alienware-wmi.c +++ /dev/null @@ -1,853 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Alienware AlienFX control - * - * Copyright (C) 2014 Dell Inc - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include - -#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" -#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" -#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" - -#define WMAX_METHOD_HDMI_SOURCE 0x1 -#define WMAX_METHOD_HDMI_STATUS 0x2 -#define WMAX_METHOD_BRIGHTNESS 0x3 -#define WMAX_METHOD_ZONE_CONTROL 0x4 -#define WMAX_METHOD_HDMI_CABLE 0x5 -#define WMAX_METHOD_AMPLIFIER_CABLE 0x6 -#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B -#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C - -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Alienware special feature control"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); -MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); - -enum INTERFACE_FLAGS { - LEGACY, - WMAX, -}; - -enum LEGACY_CONTROL_STATES { - LEGACY_RUNNING = 1, - LEGACY_BOOTING = 0, - LEGACY_SUSPEND = 3, -}; - -enum WMAX_CONTROL_STATES { - WMAX_RUNNING = 0xFF, - WMAX_BOOTING = 0, - WMAX_SUSPEND = 3, -}; - -struct quirk_entry { - u8 num_zones; - u8 hdmi_mux; - u8 amplifier; - u8 deepslp; -}; - -static struct quirk_entry *quirks; - - -static struct quirk_entry quirk_inspiron5675 = { - .num_zones = 2, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_unknown = { - .num_zones = 2, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_x51_r1_r2 = { - .num_zones = 3, - .hdmi_mux = 0, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_x51_r3 = { - .num_zones = 4, - .hdmi_mux = 0, - .amplifier = 1, - .deepslp = 0, -}; - -static struct quirk_entry quirk_asm100 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 0, - .deepslp = 0, -}; - -static struct quirk_entry quirk_asm200 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 0, - .deepslp = 1, -}; - -static struct quirk_entry quirk_asm201 = { - .num_zones = 2, - .hdmi_mux = 1, - .amplifier = 1, - .deepslp = 1, -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - quirks = dmi->driver_data; - return 1; -} - -static const struct dmi_system_id alienware_quirks[] __initconst = { - { - .callback = dmi_matched, - .ident = "Alienware X51 R3", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), - }, - .driver_data = &quirk_x51_r3, - }, - { - .callback = dmi_matched, - .ident = "Alienware X51 R2", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), - }, - .driver_data = &quirk_x51_r1_r2, - }, - { - .callback = dmi_matched, - .ident = "Alienware X51 R1", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), - }, - .driver_data = &quirk_x51_r1_r2, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM100", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), - }, - .driver_data = &quirk_asm100, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM200", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), - }, - .driver_data = &quirk_asm200, - }, - { - .callback = dmi_matched, - .ident = "Alienware ASM201", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), - DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), - }, - .driver_data = &quirk_asm201, - }, - { - .callback = dmi_matched, - .ident = "Dell Inc. Inspiron 5675", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), - }, - .driver_data = &quirk_inspiron5675, - }, - {} -}; - -struct color_platform { - u8 blue; - u8 green; - u8 red; -} __packed; - -struct platform_zone { - u8 location; - struct device_attribute *attr; - struct color_platform colors; -}; - -struct wmax_brightness_args { - u32 led_mask; - u32 percentage; -}; - -struct wmax_basic_args { - u8 arg; -}; - -struct legacy_led_args { - struct color_platform colors; - u8 brightness; - u8 state; -} __packed; - -struct wmax_led_args { - u32 led_mask; - struct color_platform colors; - u8 state; -} __packed; - -static struct platform_device *platform_device; -static struct device_attribute *zone_dev_attrs; -static struct attribute **zone_attrs; -static struct platform_zone *zone_data; - -static struct platform_driver platform_driver = { - .driver = { - .name = "alienware-wmi", - } -}; - -static struct attribute_group zone_attribute_group = { - .name = "rgb_zones", -}; - -static u8 interface; -static u8 lighting_control_state; -static u8 global_brightness; - -/* - * Helpers used for zone control - */ -static int parse_rgb(const char *buf, struct platform_zone *zone) -{ - long unsigned int rgb; - int ret; - union color_union { - struct color_platform cp; - int package; - } repackager; - - ret = kstrtoul(buf, 16, &rgb); - if (ret) - return ret; - - /* RGB triplet notation is 24-bit hexadecimal */ - if (rgb > 0xFFFFFF) - return -EINVAL; - - repackager.package = rgb & 0x0f0f0f0f; - pr_debug("alienware-wmi: r: %d g:%d b: %d\n", - repackager.cp.red, repackager.cp.green, repackager.cp.blue); - zone->colors = repackager.cp; - return 0; -} - -static struct platform_zone *match_zone(struct device_attribute *attr) -{ - u8 zone; - - for (zone = 0; zone < quirks->num_zones; zone++) { - if ((struct device_attribute *)zone_data[zone].attr == attr) { - pr_debug("alienware-wmi: matched zone location: %d\n", - zone_data[zone].location); - return &zone_data[zone]; - } - } - return NULL; -} - -/* - * Individual RGB zone control - */ -static int alienware_update_led(struct platform_zone *zone) -{ - int method_id; - acpi_status status; - char *guid; - struct acpi_buffer input; - struct legacy_led_args legacy_args; - struct wmax_led_args wmax_basic_args; - if (interface == WMAX) { - wmax_basic_args.led_mask = 1 << zone->location; - wmax_basic_args.colors = zone->colors; - wmax_basic_args.state = lighting_control_state; - guid = WMAX_CONTROL_GUID; - method_id = WMAX_METHOD_ZONE_CONTROL; - - input.length = (acpi_size) sizeof(wmax_basic_args); - input.pointer = &wmax_basic_args; - } else { - legacy_args.colors = zone->colors; - legacy_args.brightness = global_brightness; - legacy_args.state = 0; - if (lighting_control_state == LEGACY_BOOTING || - lighting_control_state == LEGACY_SUSPEND) { - guid = LEGACY_POWER_CONTROL_GUID; - legacy_args.state = lighting_control_state; - } else - guid = LEGACY_CONTROL_GUID; - method_id = zone->location + 1; - - input.length = (acpi_size) sizeof(legacy_args); - input.pointer = &legacy_args; - } - pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); - - status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: zone set failure: %u\n", status); - return ACPI_FAILURE(status); -} - -static ssize_t zone_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct platform_zone *target_zone; - target_zone = match_zone(attr); - if (target_zone == NULL) - return sprintf(buf, "red: -1, green: -1, blue: -1\n"); - return sprintf(buf, "red: %d, green: %d, blue: %d\n", - target_zone->colors.red, - target_zone->colors.green, target_zone->colors.blue); - -} - -static ssize_t zone_set(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct platform_zone *target_zone; - int ret; - target_zone = match_zone(attr); - if (target_zone == NULL) { - pr_err("alienware-wmi: invalid target zone\n"); - return 1; - } - ret = parse_rgb(buf, target_zone); - if (ret) - return ret; - ret = alienware_update_led(target_zone); - return ret ? ret : count; -} - -/* - * LED Brightness (Global) - */ -static int wmax_brightness(int brightness) -{ - acpi_status status; - struct acpi_buffer input; - struct wmax_brightness_args args = { - .led_mask = 0xFF, - .percentage = brightness, - }; - input.length = (acpi_size) sizeof(args); - input.pointer = &args; - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - WMAX_METHOD_BRIGHTNESS, &input, NULL); - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: brightness set failure: %u\n", status); - return ACPI_FAILURE(status); -} - -static void global_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - int ret; - global_brightness = brightness; - if (interface == WMAX) - ret = wmax_brightness(brightness); - else - ret = alienware_update_led(&zone_data[0]); - if (ret) - pr_err("LED brightness update failed\n"); -} - -static enum led_brightness global_led_get(struct led_classdev *led_cdev) -{ - return global_brightness; -} - -static struct led_classdev global_led = { - .brightness_set = global_led_set, - .brightness_get = global_led_get, - .name = "alienware::global_brightness", -}; - -/* - * Lighting control state device attribute (Global) - */ -static ssize_t show_control_state(struct device *dev, - struct device_attribute *attr, char *buf) -{ - if (lighting_control_state == LEGACY_BOOTING) - return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); - else if (lighting_control_state == LEGACY_SUSPEND) - return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); - return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); -} - -static ssize_t store_control_state(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - long unsigned int val; - if (strcmp(buf, "booting\n") == 0) - val = LEGACY_BOOTING; - else if (strcmp(buf, "suspend\n") == 0) - val = LEGACY_SUSPEND; - else if (interface == LEGACY) - val = LEGACY_RUNNING; - else - val = WMAX_RUNNING; - lighting_control_state = val; - pr_debug("alienware-wmi: updated control state to %d\n", - lighting_control_state); - return count; -} - -static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, - store_control_state); - -static int alienware_zone_init(struct platform_device *dev) -{ - u8 zone; - char buffer[10]; - char *name; - - if (interface == WMAX) { - lighting_control_state = WMAX_RUNNING; - } else if (interface == LEGACY) { - lighting_control_state = LEGACY_RUNNING; - } - global_led.max_brightness = 0x0F; - global_brightness = global_led.max_brightness; - - /* - * - zone_dev_attrs num_zones + 1 is for individual zones and then - * null terminated - * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + - * the lighting control + null terminated - * - zone_data num_zones is for the distinct zones - */ - zone_dev_attrs = - kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), - GFP_KERNEL); - if (!zone_dev_attrs) - return -ENOMEM; - - zone_attrs = - kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), - GFP_KERNEL); - if (!zone_attrs) - return -ENOMEM; - - zone_data = - kcalloc(quirks->num_zones, sizeof(struct platform_zone), - GFP_KERNEL); - if (!zone_data) - return -ENOMEM; - - for (zone = 0; zone < quirks->num_zones; zone++) { - sprintf(buffer, "zone%02hhX", zone); - name = kstrdup(buffer, GFP_KERNEL); - if (name == NULL) - return 1; - sysfs_attr_init(&zone_dev_attrs[zone].attr); - zone_dev_attrs[zone].attr.name = name; - zone_dev_attrs[zone].attr.mode = 0644; - zone_dev_attrs[zone].show = zone_show; - zone_dev_attrs[zone].store = zone_set; - zone_data[zone].location = zone; - zone_attrs[zone] = &zone_dev_attrs[zone].attr; - zone_data[zone].attr = &zone_dev_attrs[zone]; - } - zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; - zone_attribute_group.attrs = zone_attrs; - - led_classdev_register(&dev->dev, &global_led); - - return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); -} - -static void alienware_zone_exit(struct platform_device *dev) -{ - u8 zone; - - sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); - led_classdev_unregister(&global_led); - if (zone_dev_attrs) { - for (zone = 0; zone < quirks->num_zones; zone++) - kfree(zone_dev_attrs[zone].attr.name); - } - kfree(zone_dev_attrs); - kfree(zone_data); - kfree(zone_attrs); -} - -static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, - u32 command, int *out_data) -{ - acpi_status status; - union acpi_object *obj; - struct acpi_buffer input; - struct acpi_buffer output; - - input.length = (acpi_size) sizeof(*in_args); - input.pointer = in_args; - if (out_data) { - output.length = ACPI_ALLOCATE_BUFFER; - output.pointer = NULL; - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - command, &input, &output); - if (ACPI_SUCCESS(status)) { - obj = (union acpi_object *)output.pointer; - if (obj && obj->type == ACPI_TYPE_INTEGER) - *out_data = (u32)obj->integer.value; - } - kfree(output.pointer); - } else { - status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, - command, &input, NULL); - } - return status; -} - -/* - * The HDMI mux sysfs node indicates the status of the HDMI input mux. - * It can toggle between standard system GPU output and HDMI input. - */ -static ssize_t show_hdmi_cable(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return scnprintf(buf, PAGE_SIZE, - "[unconnected] connected unknown\n"); - else if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "unconnected [connected] unknown\n"); - } - pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); - return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); -} - -static ssize_t show_hdmi_source(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS, - (u32 *) &out_data); - - if (ACPI_SUCCESS(status)) { - if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "[input] gpu unknown\n"); - else if (out_data == 2) - return scnprintf(buf, PAGE_SIZE, - "input [gpu] unknown\n"); - } - pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); - return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); -} - -static ssize_t toggle_hdmi_source(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - acpi_status status; - struct wmax_basic_args args; - if (strcmp(buf, "gpu\n") == 0) - args.arg = 1; - else if (strcmp(buf, "input\n") == 0) - args.arg = 2; - else - args.arg = 3; - pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); - - status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL); - - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", - status); - return count; -} - -static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); -static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, - toggle_hdmi_source); - -static struct attribute *hdmi_attrs[] = { - &dev_attr_cable.attr, - &dev_attr_source.attr, - NULL, -}; - -static const struct attribute_group hdmi_attribute_group = { - .name = "hdmi", - .attrs = hdmi_attrs, -}; - -static void remove_hdmi(struct platform_device *dev) -{ - if (quirks->hdmi_mux > 0) - sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); -} - -static int create_hdmi(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); - if (ret) - remove_hdmi(dev); - return ret; -} - -/* - * Alienware GFX amplifier support - * - Currently supports reading cable status - * - Leaving expansion room to possibly support dock/undock events later - */ -static ssize_t show_amplifier_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = - alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return scnprintf(buf, PAGE_SIZE, - "[unconnected] connected unknown\n"); - else if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "unconnected [connected] unknown\n"); - } - pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); - return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); -} - -static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); - -static struct attribute *amplifier_attrs[] = { - &dev_attr_status.attr, - NULL, -}; - -static const struct attribute_group amplifier_attribute_group = { - .name = "amplifier", - .attrs = amplifier_attrs, -}; - -static void remove_amplifier(struct platform_device *dev) -{ - if (quirks->amplifier > 0) - sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); -} - -static int create_amplifier(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); - if (ret) - remove_amplifier(dev); - return ret; -} - -/* - * Deep Sleep Control support - * - Modifies BIOS setting for deep sleep control allowing extra wakeup events - */ -static ssize_t show_deepsleep_status(struct device *dev, - struct device_attribute *attr, char *buf) -{ - acpi_status status; - u32 out_data; - struct wmax_basic_args in_args = { - .arg = 0, - }; - status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS, - (u32 *) &out_data); - if (ACPI_SUCCESS(status)) { - if (out_data == 0) - return scnprintf(buf, PAGE_SIZE, - "[disabled] s5 s5_s4\n"); - else if (out_data == 1) - return scnprintf(buf, PAGE_SIZE, - "disabled [s5] s5_s4\n"); - else if (out_data == 2) - return scnprintf(buf, PAGE_SIZE, - "disabled s5 [s5_s4]\n"); - } - pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); - return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n"); -} - -static ssize_t toggle_deepsleep(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - acpi_status status; - struct wmax_basic_args args; - - if (strcmp(buf, "disabled\n") == 0) - args.arg = 0; - else if (strcmp(buf, "s5\n") == 0) - args.arg = 1; - else - args.arg = 2; - pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); - - status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL, - NULL); - - if (ACPI_FAILURE(status)) - pr_err("alienware-wmi: deep sleep control failed: results: %u\n", - status); - return count; -} - -static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); - -static struct attribute *deepsleep_attrs[] = { - &dev_attr_deepsleep.attr, - NULL, -}; - -static const struct attribute_group deepsleep_attribute_group = { - .name = "deepsleep", - .attrs = deepsleep_attrs, -}; - -static void remove_deepsleep(struct platform_device *dev) -{ - if (quirks->deepslp > 0) - sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); -} - -static int create_deepsleep(struct platform_device *dev) -{ - int ret; - - ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); - if (ret) - remove_deepsleep(dev); - return ret; -} - -static int __init alienware_wmi_init(void) -{ - int ret; - - if (wmi_has_guid(LEGACY_CONTROL_GUID)) - interface = LEGACY; - else if (wmi_has_guid(WMAX_CONTROL_GUID)) - interface = WMAX; - else { - pr_warn("alienware-wmi: No known WMI GUID found\n"); - return -ENODEV; - } - - dmi_check_system(alienware_quirks); - if (quirks == NULL) - quirks = &quirk_unknown; - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - platform_device = platform_device_alloc("alienware-wmi", -1); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device1; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device2; - - if (quirks->hdmi_mux > 0) { - ret = create_hdmi(platform_device); - if (ret) - goto fail_prep_hdmi; - } - - if (quirks->amplifier > 0) { - ret = create_amplifier(platform_device); - if (ret) - goto fail_prep_amplifier; - } - - if (quirks->deepslp > 0) { - ret = create_deepsleep(platform_device); - if (ret) - goto fail_prep_deepsleep; - } - - ret = alienware_zone_init(platform_device); - if (ret) - goto fail_prep_zones; - - return 0; - -fail_prep_zones: - alienware_zone_exit(platform_device); -fail_prep_deepsleep: -fail_prep_amplifier: -fail_prep_hdmi: - platform_device_del(platform_device); -fail_platform_device2: - platform_device_put(platform_device); -fail_platform_device1: - platform_driver_unregister(&platform_driver); -fail_platform_driver: - return ret; -} - -module_init(alienware_wmi_init); - -static void __exit alienware_wmi_exit(void) -{ - if (platform_device) { - alienware_zone_exit(platform_device); - remove_hdmi(platform_device); - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } -} - -module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/dcdbas.c b/drivers/platform/x86/dcdbas.c deleted file mode 100644 index d513a59a5d47..000000000000 --- a/drivers/platform/x86/dcdbas.c +++ /dev/null @@ -1,770 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * dcdbas.c: Dell Systems Management Base Driver - * - * The Dell Systems Management Base Driver provides a sysfs interface for - * systems management software to perform System Management Interrupts (SMIs) - * and Host Control Actions (power cycle or power off after OS shutdown) on - * Dell systems. - * - * See Documentation/driver-api/dcdbas.rst for more information. - * - * Copyright (C) 1995-2006 Dell Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dcdbas.h" - -#define DRIVER_NAME "dcdbas" -#define DRIVER_VERSION "5.6.0-3.4" -#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" - -static struct platform_device *dcdbas_pdev; - -static u8 *smi_data_buf; -static dma_addr_t smi_data_buf_handle; -static unsigned long smi_data_buf_size; -static unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE; -static u32 smi_data_buf_phys_addr; -static DEFINE_MUTEX(smi_data_lock); -static u8 *bios_buffer; - -static unsigned int host_control_action; -static unsigned int host_control_smi_type; -static unsigned int host_control_on_shutdown; - -static bool wsmt_enabled; - -/** - * smi_data_buf_free: free SMI data buffer - */ -static void smi_data_buf_free(void) -{ - if (!smi_data_buf || wsmt_enabled) - return; - - dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", - __func__, smi_data_buf_phys_addr, smi_data_buf_size); - - dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf, - smi_data_buf_handle); - smi_data_buf = NULL; - smi_data_buf_handle = 0; - smi_data_buf_phys_addr = 0; - smi_data_buf_size = 0; -} - -/** - * smi_data_buf_realloc: grow SMI data buffer if needed - */ -static int smi_data_buf_realloc(unsigned long size) -{ - void *buf; - dma_addr_t handle; - - if (smi_data_buf_size >= size) - return 0; - - if (size > max_smi_data_buf_size) - return -EINVAL; - - /* new buffer is needed */ - buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL); - if (!buf) { - dev_dbg(&dcdbas_pdev->dev, - "%s: failed to allocate memory size %lu\n", - __func__, size); - return -ENOMEM; - } - /* memory zeroed by dma_alloc_coherent */ - - if (smi_data_buf) - memcpy(buf, smi_data_buf, smi_data_buf_size); - - /* free any existing buffer */ - smi_data_buf_free(); - - /* set up new buffer for use */ - smi_data_buf = buf; - smi_data_buf_handle = handle; - smi_data_buf_phys_addr = (u32) virt_to_phys(buf); - smi_data_buf_size = size; - - dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", - __func__, smi_data_buf_phys_addr, smi_data_buf_size); - - return 0; -} - -static ssize_t smi_data_buf_phys_addr_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%x\n", smi_data_buf_phys_addr); -} - -static ssize_t smi_data_buf_size_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%lu\n", smi_data_buf_size); -} - -static ssize_t smi_data_buf_size_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned long buf_size; - ssize_t ret; - - buf_size = simple_strtoul(buf, NULL, 10); - - /* make sure SMI data buffer is at least buf_size */ - mutex_lock(&smi_data_lock); - ret = smi_data_buf_realloc(buf_size); - mutex_unlock(&smi_data_lock); - if (ret) - return ret; - - return count; -} - -static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t pos, size_t count) -{ - ssize_t ret; - - mutex_lock(&smi_data_lock); - ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf, - smi_data_buf_size); - mutex_unlock(&smi_data_lock); - return ret; -} - -static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t pos, size_t count) -{ - ssize_t ret; - - if ((pos + count) > max_smi_data_buf_size) - return -EINVAL; - - mutex_lock(&smi_data_lock); - - ret = smi_data_buf_realloc(pos + count); - if (ret) - goto out; - - memcpy(smi_data_buf + pos, buf, count); - ret = count; -out: - mutex_unlock(&smi_data_lock); - return ret; -} - -static ssize_t host_control_action_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%u\n", host_control_action); -} - -static ssize_t host_control_action_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - ssize_t ret; - - /* make sure buffer is available for host control command */ - mutex_lock(&smi_data_lock); - ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); - mutex_unlock(&smi_data_lock); - if (ret) - return ret; - - host_control_action = simple_strtoul(buf, NULL, 10); - return count; -} - -static ssize_t host_control_smi_type_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%u\n", host_control_smi_type); -} - -static ssize_t host_control_smi_type_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - host_control_smi_type = simple_strtoul(buf, NULL, 10); - return count; -} - -static ssize_t host_control_on_shutdown_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - return sprintf(buf, "%u\n", host_control_on_shutdown); -} - -static ssize_t host_control_on_shutdown_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - host_control_on_shutdown = simple_strtoul(buf, NULL, 10); - return count; -} - -static int raise_smi(void *par) -{ - struct smi_cmd *smi_cmd = par; - - if (smp_processor_id() != 0) { - dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", - __func__); - return -EBUSY; - } - - /* generate SMI */ - /* inb to force posted write through and make SMI happen now */ - asm volatile ( - "outb %b0,%w1\n" - "inb %w1" - : /* no output args */ - : "a" (smi_cmd->command_code), - "d" (smi_cmd->command_address), - "b" (smi_cmd->ebx), - "c" (smi_cmd->ecx) - : "memory" - ); - - return 0; -} -/** - * dcdbas_smi_request: generate SMI request - * - * Called with smi_data_lock. - */ -int dcdbas_smi_request(struct smi_cmd *smi_cmd) -{ - int ret; - - if (smi_cmd->magic != SMI_CMD_MAGIC) { - dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", - __func__); - return -EBADR; - } - - /* SMI requires CPU 0 */ - get_online_cpus(); - ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true); - put_online_cpus(); - - return ret; -} - -/** - * smi_request_store: - * - * The valid values are: - * 0: zero SMI data buffer - * 1: generate calling interface SMI - * 2: generate raw SMI - * - * User application writes smi_cmd to smi_data before telling driver - * to generate SMI. - */ -static ssize_t smi_request_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct smi_cmd *smi_cmd; - unsigned long val = simple_strtoul(buf, NULL, 10); - ssize_t ret; - - mutex_lock(&smi_data_lock); - - if (smi_data_buf_size < sizeof(struct smi_cmd)) { - ret = -ENODEV; - goto out; - } - smi_cmd = (struct smi_cmd *)smi_data_buf; - - switch (val) { - case 2: - /* Raw SMI */ - ret = dcdbas_smi_request(smi_cmd); - if (!ret) - ret = count; - break; - case 1: - /* - * Calling Interface SMI - * - * Provide physical address of command buffer field within - * the struct smi_cmd to BIOS. - * - * Because the address that smi_cmd (smi_data_buf) points to - * will be from memremap() of a non-memory address if WSMT - * is present, we can't use virt_to_phys() on smi_cmd, so - * we have to use the physical address that was saved when - * the virtual address for smi_cmd was received. - */ - smi_cmd->ebx = smi_data_buf_phys_addr + - offsetof(struct smi_cmd, command_buffer); - ret = dcdbas_smi_request(smi_cmd); - if (!ret) - ret = count; - break; - case 0: - memset(smi_data_buf, 0, smi_data_buf_size); - ret = count; - break; - default: - ret = -EINVAL; - break; - } - -out: - mutex_unlock(&smi_data_lock); - return ret; -} -EXPORT_SYMBOL(dcdbas_smi_request); - -/** - * host_control_smi: generate host control SMI - * - * Caller must set up the host control command in smi_data_buf. - */ -static int host_control_smi(void) -{ - struct apm_cmd *apm_cmd; - u8 *data; - unsigned long flags; - u32 num_ticks; - s8 cmd_status; - u8 index; - - apm_cmd = (struct apm_cmd *)smi_data_buf; - apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; - - switch (host_control_smi_type) { - case HC_SMITYPE_TYPE1: - spin_lock_irqsave(&rtc_lock, flags); - /* write SMI data buffer physical address */ - data = (u8 *)&smi_data_buf_phys_addr; - for (index = PE1300_CMOS_CMD_STRUCT_PTR; - index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); - index++, data++) { - outb(index, - (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); - outb(*data, - (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); - } - - /* first set status to -1 as called by spec */ - cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; - outb((u8) cmd_status, PCAT_APM_STATUS_PORT); - - /* generate SMM call */ - outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); - spin_unlock_irqrestore(&rtc_lock, flags); - - /* wait a few to see if it executed */ - num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; - while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) - == ESM_STATUS_CMD_UNSUCCESSFUL) { - num_ticks--; - if (num_ticks == EXPIRED_TIMER) - return -ETIME; - } - break; - - case HC_SMITYPE_TYPE2: - case HC_SMITYPE_TYPE3: - spin_lock_irqsave(&rtc_lock, flags); - /* write SMI data buffer physical address */ - data = (u8 *)&smi_data_buf_phys_addr; - for (index = PE1400_CMOS_CMD_STRUCT_PTR; - index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); - index++, data++) { - outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); - outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); - } - - /* generate SMM call */ - if (host_control_smi_type == HC_SMITYPE_TYPE3) - outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); - else - outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); - - /* restore RTC index pointer since it was written to above */ - CMOS_READ(RTC_REG_C); - spin_unlock_irqrestore(&rtc_lock, flags); - - /* read control port back to serialize write */ - cmd_status = inb(PE1400_APM_CONTROL_PORT); - - /* wait a few to see if it executed */ - num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; - while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { - num_ticks--; - if (num_ticks == EXPIRED_TIMER) - return -ETIME; - } - break; - - default: - dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", - __func__, host_control_smi_type); - return -ENOSYS; - } - - return 0; -} - -/** - * dcdbas_host_control: initiate host control - * - * This function is called by the driver after the system has - * finished shutting down if the user application specified a - * host control action to perform on shutdown. It is safe to - * use smi_data_buf at this point because the system has finished - * shutting down and no userspace apps are running. - */ -static void dcdbas_host_control(void) -{ - struct apm_cmd *apm_cmd; - u8 action; - - if (host_control_action == HC_ACTION_NONE) - return; - - action = host_control_action; - host_control_action = HC_ACTION_NONE; - - if (!smi_data_buf) { - dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); - return; - } - - if (smi_data_buf_size < sizeof(struct apm_cmd)) { - dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", - __func__); - return; - } - - apm_cmd = (struct apm_cmd *)smi_data_buf; - - /* power off takes precedence */ - if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { - apm_cmd->command = ESM_APM_POWER_CYCLE; - apm_cmd->reserved = 0; - *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; - host_control_smi(); - } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { - apm_cmd->command = ESM_APM_POWER_CYCLE; - apm_cmd->reserved = 0; - *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; - host_control_smi(); - } -} - -/* WSMT */ - -static u8 checksum(u8 *buffer, u8 length) -{ - u8 sum = 0; - u8 *end = buffer + length; - - while (buffer < end) - sum += *buffer++; - return sum; -} - -static inline struct smm_eps_table *check_eps_table(u8 *addr) -{ - struct smm_eps_table *eps = (struct smm_eps_table *)addr; - - if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0) - return NULL; - - if (checksum(addr, eps->length) != 0) - return NULL; - - return eps; -} - -static int dcdbas_check_wsmt(void) -{ - const struct dmi_device *dev = NULL; - struct acpi_table_wsmt *wsmt = NULL; - struct smm_eps_table *eps = NULL; - u64 bios_buf_paddr; - u64 remap_size; - u8 *addr; - - acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt); - if (!wsmt) - return 0; - - /* Check if WSMT ACPI table shows that protection is enabled */ - if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) || - !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION)) - return 0; - - /* - * BIOS could provide the address/size of the protected buffer - * in an SMBIOS string or in an EPS structure in 0xFxxxx. - */ - - /* Check SMBIOS for buffer address */ - while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) - if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr, - &remap_size) == 2) - goto remap; - - /* Scan for EPS (entry point structure) */ - for (addr = (u8 *)__va(0xf0000); - addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table)); - addr += 16) { - eps = check_eps_table(addr); - if (eps) - break; - } - - if (!eps) { - dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n"); - return -ENODEV; - } - bios_buf_paddr = eps->smm_comm_buff_addr; - remap_size = eps->num_of_4k_pages * PAGE_SIZE; - -remap: - /* - * Get physical address of buffer and map to virtual address. - * Table gives size in 4K pages, regardless of actual system page size. - */ - if (upper_32_bits(bios_buf_paddr + 8)) { - dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n"); - return -EINVAL; - } - /* - * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8 - * bytes are used for a semaphore, not the data buffer itself). - */ - if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8) - remap_size = MAX_SMI_DATA_BUF_SIZE + 8; - - bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB); - if (!bios_buffer) { - dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n"); - return -ENOMEM; - } - - /* First 8 bytes is for a semaphore, not part of the smi_data_buf */ - smi_data_buf_phys_addr = bios_buf_paddr + 8; - smi_data_buf = bios_buffer + 8; - smi_data_buf_size = remap_size - 8; - max_smi_data_buf_size = smi_data_buf_size; - wsmt_enabled = true; - dev_info(&dcdbas_pdev->dev, - "WSMT found, using firmware-provided SMI buffer.\n"); - return 1; -} - -/** - * dcdbas_reboot_notify: handle reboot notification for host control - */ -static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, - void *unused) -{ - switch (code) { - case SYS_DOWN: - case SYS_HALT: - case SYS_POWER_OFF: - if (host_control_on_shutdown) { - /* firmware is going to perform host control action */ - printk(KERN_WARNING "Please wait for shutdown " - "action to complete...\n"); - dcdbas_host_control(); - } - break; - } - - return NOTIFY_DONE; -} - -static struct notifier_block dcdbas_reboot_nb = { - .notifier_call = dcdbas_reboot_notify, - .next = NULL, - .priority = INT_MIN -}; - -static DCDBAS_BIN_ATTR_RW(smi_data); - -static struct bin_attribute *dcdbas_bin_attrs[] = { - &bin_attr_smi_data, - NULL -}; - -static DCDBAS_DEV_ATTR_RW(smi_data_buf_size); -static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); -static DCDBAS_DEV_ATTR_WO(smi_request); -static DCDBAS_DEV_ATTR_RW(host_control_action); -static DCDBAS_DEV_ATTR_RW(host_control_smi_type); -static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); - -static struct attribute *dcdbas_dev_attrs[] = { - &dev_attr_smi_data_buf_size.attr, - &dev_attr_smi_data_buf_phys_addr.attr, - &dev_attr_smi_request.attr, - &dev_attr_host_control_action.attr, - &dev_attr_host_control_smi_type.attr, - &dev_attr_host_control_on_shutdown.attr, - NULL -}; - -static const struct attribute_group dcdbas_attr_group = { - .attrs = dcdbas_dev_attrs, - .bin_attrs = dcdbas_bin_attrs, -}; - -static int dcdbas_probe(struct platform_device *dev) -{ - int error; - - host_control_action = HC_ACTION_NONE; - host_control_smi_type = HC_SMITYPE_NONE; - - dcdbas_pdev = dev; - - /* Check if ACPI WSMT table specifies protected SMI buffer address */ - error = dcdbas_check_wsmt(); - if (error < 0) - return error; - - /* - * BIOS SMI calls require buffer addresses be in 32-bit address space. - * This is done by setting the DMA mask below. - */ - error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); - if (error) - return error; - - error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); - if (error) - return error; - - register_reboot_notifier(&dcdbas_reboot_nb); - - dev_info(&dev->dev, "%s (version %s)\n", - DRIVER_DESCRIPTION, DRIVER_VERSION); - - return 0; -} - -static int dcdbas_remove(struct platform_device *dev) -{ - unregister_reboot_notifier(&dcdbas_reboot_nb); - sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); - - return 0; -} - -static struct platform_driver dcdbas_driver = { - .driver = { - .name = DRIVER_NAME, - }, - .probe = dcdbas_probe, - .remove = dcdbas_remove, -}; - -static const struct platform_device_info dcdbas_dev_info __initconst = { - .name = DRIVER_NAME, - .id = -1, - .dma_mask = DMA_BIT_MASK(32), -}; - -static struct platform_device *dcdbas_pdev_reg; - -/** - * dcdbas_init: initialize driver - */ -static int __init dcdbas_init(void) -{ - int error; - - error = platform_driver_register(&dcdbas_driver); - if (error) - return error; - - dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info); - if (IS_ERR(dcdbas_pdev_reg)) { - error = PTR_ERR(dcdbas_pdev_reg); - goto err_unregister_driver; - } - - return 0; - - err_unregister_driver: - platform_driver_unregister(&dcdbas_driver); - return error; -} - -/** - * dcdbas_exit: perform driver cleanup - */ -static void __exit dcdbas_exit(void) -{ - /* - * make sure functions that use dcdbas_pdev are called - * before platform_device_unregister - */ - unregister_reboot_notifier(&dcdbas_reboot_nb); - - /* - * We have to free the buffer here instead of dcdbas_remove - * because only in module exit function we can be sure that - * all sysfs attributes belonging to this module have been - * released. - */ - if (dcdbas_pdev) - smi_data_buf_free(); - if (bios_buffer) - memunmap(bios_buffer); - platform_device_unregister(dcdbas_pdev_reg); - platform_driver_unregister(&dcdbas_driver); -} - -subsys_initcall_sync(dcdbas_init); -module_exit(dcdbas_exit); - -MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_AUTHOR("Dell Inc."); -MODULE_LICENSE("GPL"); -/* Any System or BIOS claiming to be by Dell */ -MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); diff --git a/drivers/platform/x86/dcdbas.h b/drivers/platform/x86/dcdbas.h deleted file mode 100644 index c3cca5433525..000000000000 --- a/drivers/platform/x86/dcdbas.h +++ /dev/null @@ -1,109 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * dcdbas.h: Definitions for Dell Systems Management Base driver - * - * Copyright (C) 1995-2005 Dell Inc. - */ - -#ifndef _DCDBAS_H_ -#define _DCDBAS_H_ - -#include -#include -#include - -#define MAX_SMI_DATA_BUF_SIZE (256 * 1024) - -#define HC_ACTION_NONE (0) -#define HC_ACTION_HOST_CONTROL_POWEROFF BIT(1) -#define HC_ACTION_HOST_CONTROL_POWERCYCLE BIT(2) - -#define HC_SMITYPE_NONE (0) -#define HC_SMITYPE_TYPE1 (1) -#define HC_SMITYPE_TYPE2 (2) -#define HC_SMITYPE_TYPE3 (3) - -#define ESM_APM_CMD (0x0A0) -#define ESM_APM_POWER_CYCLE (0x10) -#define ESM_STATUS_CMD_UNSUCCESSFUL (-1) - -#define CMOS_BASE_PORT (0x070) -#define CMOS_PAGE1_INDEX_PORT (0) -#define CMOS_PAGE1_DATA_PORT (1) -#define CMOS_PAGE2_INDEX_PORT_PIIX4 (2) -#define CMOS_PAGE2_DATA_PORT_PIIX4 (3) -#define PE1400_APM_CONTROL_PORT (0x0B0) -#define PCAT_APM_CONTROL_PORT (0x0B2) -#define PCAT_APM_STATUS_PORT (0x0B3) -#define PE1300_CMOS_CMD_STRUCT_PTR (0x38) -#define PE1400_CMOS_CMD_STRUCT_PTR (0x70) - -#define MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN (14) -#define MAX_SYSMGMT_LONGCMD_SGENTRY_NUM (16) - -#define TIMEOUT_USEC_SHORT_SEMA_BLOCKING (10000) -#define EXPIRED_TIMER (0) - -#define SMI_CMD_MAGIC (0x534D4931) -#define SMM_EPS_SIG "$SCB" - -#define DCDBAS_DEV_ATTR_RW(_name) \ - DEVICE_ATTR(_name,0600,_name##_show,_name##_store); - -#define DCDBAS_DEV_ATTR_RO(_name) \ - DEVICE_ATTR(_name,0400,_name##_show,NULL); - -#define DCDBAS_DEV_ATTR_WO(_name) \ - DEVICE_ATTR(_name,0200,NULL,_name##_store); - -#define DCDBAS_BIN_ATTR_RW(_name) \ -struct bin_attribute bin_attr_##_name = { \ - .attr = { .name = __stringify(_name), \ - .mode = 0600 }, \ - .read = _name##_read, \ - .write = _name##_write, \ -} - -struct smi_cmd { - __u32 magic; - __u32 ebx; - __u32 ecx; - __u16 command_address; - __u8 command_code; - __u8 reserved; - __u8 command_buffer[1]; -} __attribute__ ((packed)); - -struct apm_cmd { - __u8 command; - __s8 status; - __u16 reserved; - union { - struct { - __u8 parm[MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN]; - } __attribute__ ((packed)) shortreq; - - struct { - __u16 num_sg_entries; - struct { - __u32 size; - __u64 addr; - } __attribute__ ((packed)) - sglist[MAX_SYSMGMT_LONGCMD_SGENTRY_NUM]; - } __attribute__ ((packed)) longreq; - } __attribute__ ((packed)) parameters; -} __attribute__ ((packed)); - -int dcdbas_smi_request(struct smi_cmd *smi_cmd); - -struct smm_eps_table { - char smm_comm_buff_anchor[4]; - u8 length; - u8 checksum; - u8 version; - u64 smm_comm_buff_addr; - u64 num_of_4k_pages; -} __packed; - -#endif /* _DCDBAS_H_ */ - diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c deleted file mode 100644 index 70edc5bb3a14..000000000000 --- a/drivers/platform/x86/dell-laptop.c +++ /dev/null @@ -1,2303 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for Dell laptop extras - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * - * Based on documentation in the libsmbios package: - * Copyright (C) 2005-2014 Dell Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-rbtn.h" -#include "dell-smbios.h" - -struct quirk_entry { - bool touchpad_led; - bool kbd_led_not_present; - bool kbd_led_levels_off_1; - bool kbd_missing_ac_tag; - - bool needs_kbd_timeouts; - /* - * Ordered list of timeouts expressed in seconds. - * The list must end with -1 - */ - int kbd_timeouts[]; -}; - -static struct quirk_entry *quirks; - -static struct quirk_entry quirk_dell_vostro_v130 = { - .touchpad_led = true, -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - quirks = dmi->driver_data; - return 1; -} - -/* - * These values come from Windows utility provided by Dell. If any other value - * is used then BIOS silently set timeout to 0 without any error message. - */ -static struct quirk_entry quirk_dell_xps13_9333 = { - .needs_kbd_timeouts = true, - .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, -}; - -static struct quirk_entry quirk_dell_xps13_9370 = { - .kbd_missing_ac_tag = true, -}; - -static struct quirk_entry quirk_dell_latitude_e6410 = { - .kbd_led_levels_off_1 = true, -}; - -static struct quirk_entry quirk_dell_inspiron_1012 = { - .kbd_led_not_present = true, -}; - -static struct platform_driver platform_driver = { - .driver = { - .name = "dell-laptop", - } -}; - -static struct platform_device *platform_device; -static struct backlight_device *dell_backlight_device; -static struct rfkill *wifi_rfkill; -static struct rfkill *bluetooth_rfkill; -static struct rfkill *wwan_rfkill; -static bool force_rfkill; - -module_param(force_rfkill, bool, 0444); -MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); - -static const struct dmi_system_id dell_device_table[] __initconst = { - { - .ident = "Dell laptop", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "30"), /*Tablet*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /*Convertible*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "32"), /*Detachable*/ - }, - }, - { - .ident = "Dell Computer Corporation", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { } -}; -MODULE_DEVICE_TABLE(dmi, dell_device_table); - -static const struct dmi_system_id dell_quirks[] __initconst = { - { - .callback = dmi_matched, - .ident = "Dell Vostro V130", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro V131", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3350", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3555", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron N311z", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron M5110", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3360", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3460", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3560", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro 3450", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 5420", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 5520", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 5720", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 7420", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 7520", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 7720", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), - }, - .driver_data = &quirk_dell_vostro_v130, - }, - { - .callback = dmi_matched, - .ident = "Dell XPS13 9333", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), - }, - .driver_data = &quirk_dell_xps13_9333, - }, - { - .callback = dmi_matched, - .ident = "Dell XPS 13 9370", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "XPS 13 9370"), - }, - .driver_data = &quirk_dell_xps13_9370, - }, - { - .callback = dmi_matched, - .ident = "Dell Latitude E6410", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6410"), - }, - .driver_data = &quirk_dell_latitude_e6410, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 1012", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), - }, - .driver_data = &quirk_dell_inspiron_1012, - }, - { - .callback = dmi_matched, - .ident = "Dell Inspiron 1018", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1018"), - }, - .driver_data = &quirk_dell_inspiron_1012, - }, - { } -}; - -static void dell_fill_request(struct calling_interface_buffer *buffer, - u32 arg0, u32 arg1, u32 arg2, u32 arg3) -{ - memset(buffer, 0, sizeof(struct calling_interface_buffer)); - buffer->input[0] = arg0; - buffer->input[1] = arg1; - buffer->input[2] = arg2; - buffer->input[3] = arg3; -} - -static int dell_send_request(struct calling_interface_buffer *buffer, - u16 class, u16 select) -{ - int ret; - - buffer->cmd_class = class; - buffer->cmd_select = select; - ret = dell_smbios_call(buffer); - if (ret != 0) - return ret; - return dell_smbios_error(buffer->output[0]); -} - -/* - * Derived from information in smbios-wireless-ctl: - * - * cbSelect 17, Value 11 - * - * Return Wireless Info - * cbArg1, byte0 = 0x00 - * - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 Info bit flags: - * - * 0 Hardware switch supported (1) - * 1 WiFi locator supported (1) - * 2 WLAN supported (1) - * 3 Bluetooth (BT) supported (1) - * 4 WWAN supported (1) - * 5 Wireless KBD supported (1) - * 6 Uw b supported (1) - * 7 WiGig supported (1) - * 8 WLAN installed (1) - * 9 BT installed (1) - * 10 WWAN installed (1) - * 11 Uw b installed (1) - * 12 WiGig installed (1) - * 13-15 Reserved (0) - * 16 Hardware (HW) switch is On (1) - * 17 WLAN disabled (1) - * 18 BT disabled (1) - * 19 WWAN disabled (1) - * 20 Uw b disabled (1) - * 21 WiGig disabled (1) - * 20-31 Reserved (0) - * - * cbRes3 NVRAM size in bytes - * cbRes4, byte 0 NVRAM format version number - * - * - * Set QuickSet Radio Disable Flag - * cbArg1, byte0 = 0x01 - * cbArg1, byte1 - * Radio ID value: - * 0 Radio Status - * 1 WLAN ID - * 2 BT ID - * 3 WWAN ID - * 4 UWB ID - * 5 WIGIG ID - * cbArg1, byte2 Flag bits: - * 0 QuickSet disables radio (1) - * 1-7 Reserved (0) - * - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 QuickSet (QS) radio disable bit map: - * 0 QS disables WLAN - * 1 QS disables BT - * 2 QS disables WWAN - * 3 QS disables UWB - * 4 QS disables WIGIG - * 5-31 Reserved (0) - * - * Wireless Switch Configuration - * cbArg1, byte0 = 0x02 - * - * cbArg1, byte1 - * Subcommand: - * 0 Get config - * 1 Set config - * 2 Set WiFi locator enable/disable - * cbArg1,byte2 - * Switch settings (if byte 1==1): - * 0 WLAN sw itch control (1) - * 1 BT sw itch control (1) - * 2 WWAN sw itch control (1) - * 3 UWB sw itch control (1) - * 4 WiGig sw itch control (1) - * 5-7 Reserved (0) - * cbArg1, byte2 Enable bits (if byte 1==2): - * 0 Enable WiFi locator (1) - * - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 QuickSet radio disable bit map: - * 0 WLAN controlled by sw itch (1) - * 1 BT controlled by sw itch (1) - * 2 WWAN controlled by sw itch (1) - * 3 UWB controlled by sw itch (1) - * 4 WiGig controlled by sw itch (1) - * 5-6 Reserved (0) - * 7 Wireless sw itch config locked (1) - * 8 WiFi locator enabled (1) - * 9-14 Reserved (0) - * 15 WiFi locator setting locked (1) - * 16-31 Reserved (0) - * - * Read Local Config Data (LCD) - * cbArg1, byte0 = 0x10 - * cbArg1, byte1 NVRAM index low byte - * cbArg1, byte2 NVRAM index high byte - * cbRes1 Standard return codes (0, -1, -2) - * cbRes2 4 bytes read from LCD[index] - * cbRes3 4 bytes read from LCD[index+4] - * cbRes4 4 bytes read from LCD[index+8] - * - * Write Local Config Data (LCD) - * cbArg1, byte0 = 0x11 - * cbArg1, byte1 NVRAM index low byte - * cbArg1, byte2 NVRAM index high byte - * cbArg2 4 bytes to w rite at LCD[index] - * cbArg3 4 bytes to w rite at LCD[index+4] - * cbArg4 4 bytes to w rite at LCD[index+8] - * cbRes1 Standard return codes (0, -1, -2) - * - * Populate Local Config Data from NVRAM - * cbArg1, byte0 = 0x12 - * cbRes1 Standard return codes (0, -1, -2) - * - * Commit Local Config Data to NVRAM - * cbArg1, byte0 = 0x13 - * cbRes1 Standard return codes (0, -1, -2) - */ - -static int dell_rfkill_set(void *data, bool blocked) -{ - int disable = blocked ? 1 : 0; - unsigned long radio = (unsigned long)data; - int hwswitch_bit = (unsigned long)data - 1; - struct calling_interface_buffer buffer; - int hwswitch; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (ret) - return ret; - status = buffer.output[1]; - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (ret) - return ret; - hwswitch = buffer.output[1]; - - /* If the hardware switch controls this radio, and the hardware - switch is disabled, always disable the radio */ - if (ret == 0 && (hwswitch & BIT(hwswitch_bit)) && - (status & BIT(0)) && !(status & BIT(16))) - disable = 1; - - dell_fill_request(&buffer, 1 | (radio<<8) | (disable << 16), 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - return ret; -} - -static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, - int status) -{ - if (status & BIT(0)) { - /* Has hw-switch, sync sw_state to BIOS */ - struct calling_interface_buffer buffer; - int block = rfkill_blocked(rfkill); - dell_fill_request(&buffer, - 1 | (radio << 8) | (block << 16), 0, 0, 0); - dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - } else { - /* No hw-switch, sync BIOS state to sw_state */ - rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); - } -} - -static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, - int status, int hwswitch) -{ - if (hwswitch & (BIT(radio - 1))) - rfkill_set_hw_state(rfkill, !(status & BIT(16))); -} - -static void dell_rfkill_query(struct rfkill *rfkill, void *data) -{ - int radio = ((unsigned long)data & 0xF); - struct calling_interface_buffer buffer; - int hwswitch; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - status = buffer.output[1]; - - if (ret != 0 || !(status & BIT(0))) { - return; - } - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - hwswitch = buffer.output[1]; - - if (ret != 0) - return; - - dell_rfkill_update_hw_state(rfkill, radio, status, hwswitch); -} - -static const struct rfkill_ops dell_rfkill_ops = { - .set_block = dell_rfkill_set, - .query = dell_rfkill_query, -}; - -static struct dentry *dell_laptop_dir; - -static int dell_debugfs_show(struct seq_file *s, void *data) -{ - struct calling_interface_buffer buffer; - int hwswitch_state; - int hwswitch_ret; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (ret) - return ret; - status = buffer.output[1]; - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - hwswitch_ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - if (hwswitch_ret) - return hwswitch_ret; - hwswitch_state = buffer.output[1]; - - seq_printf(s, "return:\t%d\n", ret); - seq_printf(s, "status:\t0x%X\n", status); - seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n", - status & BIT(0)); - seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n", - (status & BIT(1)) >> 1); - seq_printf(s, "Bit 2 : Wifi is supported: %lu\n", - (status & BIT(2)) >> 2); - seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n", - (status & BIT(3)) >> 3); - seq_printf(s, "Bit 4 : WWAN is supported: %lu\n", - (status & BIT(4)) >> 4); - seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n", - (status & BIT(5)) >> 5); - seq_printf(s, "Bit 6 : UWB supported: %lu\n", - (status & BIT(6)) >> 6); - seq_printf(s, "Bit 7 : WiGig supported: %lu\n", - (status & BIT(7)) >> 7); - seq_printf(s, "Bit 8 : Wifi is installed: %lu\n", - (status & BIT(8)) >> 8); - seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n", - (status & BIT(9)) >> 9); - seq_printf(s, "Bit 10: WWAN is installed: %lu\n", - (status & BIT(10)) >> 10); - seq_printf(s, "Bit 11: UWB installed: %lu\n", - (status & BIT(11)) >> 11); - seq_printf(s, "Bit 12: WiGig installed: %lu\n", - (status & BIT(12)) >> 12); - - seq_printf(s, "Bit 16: Hardware switch is on: %lu\n", - (status & BIT(16)) >> 16); - seq_printf(s, "Bit 17: Wifi is blocked: %lu\n", - (status & BIT(17)) >> 17); - seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n", - (status & BIT(18)) >> 18); - seq_printf(s, "Bit 19: WWAN is blocked: %lu\n", - (status & BIT(19)) >> 19); - seq_printf(s, "Bit 20: UWB is blocked: %lu\n", - (status & BIT(20)) >> 20); - seq_printf(s, "Bit 21: WiGig is blocked: %lu\n", - (status & BIT(21)) >> 21); - - seq_printf(s, "\nhwswitch_return:\t%d\n", hwswitch_ret); - seq_printf(s, "hwswitch_state:\t0x%X\n", hwswitch_state); - seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n", - hwswitch_state & BIT(0)); - seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n", - (hwswitch_state & BIT(1)) >> 1); - seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n", - (hwswitch_state & BIT(2)) >> 2); - seq_printf(s, "Bit 3 : UWB controlled by switch: %lu\n", - (hwswitch_state & BIT(3)) >> 3); - seq_printf(s, "Bit 4 : WiGig controlled by switch: %lu\n", - (hwswitch_state & BIT(4)) >> 4); - seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n", - (hwswitch_state & BIT(7)) >> 7); - seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n", - (hwswitch_state & BIT(8)) >> 8); - seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n", - (hwswitch_state & BIT(15)) >> 15); - - return 0; -} -DEFINE_SHOW_ATTRIBUTE(dell_debugfs); - -static void dell_update_rfkill(struct work_struct *ignored) -{ - struct calling_interface_buffer buffer; - int hwswitch = 0; - int status; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - status = buffer.output[1]; - - if (ret != 0) - return; - - dell_fill_request(&buffer, 0x2, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - - if (ret == 0 && (status & BIT(0))) - hwswitch = buffer.output[1]; - - if (wifi_rfkill) { - dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch); - dell_rfkill_update_sw_state(wifi_rfkill, 1, status); - } - if (bluetooth_rfkill) { - dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status, - hwswitch); - dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); - } - if (wwan_rfkill) { - dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch); - dell_rfkill_update_sw_state(wwan_rfkill, 3, status); - } -} -static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); - -static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, - struct serio *port) -{ - static bool extended; - - if (str & I8042_STR_AUXDATA) - return false; - - if (unlikely(data == 0xe0)) { - extended = true; - return false; - } else if (unlikely(extended)) { - switch (data) { - case 0x8: - schedule_delayed_work(&dell_rfkill_work, - round_jiffies_relative(HZ / 4)); - break; - } - extended = false; - } - - return false; -} - -static int (*dell_rbtn_notifier_register_func)(struct notifier_block *); -static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *); - -static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb, - unsigned long action, void *data) -{ - schedule_delayed_work(&dell_rfkill_work, 0); - return NOTIFY_OK; -} - -static struct notifier_block dell_laptop_rbtn_notifier = { - .notifier_call = dell_laptop_rbtn_notifier_call, -}; - -static int __init dell_setup_rfkill(void) -{ - struct calling_interface_buffer buffer; - int status, ret, whitelisted; - const char *product; - - /* - * rfkill support causes trouble on various models, mostly Inspirons. - * So we whitelist certain series, and don't support rfkill on others. - */ - whitelisted = 0; - product = dmi_get_system_info(DMI_PRODUCT_NAME); - if (product && (strncmp(product, "Latitude", 8) == 0 || - strncmp(product, "Precision", 9) == 0)) - whitelisted = 1; - if (!force_rfkill && !whitelisted) - return 0; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); - status = buffer.output[1]; - - /* dell wireless info smbios call is not supported */ - if (ret != 0) - return 0; - - /* rfkill is only tested on laptops with a hwswitch */ - if (!(status & BIT(0)) && !force_rfkill) - return 0; - - if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { - wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, - RFKILL_TYPE_WLAN, - &dell_rfkill_ops, (void *) 1); - if (!wifi_rfkill) { - ret = -ENOMEM; - goto err_wifi; - } - ret = rfkill_register(wifi_rfkill); - if (ret) - goto err_wifi; - } - - if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { - bluetooth_rfkill = rfkill_alloc("dell-bluetooth", - &platform_device->dev, - RFKILL_TYPE_BLUETOOTH, - &dell_rfkill_ops, (void *) 2); - if (!bluetooth_rfkill) { - ret = -ENOMEM; - goto err_bluetooth; - } - ret = rfkill_register(bluetooth_rfkill); - if (ret) - goto err_bluetooth; - } - - if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { - wwan_rfkill = rfkill_alloc("dell-wwan", - &platform_device->dev, - RFKILL_TYPE_WWAN, - &dell_rfkill_ops, (void *) 3); - if (!wwan_rfkill) { - ret = -ENOMEM; - goto err_wwan; - } - ret = rfkill_register(wwan_rfkill); - if (ret) - goto err_wwan; - } - - /* - * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices - * which can receive events from HW slider switch. - * - * Dell SMBIOS on whitelisted models supports controlling radio devices - * but does not support receiving HW button switch events. We can use - * i8042 filter hook function to receive keyboard data and handle - * keycode for HW button. - * - * So if it is possible we will use Dell Airplane Mode Switch ACPI - * driver for receiving HW events and Dell SMBIOS for setting rfkill - * states. If ACPI driver or device is not available we will fallback to - * i8042 filter hook function. - * - * To prevent duplicate rfkill devices which control and do same thing, - * dell-rbtn driver will automatically remove its own rfkill devices - * once function dell_rbtn_notifier_register() is called. - */ - - dell_rbtn_notifier_register_func = - symbol_request(dell_rbtn_notifier_register); - if (dell_rbtn_notifier_register_func) { - dell_rbtn_notifier_unregister_func = - symbol_request(dell_rbtn_notifier_unregister); - if (!dell_rbtn_notifier_unregister_func) { - symbol_put(dell_rbtn_notifier_register); - dell_rbtn_notifier_register_func = NULL; - } - } - - if (dell_rbtn_notifier_register_func) { - ret = dell_rbtn_notifier_register_func( - &dell_laptop_rbtn_notifier); - symbol_put(dell_rbtn_notifier_register); - dell_rbtn_notifier_register_func = NULL; - if (ret != 0) { - symbol_put(dell_rbtn_notifier_unregister); - dell_rbtn_notifier_unregister_func = NULL; - } - } else { - pr_info("Symbols from dell-rbtn acpi driver are not available\n"); - ret = -ENODEV; - } - - if (ret == 0) { - pr_info("Using dell-rbtn acpi driver for receiving events\n"); - } else if (ret != -ENODEV) { - pr_warn("Unable to register dell rbtn notifier\n"); - goto err_filter; - } else { - ret = i8042_install_filter(dell_laptop_i8042_filter); - if (ret) { - pr_warn("Unable to install key filter\n"); - goto err_filter; - } - pr_info("Using i8042 filter function for receiving events\n"); - } - - return 0; -err_filter: - if (wwan_rfkill) - rfkill_unregister(wwan_rfkill); -err_wwan: - rfkill_destroy(wwan_rfkill); - if (bluetooth_rfkill) - rfkill_unregister(bluetooth_rfkill); -err_bluetooth: - rfkill_destroy(bluetooth_rfkill); - if (wifi_rfkill) - rfkill_unregister(wifi_rfkill); -err_wifi: - rfkill_destroy(wifi_rfkill); - - return ret; -} - -static void dell_cleanup_rfkill(void) -{ - if (dell_rbtn_notifier_unregister_func) { - dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier); - symbol_put(dell_rbtn_notifier_unregister); - dell_rbtn_notifier_unregister_func = NULL; - } else { - i8042_remove_filter(dell_laptop_i8042_filter); - } - cancel_delayed_work_sync(&dell_rfkill_work); - if (wifi_rfkill) { - rfkill_unregister(wifi_rfkill); - rfkill_destroy(wifi_rfkill); - } - if (bluetooth_rfkill) { - rfkill_unregister(bluetooth_rfkill); - rfkill_destroy(bluetooth_rfkill); - } - if (wwan_rfkill) { - rfkill_unregister(wwan_rfkill); - rfkill_destroy(wwan_rfkill); - } -} - -static int dell_send_intensity(struct backlight_device *bd) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, - token->location, bd->props.brightness, 0, 0); - if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(&buffer, - CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); - else - ret = dell_send_request(&buffer, - CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); - - return ret; -} - -static int dell_get_intensity(struct backlight_device *bd) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_AC); - else - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_BAT); - - if (ret == 0) - ret = buffer.output[1]; - - return ret; -} - -static const struct backlight_ops dell_ops = { - .get_brightness = dell_get_intensity, - .update_status = dell_send_intensity, -}; - -static void touchpad_led_on(void) -{ - int command = 0x97; - char data = 1; - i8042_command(&data, command | 1 << 12); -} - -static void touchpad_led_off(void) -{ - int command = 0x97; - char data = 2; - i8042_command(&data, command | 1 << 12); -} - -static void touchpad_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - if (value > 0) - touchpad_led_on(); - else - touchpad_led_off(); -} - -static struct led_classdev touchpad_led = { - .name = "dell-laptop::touchpad", - .brightness_set = touchpad_led_set, - .flags = LED_CORE_SUSPENDRESUME, -}; - -static int __init touchpad_led_init(struct device *dev) -{ - return led_classdev_register(dev, &touchpad_led); -} - -static void touchpad_led_exit(void) -{ - led_classdev_unregister(&touchpad_led); -} - -/* - * Derived from information in smbios-keyboard-ctl: - * - * cbClass 4 - * cbSelect 11 - * Keyboard illumination - * cbArg1 determines the function to be performed - * - * cbArg1 0x0 = Get Feature Information - * cbRES1 Standard return codes (0, -1, -2) - * cbRES2, word0 Bitmap of user-selectable modes - * bit 0 Always off (All systems) - * bit 1 Always on (Travis ATG, Siberia) - * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) - * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off - * bit 4 Auto: Input-activity-based On; input-activity based Off - * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off - * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off - * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off - * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off - * bits 9-15 Reserved for future use - * cbRES2, byte2 Reserved for future use - * cbRES2, byte3 Keyboard illumination type - * 0 Reserved - * 1 Tasklight - * 2 Backlight - * 3-255 Reserved for future use - * cbRES3, byte0 Supported auto keyboard illumination trigger bitmap. - * bit 0 Any keystroke - * bit 1 Touchpad activity - * bit 2 Pointing stick - * bit 3 Any mouse - * bits 4-7 Reserved for future use - * cbRES3, byte1 Supported timeout unit bitmap - * bit 0 Seconds - * bit 1 Minutes - * bit 2 Hours - * bit 3 Days - * bits 4-7 Reserved for future use - * cbRES3, byte2 Number of keyboard light brightness levels - * cbRES4, byte0 Maximum acceptable seconds value (0 if seconds not supported). - * cbRES4, byte1 Maximum acceptable minutes value (0 if minutes not supported). - * cbRES4, byte2 Maximum acceptable hours value (0 if hours not supported). - * cbRES4, byte3 Maximum acceptable days value (0 if days not supported) - * - * cbArg1 0x1 = Get Current State - * cbRES1 Standard return codes (0, -1, -2) - * cbRES2, word0 Bitmap of current mode state - * bit 0 Always off (All systems) - * bit 1 Always on (Travis ATG, Siberia) - * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) - * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off - * bit 4 Auto: Input-activity-based On; input-activity based Off - * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off - * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off - * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off - * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off - * bits 9-15 Reserved for future use - * Note: Only One bit can be set - * cbRES2, byte2 Currently active auto keyboard illumination triggers. - * bit 0 Any keystroke - * bit 1 Touchpad activity - * bit 2 Pointing stick - * bit 3 Any mouse - * bits 4-7 Reserved for future use - * cbRES2, byte3 Current Timeout on battery - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * bits 5:0 Timeout value (0-63) in sec/min/hr/day - * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte - * are set upon return from the [Get feature information] call. - * cbRES3, byte0 Current setting of ALS value that turns the light on or off. - * cbRES3, byte1 Current ALS reading - * cbRES3, byte2 Current keyboard light level. - * cbRES3, byte3 Current timeout on AC Power - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * Bits 5:0 Timeout value (0-63) in sec/min/hr/day - * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2 - * are set upon return from the upon return from the [Get Feature information] call. - * - * cbArg1 0x2 = Set New State - * cbRES1 Standard return codes (0, -1, -2) - * cbArg2, word0 Bitmap of current mode state - * bit 0 Always off (All systems) - * bit 1 Always on (Travis ATG, Siberia) - * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) - * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off - * bit 4 Auto: Input-activity-based On; input-activity based Off - * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off - * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off - * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off - * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off - * bits 9-15 Reserved for future use - * Note: Only One bit can be set - * cbArg2, byte2 Desired auto keyboard illumination triggers. Must remain inactive to allow - * keyboard to turn off automatically. - * bit 0 Any keystroke - * bit 1 Touchpad activity - * bit 2 Pointing stick - * bit 3 Any mouse - * bits 4-7 Reserved for future use - * cbArg2, byte3 Desired Timeout on battery - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * bits 5:0 Timeout value (0-63) in sec/min/hr/day - * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. - * cbArg3, byte2 Desired keyboard light level. - * cbArg3, byte3 Desired Timeout on AC power - * bits 7:6 Timeout units indicator: - * 00b Seconds - * 01b Minutes - * 10b Hours - * 11b Days - * bits 5:0 Timeout value (0-63) in sec/min/hr/day - */ - - -enum kbd_timeout_unit { - KBD_TIMEOUT_SECONDS = 0, - KBD_TIMEOUT_MINUTES, - KBD_TIMEOUT_HOURS, - KBD_TIMEOUT_DAYS, -}; - -enum kbd_mode_bit { - KBD_MODE_BIT_OFF = 0, - KBD_MODE_BIT_ON, - KBD_MODE_BIT_ALS, - KBD_MODE_BIT_TRIGGER_ALS, - KBD_MODE_BIT_TRIGGER, - KBD_MODE_BIT_TRIGGER_25, - KBD_MODE_BIT_TRIGGER_50, - KBD_MODE_BIT_TRIGGER_75, - KBD_MODE_BIT_TRIGGER_100, -}; - -#define kbd_is_als_mode_bit(bit) \ - ((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS) -#define kbd_is_trigger_mode_bit(bit) \ - ((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100) -#define kbd_is_level_mode_bit(bit) \ - ((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100) - -struct kbd_info { - u16 modes; - u8 type; - u8 triggers; - u8 levels; - u8 seconds; - u8 minutes; - u8 hours; - u8 days; -}; - -struct kbd_state { - u8 mode_bit; - u8 triggers; - u8 timeout_value; - u8 timeout_unit; - u8 timeout_value_ac; - u8 timeout_unit_ac; - u8 als_setting; - u8 als_value; - u8 level; -}; - -static const int kbd_tokens[] = { - KBD_LED_OFF_TOKEN, - KBD_LED_AUTO_25_TOKEN, - KBD_LED_AUTO_50_TOKEN, - KBD_LED_AUTO_75_TOKEN, - KBD_LED_AUTO_100_TOKEN, - KBD_LED_ON_TOKEN, -}; - -static u16 kbd_token_bits; - -static struct kbd_info kbd_info; -static bool kbd_als_supported; -static bool kbd_triggers_supported; -static bool kbd_timeout_ac_supported; - -static u8 kbd_mode_levels[16]; -static int kbd_mode_levels_count; - -static u8 kbd_previous_level; -static u8 kbd_previous_mode_bit; - -static bool kbd_led_present; -static DEFINE_MUTEX(kbd_led_mutex); -static enum led_brightness kbd_led_level; - -/* - * NOTE: there are three ways to set the keyboard backlight level. - * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value). - * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels). - * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens) - * - * There are laptops which support only one of these methods. If we want to - * support as many machines as possible we need to implement all three methods. - * The first two methods use the kbd_state structure. The third uses SMBIOS - * tokens. If kbd_info.levels == 0, the machine does not support setting the - * keyboard backlight level via kbd_state.level. - */ - -static int kbd_get_info(struct kbd_info *info) -{ - struct calling_interface_buffer buffer; - u8 units; - int ret; - - dell_fill_request(&buffer, 0, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - if (ret) - return ret; - - info->modes = buffer.output[1] & 0xFFFF; - info->type = (buffer.output[1] >> 24) & 0xFF; - info->triggers = buffer.output[2] & 0xFF; - units = (buffer.output[2] >> 8) & 0xFF; - info->levels = (buffer.output[2] >> 16) & 0xFF; - - if (quirks && quirks->kbd_led_levels_off_1 && info->levels) - info->levels--; - - if (units & BIT(0)) - info->seconds = (buffer.output[3] >> 0) & 0xFF; - if (units & BIT(1)) - info->minutes = (buffer.output[3] >> 8) & 0xFF; - if (units & BIT(2)) - info->hours = (buffer.output[3] >> 16) & 0xFF; - if (units & BIT(3)) - info->days = (buffer.output[3] >> 24) & 0xFF; - - return ret; -} - -static unsigned int kbd_get_max_level(void) -{ - if (kbd_info.levels != 0) - return kbd_info.levels; - if (kbd_mode_levels_count > 0) - return kbd_mode_levels_count - 1; - return 0; -} - -static int kbd_get_level(struct kbd_state *state) -{ - int i; - - if (kbd_info.levels != 0) - return state->level; - - if (kbd_mode_levels_count > 0) { - for (i = 0; i < kbd_mode_levels_count; ++i) - if (kbd_mode_levels[i] == state->mode_bit) - return i; - return 0; - } - - return -EINVAL; -} - -static int kbd_set_level(struct kbd_state *state, u8 level) -{ - if (kbd_info.levels != 0) { - if (level != 0) - kbd_previous_level = level; - if (state->level == level) - return 0; - state->level = level; - if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF) - state->mode_bit = kbd_previous_mode_bit; - else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) { - kbd_previous_mode_bit = state->mode_bit; - state->mode_bit = KBD_MODE_BIT_OFF; - } - return 0; - } - - if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) { - if (level != 0) - kbd_previous_level = level; - state->mode_bit = kbd_mode_levels[level]; - return 0; - } - - return -EINVAL; -} - -static int kbd_get_state(struct kbd_state *state) -{ - struct calling_interface_buffer buffer; - int ret; - - dell_fill_request(&buffer, 0x1, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - if (ret) - return ret; - - state->mode_bit = ffs(buffer.output[1] & 0xFFFF); - if (state->mode_bit != 0) - state->mode_bit--; - - state->triggers = (buffer.output[1] >> 16) & 0xFF; - state->timeout_value = (buffer.output[1] >> 24) & 0x3F; - state->timeout_unit = (buffer.output[1] >> 30) & 0x3; - state->als_setting = buffer.output[2] & 0xFF; - state->als_value = (buffer.output[2] >> 8) & 0xFF; - state->level = (buffer.output[2] >> 16) & 0xFF; - state->timeout_value_ac = (buffer.output[2] >> 24) & 0x3F; - state->timeout_unit_ac = (buffer.output[2] >> 30) & 0x3; - - return ret; -} - -static int kbd_set_state(struct kbd_state *state) -{ - struct calling_interface_buffer buffer; - int ret; - u32 input1; - u32 input2; - - input1 = BIT(state->mode_bit) & 0xFFFF; - input1 |= (state->triggers & 0xFF) << 16; - input1 |= (state->timeout_value & 0x3F) << 24; - input1 |= (state->timeout_unit & 0x3) << 30; - input2 = state->als_setting & 0xFF; - input2 |= (state->level & 0xFF) << 16; - input2 |= (state->timeout_value_ac & 0x3F) << 24; - input2 |= (state->timeout_unit_ac & 0x3) << 30; - dell_fill_request(&buffer, 0x2, input1, input2, 0); - ret = dell_send_request(&buffer, - CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); - - return ret; -} - -static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) -{ - int ret; - - ret = kbd_set_state(state); - if (ret == 0) - return 0; - - /* - * When setting the new state fails,try to restore the previous one. - * This is needed on some machines where BIOS sets a default state when - * setting a new state fails. This default state could be all off. - */ - - if (kbd_set_state(old)) - pr_err("Setting old previous keyboard state failed\n"); - - return ret; -} - -static int kbd_set_token_bit(u8 bit) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - - if (bit >= ARRAY_SIZE(kbd_tokens)) - return -EINVAL; - - token = dell_smbios_find_token(kbd_tokens[bit]); - if (!token) - return -EINVAL; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - ret = dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - - return ret; -} - -static int kbd_get_token_bit(u8 bit) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int ret; - int val; - - if (bit >= ARRAY_SIZE(kbd_tokens)) - return -EINVAL; - - token = dell_smbios_find_token(kbd_tokens[bit]); - if (!token) - return -EINVAL; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - ret = dell_send_request(&buffer, CLASS_TOKEN_READ, SELECT_TOKEN_STD); - val = buffer.output[1]; - - if (ret) - return ret; - - return (val == token->value); -} - -static int kbd_get_first_active_token_bit(void) -{ - int i; - int ret; - - for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) { - ret = kbd_get_token_bit(i); - if (ret == 1) - return i; - } - - return ret; -} - -static int kbd_get_valid_token_counts(void) -{ - return hweight16(kbd_token_bits); -} - -static inline int kbd_init_info(void) -{ - struct kbd_state state; - int ret; - int i; - - ret = kbd_get_info(&kbd_info); - if (ret) - return ret; - - /* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one - * timeout value which is shared for both battery and AC power - * settings. So do not try to set AC values on old models. - */ - if ((quirks && quirks->kbd_missing_ac_tag) || - dell_smbios_find_token(KBD_LED_AC_TOKEN)) - kbd_timeout_ac_supported = true; - - kbd_get_state(&state); - - /* NOTE: timeout value is stored in 6 bits so max value is 63 */ - if (kbd_info.seconds > 63) - kbd_info.seconds = 63; - if (kbd_info.minutes > 63) - kbd_info.minutes = 63; - if (kbd_info.hours > 63) - kbd_info.hours = 63; - if (kbd_info.days > 63) - kbd_info.days = 63; - - /* NOTE: On tested machines ON mode did not work and caused - * problems (turned backlight off) so do not use it - */ - kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON); - - kbd_previous_level = kbd_get_level(&state); - kbd_previous_mode_bit = state.mode_bit; - - if (kbd_previous_level == 0 && kbd_get_max_level() != 0) - kbd_previous_level = 1; - - if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) { - kbd_previous_mode_bit = - ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF)); - if (kbd_previous_mode_bit != 0) - kbd_previous_mode_bit--; - } - - if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) | - BIT(KBD_MODE_BIT_TRIGGER_ALS))) - kbd_als_supported = true; - - if (kbd_info.modes & ( - BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) | - BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) | - BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100) - )) - kbd_triggers_supported = true; - - /* kbd_mode_levels[0] is reserved, see below */ - for (i = 0; i < 16; ++i) - if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes)) - kbd_mode_levels[1 + kbd_mode_levels_count++] = i; - - /* - * Find the first supported mode and assign to kbd_mode_levels[0]. - * This should be 0 (off), but we cannot depend on the BIOS to - * support 0. - */ - if (kbd_mode_levels_count > 0) { - for (i = 0; i < 16; ++i) { - if (BIT(i) & kbd_info.modes) { - kbd_mode_levels[0] = i; - break; - } - } - kbd_mode_levels_count++; - } - - return 0; - -} - -static inline void kbd_init_tokens(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) - if (dell_smbios_find_token(kbd_tokens[i])) - kbd_token_bits |= BIT(i); -} - -static void kbd_init(void) -{ - int ret; - - if (quirks && quirks->kbd_led_not_present) - return; - - ret = kbd_init_info(); - kbd_init_tokens(); - - /* - * Only supports keyboard backlight when it has at least two modes. - */ - if ((ret == 0 && (kbd_info.levels != 0 || kbd_mode_levels_count >= 2)) - || kbd_get_valid_token_counts() >= 2) - kbd_led_present = true; -} - -static ssize_t kbd_led_timeout_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state new_state; - struct kbd_state state; - bool convert; - int value; - int ret; - char ch; - u8 unit; - int i; - - ret = sscanf(buf, "%d %c", &value, &ch); - if (ret < 1) - return -EINVAL; - else if (ret == 1) - ch = 's'; - - if (value < 0) - return -EINVAL; - - convert = false; - - switch (ch) { - case 's': - if (value > kbd_info.seconds) - convert = true; - unit = KBD_TIMEOUT_SECONDS; - break; - case 'm': - if (value > kbd_info.minutes) - convert = true; - unit = KBD_TIMEOUT_MINUTES; - break; - case 'h': - if (value > kbd_info.hours) - convert = true; - unit = KBD_TIMEOUT_HOURS; - break; - case 'd': - if (value > kbd_info.days) - convert = true; - unit = KBD_TIMEOUT_DAYS; - break; - default: - return -EINVAL; - } - - if (quirks && quirks->needs_kbd_timeouts) - convert = true; - - if (convert) { - /* Convert value from current units to seconds */ - switch (unit) { - case KBD_TIMEOUT_DAYS: - value *= 24; - fallthrough; - case KBD_TIMEOUT_HOURS: - value *= 60; - fallthrough; - case KBD_TIMEOUT_MINUTES: - value *= 60; - unit = KBD_TIMEOUT_SECONDS; - } - - if (quirks && quirks->needs_kbd_timeouts) { - for (i = 0; quirks->kbd_timeouts[i] != -1; i++) { - if (value <= quirks->kbd_timeouts[i]) { - value = quirks->kbd_timeouts[i]; - break; - } - } - } - - if (value <= kbd_info.seconds && kbd_info.seconds) { - unit = KBD_TIMEOUT_SECONDS; - } else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) { - value /= 60; - unit = KBD_TIMEOUT_MINUTES; - } else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) { - value /= (60 * 60); - unit = KBD_TIMEOUT_HOURS; - } else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) { - value /= (60 * 60 * 24); - unit = KBD_TIMEOUT_DAYS; - } else { - return -EINVAL; - } - } - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - new_state = state; - - if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { - new_state.timeout_value_ac = value; - new_state.timeout_unit_ac = unit; - } else { - new_state.timeout_value = value; - new_state.timeout_unit = unit; - } - - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_timeout_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kbd_state state; - int value; - int ret; - int len; - u8 unit; - - ret = kbd_get_state(&state); - if (ret) - return ret; - - if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { - value = state.timeout_value_ac; - unit = state.timeout_unit_ac; - } else { - value = state.timeout_value; - unit = state.timeout_unit; - } - - len = sprintf(buf, "%d", value); - - switch (unit) { - case KBD_TIMEOUT_SECONDS: - return len + sprintf(buf+len, "s\n"); - case KBD_TIMEOUT_MINUTES: - return len + sprintf(buf+len, "m\n"); - case KBD_TIMEOUT_HOURS: - return len + sprintf(buf+len, "h\n"); - case KBD_TIMEOUT_DAYS: - return len + sprintf(buf+len, "d\n"); - default: - return -EINVAL; - } - - return len; -} - -static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR, - kbd_led_timeout_show, kbd_led_timeout_store); - -static const char * const kbd_led_triggers[] = { - "keyboard", - "touchpad", - /*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */ - "mouse", -}; - -static ssize_t kbd_led_triggers_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state new_state; - struct kbd_state state; - bool triggers_enabled = false; - int trigger_bit = -1; - char trigger[21]; - int i, ret; - - ret = sscanf(buf, "%20s", trigger); - if (ret != 1) - return -EINVAL; - - if (trigger[0] != '+' && trigger[0] != '-') - return -EINVAL; - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - if (kbd_triggers_supported) - triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); - - if (kbd_triggers_supported) { - for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { - if (!(kbd_info.triggers & BIT(i))) - continue; - if (!kbd_led_triggers[i]) - continue; - if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) - continue; - if (trigger[0] == '+' && - triggers_enabled && (state.triggers & BIT(i))) { - ret = count; - goto out; - } - if (trigger[0] == '-' && - (!triggers_enabled || !(state.triggers & BIT(i)))) { - ret = count; - goto out; - } - trigger_bit = i; - break; - } - } - - if (trigger_bit == -1) { - ret = -EINVAL; - goto out; - } - - new_state = state; - if (trigger[0] == '+') - new_state.triggers |= BIT(trigger_bit); - else { - new_state.triggers &= ~BIT(trigger_bit); - /* - * NOTE: trackstick bit (2) must be disabled when - * disabling touchpad bit (1), otherwise touchpad - * bit (1) will not be disabled - */ - if (trigger_bit == 1) - new_state.triggers &= ~BIT(2); - } - if ((kbd_info.triggers & new_state.triggers) != - new_state.triggers) { - ret = -EINVAL; - goto out; - } - if (new_state.triggers && !triggers_enabled) { - new_state.mode_bit = KBD_MODE_BIT_TRIGGER; - kbd_set_level(&new_state, kbd_previous_level); - } else if (new_state.triggers == 0) { - kbd_set_level(&new_state, 0); - } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) { - ret = -EINVAL; - goto out; - } - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - if (new_state.mode_bit != KBD_MODE_BIT_OFF) - kbd_previous_mode_bit = new_state.mode_bit; - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_triggers_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct kbd_state state; - bool triggers_enabled; - int level, i, ret; - int len = 0; - - ret = kbd_get_state(&state); - if (ret) - return ret; - - len = 0; - - if (kbd_triggers_supported) { - triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); - level = kbd_get_level(&state); - for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { - if (!(kbd_info.triggers & BIT(i))) - continue; - if (!kbd_led_triggers[i]) - continue; - if ((triggers_enabled || level <= 0) && - (state.triggers & BIT(i))) - buf[len++] = '+'; - else - buf[len++] = '-'; - len += sprintf(buf+len, "%s ", kbd_led_triggers[i]); - } - } - - if (len) - buf[len - 1] = '\n'; - - return len; -} - -static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR, - kbd_led_triggers_show, kbd_led_triggers_store); - -static ssize_t kbd_led_als_enabled_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state new_state; - struct kbd_state state; - bool triggers_enabled = false; - int enable; - int ret; - - ret = kstrtoint(buf, 0, &enable); - if (ret) - return ret; - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - if (enable == kbd_is_als_mode_bit(state.mode_bit)) { - ret = count; - goto out; - } - - new_state = state; - - if (kbd_triggers_supported) - triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); - - if (enable) { - if (triggers_enabled) - new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS; - else - new_state.mode_bit = KBD_MODE_BIT_ALS; - } else { - if (triggers_enabled) { - new_state.mode_bit = KBD_MODE_BIT_TRIGGER; - kbd_set_level(&new_state, kbd_previous_level); - } else { - new_state.mode_bit = KBD_MODE_BIT_ON; - } - } - if (!(kbd_info.modes & BIT(new_state.mode_bit))) { - ret = -EINVAL; - goto out; - } - - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - kbd_previous_mode_bit = new_state.mode_bit; - - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_als_enabled_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct kbd_state state; - bool enabled = false; - int ret; - - ret = kbd_get_state(&state); - if (ret) - return ret; - enabled = kbd_is_als_mode_bit(state.mode_bit); - - return sprintf(buf, "%d\n", enabled ? 1 : 0); -} - -static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR, - kbd_led_als_enabled_show, kbd_led_als_enabled_store); - -static ssize_t kbd_led_als_setting_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct kbd_state state; - struct kbd_state new_state; - u8 setting; - int ret; - - ret = kstrtou8(buf, 10, &setting); - if (ret) - return ret; - - mutex_lock(&kbd_led_mutex); - - ret = kbd_get_state(&state); - if (ret) - goto out; - - new_state = state; - new_state.als_setting = setting; - - ret = kbd_set_state_safe(&new_state, &state); - if (ret) - goto out; - - ret = count; -out: - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static ssize_t kbd_led_als_setting_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct kbd_state state; - int ret; - - ret = kbd_get_state(&state); - if (ret) - return ret; - - return sprintf(buf, "%d\n", state.als_setting); -} - -static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR, - kbd_led_als_setting_show, kbd_led_als_setting_store); - -static struct attribute *kbd_led_attrs[] = { - &dev_attr_stop_timeout.attr, - &dev_attr_start_triggers.attr, - NULL, -}; - -static const struct attribute_group kbd_led_group = { - .attrs = kbd_led_attrs, -}; - -static struct attribute *kbd_led_als_attrs[] = { - &dev_attr_als_enabled.attr, - &dev_attr_als_setting.attr, - NULL, -}; - -static const struct attribute_group kbd_led_als_group = { - .attrs = kbd_led_als_attrs, -}; - -static const struct attribute_group *kbd_led_groups[] = { - &kbd_led_group, - &kbd_led_als_group, - NULL, -}; - -static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) -{ - int ret; - u16 num; - struct kbd_state state; - - if (kbd_get_max_level()) { - ret = kbd_get_state(&state); - if (ret) - return 0; - ret = kbd_get_level(&state); - if (ret < 0) - return 0; - return ret; - } - - if (kbd_get_valid_token_counts()) { - ret = kbd_get_first_active_token_bit(); - if (ret < 0) - return 0; - for (num = kbd_token_bits; num != 0 && ret > 0; --ret) - num &= num - 1; /* clear the first bit set */ - if (num == 0) - return 0; - return ffs(num) - 1; - } - - pr_warn("Keyboard brightness level control not supported\n"); - return 0; -} - -static int kbd_led_level_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - enum led_brightness new_value = value; - struct kbd_state state; - struct kbd_state new_state; - u16 num; - int ret; - - mutex_lock(&kbd_led_mutex); - - if (kbd_get_max_level()) { - ret = kbd_get_state(&state); - if (ret) - goto out; - new_state = state; - ret = kbd_set_level(&new_state, value); - if (ret) - goto out; - ret = kbd_set_state_safe(&new_state, &state); - } else if (kbd_get_valid_token_counts()) { - for (num = kbd_token_bits; num != 0 && value > 0; --value) - num &= num - 1; /* clear the first bit set */ - if (num == 0) - ret = 0; - else - ret = kbd_set_token_bit(ffs(num) - 1); - } else { - pr_warn("Keyboard brightness level control not supported\n"); - ret = -ENXIO; - } - -out: - if (ret == 0) - kbd_led_level = new_value; - - mutex_unlock(&kbd_led_mutex); - return ret; -} - -static struct led_classdev kbd_led = { - .name = "dell::kbd_backlight", - .flags = LED_BRIGHT_HW_CHANGED, - .brightness_set_blocking = kbd_led_level_set, - .brightness_get = kbd_led_level_get, - .groups = kbd_led_groups, -}; - -static int __init kbd_led_init(struct device *dev) -{ - int ret; - - kbd_init(); - if (!kbd_led_present) - return -ENODEV; - if (!kbd_als_supported) - kbd_led_groups[1] = NULL; - kbd_led.max_brightness = kbd_get_max_level(); - if (!kbd_led.max_brightness) { - kbd_led.max_brightness = kbd_get_valid_token_counts(); - if (kbd_led.max_brightness) - kbd_led.max_brightness--; - } - - kbd_led_level = kbd_led_level_get(NULL); - - ret = led_classdev_register(dev, &kbd_led); - if (ret) - kbd_led_present = false; - - return ret; -} - -static void brightness_set_exit(struct led_classdev *led_cdev, - enum led_brightness value) -{ - /* Don't change backlight level on exit */ -}; - -static void kbd_led_exit(void) -{ - if (!kbd_led_present) - return; - kbd_led.brightness_set = brightness_set_exit; - led_classdev_unregister(&kbd_led); -} - -static int dell_laptop_notifier_call(struct notifier_block *nb, - unsigned long action, void *data) -{ - bool changed = false; - enum led_brightness new_kbd_led_level; - - switch (action) { - case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: - if (!kbd_led_present) - break; - - mutex_lock(&kbd_led_mutex); - new_kbd_led_level = kbd_led_level_get(&kbd_led); - if (kbd_led_level != new_kbd_led_level) { - kbd_led_level = new_kbd_led_level; - changed = true; - } - mutex_unlock(&kbd_led_mutex); - - if (changed) - led_classdev_notify_brightness_hw_changed(&kbd_led, - kbd_led_level); - break; - } - - return NOTIFY_OK; -} - -static struct notifier_block dell_laptop_notifier = { - .notifier_call = dell_laptop_notifier_call, -}; - -static int micmute_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - struct calling_interface_buffer buffer; - struct calling_interface_token *token; - int state = brightness != LED_OFF; - - if (state == 0) - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); - else - token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); - - if (!token) - return -ENODEV; - - dell_fill_request(&buffer, token->location, token->value, 0, 0); - dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); - - return 0; -} - -static struct led_classdev micmute_led_cdev = { - .name = "platform::micmute", - .max_brightness = 1, - .brightness_set_blocking = micmute_led_set, - .default_trigger = "audio-micmute", -}; - -static int __init dell_init(void) -{ - struct calling_interface_token *token; - int max_intensity = 0; - int ret; - - if (!dmi_check_system(dell_device_table)) - return -ENODEV; - - quirks = NULL; - /* find if this machine support other functions */ - dmi_check_system(dell_quirks); - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - platform_device = platform_device_alloc("dell-laptop", -1); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device1; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device2; - - ret = dell_setup_rfkill(); - - if (ret) { - pr_warn("Unable to setup rfkill\n"); - goto fail_rfkill; - } - - if (quirks && quirks->touchpad_led) - touchpad_led_init(&platform_device->dev); - - kbd_led_init(&platform_device->dev); - - dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); - debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, - &dell_debugfs_fops); - - dell_laptop_register_notifier(&dell_laptop_notifier); - - if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && - dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) { - micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); - ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); - if (ret < 0) - goto fail_led; - } - - if (acpi_video_get_backlight_type() != acpi_backlight_vendor) - return 0; - - token = dell_smbios_find_token(BRIGHTNESS_TOKEN); - if (token) { - struct calling_interface_buffer buffer; - - dell_fill_request(&buffer, token->location, 0, 0, 0); - ret = dell_send_request(&buffer, - CLASS_TOKEN_READ, SELECT_TOKEN_AC); - if (ret == 0) - max_intensity = buffer.output[3]; - } - - if (max_intensity) { - struct backlight_properties props; - memset(&props, 0, sizeof(struct backlight_properties)); - props.type = BACKLIGHT_PLATFORM; - props.max_brightness = max_intensity; - dell_backlight_device = backlight_device_register("dell_backlight", - &platform_device->dev, - NULL, - &dell_ops, - &props); - - if (IS_ERR(dell_backlight_device)) { - ret = PTR_ERR(dell_backlight_device); - dell_backlight_device = NULL; - goto fail_backlight; - } - - dell_backlight_device->props.brightness = - dell_get_intensity(dell_backlight_device); - if (dell_backlight_device->props.brightness < 0) { - ret = dell_backlight_device->props.brightness; - goto fail_get_brightness; - } - backlight_update_status(dell_backlight_device); - } - - return 0; - -fail_get_brightness: - backlight_device_unregister(dell_backlight_device); -fail_backlight: - led_classdev_unregister(&micmute_led_cdev); -fail_led: - dell_cleanup_rfkill(); -fail_rfkill: - platform_device_del(platform_device); -fail_platform_device2: - platform_device_put(platform_device); -fail_platform_device1: - platform_driver_unregister(&platform_driver); -fail_platform_driver: - return ret; -} - -static void __exit dell_exit(void) -{ - dell_laptop_unregister_notifier(&dell_laptop_notifier); - debugfs_remove_recursive(dell_laptop_dir); - if (quirks && quirks->touchpad_led) - touchpad_led_exit(); - kbd_led_exit(); - backlight_device_unregister(dell_backlight_device); - led_classdev_unregister(&micmute_led_cdev); - dell_cleanup_rfkill(); - if (platform_device) { - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } -} - -/* dell-rbtn.c driver export functions which will not work correctly (and could - * cause kernel crash) if they are called before dell-rbtn.c init code. This is - * not problem when dell-rbtn.c is compiled as external module. When both files - * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we - * need to ensure that dell_init() will be called after initializing dell-rbtn. - * This can be achieved by late_initcall() instead module_init(). - */ -late_initcall(dell_init); -module_exit(dell_exit); - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Gabriele Mazzotta "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("Dell laptop driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c deleted file mode 100644 index a89fad47ff13..000000000000 --- a/drivers/platform/x86/dell-rbtn.c +++ /dev/null @@ -1,499 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - Dell Airplane Mode Switch driver - Copyright (C) 2014-2015 Pali Rohár - -*/ - -#include -#include -#include -#include - -#include "dell-rbtn.h" - -enum rbtn_type { - RBTN_UNKNOWN, - RBTN_TOGGLE, - RBTN_SLIDER, -}; - -struct rbtn_data { - enum rbtn_type type; - struct rfkill *rfkill; - struct input_dev *input_dev; - bool suspended; -}; - - -/* - * acpi functions - */ - -static enum rbtn_type rbtn_check(struct acpi_device *device) -{ - unsigned long long output; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); - if (ACPI_FAILURE(status)) - return RBTN_UNKNOWN; - - switch (output) { - case 0: - case 1: - return RBTN_TOGGLE; - case 2: - case 3: - return RBTN_SLIDER; - default: - return RBTN_UNKNOWN; - } -} - -static int rbtn_get(struct acpi_device *device) -{ - unsigned long long output; - acpi_status status; - - status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); - if (ACPI_FAILURE(status)) - return -EINVAL; - - return !output; -} - -static int rbtn_acquire(struct acpi_device *device, bool enable) -{ - struct acpi_object_list input; - union acpi_object param; - acpi_status status; - - param.type = ACPI_TYPE_INTEGER; - param.integer.value = enable; - input.count = 1; - input.pointer = ¶m; - - status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); - if (ACPI_FAILURE(status)) - return -EINVAL; - - return 0; -} - - -/* - * rfkill device - */ - -static void rbtn_rfkill_query(struct rfkill *rfkill, void *data) -{ - struct acpi_device *device = data; - int state; - - state = rbtn_get(device); - if (state < 0) - return; - - rfkill_set_states(rfkill, state, state); -} - -static int rbtn_rfkill_set_block(void *data, bool blocked) -{ - /* NOTE: setting soft rfkill state is not supported */ - return -EINVAL; -} - -static const struct rfkill_ops rbtn_ops = { - .query = rbtn_rfkill_query, - .set_block = rbtn_rfkill_set_block, -}; - -static int rbtn_rfkill_init(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - int ret; - - if (rbtn_data->rfkill) - return 0; - - /* - * NOTE: rbtn controls all radio devices, not only WLAN - * but rfkill interface does not support "ANY" type - * so "WLAN" type is used - */ - rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, - RFKILL_TYPE_WLAN, &rbtn_ops, device); - if (!rbtn_data->rfkill) - return -ENOMEM; - - ret = rfkill_register(rbtn_data->rfkill); - if (ret) { - rfkill_destroy(rbtn_data->rfkill); - rbtn_data->rfkill = NULL; - return ret; - } - - return 0; -} - -static void rbtn_rfkill_exit(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - if (!rbtn_data->rfkill) - return; - - rfkill_unregister(rbtn_data->rfkill); - rfkill_destroy(rbtn_data->rfkill); - rbtn_data->rfkill = NULL; -} - -static void rbtn_rfkill_event(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - if (rbtn_data->rfkill) - rbtn_rfkill_query(rbtn_data->rfkill, device); -} - - -/* - * input device - */ - -static int rbtn_input_init(struct rbtn_data *rbtn_data) -{ - int ret; - - rbtn_data->input_dev = input_allocate_device(); - if (!rbtn_data->input_dev) - return -ENOMEM; - - rbtn_data->input_dev->name = "DELL Wireless hotkeys"; - rbtn_data->input_dev->phys = "dellabce/input0"; - rbtn_data->input_dev->id.bustype = BUS_HOST; - rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); - set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); - - ret = input_register_device(rbtn_data->input_dev); - if (ret) { - input_free_device(rbtn_data->input_dev); - rbtn_data->input_dev = NULL; - return ret; - } - - return 0; -} - -static void rbtn_input_exit(struct rbtn_data *rbtn_data) -{ - input_unregister_device(rbtn_data->input_dev); - rbtn_data->input_dev = NULL; -} - -static void rbtn_input_event(struct rbtn_data *rbtn_data) -{ - input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); - input_sync(rbtn_data->input_dev); - input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); - input_sync(rbtn_data->input_dev); -} - - -/* - * acpi driver - */ - -static int rbtn_add(struct acpi_device *device); -static int rbtn_remove(struct acpi_device *device); -static void rbtn_notify(struct acpi_device *device, u32 event); - -static const struct acpi_device_id rbtn_ids[] = { - { "DELRBTN", 0 }, - { "DELLABCE", 0 }, - - /* - * This driver can also handle the "DELLABC6" device that - * appears on the XPS 13 9350, but that device is disabled by - * the DSDT unless booted with acpi_osi="!Windows 2012" - * acpi_osi="!Windows 2013". - * - * According to Mario at Dell: - * - * DELLABC6 is a custom interface that was created solely to - * have airplane mode support for Windows 7. For Windows 10 - * the proper interface is to use that which is handled by - * intel-hid. A OEM airplane mode driver is not used. - * - * Since the kernel doesn't identify as Windows 7 it would be - * incorrect to do attempt to use that interface. - * - * Even if we override _OSI and bind to DELLABC6, we end up with - * inconsistent behavior in which userspace can get out of sync - * with the rfkill state as it conflicts with events from - * intel-hid. - * - * The upshot is that it is better to just ignore DELLABC6 - * devices. - */ - - { "", 0 }, -}; - -#ifdef CONFIG_PM_SLEEP -static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) -{ - struct rbtn_data *rbtn_data = context; - - rbtn_data->suspended = false; -} - -static int rbtn_suspend(struct device *dev) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = acpi_driver_data(device); - - rbtn_data->suspended = true; - - return 0; -} - -static int rbtn_resume(struct device *dev) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = acpi_driver_data(device); - acpi_status status; - - /* - * Upon resume, some BIOSes send an ACPI notification thet triggers - * an unwanted input event. In order to ignore it, we use a flag - * that we set at suspend and clear once we have received the extra - * ACPI notification. Since ACPI notifications are delivered - * asynchronously to drivers, we clear the flag from the workqueue - * used to deliver the notifications. This should be enough - * to have the flag cleared only after we received the extra - * notification, if any. - */ - status = acpi_os_execute(OSL_NOTIFY_HANDLER, - rbtn_clear_suspended_flag, rbtn_data); - if (ACPI_FAILURE(status)) - rbtn_clear_suspended_flag(rbtn_data); - - return 0; -} -#endif - -static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); - -static struct acpi_driver rbtn_driver = { - .name = "dell-rbtn", - .ids = rbtn_ids, - .drv.pm = &rbtn_pm_ops, - .ops = { - .add = rbtn_add, - .remove = rbtn_remove, - .notify = rbtn_notify, - }, - .owner = THIS_MODULE, -}; - - -/* - * notifier export functions - */ - -static bool auto_remove_rfkill = true; - -static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); - -static int rbtn_inc_count(struct device *dev, void *data) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = device->driver_data; - int *count = data; - - if (rbtn_data->type == RBTN_SLIDER) - (*count)++; - - return 0; -} - -static int rbtn_switch_dev(struct device *dev, void *data) -{ - struct acpi_device *device = to_acpi_device(dev); - struct rbtn_data *rbtn_data = device->driver_data; - bool enable = data; - - if (rbtn_data->type != RBTN_SLIDER) - return 0; - - if (enable) - rbtn_rfkill_init(device); - else - rbtn_rfkill_exit(device); - - return 0; -} - -int dell_rbtn_notifier_register(struct notifier_block *nb) -{ - bool first; - int count; - int ret; - - count = 0; - ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, - rbtn_inc_count); - if (ret || count == 0) - return -ENODEV; - - first = !rbtn_chain_head.head; - - ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); - if (ret != 0) - return ret; - - if (auto_remove_rfkill && first) - ret = driver_for_each_device(&rbtn_driver.drv, NULL, - (void *)false, rbtn_switch_dev); - - return ret; -} -EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); - -int dell_rbtn_notifier_unregister(struct notifier_block *nb) -{ - int ret; - - ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); - if (ret != 0) - return ret; - - if (auto_remove_rfkill && !rbtn_chain_head.head) - ret = driver_for_each_device(&rbtn_driver.drv, NULL, - (void *)true, rbtn_switch_dev); - - return ret; -} -EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); - - -/* - * acpi driver functions - */ - -static int rbtn_add(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data; - enum rbtn_type type; - int ret = 0; - - type = rbtn_check(device); - if (type == RBTN_UNKNOWN) { - dev_info(&device->dev, "Unknown device type\n"); - return -EINVAL; - } - - ret = rbtn_acquire(device, true); - if (ret < 0) { - dev_err(&device->dev, "Cannot enable device\n"); - return ret; - } - - rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); - if (!rbtn_data) - return -ENOMEM; - - rbtn_data->type = type; - device->driver_data = rbtn_data; - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - ret = rbtn_input_init(rbtn_data); - break; - case RBTN_SLIDER: - if (auto_remove_rfkill && rbtn_chain_head.head) - ret = 0; - else - ret = rbtn_rfkill_init(device); - break; - default: - ret = -EINVAL; - } - - return ret; - -} - -static int rbtn_remove(struct acpi_device *device) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - rbtn_input_exit(rbtn_data); - break; - case RBTN_SLIDER: - rbtn_rfkill_exit(device); - break; - default: - break; - } - - rbtn_acquire(device, false); - device->driver_data = NULL; - - return 0; -} - -static void rbtn_notify(struct acpi_device *device, u32 event) -{ - struct rbtn_data *rbtn_data = device->driver_data; - - /* - * Some BIOSes send a notification at resume. - * Ignore it to prevent unwanted input events. - */ - if (rbtn_data->suspended) { - dev_dbg(&device->dev, "ACPI notification ignored\n"); - return; - } - - if (event != 0x80) { - dev_info(&device->dev, "Received unknown event (0x%x)\n", - event); - return; - } - - switch (rbtn_data->type) { - case RBTN_TOGGLE: - rbtn_input_event(rbtn_data); - break; - case RBTN_SLIDER: - rbtn_rfkill_event(device); - atomic_notifier_call_chain(&rbtn_chain_head, event, device); - break; - default: - break; - } -} - - -/* - * module functions - */ - -module_acpi_driver(rbtn_driver); - -module_param(auto_remove_rfkill, bool, 0444); - -MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " - "other modules start receiving events " - "from this module and re-add them when " - "the last module stops receiving events " - "(default true)"); -MODULE_DEVICE_TABLE(acpi, rbtn_ids); -MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); -MODULE_AUTHOR("Pali Rohár "); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h deleted file mode 100644 index 5e030f926c58..000000000000 --- a/drivers/platform/x86/dell-rbtn.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - Dell Airplane Mode Switch driver - Copyright (C) 2014-2015 Pali Rohár - -*/ - -#ifndef _DELL_RBTN_H_ -#define _DELL_RBTN_H_ - -struct notifier_block; - -int dell_rbtn_notifier_register(struct notifier_block *nb); -int dell_rbtn_notifier_unregister(struct notifier_block *nb); - -#endif diff --git a/drivers/platform/x86/dell-smbios-base.c b/drivers/platform/x86/dell-smbios-base.c deleted file mode 100644 index 3a1dbf199441..000000000000 --- a/drivers/platform/x86/dell-smbios-base.c +++ /dev/null @@ -1,652 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Common functions for kernel modules using Dell SMBIOS - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * - * Based on documentation in the libsmbios package: - * Copyright (C) 2005-2014 Dell Inc. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" - -static u32 da_supported_commands; -static int da_num_tokens; -static struct platform_device *platform_device; -static struct calling_interface_token *da_tokens; -static struct device_attribute *token_location_attrs; -static struct device_attribute *token_value_attrs; -static struct attribute **token_attrs; -static DEFINE_MUTEX(smbios_mutex); - -struct smbios_device { - struct list_head list; - struct device *device; - int (*call_fn)(struct calling_interface_buffer *arg); -}; - -struct smbios_call { - u32 need_capability; - int cmd_class; - int cmd_select; -}; - -/* calls that are whitelisted for given capabilities */ -static struct smbios_call call_whitelist[] = { - /* generally tokens are allowed, but may be further filtered or - * restricted by token blacklist or whitelist - */ - {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, - {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, - {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, - {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, - {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, - {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, - /* used by userspace: fwupdate */ - {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, - /* used by userspace: fwupd */ - {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, - {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, -}; - -/* calls that are explicitly blacklisted */ -static struct smbios_call call_blacklist[] = { - {0x0000, 1, 7}, /* manufacturing use */ - {0x0000, 6, 5}, /* manufacturing use */ - {0x0000, 11, 3}, /* write once */ - {0x0000, 11, 7}, /* write once */ - {0x0000, 11, 11}, /* write once */ - {0x0000, 19, -1}, /* diagnostics */ - /* handled by kernel: dell-laptop */ - {0x0000, CLASS_INFO, SELECT_RFKILL}, - {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, -}; - -struct token_range { - u32 need_capability; - u16 min; - u16 max; -}; - -/* tokens that are whitelisted for given capabilities */ -static struct token_range token_whitelist[] = { - /* used by userspace: fwupdate */ - {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, - /* can indicate to userspace that WMI is needed */ - {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} -}; - -/* tokens that are explicitly blacklisted */ -static struct token_range token_blacklist[] = { - {0x0000, 0x0058, 0x0059}, /* ME use */ - {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ - {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ - {0x0000, 0x0175, 0x0176}, /* write once */ - {0x0000, 0x0195, 0x0197}, /* diagnostics */ - {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ - {0x0000, 0x027D, 0x0284}, /* diagnostics */ - {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ - {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ - {0x0000, 0x0300, 0x0302}, /* manufacturing use */ - {0x0000, 0x0325, 0x0326}, /* manufacturing use */ - {0x0000, 0x0332, 0x0335}, /* fan control */ - {0x0000, 0x0350, 0x0350}, /* manufacturing use */ - {0x0000, 0x0363, 0x0363}, /* manufacturing use */ - {0x0000, 0x0368, 0x0368}, /* manufacturing use */ - {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ - {0x0000, 0x049E, 0x049F}, /* manufacturing use */ - {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ - {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ - {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ - {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ - {0x0000, 0xA000, 0xBFFF}, /* write only */ - {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ - /* handled by kernel: dell-laptop */ - {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, - {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, - {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, - {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, - {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, - {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, -}; - -static LIST_HEAD(smbios_device_list); - -int dell_smbios_error(int value) -{ - switch (value) { - case 0: /* Completed successfully */ - return 0; - case -1: /* Completed with error */ - return -EIO; - case -2: /* Function not supported */ - return -ENXIO; - default: /* Unknown error */ - return -EINVAL; - } -} -EXPORT_SYMBOL_GPL(dell_smbios_error); - -int dell_smbios_register_device(struct device *d, void *call_fn) -{ - struct smbios_device *priv; - - priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); - if (!priv) - return -ENOMEM; - get_device(d); - priv->device = d; - priv->call_fn = call_fn; - mutex_lock(&smbios_mutex); - list_add_tail(&priv->list, &smbios_device_list); - mutex_unlock(&smbios_mutex); - dev_dbg(d, "Added device: %s\n", d->driver->name); - return 0; -} -EXPORT_SYMBOL_GPL(dell_smbios_register_device); - -void dell_smbios_unregister_device(struct device *d) -{ - struct smbios_device *priv; - - mutex_lock(&smbios_mutex); - list_for_each_entry(priv, &smbios_device_list, list) { - if (priv->device == d) { - list_del(&priv->list); - put_device(d); - break; - } - } - mutex_unlock(&smbios_mutex); - dev_dbg(d, "Remove device: %s\n", d->driver->name); -} -EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); - -int dell_smbios_call_filter(struct device *d, - struct calling_interface_buffer *buffer) -{ - u16 t = 0; - int i; - - /* can't make calls over 30 */ - if (buffer->cmd_class > 30) { - dev_dbg(d, "class too big: %u\n", buffer->cmd_class); - return -EINVAL; - } - - /* supported calls on the particular system */ - if (!(da_supported_commands & (1 << buffer->cmd_class))) { - dev_dbg(d, "invalid command, supported commands: 0x%8x\n", - da_supported_commands); - return -EINVAL; - } - - /* match against call blacklist */ - for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { - if (buffer->cmd_class != call_blacklist[i].cmd_class) - continue; - if (buffer->cmd_select != call_blacklist[i].cmd_select && - call_blacklist[i].cmd_select != -1) - continue; - dev_dbg(d, "blacklisted command: %u/%u\n", - buffer->cmd_class, buffer->cmd_select); - return -EINVAL; - } - - /* if a token call, find token ID */ - - if ((buffer->cmd_class == CLASS_TOKEN_READ || - buffer->cmd_class == CLASS_TOKEN_WRITE) && - buffer->cmd_select < 3) { - /* tokens enabled ? */ - if (!da_tokens) { - dev_dbg(d, "no token support on this system\n"); - return -EINVAL; - } - - /* find the matching token ID */ - for (i = 0; i < da_num_tokens; i++) { - if (da_tokens[i].location != buffer->input[0]) - continue; - t = da_tokens[i].tokenID; - break; - } - - /* token call; but token didn't exist */ - if (!t) { - dev_dbg(d, "token at location %04x doesn't exist\n", - buffer->input[0]); - return -EINVAL; - } - - /* match against token blacklist */ - for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { - if (!token_blacklist[i].min || !token_blacklist[i].max) - continue; - if (t >= token_blacklist[i].min && - t <= token_blacklist[i].max) - return -EINVAL; - } - - /* match against token whitelist */ - for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { - if (!token_whitelist[i].min || !token_whitelist[i].max) - continue; - if (t < token_whitelist[i].min || - t > token_whitelist[i].max) - continue; - if (!token_whitelist[i].need_capability || - capable(token_whitelist[i].need_capability)) { - dev_dbg(d, "whitelisted token: %x\n", t); - return 0; - } - - } - } - /* match against call whitelist */ - for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { - if (buffer->cmd_class != call_whitelist[i].cmd_class) - continue; - if (buffer->cmd_select != call_whitelist[i].cmd_select) - continue; - if (!call_whitelist[i].need_capability || - capable(call_whitelist[i].need_capability)) { - dev_dbg(d, "whitelisted capable command: %u/%u\n", - buffer->cmd_class, buffer->cmd_select); - return 0; - } - dev_dbg(d, "missing capability %d for %u/%u\n", - call_whitelist[i].need_capability, - buffer->cmd_class, buffer->cmd_select); - - } - - /* not in a whitelist, only allow processes with capabilities */ - if (capable(CAP_SYS_RAWIO)) { - dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", - buffer->cmd_class, buffer->cmd_select); - return 0; - } - - return -EACCES; -} -EXPORT_SYMBOL_GPL(dell_smbios_call_filter); - -int dell_smbios_call(struct calling_interface_buffer *buffer) -{ - int (*call_fn)(struct calling_interface_buffer *) = NULL; - struct device *selected_dev = NULL; - struct smbios_device *priv; - int ret; - - mutex_lock(&smbios_mutex); - list_for_each_entry(priv, &smbios_device_list, list) { - if (!selected_dev || priv->device->id >= selected_dev->id) { - dev_dbg(priv->device, "Trying device ID: %d\n", - priv->device->id); - call_fn = priv->call_fn; - selected_dev = priv->device; - } - } - - if (!selected_dev) { - ret = -ENODEV; - pr_err("No dell-smbios drivers are loaded\n"); - goto out_smbios_call; - } - - ret = call_fn(buffer); - -out_smbios_call: - mutex_unlock(&smbios_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_smbios_call); - -struct calling_interface_token *dell_smbios_find_token(int tokenid) -{ - int i; - - if (!da_tokens) - return NULL; - - for (i = 0; i < da_num_tokens; i++) { - if (da_tokens[i].tokenID == tokenid) - return &da_tokens[i]; - } - - return NULL; -} -EXPORT_SYMBOL_GPL(dell_smbios_find_token); - -static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); - -int dell_laptop_register_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); -} -EXPORT_SYMBOL_GPL(dell_laptop_register_notifier); - -int dell_laptop_unregister_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); -} -EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); - -void dell_laptop_call_notifier(unsigned long action, void *data) -{ - blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); -} -EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); - -static void __init parse_da_table(const struct dmi_header *dm) -{ - /* Final token is a terminator, so we don't want to copy it */ - int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; - struct calling_interface_token *new_da_tokens; - struct calling_interface_structure *table = - container_of(dm, struct calling_interface_structure, header); - - /* - * 4 bytes of table header, plus 7 bytes of Dell header - * plus at least 6 bytes of entry - */ - - if (dm->length < 17) - return; - - da_supported_commands = table->supportedCmds; - - new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * - sizeof(struct calling_interface_token), - GFP_KERNEL); - - if (!new_da_tokens) - return; - da_tokens = new_da_tokens; - - memcpy(da_tokens+da_num_tokens, table->tokens, - sizeof(struct calling_interface_token) * tokens); - - da_num_tokens += tokens; -} - -static void zero_duplicates(struct device *dev) -{ - int i, j; - - for (i = 0; i < da_num_tokens; i++) { - if (da_tokens[i].tokenID == 0) - continue; - for (j = i+1; j < da_num_tokens; j++) { - if (da_tokens[j].tokenID == 0) - continue; - if (da_tokens[i].tokenID == da_tokens[j].tokenID) { - dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", - da_tokens[j].tokenID, - da_tokens[j].location, - da_tokens[j].value); - da_tokens[j].tokenID = 0; - } - } - } -} - -static void __init find_tokens(const struct dmi_header *dm, void *dummy) -{ - switch (dm->type) { - case 0xd4: /* Indexed IO */ - case 0xd5: /* Protected Area Type 1 */ - case 0xd6: /* Protected Area Type 2 */ - break; - case 0xda: /* Calling interface */ - parse_da_table(dm); - break; - } -} - -static int match_attribute(struct device *dev, - struct device_attribute *attr) -{ - int i; - - for (i = 0; i < da_num_tokens * 2; i++) { - if (!token_attrs[i]) - continue; - if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) - return i/2; - } - dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); - return -EINVAL; -} - -static ssize_t location_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int i; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - i = match_attribute(dev, attr); - if (i > 0) - return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location); - return 0; -} - -static ssize_t value_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int i; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - i = match_attribute(dev, attr); - if (i > 0) - return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value); - return 0; -} - -static struct attribute_group smbios_attribute_group = { - .name = "tokens" -}; - -static struct platform_driver platform_driver = { - .driver = { - .name = "dell-smbios", - }, -}; - -static int build_tokens_sysfs(struct platform_device *dev) -{ - char *location_name; - char *value_name; - size_t size; - int ret; - int i, j; - - /* (number of tokens + 1 for null terminated */ - size = sizeof(struct device_attribute) * (da_num_tokens + 1); - token_location_attrs = kzalloc(size, GFP_KERNEL); - if (!token_location_attrs) - return -ENOMEM; - token_value_attrs = kzalloc(size, GFP_KERNEL); - if (!token_value_attrs) - goto out_allocate_value; - - /* need to store both location and value + terminator*/ - size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); - token_attrs = kzalloc(size, GFP_KERNEL); - if (!token_attrs) - goto out_allocate_attrs; - - for (i = 0, j = 0; i < da_num_tokens; i++) { - /* skip empty */ - if (da_tokens[i].tokenID == 0) - continue; - /* add location */ - location_name = kasprintf(GFP_KERNEL, "%04x_location", - da_tokens[i].tokenID); - if (location_name == NULL) - goto out_unwind_strings; - sysfs_attr_init(&token_location_attrs[i].attr); - token_location_attrs[i].attr.name = location_name; - token_location_attrs[i].attr.mode = 0444; - token_location_attrs[i].show = location_show; - token_attrs[j++] = &token_location_attrs[i].attr; - - /* add value */ - value_name = kasprintf(GFP_KERNEL, "%04x_value", - da_tokens[i].tokenID); - if (value_name == NULL) - goto loop_fail_create_value; - sysfs_attr_init(&token_value_attrs[i].attr); - token_value_attrs[i].attr.name = value_name; - token_value_attrs[i].attr.mode = 0444; - token_value_attrs[i].show = value_show; - token_attrs[j++] = &token_value_attrs[i].attr; - continue; - -loop_fail_create_value: - kfree(location_name); - goto out_unwind_strings; - } - smbios_attribute_group.attrs = token_attrs; - - ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); - if (ret) - goto out_unwind_strings; - return 0; - -out_unwind_strings: - while (i--) { - kfree(token_location_attrs[i].attr.name); - kfree(token_value_attrs[i].attr.name); - } - kfree(token_attrs); -out_allocate_attrs: - kfree(token_value_attrs); -out_allocate_value: - kfree(token_location_attrs); - - return -ENOMEM; -} - -static void free_group(struct platform_device *pdev) -{ - int i; - - sysfs_remove_group(&pdev->dev.kobj, - &smbios_attribute_group); - for (i = 0; i < da_num_tokens; i++) { - kfree(token_location_attrs[i].attr.name); - kfree(token_value_attrs[i].attr.name); - } - kfree(token_attrs); - kfree(token_value_attrs); - kfree(token_location_attrs); -} - -static int __init dell_smbios_init(void) -{ - int ret, wmi, smm; - - if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && - !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { - pr_err("Unable to run on non-Dell system\n"); - return -ENODEV; - } - - dmi_walk(find_tokens, NULL); - - ret = platform_driver_register(&platform_driver); - if (ret) - goto fail_platform_driver; - - platform_device = platform_device_alloc("dell-smbios", 0); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device_alloc; - } - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device_add; - - /* register backends */ - wmi = init_dell_smbios_wmi(); - if (wmi) - pr_debug("Failed to initialize WMI backend: %d\n", wmi); - smm = init_dell_smbios_smm(); - if (smm) - pr_debug("Failed to initialize SMM backend: %d\n", smm); - if (wmi && smm) { - pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", - wmi, smm); - ret = -ENODEV; - goto fail_create_group; - } - - if (da_tokens) { - /* duplicate tokens will cause problems building sysfs files */ - zero_duplicates(&platform_device->dev); - - ret = build_tokens_sysfs(platform_device); - if (ret) - goto fail_sysfs; - } - - return 0; - -fail_sysfs: - free_group(platform_device); - -fail_create_group: - platform_device_del(platform_device); - -fail_platform_device_add: - platform_device_put(platform_device); - -fail_platform_device_alloc: - platform_driver_unregister(&platform_driver); - -fail_platform_driver: - kfree(da_tokens); - return ret; -} - -static void __exit dell_smbios_exit(void) -{ - exit_dell_smbios_wmi(); - exit_dell_smbios_smm(); - mutex_lock(&smbios_mutex); - if (platform_device) { - if (da_tokens) - free_group(platform_device); - platform_device_unregister(platform_device); - platform_driver_unregister(&platform_driver); - } - kfree(da_tokens); - mutex_unlock(&smbios_mutex); -} - -module_init(dell_smbios_init); -module_exit(dell_smbios_exit); - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Gabriele Mazzotta "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell-smbios-smm.c deleted file mode 100644 index 97c52a839a3e..000000000000 --- a/drivers/platform/x86/dell-smbios-smm.c +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * SMI methods for use with dell-smbios - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * Copyright (c) 2017 Dell Inc. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include "dcdbas.h" -#include "dell-smbios.h" - -static int da_command_address; -static int da_command_code; -static struct calling_interface_buffer *buffer; -static struct platform_device *platform_device; -static DEFINE_MUTEX(smm_mutex); - -static const struct dmi_system_id dell_device_table[] __initconst = { - { - .ident = "Dell laptop", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ - }, - }, - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ - }, - }, - { - .ident = "Dell Computer Corporation", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), - DMI_MATCH(DMI_CHASSIS_TYPE, "8"), - }, - }, - { } -}; -MODULE_DEVICE_TABLE(dmi, dell_device_table); - -static void parse_da_table(const struct dmi_header *dm) -{ - struct calling_interface_structure *table = - container_of(dm, struct calling_interface_structure, header); - - /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least - * 6 bytes of entry - */ - if (dm->length < 17) - return; - - da_command_address = table->cmdIOAddress; - da_command_code = table->cmdIOCode; -} - -static void find_cmd_address(const struct dmi_header *dm, void *dummy) -{ - switch (dm->type) { - case 0xda: /* Calling interface */ - parse_da_table(dm); - break; - } -} - -static int dell_smbios_smm_call(struct calling_interface_buffer *input) -{ - struct smi_cmd command; - size_t size; - - size = sizeof(struct calling_interface_buffer); - command.magic = SMI_CMD_MAGIC; - command.command_address = da_command_address; - command.command_code = da_command_code; - command.ebx = virt_to_phys(buffer); - command.ecx = 0x42534931; - - mutex_lock(&smm_mutex); - memcpy(buffer, input, size); - dcdbas_smi_request(&command); - memcpy(input, buffer, size); - mutex_unlock(&smm_mutex); - return 0; -} - -/* When enabled this indicates that SMM won't work */ -static bool test_wsmt_enabled(void) -{ - struct calling_interface_token *wsmt; - - /* if token doesn't exist, SMM will work */ - wsmt = dell_smbios_find_token(WSMT_EN_TOKEN); - if (!wsmt) - return false; - - /* If token exists, try to access over SMM but set a dummy return. - * - If WSMT disabled it will be overwritten by SMM - * - If WSMT enabled then dummy value will remain - */ - buffer->cmd_class = CLASS_TOKEN_READ; - buffer->cmd_select = SELECT_TOKEN_STD; - memset(buffer, 0, sizeof(struct calling_interface_buffer)); - buffer->input[0] = wsmt->location; - buffer->output[0] = 99; - dell_smbios_smm_call(buffer); - if (buffer->output[0] == 99) - return true; - - return false; -} - -int init_dell_smbios_smm(void) -{ - int ret; - /* - * Allocate buffer below 4GB for SMI data--only 32-bit physical addr - * is passed to SMI handler. - */ - buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32); - if (!buffer) - return -ENOMEM; - - dmi_walk(find_cmd_address, NULL); - - if (test_wsmt_enabled()) { - pr_debug("Disabling due to WSMT enabled\n"); - ret = -ENODEV; - goto fail_wsmt; - } - - platform_device = platform_device_alloc("dell-smbios", 1); - if (!platform_device) { - ret = -ENOMEM; - goto fail_platform_device_alloc; - } - - ret = platform_device_add(platform_device); - if (ret) - goto fail_platform_device_add; - - ret = dell_smbios_register_device(&platform_device->dev, - &dell_smbios_smm_call); - if (ret) - goto fail_register; - - return 0; - -fail_register: - platform_device_del(platform_device); - -fail_platform_device_add: - platform_device_put(platform_device); - -fail_wsmt: -fail_platform_device_alloc: - free_page((unsigned long)buffer); - return ret; -} - -void exit_dell_smbios_smm(void) -{ - if (platform_device) { - dell_smbios_unregister_device(&platform_device->dev); - platform_device_unregister(platform_device); - free_page((unsigned long)buffer); - } -} diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c deleted file mode 100644 index 27a298b7c541..000000000000 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ /dev/null @@ -1,277 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * WMI methods for use with dell-smbios - * - * Copyright (c) 2017 Dell Inc. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" -#include "dell-wmi-descriptor.h" - -static DEFINE_MUTEX(call_mutex); -static DEFINE_MUTEX(list_mutex); -static int wmi_supported; - -struct misc_bios_flags_structure { - struct dmi_header header; - u16 flags0; -} __packed; -#define FLAG_HAS_ACPI_WMI 0x02 - -#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" - -struct wmi_smbios_priv { - struct dell_wmi_smbios_buffer *buf; - struct list_head list; - struct wmi_device *wdev; - struct device *child; - u32 req_buf_size; -}; -static LIST_HEAD(wmi_list); - -static inline struct wmi_smbios_priv *get_first_smbios_priv(void) -{ - return list_first_entry_or_null(&wmi_list, - struct wmi_smbios_priv, - list); -} - -static int run_smbios_call(struct wmi_device *wdev) -{ - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct wmi_smbios_priv *priv; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - - priv = dev_get_drvdata(&wdev->dev); - input.length = priv->req_buf_size - sizeof(u64); - input.pointer = &priv->buf->std; - - dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n", - priv->buf->std.cmd_class, priv->buf->std.cmd_select, - priv->buf->std.input[0], priv->buf->std.input[1], - priv->buf->std.input[2], priv->buf->std.input[3]); - - status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type != ACPI_TYPE_BUFFER) { - dev_dbg(&wdev->dev, "received type: %d\n", obj->type); - if (obj->type == ACPI_TYPE_INTEGER) - dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", - obj->integer.value); - return -EIO; - } - memcpy(&priv->buf->std, obj->buffer.pointer, obj->buffer.length); - dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", - priv->buf->std.output[0], priv->buf->std.output[1], - priv->buf->std.output[2], priv->buf->std.output[3]); - kfree(output.pointer); - - return 0; -} - -static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) -{ - struct wmi_smbios_priv *priv; - size_t difference; - size_t size; - int ret; - - mutex_lock(&call_mutex); - priv = get_first_smbios_priv(); - if (!priv) { - ret = -ENODEV; - goto out_wmi_call; - } - - size = sizeof(struct calling_interface_buffer); - difference = priv->req_buf_size - sizeof(u64) - size; - - memset(&priv->buf->ext, 0, difference); - memcpy(&priv->buf->std, buffer, size); - ret = run_smbios_call(priv->wdev); - memcpy(buffer, &priv->buf->std, size); -out_wmi_call: - mutex_unlock(&call_mutex); - - return ret; -} - -static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd, - struct wmi_ioctl_buffer *arg) -{ - struct wmi_smbios_priv *priv; - int ret = 0; - - switch (cmd) { - case DELL_WMI_SMBIOS_CMD: - mutex_lock(&call_mutex); - priv = dev_get_drvdata(&wdev->dev); - if (!priv) { - ret = -ENODEV; - goto fail_smbios_cmd; - } - memcpy(priv->buf, arg, priv->req_buf_size); - if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) { - dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n", - priv->buf->std.cmd_class, - priv->buf->std.cmd_select, - priv->buf->std.input[0]); - ret = -EFAULT; - goto fail_smbios_cmd; - } - ret = run_smbios_call(priv->wdev); - if (ret) - goto fail_smbios_cmd; - memcpy(arg, priv->buf, priv->req_buf_size); -fail_smbios_cmd: - mutex_unlock(&call_mutex); - break; - default: - ret = -ENOIOCTLCMD; - } - return ret; -} - -static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct wmi_driver *wdriver = - container_of(wdev->dev.driver, struct wmi_driver, driver); - struct wmi_smbios_priv *priv; - u32 hotfix; - int count; - int ret; - - ret = dell_wmi_get_descriptor_valid(); - if (ret) - return ret; - - priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv), - GFP_KERNEL); - if (!priv) - return -ENOMEM; - - /* WMI buffer size will be either 4k or 32k depending on machine */ - if (!dell_wmi_get_size(&priv->req_buf_size)) - return -EPROBE_DEFER; - - /* some SMBIOS calls fail unless BIOS contains hotfix */ - if (!dell_wmi_get_hotfix(&hotfix)) - return -EPROBE_DEFER; - if (!hotfix) { - dev_warn(&wdev->dev, - "WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n", - hotfix); - wdriver->filter_callback = NULL; - } - - /* add in the length object we will use internally with ioctl */ - priv->req_buf_size += sizeof(u64); - ret = set_required_buffer_size(wdev, priv->req_buf_size); - if (ret) - return ret; - - count = get_order(priv->req_buf_size); - priv->buf = (void *)__get_free_pages(GFP_KERNEL, count); - if (!priv->buf) - return -ENOMEM; - - /* ID is used by dell-smbios to set priority of drivers */ - wdev->dev.id = 1; - ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call); - if (ret) - goto fail_register; - - priv->wdev = wdev; - dev_set_drvdata(&wdev->dev, priv); - mutex_lock(&list_mutex); - list_add_tail(&priv->list, &wmi_list); - mutex_unlock(&list_mutex); - - return 0; - -fail_register: - free_pages((unsigned long)priv->buf, count); - return ret; -} - -static int dell_smbios_wmi_remove(struct wmi_device *wdev) -{ - struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); - int count; - - mutex_lock(&call_mutex); - mutex_lock(&list_mutex); - list_del(&priv->list); - mutex_unlock(&list_mutex); - dell_smbios_unregister_device(&wdev->dev); - count = get_order(priv->req_buf_size); - free_pages((unsigned long)priv->buf, count); - mutex_unlock(&call_mutex); - return 0; -} - -static const struct wmi_device_id dell_smbios_wmi_id_table[] = { - { .guid_string = DELL_WMI_SMBIOS_GUID }, - { }, -}; - -static void parse_b1_table(const struct dmi_header *dm) -{ - struct misc_bios_flags_structure *flags = - container_of(dm, struct misc_bios_flags_structure, header); - - /* 4 bytes header, 8 bytes flags */ - if (dm->length < 12) - return; - if (dm->handle != 0xb100) - return; - if ((flags->flags0 & FLAG_HAS_ACPI_WMI)) - wmi_supported = 1; -} - -static void find_b1(const struct dmi_header *dm, void *dummy) -{ - switch (dm->type) { - case 0xb1: /* misc bios flags */ - parse_b1_table(dm); - break; - } -} - -static struct wmi_driver dell_smbios_wmi_driver = { - .driver = { - .name = "dell-smbios", - }, - .probe = dell_smbios_wmi_probe, - .remove = dell_smbios_wmi_remove, - .id_table = dell_smbios_wmi_id_table, - .filter_callback = dell_smbios_wmi_filter, -}; - -int init_dell_smbios_wmi(void) -{ - dmi_walk(find_b1, NULL); - - if (!wmi_supported) - return -ENODEV; - - return wmi_driver_register(&dell_smbios_wmi_driver); -} - -void exit_dell_smbios_wmi(void) -{ - wmi_driver_unregister(&dell_smbios_wmi_driver); -} - -MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table); diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h deleted file mode 100644 index 75fa8ea0476d..000000000000 --- a/drivers/platform/x86/dell-smbios.h +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Common functions for kernel modules using Dell SMBIOS - * - * Copyright (c) Red Hat - * Copyright (c) 2014 Gabriele Mazzotta - * Copyright (c) 2014 Pali Rohár - * - * Based on documentation in the libsmbios package: - * Copyright (C) 2005-2014 Dell Inc. - */ - -#ifndef _DELL_SMBIOS_H_ -#define _DELL_SMBIOS_H_ - -#include -#include - -/* Classes and selects used only in kernel drivers */ -#define CLASS_KBD_BACKLIGHT 4 -#define SELECT_KBD_BACKLIGHT 11 - -/* Tokens used in kernel drivers, any of these - * should be filtered from userspace access - */ -#define BRIGHTNESS_TOKEN 0x007d -#define KBD_LED_AC_TOKEN 0x0451 -#define KBD_LED_OFF_TOKEN 0x01E1 -#define KBD_LED_ON_TOKEN 0x01E2 -#define KBD_LED_AUTO_TOKEN 0x01E3 -#define KBD_LED_AUTO_25_TOKEN 0x02EA -#define KBD_LED_AUTO_50_TOKEN 0x02EB -#define KBD_LED_AUTO_75_TOKEN 0x02EC -#define KBD_LED_AUTO_100_TOKEN 0x02F6 -#define GLOBAL_MIC_MUTE_ENABLE 0x0364 -#define GLOBAL_MIC_MUTE_DISABLE 0x0365 - -struct notifier_block; - -struct calling_interface_token { - u16 tokenID; - u16 location; - union { - u16 value; - u16 stringlength; - }; -}; - -struct calling_interface_structure { - struct dmi_header header; - u16 cmdIOAddress; - u8 cmdIOCode; - u32 supportedCmds; - struct calling_interface_token tokens[]; -} __packed; - -int dell_smbios_register_device(struct device *d, void *call_fn); -void dell_smbios_unregister_device(struct device *d); - -int dell_smbios_error(int value); -int dell_smbios_call_filter(struct device *d, - struct calling_interface_buffer *buffer); -int dell_smbios_call(struct calling_interface_buffer *buffer); - -struct calling_interface_token *dell_smbios_find_token(int tokenid); - -enum dell_laptop_notifier_actions { - DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, -}; - -int dell_laptop_register_notifier(struct notifier_block *nb); -int dell_laptop_unregister_notifier(struct notifier_block *nb); -void dell_laptop_call_notifier(unsigned long action, void *data); - -/* for the supported backends */ -#ifdef CONFIG_DELL_SMBIOS_WMI -int init_dell_smbios_wmi(void); -void exit_dell_smbios_wmi(void); -#else /* CONFIG_DELL_SMBIOS_WMI */ -static inline int init_dell_smbios_wmi(void) -{ - return -ENODEV; -} -static inline void exit_dell_smbios_wmi(void) -{} -#endif /* CONFIG_DELL_SMBIOS_WMI */ - -#ifdef CONFIG_DELL_SMBIOS_SMM -int init_dell_smbios_smm(void); -void exit_dell_smbios_smm(void); -#else /* CONFIG_DELL_SMBIOS_SMM */ -static inline int init_dell_smbios_smm(void) -{ - return -ENODEV; -} -static inline void exit_dell_smbios_smm(void) -{} -#endif /* CONFIG_DELL_SMBIOS_SMM */ - -#endif /* _DELL_SMBIOS_H_ */ diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c deleted file mode 100644 index 5d9304a7de1b..000000000000 --- a/drivers/platform/x86/dell-smo8800.c +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver - * - * Copyright (C) 2012 Sonal Santan - * Copyright (C) 2014 Pali Rohár - * - * This is loosely based on lis3lv02d driver. - */ - -#define DRIVER_NAME "smo8800" - -#include -#include -#include -#include -#include -#include -#include - -struct smo8800_device { - u32 irq; /* acpi device irq */ - atomic_t counter; /* count after last read */ - struct miscdevice miscdev; /* for /dev/freefall */ - unsigned long misc_opened; /* whether the device is open */ - wait_queue_head_t misc_wait; /* Wait queue for the misc dev */ - struct device *dev; /* acpi device */ -}; - -static irqreturn_t smo8800_interrupt_quick(int irq, void *data) -{ - struct smo8800_device *smo8800 = data; - - atomic_inc(&smo8800->counter); - wake_up_interruptible(&smo8800->misc_wait); - return IRQ_WAKE_THREAD; -} - -static irqreturn_t smo8800_interrupt_thread(int irq, void *data) -{ - struct smo8800_device *smo8800 = data; - - dev_info(smo8800->dev, "detected free fall\n"); - return IRQ_HANDLED; -} - -static acpi_status smo8800_get_resource(struct acpi_resource *resource, - void *context) -{ - struct acpi_resource_extended_irq *irq; - - if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) - return AE_OK; - - irq = &resource->data.extended_irq; - if (!irq || !irq->interrupt_count) - return AE_OK; - - *((u32 *)context) = irq->interrupts[0]; - return AE_CTRL_TERMINATE; -} - -static u32 smo8800_get_irq(struct acpi_device *device) -{ - u32 irq = 0; - acpi_status status; - - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, - smo8800_get_resource, &irq); - if (ACPI_FAILURE(status)) { - dev_err(&device->dev, "acpi_walk_resources failed\n"); - return 0; - } - - return irq; -} - -static ssize_t smo8800_misc_read(struct file *file, char __user *buf, - size_t count, loff_t *pos) -{ - struct smo8800_device *smo8800 = container_of(file->private_data, - struct smo8800_device, miscdev); - - u32 data = 0; - unsigned char byte_data; - ssize_t retval = 1; - - if (count < 1) - return -EINVAL; - - atomic_set(&smo8800->counter, 0); - retval = wait_event_interruptible(smo8800->misc_wait, - (data = atomic_xchg(&smo8800->counter, 0))); - - if (retval) - return retval; - - retval = 1; - - if (data < 255) - byte_data = data; - else - byte_data = 255; - - if (put_user(byte_data, buf)) - retval = -EFAULT; - - return retval; -} - -static int smo8800_misc_open(struct inode *inode, struct file *file) -{ - struct smo8800_device *smo8800 = container_of(file->private_data, - struct smo8800_device, miscdev); - - if (test_and_set_bit(0, &smo8800->misc_opened)) - return -EBUSY; /* already open */ - - atomic_set(&smo8800->counter, 0); - return 0; -} - -static int smo8800_misc_release(struct inode *inode, struct file *file) -{ - struct smo8800_device *smo8800 = container_of(file->private_data, - struct smo8800_device, miscdev); - - clear_bit(0, &smo8800->misc_opened); /* release the device */ - return 0; -} - -static const struct file_operations smo8800_misc_fops = { - .owner = THIS_MODULE, - .read = smo8800_misc_read, - .open = smo8800_misc_open, - .release = smo8800_misc_release, -}; - -static int smo8800_add(struct acpi_device *device) -{ - int err; - struct smo8800_device *smo8800; - - smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL); - if (!smo8800) { - dev_err(&device->dev, "failed to allocate device data\n"); - return -ENOMEM; - } - - smo8800->dev = &device->dev; - smo8800->miscdev.minor = MISC_DYNAMIC_MINOR; - smo8800->miscdev.name = "freefall"; - smo8800->miscdev.fops = &smo8800_misc_fops; - - init_waitqueue_head(&smo8800->misc_wait); - - err = misc_register(&smo8800->miscdev); - if (err) { - dev_err(&device->dev, "failed to register misc dev: %d\n", err); - return err; - } - - device->driver_data = smo8800; - - smo8800->irq = smo8800_get_irq(device); - if (!smo8800->irq) { - dev_err(&device->dev, "failed to obtain IRQ\n"); - err = -EINVAL; - goto error; - } - - err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, - smo8800_interrupt_thread, - IRQF_TRIGGER_RISING | IRQF_ONESHOT, - DRIVER_NAME, smo8800); - if (err) { - dev_err(&device->dev, - "failed to request thread for IRQ %d: %d\n", - smo8800->irq, err); - goto error; - } - - dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n", - smo8800->irq); - return 0; - -error: - misc_deregister(&smo8800->miscdev); - return err; -} - -static int smo8800_remove(struct acpi_device *device) -{ - struct smo8800_device *smo8800 = device->driver_data; - - free_irq(smo8800->irq, smo8800); - misc_deregister(&smo8800->miscdev); - dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); - return 0; -} - -/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ -static const struct acpi_device_id smo8800_ids[] = { - { "SMO8800", 0 }, - { "SMO8801", 0 }, - { "SMO8810", 0 }, - { "SMO8811", 0 }, - { "SMO8820", 0 }, - { "SMO8821", 0 }, - { "SMO8830", 0 }, - { "SMO8831", 0 }, - { "", 0 }, -}; - -MODULE_DEVICE_TABLE(acpi, smo8800_ids); - -static struct acpi_driver smo8800_driver = { - .name = DRIVER_NAME, - .class = "Latitude", - .ids = smo8800_ids, - .ops = { - .add = smo8800_add, - .remove = smo8800_remove, - }, - .owner = THIS_MODULE, -}; - -module_acpi_driver(smo8800_driver); - -MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Sonal Santan, Pali Rohár"); diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c deleted file mode 100644 index c7b7f1e403fb..000000000000 --- a/drivers/platform/x86/dell-wmi-aio.c +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * WMI hotkeys support for Dell All-In-One series - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); -MODULE_LICENSE("GPL"); - -#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" -#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" - -struct dell_wmi_event { - u16 length; - /* 0x000: A hot key pressed or an event occurred - * 0x00F: A sequence of hot keys are pressed */ - u16 type; - u16 event[]; -}; - -static const char *dell_wmi_aio_guids[] = { - EVENT_GUID1, - EVENT_GUID2, - NULL -}; - -MODULE_ALIAS("wmi:"EVENT_GUID1); -MODULE_ALIAS("wmi:"EVENT_GUID2); - -static const struct key_entry dell_wmi_aio_keymap[] = { - { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, - { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, - { KE_KEY, 0xe030, { KEY_VOLUMEUP } }, - { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, - { KE_KEY, 0xe020, { KEY_MUTE } }, - { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, - { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, - { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, - { KE_END, 0 } -}; - -static struct input_dev *dell_wmi_aio_input_dev; - -/* - * The new WMI event data format will follow the dell_wmi_event structure - * So, we will check if the buffer matches the format - */ -static bool dell_wmi_aio_event_check(u8 *buffer, int length) -{ - struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; - - if (event == NULL || length < 6) - return false; - - if ((event->type == 0 || event->type == 0xf) && - event->length >= 2) - return true; - - return false; -} - -static void dell_wmi_aio_notify(u32 value, void *context) -{ - struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; - struct dell_wmi_event *event; - acpi_status status; - - status = wmi_get_event_data(value, &response); - if (status != AE_OK) { - pr_info("bad event status 0x%x\n", status); - return; - } - - obj = (union acpi_object *)response.pointer; - if (obj) { - unsigned int scancode = 0; - - switch (obj->type) { - case ACPI_TYPE_INTEGER: - /* Most All-In-One correctly return integer scancode */ - scancode = obj->integer.value; - sparse_keymap_report_event(dell_wmi_aio_input_dev, - scancode, 1, true); - break; - case ACPI_TYPE_BUFFER: - if (dell_wmi_aio_event_check(obj->buffer.pointer, - obj->buffer.length)) { - event = (struct dell_wmi_event *) - obj->buffer.pointer; - scancode = event->event[0]; - } else { - /* Broken machines return the scancode in a - buffer */ - if (obj->buffer.pointer && - obj->buffer.length > 0) - scancode = obj->buffer.pointer[0]; - } - if (scancode) - sparse_keymap_report_event( - dell_wmi_aio_input_dev, - scancode, 1, true); - break; - } - } - kfree(obj); -} - -static int __init dell_wmi_aio_input_setup(void) -{ - int err; - - dell_wmi_aio_input_dev = input_allocate_device(); - - if (!dell_wmi_aio_input_dev) - return -ENOMEM; - - dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; - dell_wmi_aio_input_dev->phys = "wmi/input0"; - dell_wmi_aio_input_dev->id.bustype = BUS_HOST; - - err = sparse_keymap_setup(dell_wmi_aio_input_dev, - dell_wmi_aio_keymap, NULL); - if (err) { - pr_err("Unable to setup input device keymap\n"); - goto err_free_dev; - } - err = input_register_device(dell_wmi_aio_input_dev); - if (err) { - pr_info("Unable to register input device\n"); - goto err_free_dev; - } - return 0; - -err_free_dev: - input_free_device(dell_wmi_aio_input_dev); - return err; -} - -static const char *dell_wmi_aio_find(void) -{ - int i; - - for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) - if (wmi_has_guid(dell_wmi_aio_guids[i])) - return dell_wmi_aio_guids[i]; - - return NULL; -} - -static int __init dell_wmi_aio_init(void) -{ - int err; - const char *guid; - - guid = dell_wmi_aio_find(); - if (!guid) { - pr_warn("No known WMI GUID found\n"); - return -ENXIO; - } - - err = dell_wmi_aio_input_setup(); - if (err) - return err; - - err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); - if (err) { - pr_err("Unable to register notify handler - %d\n", err); - input_unregister_device(dell_wmi_aio_input_dev); - return err; - } - - return 0; -} - -static void __exit dell_wmi_aio_exit(void) -{ - const char *guid; - - guid = dell_wmi_aio_find(); - wmi_remove_notify_handler(guid); - input_unregister_device(dell_wmi_aio_input_dev); -} - -module_init(dell_wmi_aio_init); -module_exit(dell_wmi_aio_exit); diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c deleted file mode 100644 index a068900ae8a1..000000000000 --- a/drivers/platform/x86/dell-wmi-descriptor.c +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Dell WMI descriptor driver - * - * Copyright (C) 2017 Dell Inc. All Rights Reserved. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include "dell-wmi-descriptor.h" - -#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" - -struct descriptor_priv { - struct list_head list; - u32 interface_version; - u32 size; - u32 hotfix; -}; -static int descriptor_valid = -EPROBE_DEFER; -static LIST_HEAD(wmi_list); -static DEFINE_MUTEX(list_mutex); - -int dell_wmi_get_descriptor_valid(void) -{ - if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) - return -ENODEV; - - return descriptor_valid; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_descriptor_valid); - -bool dell_wmi_get_interface_version(u32 *version) -{ - struct descriptor_priv *priv; - bool ret = false; - - mutex_lock(&list_mutex); - priv = list_first_entry_or_null(&wmi_list, - struct descriptor_priv, - list); - if (priv) { - *version = priv->interface_version; - ret = true; - } - mutex_unlock(&list_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_interface_version); - -bool dell_wmi_get_size(u32 *size) -{ - struct descriptor_priv *priv; - bool ret = false; - - mutex_lock(&list_mutex); - priv = list_first_entry_or_null(&wmi_list, - struct descriptor_priv, - list); - if (priv) { - *size = priv->size; - ret = true; - } - mutex_unlock(&list_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_size); - -bool dell_wmi_get_hotfix(u32 *hotfix) -{ - struct descriptor_priv *priv; - bool ret = false; - - mutex_lock(&list_mutex); - priv = list_first_entry_or_null(&wmi_list, - struct descriptor_priv, - list); - if (priv) { - *hotfix = priv->hotfix; - ret = true; - } - mutex_unlock(&list_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); - -/* - * Descriptor buffer is 128 byte long and contains: - * - * Name Offset Length Value - * Vendor Signature 0 4 "DELL" - * Object Signature 4 4 " WMI" - * WMI Interface Version 8 4 - * WMI buffer length 12 4 - * WMI hotfix number 16 4 - */ -static int dell_wmi_descriptor_probe(struct wmi_device *wdev, - const void *context) -{ - union acpi_object *obj = NULL; - struct descriptor_priv *priv; - u32 *buffer; - int ret; - - obj = wmidev_block_query(wdev, 0); - if (!obj) { - dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); - ret = -EIO; - goto out; - } - - if (obj->type != ACPI_TYPE_BUFFER) { - dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } - - /* Although it's not technically a failure, this would lead to - * unexpected behavior - */ - if (obj->buffer.length != 128) { - dev_err(&wdev->dev, - "Dell descriptor buffer has unexpected length (%d)\n", - obj->buffer.length); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } - - buffer = (u32 *)obj->buffer.pointer; - - if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { - dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", - buffer); - ret = -EINVAL; - descriptor_valid = ret; - goto out; - } - descriptor_valid = 0; - - if (buffer[2] != 0 && buffer[2] != 1) - dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", - (unsigned long) buffer[2]); - - priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), - GFP_KERNEL); - - if (!priv) { - ret = -ENOMEM; - goto out; - } - - priv->interface_version = buffer[2]; - priv->size = buffer[3]; - priv->hotfix = buffer[4]; - ret = 0; - dev_set_drvdata(&wdev->dev, priv); - mutex_lock(&list_mutex); - list_add_tail(&priv->list, &wmi_list); - mutex_unlock(&list_mutex); - - dev_dbg(&wdev->dev, "Detected Dell WMI interface version %lu, buffer size %lu, hotfix %lu\n", - (unsigned long) priv->interface_version, - (unsigned long) priv->size, - (unsigned long) priv->hotfix); - -out: - kfree(obj); - return ret; -} - -static int dell_wmi_descriptor_remove(struct wmi_device *wdev) -{ - struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev); - - mutex_lock(&list_mutex); - list_del(&priv->list); - mutex_unlock(&list_mutex); - return 0; -} - -static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { - { .guid_string = DELL_WMI_DESCRIPTOR_GUID }, - { }, -}; - -static struct wmi_driver dell_wmi_descriptor_driver = { - .driver = { - .name = "dell-wmi-descriptor", - }, - .probe = dell_wmi_descriptor_probe, - .remove = dell_wmi_descriptor_remove, - .id_table = dell_wmi_descriptor_id_table, -}; - -module_wmi_driver(dell_wmi_descriptor_driver); - -MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table); -MODULE_AUTHOR("Mario Limonciello "); -MODULE_DESCRIPTION("Dell WMI descriptor driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-wmi-descriptor.h b/drivers/platform/x86/dell-wmi-descriptor.h deleted file mode 100644 index 1f469fef1535..000000000000 --- a/drivers/platform/x86/dell-wmi-descriptor.h +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Dell WMI descriptor driver - * - * Copyright (c) 2017 Dell Inc. - */ - -#ifndef _DELL_WMI_DESCRIPTOR_H_ -#define _DELL_WMI_DESCRIPTOR_H_ - -#include - -/* possible return values: - * -ENODEV: Descriptor GUID missing from WMI bus - * -EPROBE_DEFER: probing for dell-wmi-descriptor not yet run - * 0: valid descriptor, successfully probed - * < 0: invalid descriptor, don't probe dependent devices - */ -int dell_wmi_get_descriptor_valid(void); - -bool dell_wmi_get_interface_version(u32 *version); -bool dell_wmi_get_size(u32 *size); -bool dell_wmi_get_hotfix(u32 *hotfix); - -#endif diff --git a/drivers/platform/x86/dell-wmi-led.c b/drivers/platform/x86/dell-wmi-led.c deleted file mode 100644 index 5bedaf7f0633..000000000000 --- a/drivers/platform/x86/dell-wmi-led.c +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2010 Dell Inc. - * Louis Davis - * Jim Dailey - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include - -MODULE_AUTHOR("Louis Davis/Jim Dailey"); -MODULE_DESCRIPTION("Dell LED Control Driver"); -MODULE_LICENSE("GPL"); - -#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" -MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); - -/* Error Result Codes: */ -#define INVALID_DEVICE_ID 250 -#define INVALID_PARAMETER 251 -#define INVALID_BUFFER 252 -#define INTERFACE_ERROR 253 -#define UNSUPPORTED_COMMAND 254 -#define UNSPECIFIED_ERROR 255 - -/* Device ID */ -#define DEVICE_ID_PANEL_BACK 1 - -/* LED Commands */ -#define CMD_LED_ON 16 -#define CMD_LED_OFF 17 -#define CMD_LED_BLINK 18 - -struct bios_args { - unsigned char length; - unsigned char result_code; - unsigned char device_id; - unsigned char command; - unsigned char on_time; - unsigned char off_time; -}; - -static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, - u8 command, u8 on_time, u8 off_time) -{ - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - struct bios_args *bios_return; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - u8 return_code; - - struct bios_args args = { - .length = length, - .result_code = result_code, - .device_id = device_id, - .command = command, - .on_time = on_time, - .off_time = off_time - }; - - input.length = sizeof(struct bios_args); - input.pointer = &args; - - status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return status; - - obj = output.pointer; - - if (!obj) - return -EINVAL; - if (obj->type != ACPI_TYPE_BUFFER) { - kfree(obj); - return -EINVAL; - } - - bios_return = ((struct bios_args *)obj->buffer.pointer); - return_code = bios_return->result_code; - - kfree(obj); - - return return_code; -} - -static int led_on(void) -{ - return dell_led_perform_fn(3, /* Length of command */ - INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ - DEVICE_ID_PANEL_BACK, /* Device ID */ - CMD_LED_ON, /* Command */ - 0, /* not used */ - 0); /* not used */ -} - -static int led_off(void) -{ - return dell_led_perform_fn(3, /* Length of command */ - INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ - DEVICE_ID_PANEL_BACK, /* Device ID */ - CMD_LED_OFF, /* Command */ - 0, /* not used */ - 0); /* not used */ -} - -static int led_blink(unsigned char on_eighths, unsigned char off_eighths) -{ - return dell_led_perform_fn(5, /* Length of command */ - INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ - DEVICE_ID_PANEL_BACK, /* Device ID */ - CMD_LED_BLINK, /* Command */ - on_eighths, /* blink on in eigths of a second */ - off_eighths); /* blink off in eights of a second */ -} - -static void dell_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - if (value == LED_OFF) - led_off(); - else - led_on(); -} - -static int dell_led_blink(struct led_classdev *led_cdev, - unsigned long *delay_on, unsigned long *delay_off) -{ - unsigned long on_eighths; - unsigned long off_eighths; - - /* - * The Dell LED delay is based on 125ms intervals. - * Need to round up to next interval. - */ - - on_eighths = DIV_ROUND_UP(*delay_on, 125); - on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); - *delay_on = on_eighths * 125; - - off_eighths = DIV_ROUND_UP(*delay_off, 125); - off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); - *delay_off = off_eighths * 125; - - led_blink(on_eighths, off_eighths); - - return 0; -} - -static struct led_classdev dell_led = { - .name = "dell::lid", - .brightness = LED_OFF, - .max_brightness = 1, - .brightness_set = dell_led_set, - .blink_set = dell_led_blink, - .flags = LED_CORE_SUSPENDRESUME, -}; - -static int __init dell_led_init(void) -{ - int error = 0; - - if (!wmi_has_guid(DELL_LED_BIOS_GUID)) - return -ENODEV; - - error = led_off(); - if (error != 0) - return -ENODEV; - - return led_classdev_register(NULL, &dell_led); -} - -static void __exit dell_led_exit(void) -{ - led_classdev_unregister(&dell_led); - - led_off(); -} - -module_init(dell_led_init); -module_exit(dell_led_exit); diff --git a/drivers/platform/x86/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell-wmi-sysman/Makefile deleted file mode 100644 index 825fb2fbeea8..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o -dell-wmi-sysman-objs := sysman.o \ - enum-attributes.o \ - int-attributes.o \ - string-attributes.o \ - passobj-attributes.o \ - biosattr-interface.o \ - passwordattr-interface.o diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c deleted file mode 100644 index f95d8ddace5a..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to SET methods under BIOS attributes interface GUID for use - * with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include -#include "dell-wmi-sysman.h" - -#define SETDEFAULTVALUES_METHOD_ID 0x02 -#define SETBIOSDEFAULTS_METHOD_ID 0x03 -#define SETATTRIBUTE_METHOD_ID 0x04 - -static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, - int method_id) -{ - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - int ret = -EIO; - - input.length = (acpi_size) size; - input.pointer = in_args; - status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type == ACPI_TYPE_INTEGER) - ret = obj->integer.value; - - if (wmi_priv.pending_changes == 0) { - wmi_priv.pending_changes = 1; - /* let userland know it may need to check reboot pending again */ - kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); - } - kfree(output.pointer); - return map_wmi_error(ret); -} - -/** - * set_attribute() - Update an attribute value - * @a_name: The attribute name - * @a_value: The attribute value - * - * Sets an attribute to new value - */ -int set_attribute(const char *a_name, const char *a_value) -{ - size_t security_area_size, buffer_size; - size_t a_name_size, a_value_size; - char *buffer = NULL, *start; - int ret; - - mutex_lock(&wmi_priv.mutex); - if (!wmi_priv.bios_attr_wdev) { - ret = -ENODEV; - goto out; - } - - /* build/calculate buffer */ - security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); - a_name_size = calculate_string_buffer(a_name); - a_value_size = calculate_string_buffer(a_value); - buffer_size = security_area_size + a_name_size + a_value_size; - buffer = kzalloc(buffer_size, GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto out; - } - - /* build security area */ - populate_security_buffer(buffer, wmi_priv.current_admin_password); - - /* build variables to set */ - start = buffer + security_area_size; - ret = populate_string_buffer(start, a_name_size, a_name); - if (ret < 0) - goto out; - start += ret; - ret = populate_string_buffer(start, a_value_size, a_value); - if (ret < 0) - goto out; - - print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); - ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, - buffer, buffer_size, - SETATTRIBUTE_METHOD_ID); - if (ret == -EOPNOTSUPP) - dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n"); - else if (ret == -EACCES) - dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n"); - -out: - kfree(buffer); - mutex_unlock(&wmi_priv.mutex); - return ret; -} - -/** - * set_bios_defaults() - Resets BIOS defaults - * @deftype: the type of BIOS value reset to issue. - * - * Resets BIOS defaults - */ -int set_bios_defaults(u8 deftype) -{ - size_t security_area_size, buffer_size; - size_t integer_area_size = sizeof(u8); - char *buffer = NULL; - u8 *defaultType; - int ret; - - mutex_lock(&wmi_priv.mutex); - if (!wmi_priv.bios_attr_wdev) { - ret = -ENODEV; - goto out; - } - - security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); - buffer_size = security_area_size + integer_area_size; - buffer = kzalloc(buffer_size, GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto out; - } - - /* build security area */ - populate_security_buffer(buffer, wmi_priv.current_admin_password); - - defaultType = buffer + security_area_size; - *defaultType = deftype; - - ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, - SETBIOSDEFAULTS_METHOD_ID); - if (ret) - dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret); - - kfree(buffer); -out: - mutex_unlock(&wmi_priv.mutex); - return ret; -} - -static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.bios_attr_wdev = wdev; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static int bios_attr_set_interface_remove(struct wmi_device *wdev) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.bios_attr_wdev = NULL; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static const struct wmi_device_id bios_attr_set_interface_id_table[] = { - { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID }, - { }, -}; -static struct wmi_driver bios_attr_set_interface_driver = { - .driver = { - .name = DRIVER_NAME - }, - .probe = bios_attr_set_interface_probe, - .remove = bios_attr_set_interface_remove, - .id_table = bios_attr_set_interface_id_table, -}; - -int init_bios_attr_set_interface(void) -{ - return wmi_driver_register(&bios_attr_set_interface_driver); -} - -void exit_bios_attr_set_interface(void) -{ - wmi_driver_unregister(&bios_attr_set_interface_driver); -} - -MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h deleted file mode 100644 index b80f2a62ea3f..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h +++ /dev/null @@ -1,191 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 - * Definitions for kernel modules using Dell WMI System Management Driver - * - * Copyright (c) 2020 Dell Inc. - */ - -#ifndef _DELL_WMI_BIOS_ATTR_H_ -#define _DELL_WMI_BIOS_ATTR_H_ - -#include -#include -#include -#include -#include - -#define DRIVER_NAME "dell-wmi-sysman" -#define MAX_BUFF 512 - -#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5" -#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA" -#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9" -#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4" -#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4" -#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A" - -struct enumeration_data { - struct kobject *attr_name_kobj; - char display_name_language_code[MAX_BUFF]; - char dell_value_modifier[MAX_BUFF]; - char possible_values[MAX_BUFF]; - char attribute_name[MAX_BUFF]; - char default_value[MAX_BUFF]; - char dell_modifier[MAX_BUFF]; - char display_name[MAX_BUFF]; -}; - -struct integer_data { - struct kobject *attr_name_kobj; - char display_name_language_code[MAX_BUFF]; - char attribute_name[MAX_BUFF]; - char dell_modifier[MAX_BUFF]; - char display_name[MAX_BUFF]; - int scalar_increment; - int default_value; - int min_value; - int max_value; -}; - -struct str_data { - struct kobject *attr_name_kobj; - char display_name_language_code[MAX_BUFF]; - char attribute_name[MAX_BUFF]; - char display_name[MAX_BUFF]; - char default_value[MAX_BUFF]; - char dell_modifier[MAX_BUFF]; - int min_length; - int max_length; -}; - -struct po_data { - struct kobject *attr_name_kobj; - char attribute_name[MAX_BUFF]; - int min_password_length; - int max_password_length; -}; - -struct wmi_sysman_priv { - char current_admin_password[MAX_BUFF]; - char current_system_password[MAX_BUFF]; - struct wmi_device *password_attr_wdev; - struct wmi_device *bios_attr_wdev; - struct kset *authentication_dir_kset; - struct kset *main_dir_kset; - struct device *class_dev; - struct enumeration_data *enumeration_data; - int enumeration_instances_count; - struct integer_data *integer_data; - int integer_instances_count; - struct str_data *str_data; - int str_instances_count; - struct po_data *po_data; - int po_instances_count; - bool pending_changes; - struct mutex mutex; -}; - -/* global structure used by multiple WMI interfaces */ -extern struct wmi_sysman_priv wmi_priv; - -enum { ENUM, INT, STR, PO }; - -enum { - ATTR_NAME, - DISPL_NAME_LANG_CODE, - DISPLAY_NAME, - DEFAULT_VAL, - CURRENT_VAL, - MODIFIER -}; - -#define get_instance_id(type) \ -static int get_##type##_instance_id(struct kobject *kobj) \ -{ \ - int i; \ - for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ - if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ - return i; \ - } \ - return -EIO; \ -} - -#define attribute_s_property_show(name, type) \ -static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ - char *buf) \ -{ \ - int i = get_##type##_instance_id(kobj); \ - if (i >= 0) \ - return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \ - return 0; \ -} - -#define attribute_n_property_show(name, type) \ -static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ - char *buf) \ -{ \ - int i = get_##type##_instance_id(kobj); \ - if (i >= 0) \ - return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \ - return 0; \ -} - -#define attribute_property_store(curr_val, type) \ -static ssize_t curr_val##_store(struct kobject *kobj, \ - struct kobj_attribute *attr, \ - const char *buf, size_t count) \ -{ \ - char *p, *buf_cp; \ - int i, ret = -EIO; \ - buf_cp = kstrdup(buf, GFP_KERNEL); \ - if (!buf_cp) \ - return -ENOMEM; \ - p = memchr(buf_cp, '\n', count); \ - \ - if (p != NULL) \ - *p = '\0'; \ - i = get_##type##_instance_id(kobj); \ - if (i >= 0) \ - ret = validate_##type##_input(i, buf_cp); \ - if (!ret) \ - ret = set_attribute(kobj->name, buf_cp); \ - kfree(buf_cp); \ - return ret ? ret : count; \ -} - -union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); -int get_instance_count(const char *guid_string); -void strlcpy_attr(char *dest, char *src); - -int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, - struct kobject *attr_name_kobj); -int alloc_enum_data(void); -void exit_enum_attributes(void); - -int populate_int_data(union acpi_object *integer_obj, int instance_id, - struct kobject *attr_name_kobj); -int alloc_int_data(void); -void exit_int_attributes(void); - -int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj); -int alloc_str_data(void); -void exit_str_attributes(void); - -int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj); -int alloc_po_data(void); -void exit_po_attributes(void); - -int set_attribute(const char *a_name, const char *a_value); -int set_bios_defaults(u8 defType); - -void exit_bios_attr_set_interface(void); -int init_bios_attr_set_interface(void); -int map_wmi_error(int error_code); -size_t calculate_string_buffer(const char *str); -size_t calculate_security_buffer(char *authentication); -void populate_security_buffer(char *buffer, char *authentication); -ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); -int set_new_password(const char *password_type, const char *new); -int init_bios_attr_pass_interface(void); -void exit_bios_attr_pass_interface(void); - -#endif diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c deleted file mode 100644 index 80f4b7785c6c..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to enumeration type attributes under - * BIOS Enumeration GUID for use with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -get_instance_id(enumeration); - -static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - int instance_id = get_enumeration_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return instance_id; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); - kfree(obj); - return ret; -} - -/** - * validate_enumeration_input() - Validate input of current_value against possible values - * @instance_id: The instance on which input is validated - * @buf: Input value - */ -static int validate_enumeration_input(int instance_id, const char *buf) -{ - char *options, *tmp, *p; - int ret = -EINVAL; - - options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values, - GFP_KERNEL); - if (!options) - return -ENOMEM; - - while ((p = strsep(&options, ";")) != NULL) { - if (!*p) - continue; - if (!strcasecmp(p, buf)) { - ret = 0; - break; - } - } - - kfree(tmp); - return ret; -} - -attribute_s_property_show(display_name_language_code, enumeration); -static struct kobj_attribute displ_langcode = - __ATTR_RO(display_name_language_code); - -attribute_s_property_show(display_name, enumeration); -static struct kobj_attribute displ_name = - __ATTR_RO(display_name); - -attribute_s_property_show(default_value, enumeration); -static struct kobj_attribute default_val = - __ATTR_RO(default_value); - -attribute_property_store(current_value, enumeration); -static struct kobj_attribute current_val = - __ATTR_RW_MODE(current_value, 0600); - -attribute_s_property_show(dell_modifier, enumeration); -static struct kobj_attribute modifier = - __ATTR_RO(dell_modifier); - -attribute_s_property_show(dell_value_modifier, enumeration); -static struct kobj_attribute value_modfr = - __ATTR_RO(dell_value_modifier); - -attribute_s_property_show(possible_values, enumeration); -static struct kobj_attribute poss_val = - __ATTR_RO(possible_values); - -static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "enumeration\n"); -} -static struct kobj_attribute type = - __ATTR_RO(type); - -static struct attribute *enumeration_attrs[] = { - &displ_langcode.attr, - &displ_name.attr, - &default_val.attr, - ¤t_val.attr, - &modifier.attr, - &value_modfr.attr, - &poss_val.attr, - &type.attr, - NULL, -}; - -static const struct attribute_group enumeration_attr_group = { - .attrs = enumeration_attrs, -}; - -int alloc_enum_data(void) -{ - int ret = 0; - - wmi_priv.enumeration_instances_count = - get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count, - sizeof(struct enumeration_data), GFP_KERNEL); - if (!wmi_priv.enumeration_data) { - wmi_priv.enumeration_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_enum_data() - Populate all properties of an instance under enumeration attribute - * @enumeration_obj: ACPI object with enumeration data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, - struct kobject *attr_name_kobj) -{ - int i, next_obj, value_modifier_count, possible_values_count; - - wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, - enumeration_obj[ATTR_NAME].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, - enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, - enumeration_obj[DISPLAY_NAME].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, - enumeration_obj[DEFAULT_VAL].string.pointer); - strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, - enumeration_obj[MODIFIER].string.pointer); - - next_obj = MODIFIER + 1; - - value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; - - for (i = 0; i < value_modifier_count; i++) { - strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, - enumeration_obj[++next_obj].string.pointer); - strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); - } - - possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; - - for (i = 0; i < possible_values_count; i++) { - strcat(wmi_priv.enumeration_data[instance_id].possible_values, - enumeration_obj[++next_obj].string.pointer); - strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); - } - - return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); -} - -/** - * exit_enum_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_enum_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) { - if (wmi_priv.enumeration_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj, - &enumeration_attr_group); - } - kfree(wmi_priv.enumeration_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell-wmi-sysman/int-attributes.c deleted file mode 100644 index 75aedbb733be..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/int-attributes.c +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to integer type attributes under BIOS Integer GUID for use with - * dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR}; - -get_instance_id(integer); - -static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - int instance_id = get_integer_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return instance_id; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value); - kfree(obj); - return ret; -} - -/** - * validate_integer_input() - Validate input of current_value against lower and upper bound - * @instance_id: The instance on which input is validated - * @buf: Input value - */ -static int validate_integer_input(int instance_id, char *buf) -{ - int in_val; - int ret; - - ret = kstrtoint(buf, 0, &in_val); - if (ret) - return ret; - if (in_val < wmi_priv.integer_data[instance_id].min_value || - in_val > wmi_priv.integer_data[instance_id].max_value) - return -EINVAL; - - /* workaround for BIOS error. - * validate input to avoid setting 0 when integer input passed with + sign - */ - if (*buf == '+') - memmove(buf, (buf + 1), strlen(buf + 1) + 1); - - return ret; -} - -attribute_s_property_show(display_name_language_code, integer); -static struct kobj_attribute integer_displ_langcode = - __ATTR_RO(display_name_language_code); - -attribute_s_property_show(display_name, integer); -static struct kobj_attribute integer_displ_name = - __ATTR_RO(display_name); - -attribute_n_property_show(default_value, integer); -static struct kobj_attribute integer_default_val = - __ATTR_RO(default_value); - -attribute_property_store(current_value, integer); -static struct kobj_attribute integer_current_val = - __ATTR_RW_MODE(current_value, 0600); - -attribute_s_property_show(dell_modifier, integer); -static struct kobj_attribute integer_modifier = - __ATTR_RO(dell_modifier); - -attribute_n_property_show(min_value, integer); -static struct kobj_attribute integer_lower_bound = - __ATTR_RO(min_value); - -attribute_n_property_show(max_value, integer); -static struct kobj_attribute integer_upper_bound = - __ATTR_RO(max_value); - -attribute_n_property_show(scalar_increment, integer); -static struct kobj_attribute integer_scalar_increment = - __ATTR_RO(scalar_increment); - -static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "integer\n"); -} -static struct kobj_attribute integer_type = - __ATTR_RO(type); - -static struct attribute *integer_attrs[] = { - &integer_displ_langcode.attr, - &integer_displ_name.attr, - &integer_default_val.attr, - &integer_current_val.attr, - &integer_modifier.attr, - &integer_lower_bound.attr, - &integer_upper_bound.attr, - &integer_scalar_increment.attr, - &integer_type.attr, - NULL, -}; - -static const struct attribute_group integer_attr_group = { - .attrs = integer_attrs, -}; - -int alloc_int_data(void) -{ - int ret = 0; - - wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count, - sizeof(struct integer_data), GFP_KERNEL); - if (!wmi_priv.integer_data) { - wmi_priv.integer_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_int_data() - Populate all properties of an instance under integer attribute - * @integer_obj: ACPI object with integer data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_int_data(union acpi_object *integer_obj, int instance_id, - struct kobject *attr_name_kobj) -{ - wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, - integer_obj[ATTR_NAME].string.pointer); - strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, - integer_obj[DISPL_NAME_LANG_CODE].string.pointer); - strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, - integer_obj[DISPLAY_NAME].string.pointer); - wmi_priv.integer_data[instance_id].default_value = - (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; - strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, - integer_obj[MODIFIER].string.pointer); - wmi_priv.integer_data[instance_id].min_value = - (uintptr_t)integer_obj[MIN_VALUE].string.pointer; - wmi_priv.integer_data[instance_id].max_value = - (uintptr_t)integer_obj[MAX_VALUE].string.pointer; - wmi_priv.integer_data[instance_id].scalar_increment = - (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; - - return sysfs_create_group(attr_name_kobj, &integer_attr_group); -} - -/** - * exit_int_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_int_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) { - if (wmi_priv.integer_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj, - &integer_attr_group); - } - kfree(wmi_priv.integer_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c deleted file mode 100644 index 3abcd95477c0..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to password object type attributes under BIOS Password Object GUID for - * use with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN}; - -get_instance_id(po); - -static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - int instance_id = get_po_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return instance_id; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value); - kfree(obj); - return ret; -} - -static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled); - -static ssize_t current_password_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t count) -{ - char *target = NULL; - int length; - - length = strlen(buf); - if (buf[length-1] == '\n') - length--; - - /* firmware does verifiation of min/max password length, - * hence only check for not exceeding MAX_BUFF here. - */ - if (length >= MAX_BUFF) - return -EINVAL; - - if (strcmp(kobj->name, "Admin") == 0) - target = wmi_priv.current_admin_password; - else if (strcmp(kobj->name, "System") == 0) - target = wmi_priv.current_system_password; - if (!target) - return -EIO; - memcpy(target, buf, length); - target[length] = '\0'; - - return count; -} - -static struct kobj_attribute po_current_password = __ATTR_WO(current_password); - -static ssize_t new_password_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t count) -{ - char *p, *buf_cp; - int ret; - - buf_cp = kstrdup(buf, GFP_KERNEL); - if (!buf_cp) - return -ENOMEM; - p = memchr(buf_cp, '\n', count); - - if (p != NULL) - *p = '\0'; - if (strlen(buf_cp) > MAX_BUFF) { - ret = -EINVAL; - goto out; - } - - ret = set_new_password(kobj->name, buf_cp); - -out: - kfree(buf_cp); - return ret ? ret : count; -} - -static struct kobj_attribute po_new_password = __ATTR_WO(new_password); - -attribute_n_property_show(min_password_length, po); -static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length); - -attribute_n_property_show(max_password_length, po); -static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length); - -static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "password\n"); -} - -static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism); - -static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - if (strcmp(kobj->name, "Admin") == 0) - return sprintf(buf, "bios-admin\n"); - else if (strcmp(kobj->name, "System") == 0) - return sprintf(buf, "power-on\n"); - return -EIO; -} - -static struct kobj_attribute po_role = __ATTR_RO(role); - -static struct attribute *po_attrs[] = { - &po_is_pass_set.attr, - &po_min_pass_length.attr, - &po_max_pass_length.attr, - &po_current_password.attr, - &po_new_password.attr, - &po_role.attr, - &po_mechanism.attr, - NULL, -}; - -static const struct attribute_group po_attr_group = { - .attrs = po_attrs, -}; - -int alloc_po_data(void) -{ - int ret = 0; - - wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); - wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL); - if (!wmi_priv.po_data) { - wmi_priv.po_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_po_data() - Populate all properties of an instance under password object attribute - * @po_obj: ACPI object with password object data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) -{ - wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, - po_obj[ATTR_NAME].string.pointer); - wmi_priv.po_data[instance_id].min_password_length = - (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; - wmi_priv.po_data[instance_id].max_password_length = - (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; - - return sysfs_create_group(attr_name_kobj, &po_attr_group); -} - -/** - * exit_po_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_po_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) { - if (wmi_priv.po_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj, - &po_attr_group); - } - kfree(wmi_priv.po_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c deleted file mode 100644 index 5780b4d94759..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to SET password methods under BIOS attributes interface GUID - * - * Copyright (c) 2020 Dell Inc. - */ - -#include -#include "dell-wmi-sysman.h" - -static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size) -{ - struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; - struct acpi_buffer input; - union acpi_object *obj; - acpi_status status; - int ret = -EIO; - - input.length = (acpi_size) size; - input.pointer = in_args; - status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); - if (ACPI_FAILURE(status)) - return -EIO; - obj = (union acpi_object *)output.pointer; - if (obj->type == ACPI_TYPE_INTEGER) - ret = obj->integer.value; - - kfree(output.pointer); - /* let userland know it may need to check is_password_set again */ - kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); - return map_wmi_error(ret); -} - -/** - * set_new_password() - Sets a system admin password - * @password_type: The type of password to set - * @new: The new password - * - * Sets the password using plaintext interface - */ -int set_new_password(const char *password_type, const char *new) -{ - size_t password_type_size, current_password_size, new_size; - size_t security_area_size, buffer_size; - char *buffer = NULL, *start; - char *current_password; - int ret; - - mutex_lock(&wmi_priv.mutex); - if (!wmi_priv.password_attr_wdev) { - ret = -ENODEV; - goto out; - } - if (strcmp(password_type, "Admin") == 0) { - current_password = wmi_priv.current_admin_password; - } else if (strcmp(password_type, "System") == 0) { - current_password = wmi_priv.current_system_password; - } else { - ret = -EINVAL; - dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n", - password_type); - goto out; - } - - /* build/calculate buffer */ - security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); - password_type_size = calculate_string_buffer(password_type); - current_password_size = calculate_string_buffer(current_password); - new_size = calculate_string_buffer(new); - buffer_size = security_area_size + password_type_size + current_password_size + new_size; - buffer = kzalloc(buffer_size, GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto out; - } - - /* build security area */ - populate_security_buffer(buffer, wmi_priv.current_admin_password); - - /* build variables to set */ - start = buffer + security_area_size; - ret = populate_string_buffer(start, password_type_size, password_type); - if (ret < 0) - goto out; - - start += ret; - ret = populate_string_buffer(start, current_password_size, current_password); - if (ret < 0) - goto out; - - start += ret; - ret = populate_string_buffer(start, new_size, new); - if (ret < 0) - goto out; - - print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); - ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); - /* clear current_password here and use user input from wmi_priv.current_password */ - if (!ret) - memset(current_password, 0, MAX_BUFF); - /* explain to user the detailed failure reason */ - else if (ret == -EOPNOTSUPP) - dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); - else if (ret == -EACCES) - dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n"); - -out: - kfree(buffer); - mutex_unlock(&wmi_priv.mutex); - - return ret; -} - -static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.password_attr_wdev = wdev; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static int bios_attr_pass_interface_remove(struct wmi_device *wdev) -{ - mutex_lock(&wmi_priv.mutex); - wmi_priv.password_attr_wdev = NULL; - mutex_unlock(&wmi_priv.mutex); - return 0; -} - -static const struct wmi_device_id bios_attr_pass_interface_id_table[] = { - { .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID }, - { }, -}; -static struct wmi_driver bios_attr_pass_interface_driver = { - .driver = { - .name = DRIVER_NAME"-password" - }, - .probe = bios_attr_pass_interface_probe, - .remove = bios_attr_pass_interface_remove, - .id_table = bios_attr_pass_interface_id_table, -}; - -int init_bios_attr_pass_interface(void) -{ - return wmi_driver_register(&bios_attr_pass_interface_driver); -} - -void exit_bios_attr_pass_interface(void) -{ - wmi_driver_unregister(&bios_attr_pass_interface_driver); -} - -MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell-wmi-sysman/string-attributes.c deleted file mode 100644 index ac75dce88a4c..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/string-attributes.c +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Functions corresponding to string type attributes under BIOS String GUID for use with - * dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#include "dell-wmi-sysman.h" - -enum string_properties {MIN_LEN = 6, MAX_LEN}; - -get_instance_id(str); - -static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - int instance_id = get_str_instance_id(kobj); - union acpi_object *obj; - ssize_t ret; - - if (instance_id < 0) - return -EIO; - - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - if (!obj) - return -EIO; - if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { - kfree(obj); - return -EINVAL; - } - ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); - kfree(obj); - return ret; -} - -/** - * validate_str_input() - Validate input of current_value against min and max lengths - * @instance_id: The instance on which input is validated - * @buf: Input value - */ -static int validate_str_input(int instance_id, const char *buf) -{ - int in_len = strlen(buf); - - if ((in_len < wmi_priv.str_data[instance_id].min_length) || - (in_len > wmi_priv.str_data[instance_id].max_length)) - return -EINVAL; - - return 0; -} - -attribute_s_property_show(display_name_language_code, str); -static struct kobj_attribute str_displ_langcode = - __ATTR_RO(display_name_language_code); - -attribute_s_property_show(display_name, str); -static struct kobj_attribute str_displ_name = - __ATTR_RO(display_name); - -attribute_s_property_show(default_value, str); -static struct kobj_attribute str_default_val = - __ATTR_RO(default_value); - -attribute_property_store(current_value, str); -static struct kobj_attribute str_current_val = - __ATTR_RW_MODE(current_value, 0600); - -attribute_s_property_show(dell_modifier, str); -static struct kobj_attribute str_modifier = - __ATTR_RO(dell_modifier); - -attribute_n_property_show(min_length, str); -static struct kobj_attribute str_min_length = - __ATTR_RO(min_length); - -attribute_n_property_show(max_length, str); -static struct kobj_attribute str_max_length = - __ATTR_RO(max_length); - -static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "string\n"); -} -static struct kobj_attribute str_type = - __ATTR_RO(type); - -static struct attribute *str_attrs[] = { - &str_displ_langcode.attr, - &str_displ_name.attr, - &str_default_val.attr, - &str_current_val.attr, - &str_modifier.attr, - &str_min_length.attr, - &str_max_length.attr, - &str_type.attr, - NULL, -}; - -static const struct attribute_group str_attr_group = { - .attrs = str_attrs, -}; - -int alloc_str_data(void) -{ - int ret = 0; - - wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count, - sizeof(struct str_data), GFP_KERNEL); - if (!wmi_priv.str_data) { - wmi_priv.str_instances_count = 0; - ret = -ENOMEM; - } - return ret; -} - -/** - * populate_str_data() - Populate all properties of an instance under string attribute - * @str_obj: ACPI object with integer data - * @instance_id: The instance to enumerate - * @attr_name_kobj: The parent kernel object - */ -int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) -{ - wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; - strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, - str_obj[ATTR_NAME].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, - str_obj[DISPL_NAME_LANG_CODE].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].display_name, - str_obj[DISPLAY_NAME].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].default_value, - str_obj[DEFAULT_VAL].string.pointer); - strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, - str_obj[MODIFIER].string.pointer); - wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; - wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; - - return sysfs_create_group(attr_name_kobj, &str_attr_group); -} - -/** - * exit_str_attributes() - Clear all attribute data - * - * Clears all data allocated for this group of attributes - */ -void exit_str_attributes(void) -{ - int instance_id; - - for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) { - if (wmi_priv.str_data[instance_id].attr_name_kobj) - sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj, - &str_attr_group); - } - kfree(wmi_priv.str_data); -} diff --git a/drivers/platform/x86/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell-wmi-sysman/sysman.c deleted file mode 100644 index cb81010ba1a2..000000000000 --- a/drivers/platform/x86/dell-wmi-sysman/sysman.c +++ /dev/null @@ -1,631 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Common methods for use with dell-wmi-sysman - * - * Copyright (c) 2020 Dell Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include "dell-wmi-sysman.h" - -#define MAX_TYPES 4 -#include - -static struct class firmware_attributes_class = { - .name = "firmware-attributes", -}; - -struct wmi_sysman_priv wmi_priv = { - .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), -}; - -/* reset bios to defaults */ -static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; -static int reset_option = -1; - - -/** - * populate_string_buffer() - populates a string buffer - * @buffer: the start of the destination buffer - * @buffer_len: length of the destination buffer - * @str: the string to insert into buffer - */ -ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) -{ - u16 *length = (u16 *)buffer; - u16 *target = length + 1; - int ret; - - ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, - target, buffer_len - sizeof(u16)); - if (ret < 0) { - dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n"); - return ret; - } - - if ((ret * sizeof(u16)) > U16_MAX) { - dev_err(wmi_priv.class_dev, "Error string too long\n"); - return -ERANGE; - } - - *length = ret * sizeof(u16); - return sizeof(u16) + *length; -} - -/** - * calculate_string_buffer() - determines size of string buffer for use with BIOS communication - * @str: the string to calculate based upon - * - */ -size_t calculate_string_buffer(const char *str) -{ - /* u16 length field + one UTF16 char for each input char */ - return sizeof(u16) + strlen(str) * sizeof(u16); -} - -/** - * calculate_security_buffer() - determines size of security buffer for authentication scheme - * @authentication: the authentication content - * - * Currently only supported type is Admin password - */ -size_t calculate_security_buffer(char *authentication) -{ - if (strlen(authentication) > 0) { - return (sizeof(u32) * 2) + strlen(authentication) + - strlen(authentication) % 2; - } - return sizeof(u32) * 2; -} - -/** - * populate_security_buffer() - builds a security buffer for authentication scheme - * @buffer: the buffer to populate - * @authentication: the authentication content - * - * Currently only supported type is PLAIN TEXT - */ -void populate_security_buffer(char *buffer, char *authentication) -{ - char *auth = buffer + sizeof(u32) * 2; - u32 *sectype = (u32 *) buffer; - u32 *seclen = sectype + 1; - - *sectype = strlen(authentication) > 0 ? 1 : 0; - *seclen = strlen(authentication); - - /* plain text */ - if (strlen(authentication) > 0) - memcpy(auth, authentication, *seclen); -} - -/** - * map_wmi_error() - map errors from WMI methods to kernel error codes - * @error_code: integer error code returned from Dell's firmware - */ -int map_wmi_error(int error_code) -{ - switch (error_code) { - case 0: - /* success */ - return 0; - case 1: - /* failed */ - return -EIO; - case 2: - /* invalid parameter */ - return -EINVAL; - case 3: - /* access denied */ - return -EACCES; - case 4: - /* not supported */ - return -EOPNOTSUPP; - case 5: - /* memory error */ - return -ENOMEM; - case 6: - /* protocol error */ - return -EPROTO; - } - /* unspecified error */ - return -EIO; -} - -/** - * reset_bios_show() - sysfs implementaton for read reset_bios - * @kobj: Kernel object for this attribute - * @attr: Kernel object attribute - * @buf: The buffer to display to userspace - */ -static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -{ - char *start = buf; - int i; - - for (i = 0; i < MAX_TYPES; i++) { - if (i == reset_option) - buf += sprintf(buf, "[%s] ", reset_types[i]); - else - buf += sprintf(buf, "%s ", reset_types[i]); - } - buf += sprintf(buf, "\n"); - return buf-start; -} - -/** - * reset_bios_store() - sysfs implementaton for write reset_bios - * @kobj: Kernel object for this attribute - * @attr: Kernel object attribute - * @buf: The buffer from userspace - * @count: the size of the buffer from userspace - */ -static ssize_t reset_bios_store(struct kobject *kobj, - struct kobj_attribute *attr, const char *buf, size_t count) -{ - int type = sysfs_match_string(reset_types, buf); - int ret; - - if (type < 0) - return type; - - ret = set_bios_defaults(type); - pr_debug("reset all attributes request type %d: %d\n", type, ret); - if (!ret) { - reset_option = type; - ret = count; - } - - return ret; -} - -/** - * pending_reboot_show() - sysfs implementaton for read pending_reboot - * @kobj: Kernel object for this attribute - * @attr: Kernel object attribute - * @buf: The buffer to display to userspace - * - * Stores default value as 0 - * When current_value is changed this attribute is set to 1 to notify reboot may be required - */ -static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, - char *buf) -{ - return sprintf(buf, "%d\n", wmi_priv.pending_changes); -} - -static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); -static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); - - -/** - * create_attributes_level_sysfs_files() - Creates reset_bios and - * pending_reboot attributes - */ -static int create_attributes_level_sysfs_files(void) -{ - int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - - if (ret) { - pr_debug("could not create reset_bios file\n"); - return ret; - } - - ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); - if (ret) { - pr_debug("could not create changing_pending_reboot file\n"); - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - } - return ret; -} - -static void release_reset_bios_data(void) -{ - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); - sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); -} - -static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, - char *buf) -{ - struct kobj_attribute *kattr; - ssize_t ret = -EIO; - - kattr = container_of(attr, struct kobj_attribute, attr); - if (kattr->show) - ret = kattr->show(kobj, kattr, buf); - return ret; -} - -static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, - const char *buf, size_t count) -{ - struct kobj_attribute *kattr; - ssize_t ret = -EIO; - - kattr = container_of(attr, struct kobj_attribute, attr); - if (kattr->store) - ret = kattr->store(kobj, kattr, buf, count); - return ret; -} - -static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { - .show = wmi_sysman_attr_show, - .store = wmi_sysman_attr_store, -}; - -static void attr_name_release(struct kobject *kobj) -{ - kfree(kobj); -} - -static struct kobj_type attr_name_ktype = { - .release = attr_name_release, - .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, -}; - -/** - * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks - * @dest: Where to copy the string to - * @src: Where to copy the string from - */ -void strlcpy_attr(char *dest, char *src) -{ - size_t len = strlen(src) + 1; - - if (len > 1 && len <= MAX_BUFF) - strlcpy(dest, src, len); - - /*len can be zero because any property not-applicable to attribute can - * be empty so check only for too long buffers and log error - */ - if (len > MAX_BUFF) - pr_err("Source string returned from BIOS is out of bound!\n"); -} - -/** - * get_wmiobj_pointer() - Get Content of WMI block for particular instance - * @instance_id: WMI instance ID - * @guid_string: WMI GUID (in str form) - * - * Fetches the content for WMI block (instance_id) under GUID (guid_string) - * Caller must kfree the return - */ -union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) -{ - struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_status status; - - status = wmi_query_block(guid_string, instance_id, &out); - - return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; -} - -/** - * get_instance_count() - Compute total number of instances under guid_string - * @guid_string: WMI GUID (in string form) - */ -int get_instance_count(const char *guid_string) -{ - union acpi_object *wmi_obj = NULL; - int i = 0; - - do { - kfree(wmi_obj); - wmi_obj = get_wmiobj_pointer(i, guid_string); - i++; - } while (wmi_obj); - - return (i-1); -} - -/** - * alloc_attributes_data() - Allocate attributes data for a particular type - * @attr_type: Attribute type to allocate - */ -static int alloc_attributes_data(int attr_type) -{ - int retval = 0; - - switch (attr_type) { - case ENUM: - retval = alloc_enum_data(); - break; - case INT: - retval = alloc_int_data(); - break; - case STR: - retval = alloc_str_data(); - break; - case PO: - retval = alloc_po_data(); - break; - default: - break; - } - - return retval; -} - -/** - * destroy_attribute_objs() - Free a kset of kobjects - * @kset: The kset to destroy - * - * Fress kobjects created for each attribute_name under attribute type kset - */ -static void destroy_attribute_objs(struct kset *kset) -{ - struct kobject *pos, *next; - - list_for_each_entry_safe(pos, next, &kset->list, entry) { - kobject_put(pos); - } -} - -/** - * release_attributes_data() - Clean-up all sysfs directories and files created - */ -static void release_attributes_data(void) -{ - release_reset_bios_data(); - - mutex_lock(&wmi_priv.mutex); - exit_enum_attributes(); - exit_int_attributes(); - exit_str_attributes(); - exit_po_attributes(); - if (wmi_priv.authentication_dir_kset) { - destroy_attribute_objs(wmi_priv.authentication_dir_kset); - kset_unregister(wmi_priv.authentication_dir_kset); - wmi_priv.authentication_dir_kset = NULL; - } - if (wmi_priv.main_dir_kset) { - destroy_attribute_objs(wmi_priv.main_dir_kset); - kset_unregister(wmi_priv.main_dir_kset); - } - mutex_unlock(&wmi_priv.mutex); - -} - -/** - * init_bios_attributes() - Initialize all attributes for a type - * @attr_type: The attribute type to initialize - * @guid: The WMI GUID associated with this type to initialize - * - * Initialiaze all 4 types of attributes enumeration, integer, string and password object. - * Populates each attrbute typ's respective properties under sysfs files - */ -static int init_bios_attributes(int attr_type, const char *guid) -{ - struct kobject *attr_name_kobj; //individual attribute names - union acpi_object *obj = NULL; - union acpi_object *elements; - struct kset *tmp_set; - - /* instance_id needs to be reset for each type GUID - * also, instance IDs are unique within GUID but not across - */ - int instance_id = 0; - int retval = 0; - - retval = alloc_attributes_data(attr_type); - if (retval) - return retval; - /* need to use specific instance_id and guid combination to get right data */ - obj = get_wmiobj_pointer(instance_id, guid); - if (!obj || obj->type != ACPI_TYPE_PACKAGE) - return -ENODEV; - elements = obj->package.elements; - - mutex_lock(&wmi_priv.mutex); - while (elements) { - /* sanity checking */ - if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) { - pr_debug("incorrect element type\n"); - goto nextobj; - } - if (strlen(elements[ATTR_NAME].string.pointer) == 0) { - pr_debug("empty attribute found\n"); - goto nextobj; - } - if (attr_type == PO) - tmp_set = wmi_priv.authentication_dir_kset; - else - tmp_set = wmi_priv.main_dir_kset; - - if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) { - pr_debug("duplicate attribute name found - %s\n", - elements[ATTR_NAME].string.pointer); - goto nextobj; - } - - /* build attribute */ - attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); - if (!attr_name_kobj) { - retval = -ENOMEM; - goto err_attr_init; - } - - attr_name_kobj->kset = tmp_set; - - retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s", - elements[ATTR_NAME].string.pointer); - if (retval) { - kobject_put(attr_name_kobj); - goto err_attr_init; - } - - /* enumerate all of this attribute */ - switch (attr_type) { - case ENUM: - retval = populate_enum_data(elements, instance_id, attr_name_kobj); - break; - case INT: - retval = populate_int_data(elements, instance_id, attr_name_kobj); - break; - case STR: - retval = populate_str_data(elements, instance_id, attr_name_kobj); - break; - case PO: - retval = populate_po_data(elements, instance_id, attr_name_kobj); - break; - default: - break; - } - - if (retval) { - pr_debug("failed to populate %s\n", - elements[ATTR_NAME].string.pointer); - goto err_attr_init; - } - -nextobj: - kfree(obj); - instance_id++; - obj = get_wmiobj_pointer(instance_id, guid); - elements = obj ? obj->package.elements : NULL; - } - - mutex_unlock(&wmi_priv.mutex); - return 0; - -err_attr_init: - mutex_unlock(&wmi_priv.mutex); - release_attributes_data(); - kfree(obj); - return retval; -} - -static int __init sysman_init(void) -{ - int ret = 0; - - if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && - !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { - pr_err("Unable to run on non-Dell system\n"); - return -ENODEV; - } - - ret = init_bios_attr_set_interface(); - if (ret || !wmi_priv.bios_attr_wdev) { - pr_debug("failed to initialize set interface\n"); - goto fail_set_interface; - } - - ret = init_bios_attr_pass_interface(); - if (ret || !wmi_priv.password_attr_wdev) { - pr_debug("failed to initialize pass interface\n"); - goto fail_pass_interface; - } - - ret = class_register(&firmware_attributes_class); - if (ret) - goto fail_class; - - wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), - NULL, "%s", DRIVER_NAME); - if (IS_ERR(wmi_priv.class_dev)) { - ret = PTR_ERR(wmi_priv.class_dev); - goto fail_classdev; - } - - wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, - &wmi_priv.class_dev->kobj); - if (!wmi_priv.main_dir_kset) { - ret = -ENOMEM; - goto fail_main_kset; - } - - wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, - &wmi_priv.class_dev->kobj); - if (!wmi_priv.authentication_dir_kset) { - ret = -ENOMEM; - goto fail_authentication_kset; - } - - ret = create_attributes_level_sysfs_files(); - if (ret) { - pr_debug("could not create reset BIOS attribute\n"); - goto fail_reset_bios; - } - - ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate enumeration type attributes\n"); - goto fail_create_group; - } - - ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate integer type attributes\n"); - goto fail_create_group; - } - - ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate string type attributes\n"); - goto fail_create_group; - } - - ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); - if (ret) { - pr_debug("failed to populate pass object type attributes\n"); - goto fail_create_group; - } - - return 0; - -fail_create_group: - release_attributes_data(); - -fail_reset_bios: - if (wmi_priv.authentication_dir_kset) { - kset_unregister(wmi_priv.authentication_dir_kset); - wmi_priv.authentication_dir_kset = NULL; - } - -fail_authentication_kset: - if (wmi_priv.main_dir_kset) { - kset_unregister(wmi_priv.main_dir_kset); - wmi_priv.main_dir_kset = NULL; - } - -fail_main_kset: - device_destroy(&firmware_attributes_class, MKDEV(0, 0)); - -fail_classdev: - class_unregister(&firmware_attributes_class); - -fail_class: - exit_bios_attr_pass_interface(); - -fail_pass_interface: - exit_bios_attr_set_interface(); - -fail_set_interface: - return ret; -} - -static void __exit sysman_exit(void) -{ - release_attributes_data(); - device_destroy(&firmware_attributes_class, MKDEV(0, 0)); - class_unregister(&firmware_attributes_class); - exit_bios_attr_set_interface(); - exit_bios_attr_pass_interface(); -} - -module_init(sysman_init); -module_exit(sysman_exit); - -MODULE_AUTHOR("Mario Limonciello "); -MODULE_AUTHOR("Prasanth Ksr "); -MODULE_AUTHOR("Divya Bharathi "); -MODULE_DESCRIPTION("Dell platform setting control interface"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c deleted file mode 100644 index bbdb3e860892..000000000000 --- a/drivers/platform/x86/dell-wmi.c +++ /dev/null @@ -1,764 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Dell WMI hotkeys - * - * Copyright (C) 2008 Red Hat - * Copyright (C) 2014-2015 Pali Rohár - * - * Portions based on wistron_btns.c: - * Copyright (C) 2005 Miloslav Trmac - * Copyright (C) 2005 Bernhard Rosenkraenzer - * Copyright (C) 2005 Dmitry Torokhov - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" -#include "dell-wmi-descriptor.h" - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); -MODULE_LICENSE("GPL"); - -#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" - -static bool wmi_requires_smbios_request; - -struct dell_wmi_priv { - struct input_dev *input_dev; - u32 interface_version; -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - wmi_requires_smbios_request = 1; - return 1; -} - -static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { - { - .callback = dmi_matched, - .ident = "Dell Inspiron M5110", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), - }, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro V131", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), - }, - }, - { } -}; - -/* - * Keymap for WMI events of type 0x0000 - * - * Certain keys are flagged as KE_IGNORE. All of these are either - * notifications (rather than requests for change) or are also sent - * via the keyboard controller so should not be sent again. - */ -static const struct key_entry dell_wmi_keymap_type_0000[] = { - { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, - - /* Key code is followed by brightness level */ - { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, - - /* Battery health status button */ - { KE_KEY, 0xe007, { KEY_BATTERY } }, - - /* Radio devices state change, key code is followed by other values */ - { KE_IGNORE, 0xe008, { KEY_RFKILL } }, - - { KE_KEY, 0xe009, { KEY_EJECTCD } }, - - /* Key code is followed by: next, active and attached devices */ - { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, - - /* Key code is followed by keyboard illumination level */ - { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, - - /* BIOS error detected */ - { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, - - /* Battery was removed or inserted */ - { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, - - /* Wifi Catcher */ - { KE_KEY, 0xe011, { KEY_WLAN } }, - - /* Ambient light sensor toggle */ - { KE_IGNORE, 0xe013, { KEY_RESERVED } }, - - { KE_IGNORE, 0xe020, { KEY_MUTE } }, - - /* Unknown, defined in ACPI DSDT */ - /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ - - /* Untested, Dell Instant Launch key on Inspiron 7520 */ - /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ - - /* Dell Instant Launch key */ - { KE_KEY, 0xe025, { KEY_PROG4 } }, - - /* Audio panel key */ - { KE_IGNORE, 0xe026, { KEY_RESERVED } }, - - /* LCD Display On/Off Control key */ - { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, - - /* Untested, Multimedia key on Dell Vostro 3560 */ - /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ - - /* Dell Instant Launch key */ - { KE_KEY, 0xe029, { KEY_PROG4 } }, - - /* Untested, Windows Mobility Center button on Inspiron 7520 */ - /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ - - /* Unknown, defined in ACPI DSDT */ - /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ - - /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ - /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ - - { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, - { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, - { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, - { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, - { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, - - /* NIC Link is Up */ - { KE_IGNORE, 0xe043, { KEY_RESERVED } }, - - /* NIC Link is Down */ - { KE_IGNORE, 0xe044, { KEY_RESERVED } }, - - /* - * This entry is very suspicious! - * Originally Matthew Garrett created this dell-wmi driver specially for - * "button with a picture of a battery" which has event code 0xe045. - * Later Mario Limonciello from Dell told us that event code 0xe045 is - * reported by Num Lock and should be ignored because key is send also - * by keyboard controller. - * So for now we will ignore this event to prevent potential double - * Num Lock key press. - */ - { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, - - /* Scroll lock and also going to tablet mode on portable devices */ - { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, - - /* Untested, going from tablet mode on portable devices */ - /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ - - /* Dell Support Center key */ - { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, - - { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, - { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, - { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, -}; - -struct dell_bios_keymap_entry { - u16 scancode; - u16 keycode; -}; - -struct dell_bios_hotkey_table { - struct dmi_header header; - struct dell_bios_keymap_entry keymap[]; - -}; - -struct dell_dmi_results { - int err; - int keymap_size; - struct key_entry *keymap; -}; - -/* Uninitialized entries here are KEY_RESERVED == 0. */ -static const u16 bios_to_linux_keycode[256] = { - [0] = KEY_MEDIA, - [1] = KEY_NEXTSONG, - [2] = KEY_PLAYPAUSE, - [3] = KEY_PREVIOUSSONG, - [4] = KEY_STOPCD, - [5] = KEY_UNKNOWN, - [6] = KEY_UNKNOWN, - [7] = KEY_UNKNOWN, - [8] = KEY_WWW, - [9] = KEY_UNKNOWN, - [10] = KEY_VOLUMEDOWN, - [11] = KEY_MUTE, - [12] = KEY_VOLUMEUP, - [13] = KEY_UNKNOWN, - [14] = KEY_BATTERY, - [15] = KEY_EJECTCD, - [16] = KEY_UNKNOWN, - [17] = KEY_SLEEP, - [18] = KEY_PROG1, - [19] = KEY_BRIGHTNESSDOWN, - [20] = KEY_BRIGHTNESSUP, - [21] = KEY_BRIGHTNESS_AUTO, - [22] = KEY_KBDILLUMTOGGLE, - [23] = KEY_UNKNOWN, - [24] = KEY_SWITCHVIDEOMODE, - [25] = KEY_UNKNOWN, - [26] = KEY_UNKNOWN, - [27] = KEY_SWITCHVIDEOMODE, - [28] = KEY_UNKNOWN, - [29] = KEY_UNKNOWN, - [30] = KEY_PROG2, - [31] = KEY_UNKNOWN, - [32] = KEY_UNKNOWN, - [33] = KEY_UNKNOWN, - [34] = KEY_UNKNOWN, - [35] = KEY_UNKNOWN, - [36] = KEY_UNKNOWN, - [37] = KEY_UNKNOWN, - [38] = KEY_MICMUTE, - [255] = KEY_PROG3, -}; - -/* - * Keymap for WMI events of type 0x0010 - * - * These are applied if the 0xB2 DMI hotkey table is present and doesn't - * override them. - */ -static const struct key_entry dell_wmi_keymap_type_0010[] = { - /* Fn-lock switched to function keys */ - { KE_IGNORE, 0x0, { KEY_RESERVED } }, - - /* Fn-lock switched to multimedia keys */ - { KE_IGNORE, 0x1, { KEY_RESERVED } }, - - /* Keyboard backlight change notification */ - { KE_IGNORE, 0x3f, { KEY_RESERVED } }, - - /* Backlight brightness level */ - { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, - - /* Mic mute */ - { KE_KEY, 0x150, { KEY_MICMUTE } }, - - /* Fn-lock */ - { KE_IGNORE, 0x151, { KEY_RESERVED } }, - - /* Change keyboard illumination */ - { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, - - /* - * Radio disable (notify only -- there is no model for which the - * WMI event is supposed to trigger an action). - */ - { KE_IGNORE, 0x153, { KEY_RFKILL } }, - - /* RGB keyboard backlight control */ - { KE_IGNORE, 0x154, { KEY_RESERVED } }, - - /* - * Stealth mode toggle. This will "disable all lights and sounds". - * The action is performed by the BIOS and EC; the WMI event is just - * a notification. On the XPS 13 9350, this is Fn+F7, and there's - * a BIOS setting to enable and disable the hotkey. - */ - { KE_IGNORE, 0x155, { KEY_RESERVED } }, - - /* Rugged magnetic dock attach/detach events */ - { KE_IGNORE, 0x156, { KEY_RESERVED } }, - { KE_IGNORE, 0x157, { KEY_RESERVED } }, - - /* Rugged programmable (P1/P2/P3 keys) */ - { KE_KEY, 0x850, { KEY_PROG1 } }, - { KE_KEY, 0x851, { KEY_PROG2 } }, - { KE_KEY, 0x852, { KEY_PROG3 } }, - - /* - * Radio disable (notify only -- there is no model for which the - * WMI event is supposed to trigger an action). - */ - { KE_IGNORE, 0xe008, { KEY_RFKILL } }, - - /* Fn-lock */ - { KE_IGNORE, 0xe035, { KEY_RESERVED } }, -}; - -/* - * Keymap for WMI events of type 0x0011 - */ -static const struct key_entry dell_wmi_keymap_type_0011[] = { - /* Battery unplugged */ - { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, - - /* Battery inserted */ - { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, - - /* - * Detachable keyboard detached / undocked - * Note SW_TABLET_MODE is already reported through the intel_vbtn - * driver for this, so we ignore it. - */ - { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, - - /* Detachable keyboard attached / docked */ - { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, - - /* Keyboard backlight level changed */ - { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, -}; - -/* - * Keymap for WMI events of type 0x0012 - * They are events with extended data - */ -static const struct key_entry dell_wmi_keymap_type_0012[] = { - /* Fn-lock button pressed */ - { KE_IGNORE, 0xe035, { KEY_RESERVED } }, -}; - -static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - const struct key_entry *key; - - key = sparse_keymap_entry_from_scancode(priv->input_dev, - (type << 16) | code); - if (!key) { - pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", - type, code); - return; - } - - pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); - - /* Don't report brightness notifications that will also come via ACPI */ - if ((key->keycode == KEY_BRIGHTNESSUP || - key->keycode == KEY_BRIGHTNESSDOWN) && - acpi_video_handles_brightness_key_presses()) - return; - - if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) - return; - - if (key->keycode == KEY_KBDILLUMTOGGLE) - dell_laptop_call_notifier( - DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); - - sparse_keymap_report_entry(priv->input_dev, key, 1, true); -} - -static void dell_wmi_notify(struct wmi_device *wdev, - union acpi_object *obj) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - u16 *buffer_entry, *buffer_end; - acpi_size buffer_size; - int len, i; - - if (obj->type != ACPI_TYPE_BUFFER) { - pr_warn("bad response type %x\n", obj->type); - return; - } - - pr_debug("Received WMI event (%*ph)\n", - obj->buffer.length, obj->buffer.pointer); - - buffer_entry = (u16 *)obj->buffer.pointer; - buffer_size = obj->buffer.length/2; - buffer_end = buffer_entry + buffer_size; - - /* - * BIOS/ACPI on devices with WMI interface version 0 does not clear - * buffer before filling it. So next time when BIOS/ACPI send WMI event - * which is smaller as previous then it contains garbage in buffer from - * previous event. - * - * BIOS/ACPI on devices with WMI interface version 1 clears buffer and - * sometimes send more events in buffer at one call. - * - * So to prevent reading garbage from buffer we will process only first - * one event on devices with WMI interface version 0. - */ - if (priv->interface_version == 0 && buffer_entry < buffer_end) - if (buffer_end > buffer_entry + buffer_entry[0] + 1) - buffer_end = buffer_entry + buffer_entry[0] + 1; - - while (buffer_entry < buffer_end) { - - len = buffer_entry[0]; - if (len == 0) - break; - - len++; - - if (buffer_entry + len > buffer_end) { - pr_warn("Invalid length of WMI event\n"); - break; - } - - pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); - - switch (buffer_entry[1]) { - case 0x0000: /* One key pressed or event occurred */ - case 0x0012: /* Event with extended data occurred */ - if (len > 2) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[2]); - /* Extended data is currently ignored */ - break; - case 0x0010: /* Sequence of keys pressed */ - case 0x0011: /* Sequence of events occurred */ - for (i = 2; i < len; ++i) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[i]); - break; - default: /* Unknown event */ - pr_info("Unknown WMI event type 0x%x\n", - (int)buffer_entry[1]); - break; - } - - buffer_entry += len; - - } - -} - -static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) -{ - int i; - - for (i = 0; i < len; i++) - if (keymap[i].code == scancode) - return true; - - return false; -} - -static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) -{ - struct dell_dmi_results *results = opaque; - struct dell_bios_hotkey_table *table; - int hotkey_num, i, pos = 0; - struct key_entry *keymap; - - if (results->err || results->keymap) - return; /* We already found the hotkey table. */ - - /* The Dell hotkey table is type 0xB2. Scan until we find it. */ - if (dm->type != 0xb2) - return; - - table = container_of(dm, struct dell_bios_hotkey_table, header); - - hotkey_num = (table->header.length - - sizeof(struct dell_bios_hotkey_table)) / - sizeof(struct dell_bios_keymap_entry); - if (hotkey_num < 1) { - /* - * Historically, dell-wmi would ignore a DMI entry of - * fewer than 7 bytes. Sizes between 4 and 8 bytes are - * nonsensical (both the header and all entries are 4 - * bytes), so we approximate the old behavior by - * ignoring tables with fewer than one entry. - */ - return; - } - - keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); - if (!keymap) { - results->err = -ENOMEM; - return; - } - - for (i = 0; i < hotkey_num; i++) { - const struct dell_bios_keymap_entry *bios_entry = - &table->keymap[i]; - - /* Uninitialized entries are 0 aka KEY_RESERVED. */ - u16 keycode = (bios_entry->keycode < - ARRAY_SIZE(bios_to_linux_keycode)) ? - bios_to_linux_keycode[bios_entry->keycode] : - (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); - - /* - * Log if we find an entry in the DMI table that we don't - * understand. If this happens, we should figure out what - * the entry means and add it to bios_to_linux_keycode. - */ - if (keycode == KEY_RESERVED) { - pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", - bios_entry->scancode, bios_entry->keycode); - continue; - } - - if (keycode == KEY_KBDILLUMTOGGLE) - keymap[pos].type = KE_IGNORE; - else - keymap[pos].type = KE_KEY; - keymap[pos].code = bios_entry->scancode; - keymap[pos].keycode = keycode; - - pos++; - } - - results->keymap = keymap; - results->keymap_size = pos; -} - -static int dell_wmi_input_setup(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - struct dell_dmi_results dmi_results = {}; - struct key_entry *keymap; - int err, i, pos = 0; - - priv->input_dev = input_allocate_device(); - if (!priv->input_dev) - return -ENOMEM; - - priv->input_dev->name = "Dell WMI hotkeys"; - priv->input_dev->id.bustype = BUS_HOST; - priv->input_dev->dev.parent = &wdev->dev; - - if (dmi_walk(handle_dmi_entry, &dmi_results)) { - /* - * Historically, dell-wmi ignored dmi_walk errors. A failure - * is certainly surprising, but it probably just indicates - * a very old laptop. - */ - pr_warn("no DMI; using the old-style hotkey interface\n"); - } - - if (dmi_results.err) { - err = dmi_results.err; - goto err_free_dev; - } - - keymap = kcalloc(dmi_results.keymap_size + - ARRAY_SIZE(dell_wmi_keymap_type_0000) + - ARRAY_SIZE(dell_wmi_keymap_type_0010) + - ARRAY_SIZE(dell_wmi_keymap_type_0011) + - ARRAY_SIZE(dell_wmi_keymap_type_0012) + - 1, - sizeof(struct key_entry), GFP_KERNEL); - if (!keymap) { - kfree(dmi_results.keymap); - err = -ENOMEM; - goto err_free_dev; - } - - /* Append table with events of type 0x0010 which comes from DMI */ - for (i = 0; i < dmi_results.keymap_size; i++) { - keymap[pos] = dmi_results.keymap[i]; - keymap[pos].code |= (0x0010 << 16); - pos++; - } - - kfree(dmi_results.keymap); - - /* Append table with extra events of type 0x0010 which are not in DMI */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { - const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; - - /* - * Check if we've already found this scancode. This takes - * quadratic time, but it doesn't matter unless the list - * of extra keys gets very long. - */ - if (dmi_results.keymap_size && - have_scancode(entry->code | (0x0010 << 16), - keymap, dmi_results.keymap_size) - ) - continue; - - keymap[pos] = *entry; - keymap[pos].code |= (0x0010 << 16); - pos++; - } - - /* Append table with events of type 0x0011 */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { - keymap[pos] = dell_wmi_keymap_type_0011[i]; - keymap[pos].code |= (0x0011 << 16); - pos++; - } - - /* Append table with events of type 0x0012 */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { - keymap[pos] = dell_wmi_keymap_type_0012[i]; - keymap[pos].code |= (0x0012 << 16); - pos++; - } - - /* - * Now append also table with "legacy" events of type 0x0000. Some of - * them are reported also on laptops which have scancodes in DMI. - */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { - keymap[pos] = dell_wmi_keymap_type_0000[i]; - pos++; - } - - keymap[pos].type = KE_END; - - err = sparse_keymap_setup(priv->input_dev, keymap, NULL); - /* - * Sparse keymap library makes a copy of keymap so we don't need the - * original one that was allocated. - */ - kfree(keymap); - if (err) - goto err_free_dev; - - err = input_register_device(priv->input_dev); - if (err) - goto err_free_dev; - - return 0; - - err_free_dev: - input_free_device(priv->input_dev); - return err; -} - -static void dell_wmi_input_destroy(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - - input_unregister_device(priv->input_dev); -} - -/* - * According to Dell SMBIOS documentation: - * - * 17 3 Application Program Registration - * - * cbArg1 Application ID 1 = 0x00010000 - * cbArg2 Application ID 2 - * QUICKSET/DCP = 0x51534554 "QSET" - * ALS Driver = 0x416c7353 "AlsS" - * Latitude ON = 0x4c6f6e52 "LonR" - * cbArg3 Application version or revision number - * cbArg4 0 = Unregister application - * 1 = Register application - * cbRes1 Standard return codes (0, -1, -2) - */ - -static int dell_wmi_events_set_enabled(bool enable) -{ - struct calling_interface_buffer *buffer; - int ret; - - buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); - if (!buffer) - return -ENOMEM; - buffer->cmd_class = CLASS_INFO; - buffer->cmd_select = SELECT_APP_REGISTRATION; - buffer->input[0] = 0x10000; - buffer->input[1] = 0x51534554; - buffer->input[3] = enable; - ret = dell_smbios_call(buffer); - if (ret == 0) - ret = buffer->output[0]; - kfree(buffer); - - return dell_smbios_error(ret); -} - -static int dell_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct dell_wmi_priv *priv; - int ret; - - ret = dell_wmi_get_descriptor_valid(); - if (ret) - return ret; - - priv = devm_kzalloc( - &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(&wdev->dev, priv); - - if (!dell_wmi_get_interface_version(&priv->interface_version)) - return -EPROBE_DEFER; - - return dell_wmi_input_setup(wdev); -} - -static int dell_wmi_remove(struct wmi_device *wdev) -{ - dell_wmi_input_destroy(wdev); - return 0; -} -static const struct wmi_device_id dell_wmi_id_table[] = { - { .guid_string = DELL_EVENT_GUID }, - { }, -}; - -static struct wmi_driver dell_wmi_driver = { - .driver = { - .name = "dell-wmi", - }, - .id_table = dell_wmi_id_table, - .probe = dell_wmi_probe, - .remove = dell_wmi_remove, - .notify = dell_wmi_notify, -}; - -static int __init dell_wmi_init(void) -{ - int err; - - dmi_check_system(dell_wmi_smbios_list); - - if (wmi_requires_smbios_request) { - err = dell_wmi_events_set_enabled(true); - if (err) { - pr_err("Failed to enable WMI events\n"); - return err; - } - } - - return wmi_driver_register(&dell_wmi_driver); -} -late_initcall(dell_wmi_init); - -static void __exit dell_wmi_exit(void) -{ - if (wmi_requires_smbios_request) - dell_wmi_events_set_enabled(false); - - wmi_driver_unregister(&dell_wmi_driver); -} -module_exit(dell_wmi_exit); - -MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig new file mode 100644 index 000000000000..e0a55337f51a --- /dev/null +++ b/drivers/platform/x86/dell/Kconfig @@ -0,0 +1,207 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Dell X86 Platform Specific Drivers +# + +menuconfig X86_PLATFORM_DRIVERS_DELL + bool "Dell X86 Platform Specific Device Drivers" + default n + depends on X86_PLATFORM_DEVICES + help + Say Y here to get to see options for device drivers for various + Dell x86 platforms, including vendor-specific laptop extension drivers. + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if X86_PLATFORM_DRIVERS_DELL + +config ALIENWARE_WMI + tristate "Alienware Special feature control" + default m + depends on ACPI + depends on LEDS_CLASS + depends on NEW_LEDS + depends on ACPI_WMI + help + This is a driver for controlling Alienware BIOS driven + features. It exposes an interface for controlling the AlienFX + zones on Alienware machines that don't contain a dedicated AlienFX + USB MCU such as the X51 and X51-R2. + +config DCDBAS + tristate "Dell Systems Management Base Driver" + default m + depends on X86 + help + The Dell Systems Management Base Driver provides a sysfs interface + for systems management software to perform System Management + Interrupts (SMIs) and Host Control Actions (system power cycle or + power off after OS shutdown) on certain Dell systems. + + See for more details on the driver + and the Dell systems on which Dell systems management software makes + use of this driver. + + Say Y or M here to enable the driver for use by Dell systems + management software such as Dell OpenManage. + +config DELL_LAPTOP + tristate "Dell Laptop Extras" + default m + depends on DMI + depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on RFKILL || RFKILL = n + depends on SERIO_I8042 + depends on DELL_SMBIOS + select POWER_SUPPLY + select LEDS_CLASS + select NEW_LEDS + select LEDS_TRIGGERS + select LEDS_TRIGGER_AUDIO + help + This driver adds support for rfkill and backlight control to Dell + laptops (except for some models covered by the Compal driver). + +config DELL_RBU + tristate "BIOS update support for DELL systems via sysfs" + default m + depends on X86 + select FW_LOADER + select FW_LOADER_USER_HELPER + help + Say m if you want to have the option of updating the BIOS for your + DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) + supporting application to communicate with the BIOS regarding the new + image for the image update to take effect. + See for more details on the driver. + +config DELL_RBTN + tristate "Dell Airplane Mode Switch driver" + default m + depends on ACPI + depends on INPUT + depends on RFKILL + help + Say Y here if you want to support Dell Airplane Mode Switch ACPI + device on Dell laptops. Sometimes it has names: DELLABCE or DELRBTN. + This driver register rfkill device or input hotkey device depending + on hardware type (hw switch slider or keyboard toggle button). For + rfkill devices it receive HW switch events and set correct hard + rfkill state. + + To compile this driver as a module, choose M here: the module will + be called dell-rbtn. + +# +# The DELL_SMBIOS driver depends on ACPI_WMI and/or DCDBAS if those +# backends are selected. The "depends" line prevents a configuration +# where DELL_SMBIOS=y while either of those dependencies =m. +# +config DELL_SMBIOS + tristate "Dell SMBIOS driver" + default m + depends on DCDBAS || DCDBAS=n + depends on ACPI_WMI || ACPI_WMI=n + help + This provides support for the Dell SMBIOS calling interface. + If you have a Dell computer you should enable this option. + + Be sure to select at least one backend for it to work properly. + +config DELL_SMBIOS_WMI + bool "Dell SMBIOS driver WMI backend" + default y + depends on ACPI_WMI + select DELL_WMI_DESCRIPTOR + depends on DELL_SMBIOS + help + This provides an implementation for the Dell SMBIOS calling interface + communicated over ACPI-WMI. + + If you have a Dell computer from >2007 you should say Y here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMBIOS_SMM + bool "Dell SMBIOS driver SMM backend" + default y + depends on DCDBAS + depends on DELL_SMBIOS + help + This provides an implementation for the Dell SMBIOS calling interface + communicated over SMI/SMM. + + If you have a Dell computer from <=2017 you should say Y here. + If you aren't sure and this module doesn't work for your computer + it just won't load. + +config DELL_SMO8800 + tristate "Dell Latitude freefall driver (ACPI SMO88XX)" + default m + depends on ACPI + help + Say Y here if you want to support SMO88XX freefall devices + on Dell Latitude laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-smo8800. + +config DELL_WMI + tristate "Dell WMI notifications" + default m + depends on ACPI_WMI + depends on DMI + depends on INPUT + depends on ACPI_VIDEO || ACPI_VIDEO = n + depends on DELL_SMBIOS + select DELL_WMI_DESCRIPTOR + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on Dell laptops. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi. + +config DELL_WMI_AIO + tristate "WMI Hotkeys for Dell All-In-One series" + default m + depends on ACPI_WMI + depends on INPUT + select INPUT_SPARSEKMAP + help + Say Y here if you want to support WMI-based hotkeys on Dell + All-In-One machines. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-aio. + +config DELL_WMI_DESCRIPTOR + tristate + default m + depends on ACPI_WMI + +config DELL_WMI_LED + tristate "External LED on Dell Business Netbooks" + default m + depends on LEDS_CLASS + depends on ACPI_WMI + help + This adds support for the Latitude 2100 and similar + notebooks that have an external LED. + +config DELL_WMI_SYSMAN + tristate "Dell WMI-based Systems management driver" + default m + depends on ACPI_WMI + depends on DMI + select NLS + help + This driver allows changing BIOS settings on many Dell machines from + 2018 and newer without the use of any additional software. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-sysman. + +endif # X86_PLATFORM_DRIVERS_DELL diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile new file mode 100644 index 000000000000..d720a3e42ae3 --- /dev/null +++ b/drivers/platform/x86/dell/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/dell +# Dell x86 Platform-Specific Drivers +# + +obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o +obj-$(CONFIG_DCDBAS) += dcdbas.o +obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o +obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o +obj-$(CONFIG_DELL_RBU) += dell_rbu.o +obj-$(CONFIG_DELL_SMBIOS) += dell-smbios.o +dell-smbios-objs := dell-smbios-base.o +dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o +dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o +obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o +obj-$(CONFIG_DELL_WMI) += dell-wmi.o +obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o +obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o +obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ diff --git a/drivers/platform/x86/dell/alienware-wmi.c b/drivers/platform/x86/dell/alienware-wmi.c new file mode 100644 index 000000000000..5bb2859c8285 --- /dev/null +++ b/drivers/platform/x86/dell/alienware-wmi.c @@ -0,0 +1,853 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Alienware AlienFX control + * + * Copyright (C) 2014 Dell Inc + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#define LEGACY_CONTROL_GUID "A90597CE-A997-11DA-B012-B622A1EF5492" +#define LEGACY_POWER_CONTROL_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" +#define WMAX_CONTROL_GUID "A70591CE-A997-11DA-B012-B622A1EF5492" + +#define WMAX_METHOD_HDMI_SOURCE 0x1 +#define WMAX_METHOD_HDMI_STATUS 0x2 +#define WMAX_METHOD_BRIGHTNESS 0x3 +#define WMAX_METHOD_ZONE_CONTROL 0x4 +#define WMAX_METHOD_HDMI_CABLE 0x5 +#define WMAX_METHOD_AMPLIFIER_CABLE 0x6 +#define WMAX_METHOD_DEEP_SLEEP_CONTROL 0x0B +#define WMAX_METHOD_DEEP_SLEEP_STATUS 0x0C + +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Alienware special feature control"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("wmi:" LEGACY_CONTROL_GUID); +MODULE_ALIAS("wmi:" WMAX_CONTROL_GUID); + +enum INTERFACE_FLAGS { + LEGACY, + WMAX, +}; + +enum LEGACY_CONTROL_STATES { + LEGACY_RUNNING = 1, + LEGACY_BOOTING = 0, + LEGACY_SUSPEND = 3, +}; + +enum WMAX_CONTROL_STATES { + WMAX_RUNNING = 0xFF, + WMAX_BOOTING = 0, + WMAX_SUSPEND = 3, +}; + +struct quirk_entry { + u8 num_zones; + u8 hdmi_mux; + u8 amplifier; + u8 deepslp; +}; + +static struct quirk_entry *quirks; + + +static struct quirk_entry quirk_inspiron5675 = { + .num_zones = 2, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_unknown = { + .num_zones = 2, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_x51_r1_r2 = { + .num_zones = 3, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_x51_r3 = { + .num_zones = 4, + .hdmi_mux = 0, + .amplifier = 1, + .deepslp = 0, +}; + +static struct quirk_entry quirk_asm100 = { + .num_zones = 2, + .hdmi_mux = 1, + .amplifier = 0, + .deepslp = 0, +}; + +static struct quirk_entry quirk_asm200 = { + .num_zones = 2, + .hdmi_mux = 1, + .amplifier = 0, + .deepslp = 1, +}; + +static struct quirk_entry quirk_asm201 = { + .num_zones = 2, + .hdmi_mux = 1, + .amplifier = 1, + .deepslp = 1, +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +static const struct dmi_system_id alienware_quirks[] __initconst = { + { + .callback = dmi_matched, + .ident = "Alienware X51 R3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R3"), + }, + .driver_data = &quirk_x51_r3, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51 R2"), + }, + .driver_data = &quirk_x51_r1_r2, + }, + { + .callback = dmi_matched, + .ident = "Alienware X51 R1", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "Alienware X51"), + }, + .driver_data = &quirk_x51_r1_r2, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM100"), + }, + .driver_data = &quirk_asm100, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM200", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM200"), + }, + .driver_data = &quirk_asm200, + }, + { + .callback = dmi_matched, + .ident = "Alienware ASM201", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Alienware"), + DMI_MATCH(DMI_PRODUCT_NAME, "ASM201"), + }, + .driver_data = &quirk_asm201, + }, + { + .callback = dmi_matched, + .ident = "Dell Inc. Inspiron 5675", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), + }, + .driver_data = &quirk_inspiron5675, + }, + {} +}; + +struct color_platform { + u8 blue; + u8 green; + u8 red; +} __packed; + +struct platform_zone { + u8 location; + struct device_attribute *attr; + struct color_platform colors; +}; + +struct wmax_brightness_args { + u32 led_mask; + u32 percentage; +}; + +struct wmax_basic_args { + u8 arg; +}; + +struct legacy_led_args { + struct color_platform colors; + u8 brightness; + u8 state; +} __packed; + +struct wmax_led_args { + u32 led_mask; + struct color_platform colors; + u8 state; +} __packed; + +static struct platform_device *platform_device; +static struct device_attribute *zone_dev_attrs; +static struct attribute **zone_attrs; +static struct platform_zone *zone_data; + +static struct platform_driver platform_driver = { + .driver = { + .name = "alienware-wmi", + } +}; + +static struct attribute_group zone_attribute_group = { + .name = "rgb_zones", +}; + +static u8 interface; +static u8 lighting_control_state; +static u8 global_brightness; + +/* + * Helpers used for zone control + */ +static int parse_rgb(const char *buf, struct platform_zone *zone) +{ + long unsigned int rgb; + int ret; + union color_union { + struct color_platform cp; + int package; + } repackager; + + ret = kstrtoul(buf, 16, &rgb); + if (ret) + return ret; + + /* RGB triplet notation is 24-bit hexadecimal */ + if (rgb > 0xFFFFFF) + return -EINVAL; + + repackager.package = rgb & 0x0f0f0f0f; + pr_debug("alienware-wmi: r: %d g:%d b: %d\n", + repackager.cp.red, repackager.cp.green, repackager.cp.blue); + zone->colors = repackager.cp; + return 0; +} + +static struct platform_zone *match_zone(struct device_attribute *attr) +{ + u8 zone; + + for (zone = 0; zone < quirks->num_zones; zone++) { + if ((struct device_attribute *)zone_data[zone].attr == attr) { + pr_debug("alienware-wmi: matched zone location: %d\n", + zone_data[zone].location); + return &zone_data[zone]; + } + } + return NULL; +} + +/* + * Individual RGB zone control + */ +static int alienware_update_led(struct platform_zone *zone) +{ + int method_id; + acpi_status status; + char *guid; + struct acpi_buffer input; + struct legacy_led_args legacy_args; + struct wmax_led_args wmax_basic_args; + if (interface == WMAX) { + wmax_basic_args.led_mask = 1 << zone->location; + wmax_basic_args.colors = zone->colors; + wmax_basic_args.state = lighting_control_state; + guid = WMAX_CONTROL_GUID; + method_id = WMAX_METHOD_ZONE_CONTROL; + + input.length = (acpi_size) sizeof(wmax_basic_args); + input.pointer = &wmax_basic_args; + } else { + legacy_args.colors = zone->colors; + legacy_args.brightness = global_brightness; + legacy_args.state = 0; + if (lighting_control_state == LEGACY_BOOTING || + lighting_control_state == LEGACY_SUSPEND) { + guid = LEGACY_POWER_CONTROL_GUID; + legacy_args.state = lighting_control_state; + } else + guid = LEGACY_CONTROL_GUID; + method_id = zone->location + 1; + + input.length = (acpi_size) sizeof(legacy_args); + input.pointer = &legacy_args; + } + pr_debug("alienware-wmi: guid %s method %d\n", guid, method_id); + + status = wmi_evaluate_method(guid, 0, method_id, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: zone set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static ssize_t zone_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_zone *target_zone; + target_zone = match_zone(attr); + if (target_zone == NULL) + return sprintf(buf, "red: -1, green: -1, blue: -1\n"); + return sprintf(buf, "red: %d, green: %d, blue: %d\n", + target_zone->colors.red, + target_zone->colors.green, target_zone->colors.blue); + +} + +static ssize_t zone_set(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_zone *target_zone; + int ret; + target_zone = match_zone(attr); + if (target_zone == NULL) { + pr_err("alienware-wmi: invalid target zone\n"); + return 1; + } + ret = parse_rgb(buf, target_zone); + if (ret) + return ret; + ret = alienware_update_led(target_zone); + return ret ? ret : count; +} + +/* + * LED Brightness (Global) + */ +static int wmax_brightness(int brightness) +{ + acpi_status status; + struct acpi_buffer input; + struct wmax_brightness_args args = { + .led_mask = 0xFF, + .percentage = brightness, + }; + input.length = (acpi_size) sizeof(args); + input.pointer = &args; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, + WMAX_METHOD_BRIGHTNESS, &input, NULL); + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: brightness set failure: %u\n", status); + return ACPI_FAILURE(status); +} + +static void global_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int ret; + global_brightness = brightness; + if (interface == WMAX) + ret = wmax_brightness(brightness); + else + ret = alienware_update_led(&zone_data[0]); + if (ret) + pr_err("LED brightness update failed\n"); +} + +static enum led_brightness global_led_get(struct led_classdev *led_cdev) +{ + return global_brightness; +} + +static struct led_classdev global_led = { + .brightness_set = global_led_set, + .brightness_get = global_led_get, + .name = "alienware::global_brightness", +}; + +/* + * Lighting control state device attribute (Global) + */ +static ssize_t show_control_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + if (lighting_control_state == LEGACY_BOOTING) + return scnprintf(buf, PAGE_SIZE, "[booting] running suspend\n"); + else if (lighting_control_state == LEGACY_SUSPEND) + return scnprintf(buf, PAGE_SIZE, "booting running [suspend]\n"); + return scnprintf(buf, PAGE_SIZE, "booting [running] suspend\n"); +} + +static ssize_t store_control_state(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + long unsigned int val; + if (strcmp(buf, "booting\n") == 0) + val = LEGACY_BOOTING; + else if (strcmp(buf, "suspend\n") == 0) + val = LEGACY_SUSPEND; + else if (interface == LEGACY) + val = LEGACY_RUNNING; + else + val = WMAX_RUNNING; + lighting_control_state = val; + pr_debug("alienware-wmi: updated control state to %d\n", + lighting_control_state); + return count; +} + +static DEVICE_ATTR(lighting_control_state, 0644, show_control_state, + store_control_state); + +static int alienware_zone_init(struct platform_device *dev) +{ + u8 zone; + char buffer[10]; + char *name; + + if (interface == WMAX) { + lighting_control_state = WMAX_RUNNING; + } else if (interface == LEGACY) { + lighting_control_state = LEGACY_RUNNING; + } + global_led.max_brightness = 0x0F; + global_brightness = global_led.max_brightness; + + /* + * - zone_dev_attrs num_zones + 1 is for individual zones and then + * null terminated + * - zone_attrs num_zones + 2 is for all attrs in zone_dev_attrs + + * the lighting control + null terminated + * - zone_data num_zones is for the distinct zones + */ + zone_dev_attrs = + kcalloc(quirks->num_zones + 1, sizeof(struct device_attribute), + GFP_KERNEL); + if (!zone_dev_attrs) + return -ENOMEM; + + zone_attrs = + kcalloc(quirks->num_zones + 2, sizeof(struct attribute *), + GFP_KERNEL); + if (!zone_attrs) + return -ENOMEM; + + zone_data = + kcalloc(quirks->num_zones, sizeof(struct platform_zone), + GFP_KERNEL); + if (!zone_data) + return -ENOMEM; + + for (zone = 0; zone < quirks->num_zones; zone++) { + sprintf(buffer, "zone%02hhX", zone); + name = kstrdup(buffer, GFP_KERNEL); + if (name == NULL) + return 1; + sysfs_attr_init(&zone_dev_attrs[zone].attr); + zone_dev_attrs[zone].attr.name = name; + zone_dev_attrs[zone].attr.mode = 0644; + zone_dev_attrs[zone].show = zone_show; + zone_dev_attrs[zone].store = zone_set; + zone_data[zone].location = zone; + zone_attrs[zone] = &zone_dev_attrs[zone].attr; + zone_data[zone].attr = &zone_dev_attrs[zone]; + } + zone_attrs[quirks->num_zones] = &dev_attr_lighting_control_state.attr; + zone_attribute_group.attrs = zone_attrs; + + led_classdev_register(&dev->dev, &global_led); + + return sysfs_create_group(&dev->dev.kobj, &zone_attribute_group); +} + +static void alienware_zone_exit(struct platform_device *dev) +{ + u8 zone; + + sysfs_remove_group(&dev->dev.kobj, &zone_attribute_group); + led_classdev_unregister(&global_led); + if (zone_dev_attrs) { + for (zone = 0; zone < quirks->num_zones; zone++) + kfree(zone_dev_attrs[zone].attr.name); + } + kfree(zone_dev_attrs); + kfree(zone_data); + kfree(zone_attrs); +} + +static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, + u32 command, int *out_data) +{ + acpi_status status; + union acpi_object *obj; + struct acpi_buffer input; + struct acpi_buffer output; + + input.length = (acpi_size) sizeof(*in_args); + input.pointer = in_args; + if (out_data) { + output.length = ACPI_ALLOCATE_BUFFER; + output.pointer = NULL; + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, + command, &input, &output); + if (ACPI_SUCCESS(status)) { + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + *out_data = (u32)obj->integer.value; + } + kfree(output.pointer); + } else { + status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, + command, &input, NULL); + } + return status; +} + +/* + * The HDMI mux sysfs node indicates the status of the HDMI input mux. + * It can toggle between standard system GPU output and HDMI input. + */ +static ssize_t show_hdmi_cable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = + alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_CABLE, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[unconnected] connected unknown\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "unconnected [connected] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI cable status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); +} + +static ssize_t show_hdmi_source(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = + alienware_wmax_command(&in_args, WMAX_METHOD_HDMI_STATUS, + (u32 *) &out_data); + + if (ACPI_SUCCESS(status)) { + if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "[input] gpu unknown\n"); + else if (out_data == 2) + return scnprintf(buf, PAGE_SIZE, + "input [gpu] unknown\n"); + } + pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); + return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); +} + +static ssize_t toggle_hdmi_source(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + acpi_status status; + struct wmax_basic_args args; + if (strcmp(buf, "gpu\n") == 0) + args.arg = 1; + else if (strcmp(buf, "input\n") == 0) + args.arg = 2; + else + args.arg = 3; + pr_debug("alienware-wmi: setting hdmi to %d : %s", args.arg, buf); + + status = alienware_wmax_command(&args, WMAX_METHOD_HDMI_SOURCE, NULL); + + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: HDMI toggle failed: results: %u\n", + status); + return count; +} + +static DEVICE_ATTR(cable, S_IRUGO, show_hdmi_cable, NULL); +static DEVICE_ATTR(source, S_IRUGO | S_IWUSR, show_hdmi_source, + toggle_hdmi_source); + +static struct attribute *hdmi_attrs[] = { + &dev_attr_cable.attr, + &dev_attr_source.attr, + NULL, +}; + +static const struct attribute_group hdmi_attribute_group = { + .name = "hdmi", + .attrs = hdmi_attrs, +}; + +static void remove_hdmi(struct platform_device *dev) +{ + if (quirks->hdmi_mux > 0) + sysfs_remove_group(&dev->dev.kobj, &hdmi_attribute_group); +} + +static int create_hdmi(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &hdmi_attribute_group); + if (ret) + remove_hdmi(dev); + return ret; +} + +/* + * Alienware GFX amplifier support + * - Currently supports reading cable status + * - Leaving expansion room to possibly support dock/undock events later + */ +static ssize_t show_amplifier_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = + alienware_wmax_command(&in_args, WMAX_METHOD_AMPLIFIER_CABLE, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[unconnected] connected unknown\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "unconnected [connected] unknown\n"); + } + pr_err("alienware-wmi: unknown amplifier cable status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "unconnected connected [unknown]\n"); +} + +static DEVICE_ATTR(status, S_IRUGO, show_amplifier_status, NULL); + +static struct attribute *amplifier_attrs[] = { + &dev_attr_status.attr, + NULL, +}; + +static const struct attribute_group amplifier_attribute_group = { + .name = "amplifier", + .attrs = amplifier_attrs, +}; + +static void remove_amplifier(struct platform_device *dev) +{ + if (quirks->amplifier > 0) + sysfs_remove_group(&dev->dev.kobj, &lifier_attribute_group); +} + +static int create_amplifier(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &lifier_attribute_group); + if (ret) + remove_amplifier(dev); + return ret; +} + +/* + * Deep Sleep Control support + * - Modifies BIOS setting for deep sleep control allowing extra wakeup events + */ +static ssize_t show_deepsleep_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + acpi_status status; + u32 out_data; + struct wmax_basic_args in_args = { + .arg = 0, + }; + status = alienware_wmax_command(&in_args, WMAX_METHOD_DEEP_SLEEP_STATUS, + (u32 *) &out_data); + if (ACPI_SUCCESS(status)) { + if (out_data == 0) + return scnprintf(buf, PAGE_SIZE, + "[disabled] s5 s5_s4\n"); + else if (out_data == 1) + return scnprintf(buf, PAGE_SIZE, + "disabled [s5] s5_s4\n"); + else if (out_data == 2) + return scnprintf(buf, PAGE_SIZE, + "disabled s5 [s5_s4]\n"); + } + pr_err("alienware-wmi: unknown deep sleep status: %d\n", status); + return scnprintf(buf, PAGE_SIZE, "disabled s5 s5_s4 [unknown]\n"); +} + +static ssize_t toggle_deepsleep(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + acpi_status status; + struct wmax_basic_args args; + + if (strcmp(buf, "disabled\n") == 0) + args.arg = 0; + else if (strcmp(buf, "s5\n") == 0) + args.arg = 1; + else + args.arg = 2; + pr_debug("alienware-wmi: setting deep sleep to %d : %s", args.arg, buf); + + status = alienware_wmax_command(&args, WMAX_METHOD_DEEP_SLEEP_CONTROL, + NULL); + + if (ACPI_FAILURE(status)) + pr_err("alienware-wmi: deep sleep control failed: results: %u\n", + status); + return count; +} + +static DEVICE_ATTR(deepsleep, S_IRUGO | S_IWUSR, show_deepsleep_status, toggle_deepsleep); + +static struct attribute *deepsleep_attrs[] = { + &dev_attr_deepsleep.attr, + NULL, +}; + +static const struct attribute_group deepsleep_attribute_group = { + .name = "deepsleep", + .attrs = deepsleep_attrs, +}; + +static void remove_deepsleep(struct platform_device *dev) +{ + if (quirks->deepslp > 0) + sysfs_remove_group(&dev->dev.kobj, &deepsleep_attribute_group); +} + +static int create_deepsleep(struct platform_device *dev) +{ + int ret; + + ret = sysfs_create_group(&dev->dev.kobj, &deepsleep_attribute_group); + if (ret) + remove_deepsleep(dev); + return ret; +} + +static int __init alienware_wmi_init(void) +{ + int ret; + + if (wmi_has_guid(LEGACY_CONTROL_GUID)) + interface = LEGACY; + else if (wmi_has_guid(WMAX_CONTROL_GUID)) + interface = WMAX; + else { + pr_warn("alienware-wmi: No known WMI GUID found\n"); + return -ENODEV; + } + + dmi_check_system(alienware_quirks); + if (quirks == NULL) + quirks = &quirk_unknown; + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("alienware-wmi", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + + if (quirks->hdmi_mux > 0) { + ret = create_hdmi(platform_device); + if (ret) + goto fail_prep_hdmi; + } + + if (quirks->amplifier > 0) { + ret = create_amplifier(platform_device); + if (ret) + goto fail_prep_amplifier; + } + + if (quirks->deepslp > 0) { + ret = create_deepsleep(platform_device); + if (ret) + goto fail_prep_deepsleep; + } + + ret = alienware_zone_init(platform_device); + if (ret) + goto fail_prep_zones; + + return 0; + +fail_prep_zones: + alienware_zone_exit(platform_device); +fail_prep_deepsleep: +fail_prep_amplifier: +fail_prep_hdmi: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: + return ret; +} + +module_init(alienware_wmi_init); + +static void __exit alienware_wmi_exit(void) +{ + if (platform_device) { + alienware_zone_exit(platform_device); + remove_hdmi(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } +} + +module_exit(alienware_wmi_exit); diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c new file mode 100644 index 000000000000..d513a59a5d47 --- /dev/null +++ b/drivers/platform/x86/dell/dcdbas.c @@ -0,0 +1,770 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dcdbas.c: Dell Systems Management Base Driver + * + * The Dell Systems Management Base Driver provides a sysfs interface for + * systems management software to perform System Management Interrupts (SMIs) + * and Host Control Actions (power cycle or power off after OS shutdown) on + * Dell systems. + * + * See Documentation/driver-api/dcdbas.rst for more information. + * + * Copyright (C) 1995-2006 Dell Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dcdbas.h" + +#define DRIVER_NAME "dcdbas" +#define DRIVER_VERSION "5.6.0-3.4" +#define DRIVER_DESCRIPTION "Dell Systems Management Base Driver" + +static struct platform_device *dcdbas_pdev; + +static u8 *smi_data_buf; +static dma_addr_t smi_data_buf_handle; +static unsigned long smi_data_buf_size; +static unsigned long max_smi_data_buf_size = MAX_SMI_DATA_BUF_SIZE; +static u32 smi_data_buf_phys_addr; +static DEFINE_MUTEX(smi_data_lock); +static u8 *bios_buffer; + +static unsigned int host_control_action; +static unsigned int host_control_smi_type; +static unsigned int host_control_on_shutdown; + +static bool wsmt_enabled; + +/** + * smi_data_buf_free: free SMI data buffer + */ +static void smi_data_buf_free(void) +{ + if (!smi_data_buf || wsmt_enabled) + return; + + dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", + __func__, smi_data_buf_phys_addr, smi_data_buf_size); + + dma_free_coherent(&dcdbas_pdev->dev, smi_data_buf_size, smi_data_buf, + smi_data_buf_handle); + smi_data_buf = NULL; + smi_data_buf_handle = 0; + smi_data_buf_phys_addr = 0; + smi_data_buf_size = 0; +} + +/** + * smi_data_buf_realloc: grow SMI data buffer if needed + */ +static int smi_data_buf_realloc(unsigned long size) +{ + void *buf; + dma_addr_t handle; + + if (smi_data_buf_size >= size) + return 0; + + if (size > max_smi_data_buf_size) + return -EINVAL; + + /* new buffer is needed */ + buf = dma_alloc_coherent(&dcdbas_pdev->dev, size, &handle, GFP_KERNEL); + if (!buf) { + dev_dbg(&dcdbas_pdev->dev, + "%s: failed to allocate memory size %lu\n", + __func__, size); + return -ENOMEM; + } + /* memory zeroed by dma_alloc_coherent */ + + if (smi_data_buf) + memcpy(buf, smi_data_buf, smi_data_buf_size); + + /* free any existing buffer */ + smi_data_buf_free(); + + /* set up new buffer for use */ + smi_data_buf = buf; + smi_data_buf_handle = handle; + smi_data_buf_phys_addr = (u32) virt_to_phys(buf); + smi_data_buf_size = size; + + dev_dbg(&dcdbas_pdev->dev, "%s: phys: %x size: %lu\n", + __func__, smi_data_buf_phys_addr, smi_data_buf_size); + + return 0; +} + +static ssize_t smi_data_buf_phys_addr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%x\n", smi_data_buf_phys_addr); +} + +static ssize_t smi_data_buf_size_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", smi_data_buf_size); +} + +static ssize_t smi_data_buf_size_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long buf_size; + ssize_t ret; + + buf_size = simple_strtoul(buf, NULL, 10); + + /* make sure SMI data buffer is at least buf_size */ + mutex_lock(&smi_data_lock); + ret = smi_data_buf_realloc(buf_size); + mutex_unlock(&smi_data_lock); + if (ret) + return ret; + + return count; +} + +static ssize_t smi_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + ssize_t ret; + + mutex_lock(&smi_data_lock); + ret = memory_read_from_buffer(buf, count, &pos, smi_data_buf, + smi_data_buf_size); + mutex_unlock(&smi_data_lock); + return ret; +} + +static ssize_t smi_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t count) +{ + ssize_t ret; + + if ((pos + count) > max_smi_data_buf_size) + return -EINVAL; + + mutex_lock(&smi_data_lock); + + ret = smi_data_buf_realloc(pos + count); + if (ret) + goto out; + + memcpy(smi_data_buf + pos, buf, count); + ret = count; +out: + mutex_unlock(&smi_data_lock); + return ret; +} + +static ssize_t host_control_action_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_action); +} + +static ssize_t host_control_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t ret; + + /* make sure buffer is available for host control command */ + mutex_lock(&smi_data_lock); + ret = smi_data_buf_realloc(sizeof(struct apm_cmd)); + mutex_unlock(&smi_data_lock); + if (ret) + return ret; + + host_control_action = simple_strtoul(buf, NULL, 10); + return count; +} + +static ssize_t host_control_smi_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_smi_type); +} + +static ssize_t host_control_smi_type_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + host_control_smi_type = simple_strtoul(buf, NULL, 10); + return count; +} + +static ssize_t host_control_on_shutdown_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%u\n", host_control_on_shutdown); +} + +static ssize_t host_control_on_shutdown_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + host_control_on_shutdown = simple_strtoul(buf, NULL, 10); + return count; +} + +static int raise_smi(void *par) +{ + struct smi_cmd *smi_cmd = par; + + if (smp_processor_id() != 0) { + dev_dbg(&dcdbas_pdev->dev, "%s: failed to get CPU 0\n", + __func__); + return -EBUSY; + } + + /* generate SMI */ + /* inb to force posted write through and make SMI happen now */ + asm volatile ( + "outb %b0,%w1\n" + "inb %w1" + : /* no output args */ + : "a" (smi_cmd->command_code), + "d" (smi_cmd->command_address), + "b" (smi_cmd->ebx), + "c" (smi_cmd->ecx) + : "memory" + ); + + return 0; +} +/** + * dcdbas_smi_request: generate SMI request + * + * Called with smi_data_lock. + */ +int dcdbas_smi_request(struct smi_cmd *smi_cmd) +{ + int ret; + + if (smi_cmd->magic != SMI_CMD_MAGIC) { + dev_info(&dcdbas_pdev->dev, "%s: invalid magic value\n", + __func__); + return -EBADR; + } + + /* SMI requires CPU 0 */ + get_online_cpus(); + ret = smp_call_on_cpu(0, raise_smi, smi_cmd, true); + put_online_cpus(); + + return ret; +} + +/** + * smi_request_store: + * + * The valid values are: + * 0: zero SMI data buffer + * 1: generate calling interface SMI + * 2: generate raw SMI + * + * User application writes smi_cmd to smi_data before telling driver + * to generate SMI. + */ +static ssize_t smi_request_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct smi_cmd *smi_cmd; + unsigned long val = simple_strtoul(buf, NULL, 10); + ssize_t ret; + + mutex_lock(&smi_data_lock); + + if (smi_data_buf_size < sizeof(struct smi_cmd)) { + ret = -ENODEV; + goto out; + } + smi_cmd = (struct smi_cmd *)smi_data_buf; + + switch (val) { + case 2: + /* Raw SMI */ + ret = dcdbas_smi_request(smi_cmd); + if (!ret) + ret = count; + break; + case 1: + /* + * Calling Interface SMI + * + * Provide physical address of command buffer field within + * the struct smi_cmd to BIOS. + * + * Because the address that smi_cmd (smi_data_buf) points to + * will be from memremap() of a non-memory address if WSMT + * is present, we can't use virt_to_phys() on smi_cmd, so + * we have to use the physical address that was saved when + * the virtual address for smi_cmd was received. + */ + smi_cmd->ebx = smi_data_buf_phys_addr + + offsetof(struct smi_cmd, command_buffer); + ret = dcdbas_smi_request(smi_cmd); + if (!ret) + ret = count; + break; + case 0: + memset(smi_data_buf, 0, smi_data_buf_size); + ret = count; + break; + default: + ret = -EINVAL; + break; + } + +out: + mutex_unlock(&smi_data_lock); + return ret; +} +EXPORT_SYMBOL(dcdbas_smi_request); + +/** + * host_control_smi: generate host control SMI + * + * Caller must set up the host control command in smi_data_buf. + */ +static int host_control_smi(void) +{ + struct apm_cmd *apm_cmd; + u8 *data; + unsigned long flags; + u32 num_ticks; + s8 cmd_status; + u8 index; + + apm_cmd = (struct apm_cmd *)smi_data_buf; + apm_cmd->status = ESM_STATUS_CMD_UNSUCCESSFUL; + + switch (host_control_smi_type) { + case HC_SMITYPE_TYPE1: + spin_lock_irqsave(&rtc_lock, flags); + /* write SMI data buffer physical address */ + data = (u8 *)&smi_data_buf_phys_addr; + for (index = PE1300_CMOS_CMD_STRUCT_PTR; + index < (PE1300_CMOS_CMD_STRUCT_PTR + 4); + index++, data++) { + outb(index, + (CMOS_BASE_PORT + CMOS_PAGE2_INDEX_PORT_PIIX4)); + outb(*data, + (CMOS_BASE_PORT + CMOS_PAGE2_DATA_PORT_PIIX4)); + } + + /* first set status to -1 as called by spec */ + cmd_status = ESM_STATUS_CMD_UNSUCCESSFUL; + outb((u8) cmd_status, PCAT_APM_STATUS_PORT); + + /* generate SMM call */ + outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); + spin_unlock_irqrestore(&rtc_lock, flags); + + /* wait a few to see if it executed */ + num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; + while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) + == ESM_STATUS_CMD_UNSUCCESSFUL) { + num_ticks--; + if (num_ticks == EXPIRED_TIMER) + return -ETIME; + } + break; + + case HC_SMITYPE_TYPE2: + case HC_SMITYPE_TYPE3: + spin_lock_irqsave(&rtc_lock, flags); + /* write SMI data buffer physical address */ + data = (u8 *)&smi_data_buf_phys_addr; + for (index = PE1400_CMOS_CMD_STRUCT_PTR; + index < (PE1400_CMOS_CMD_STRUCT_PTR + 4); + index++, data++) { + outb(index, (CMOS_BASE_PORT + CMOS_PAGE1_INDEX_PORT)); + outb(*data, (CMOS_BASE_PORT + CMOS_PAGE1_DATA_PORT)); + } + + /* generate SMM call */ + if (host_control_smi_type == HC_SMITYPE_TYPE3) + outb(ESM_APM_CMD, PCAT_APM_CONTROL_PORT); + else + outb(ESM_APM_CMD, PE1400_APM_CONTROL_PORT); + + /* restore RTC index pointer since it was written to above */ + CMOS_READ(RTC_REG_C); + spin_unlock_irqrestore(&rtc_lock, flags); + + /* read control port back to serialize write */ + cmd_status = inb(PE1400_APM_CONTROL_PORT); + + /* wait a few to see if it executed */ + num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; + while (apm_cmd->status == ESM_STATUS_CMD_UNSUCCESSFUL) { + num_ticks--; + if (num_ticks == EXPIRED_TIMER) + return -ETIME; + } + break; + + default: + dev_dbg(&dcdbas_pdev->dev, "%s: invalid SMI type %u\n", + __func__, host_control_smi_type); + return -ENOSYS; + } + + return 0; +} + +/** + * dcdbas_host_control: initiate host control + * + * This function is called by the driver after the system has + * finished shutting down if the user application specified a + * host control action to perform on shutdown. It is safe to + * use smi_data_buf at this point because the system has finished + * shutting down and no userspace apps are running. + */ +static void dcdbas_host_control(void) +{ + struct apm_cmd *apm_cmd; + u8 action; + + if (host_control_action == HC_ACTION_NONE) + return; + + action = host_control_action; + host_control_action = HC_ACTION_NONE; + + if (!smi_data_buf) { + dev_dbg(&dcdbas_pdev->dev, "%s: no SMI buffer\n", __func__); + return; + } + + if (smi_data_buf_size < sizeof(struct apm_cmd)) { + dev_dbg(&dcdbas_pdev->dev, "%s: SMI buffer too small\n", + __func__); + return; + } + + apm_cmd = (struct apm_cmd *)smi_data_buf; + + /* power off takes precedence */ + if (action & HC_ACTION_HOST_CONTROL_POWEROFF) { + apm_cmd->command = ESM_APM_POWER_CYCLE; + apm_cmd->reserved = 0; + *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 0; + host_control_smi(); + } else if (action & HC_ACTION_HOST_CONTROL_POWERCYCLE) { + apm_cmd->command = ESM_APM_POWER_CYCLE; + apm_cmd->reserved = 0; + *((s16 *)&apm_cmd->parameters.shortreq.parm[0]) = (s16) 20; + host_control_smi(); + } +} + +/* WSMT */ + +static u8 checksum(u8 *buffer, u8 length) +{ + u8 sum = 0; + u8 *end = buffer + length; + + while (buffer < end) + sum += *buffer++; + return sum; +} + +static inline struct smm_eps_table *check_eps_table(u8 *addr) +{ + struct smm_eps_table *eps = (struct smm_eps_table *)addr; + + if (strncmp(eps->smm_comm_buff_anchor, SMM_EPS_SIG, 4) != 0) + return NULL; + + if (checksum(addr, eps->length) != 0) + return NULL; + + return eps; +} + +static int dcdbas_check_wsmt(void) +{ + const struct dmi_device *dev = NULL; + struct acpi_table_wsmt *wsmt = NULL; + struct smm_eps_table *eps = NULL; + u64 bios_buf_paddr; + u64 remap_size; + u8 *addr; + + acpi_get_table(ACPI_SIG_WSMT, 0, (struct acpi_table_header **)&wsmt); + if (!wsmt) + return 0; + + /* Check if WSMT ACPI table shows that protection is enabled */ + if (!(wsmt->protection_flags & ACPI_WSMT_FIXED_COMM_BUFFERS) || + !(wsmt->protection_flags & ACPI_WSMT_COMM_BUFFER_NESTED_PTR_PROTECTION)) + return 0; + + /* + * BIOS could provide the address/size of the protected buffer + * in an SMBIOS string or in an EPS structure in 0xFxxxx. + */ + + /* Check SMBIOS for buffer address */ + while ((dev = dmi_find_device(DMI_DEV_TYPE_OEM_STRING, NULL, dev))) + if (sscanf(dev->name, "30[%16llx;%8llx]", &bios_buf_paddr, + &remap_size) == 2) + goto remap; + + /* Scan for EPS (entry point structure) */ + for (addr = (u8 *)__va(0xf0000); + addr < (u8 *)__va(0x100000 - sizeof(struct smm_eps_table)); + addr += 16) { + eps = check_eps_table(addr); + if (eps) + break; + } + + if (!eps) { + dev_dbg(&dcdbas_pdev->dev, "found WSMT, but no firmware buffer found\n"); + return -ENODEV; + } + bios_buf_paddr = eps->smm_comm_buff_addr; + remap_size = eps->num_of_4k_pages * PAGE_SIZE; + +remap: + /* + * Get physical address of buffer and map to virtual address. + * Table gives size in 4K pages, regardless of actual system page size. + */ + if (upper_32_bits(bios_buf_paddr + 8)) { + dev_warn(&dcdbas_pdev->dev, "found WSMT, but buffer address is above 4GB\n"); + return -EINVAL; + } + /* + * Limit remap size to MAX_SMI_DATA_BUF_SIZE + 8 (since the first 8 + * bytes are used for a semaphore, not the data buffer itself). + */ + if (remap_size > MAX_SMI_DATA_BUF_SIZE + 8) + remap_size = MAX_SMI_DATA_BUF_SIZE + 8; + + bios_buffer = memremap(bios_buf_paddr, remap_size, MEMREMAP_WB); + if (!bios_buffer) { + dev_warn(&dcdbas_pdev->dev, "found WSMT, but failed to map buffer\n"); + return -ENOMEM; + } + + /* First 8 bytes is for a semaphore, not part of the smi_data_buf */ + smi_data_buf_phys_addr = bios_buf_paddr + 8; + smi_data_buf = bios_buffer + 8; + smi_data_buf_size = remap_size - 8; + max_smi_data_buf_size = smi_data_buf_size; + wsmt_enabled = true; + dev_info(&dcdbas_pdev->dev, + "WSMT found, using firmware-provided SMI buffer.\n"); + return 1; +} + +/** + * dcdbas_reboot_notify: handle reboot notification for host control + */ +static int dcdbas_reboot_notify(struct notifier_block *nb, unsigned long code, + void *unused) +{ + switch (code) { + case SYS_DOWN: + case SYS_HALT: + case SYS_POWER_OFF: + if (host_control_on_shutdown) { + /* firmware is going to perform host control action */ + printk(KERN_WARNING "Please wait for shutdown " + "action to complete...\n"); + dcdbas_host_control(); + } + break; + } + + return NOTIFY_DONE; +} + +static struct notifier_block dcdbas_reboot_nb = { + .notifier_call = dcdbas_reboot_notify, + .next = NULL, + .priority = INT_MIN +}; + +static DCDBAS_BIN_ATTR_RW(smi_data); + +static struct bin_attribute *dcdbas_bin_attrs[] = { + &bin_attr_smi_data, + NULL +}; + +static DCDBAS_DEV_ATTR_RW(smi_data_buf_size); +static DCDBAS_DEV_ATTR_RO(smi_data_buf_phys_addr); +static DCDBAS_DEV_ATTR_WO(smi_request); +static DCDBAS_DEV_ATTR_RW(host_control_action); +static DCDBAS_DEV_ATTR_RW(host_control_smi_type); +static DCDBAS_DEV_ATTR_RW(host_control_on_shutdown); + +static struct attribute *dcdbas_dev_attrs[] = { + &dev_attr_smi_data_buf_size.attr, + &dev_attr_smi_data_buf_phys_addr.attr, + &dev_attr_smi_request.attr, + &dev_attr_host_control_action.attr, + &dev_attr_host_control_smi_type.attr, + &dev_attr_host_control_on_shutdown.attr, + NULL +}; + +static const struct attribute_group dcdbas_attr_group = { + .attrs = dcdbas_dev_attrs, + .bin_attrs = dcdbas_bin_attrs, +}; + +static int dcdbas_probe(struct platform_device *dev) +{ + int error; + + host_control_action = HC_ACTION_NONE; + host_control_smi_type = HC_SMITYPE_NONE; + + dcdbas_pdev = dev; + + /* Check if ACPI WSMT table specifies protected SMI buffer address */ + error = dcdbas_check_wsmt(); + if (error < 0) + return error; + + /* + * BIOS SMI calls require buffer addresses be in 32-bit address space. + * This is done by setting the DMA mask below. + */ + error = dma_set_coherent_mask(&dcdbas_pdev->dev, DMA_BIT_MASK(32)); + if (error) + return error; + + error = sysfs_create_group(&dev->dev.kobj, &dcdbas_attr_group); + if (error) + return error; + + register_reboot_notifier(&dcdbas_reboot_nb); + + dev_info(&dev->dev, "%s (version %s)\n", + DRIVER_DESCRIPTION, DRIVER_VERSION); + + return 0; +} + +static int dcdbas_remove(struct platform_device *dev) +{ + unregister_reboot_notifier(&dcdbas_reboot_nb); + sysfs_remove_group(&dev->dev.kobj, &dcdbas_attr_group); + + return 0; +} + +static struct platform_driver dcdbas_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = dcdbas_probe, + .remove = dcdbas_remove, +}; + +static const struct platform_device_info dcdbas_dev_info __initconst = { + .name = DRIVER_NAME, + .id = -1, + .dma_mask = DMA_BIT_MASK(32), +}; + +static struct platform_device *dcdbas_pdev_reg; + +/** + * dcdbas_init: initialize driver + */ +static int __init dcdbas_init(void) +{ + int error; + + error = platform_driver_register(&dcdbas_driver); + if (error) + return error; + + dcdbas_pdev_reg = platform_device_register_full(&dcdbas_dev_info); + if (IS_ERR(dcdbas_pdev_reg)) { + error = PTR_ERR(dcdbas_pdev_reg); + goto err_unregister_driver; + } + + return 0; + + err_unregister_driver: + platform_driver_unregister(&dcdbas_driver); + return error; +} + +/** + * dcdbas_exit: perform driver cleanup + */ +static void __exit dcdbas_exit(void) +{ + /* + * make sure functions that use dcdbas_pdev are called + * before platform_device_unregister + */ + unregister_reboot_notifier(&dcdbas_reboot_nb); + + /* + * We have to free the buffer here instead of dcdbas_remove + * because only in module exit function we can be sure that + * all sysfs attributes belonging to this module have been + * released. + */ + if (dcdbas_pdev) + smi_data_buf_free(); + if (bios_buffer) + memunmap(bios_buffer); + platform_device_unregister(dcdbas_pdev_reg); + platform_driver_unregister(&dcdbas_driver); +} + +subsys_initcall_sync(dcdbas_init); +module_exit(dcdbas_exit); + +MODULE_DESCRIPTION(DRIVER_DESCRIPTION " (version " DRIVER_VERSION ")"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR("Dell Inc."); +MODULE_LICENSE("GPL"); +/* Any System or BIOS claiming to be by Dell */ +MODULE_ALIAS("dmi:*:[bs]vnD[Ee][Ll][Ll]*:*"); diff --git a/drivers/platform/x86/dell/dcdbas.h b/drivers/platform/x86/dell/dcdbas.h new file mode 100644 index 000000000000..c3cca5433525 --- /dev/null +++ b/drivers/platform/x86/dell/dcdbas.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * dcdbas.h: Definitions for Dell Systems Management Base driver + * + * Copyright (C) 1995-2005 Dell Inc. + */ + +#ifndef _DCDBAS_H_ +#define _DCDBAS_H_ + +#include +#include +#include + +#define MAX_SMI_DATA_BUF_SIZE (256 * 1024) + +#define HC_ACTION_NONE (0) +#define HC_ACTION_HOST_CONTROL_POWEROFF BIT(1) +#define HC_ACTION_HOST_CONTROL_POWERCYCLE BIT(2) + +#define HC_SMITYPE_NONE (0) +#define HC_SMITYPE_TYPE1 (1) +#define HC_SMITYPE_TYPE2 (2) +#define HC_SMITYPE_TYPE3 (3) + +#define ESM_APM_CMD (0x0A0) +#define ESM_APM_POWER_CYCLE (0x10) +#define ESM_STATUS_CMD_UNSUCCESSFUL (-1) + +#define CMOS_BASE_PORT (0x070) +#define CMOS_PAGE1_INDEX_PORT (0) +#define CMOS_PAGE1_DATA_PORT (1) +#define CMOS_PAGE2_INDEX_PORT_PIIX4 (2) +#define CMOS_PAGE2_DATA_PORT_PIIX4 (3) +#define PE1400_APM_CONTROL_PORT (0x0B0) +#define PCAT_APM_CONTROL_PORT (0x0B2) +#define PCAT_APM_STATUS_PORT (0x0B3) +#define PE1300_CMOS_CMD_STRUCT_PTR (0x38) +#define PE1400_CMOS_CMD_STRUCT_PTR (0x70) + +#define MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN (14) +#define MAX_SYSMGMT_LONGCMD_SGENTRY_NUM (16) + +#define TIMEOUT_USEC_SHORT_SEMA_BLOCKING (10000) +#define EXPIRED_TIMER (0) + +#define SMI_CMD_MAGIC (0x534D4931) +#define SMM_EPS_SIG "$SCB" + +#define DCDBAS_DEV_ATTR_RW(_name) \ + DEVICE_ATTR(_name,0600,_name##_show,_name##_store); + +#define DCDBAS_DEV_ATTR_RO(_name) \ + DEVICE_ATTR(_name,0400,_name##_show,NULL); + +#define DCDBAS_DEV_ATTR_WO(_name) \ + DEVICE_ATTR(_name,0200,NULL,_name##_store); + +#define DCDBAS_BIN_ATTR_RW(_name) \ +struct bin_attribute bin_attr_##_name = { \ + .attr = { .name = __stringify(_name), \ + .mode = 0600 }, \ + .read = _name##_read, \ + .write = _name##_write, \ +} + +struct smi_cmd { + __u32 magic; + __u32 ebx; + __u32 ecx; + __u16 command_address; + __u8 command_code; + __u8 reserved; + __u8 command_buffer[1]; +} __attribute__ ((packed)); + +struct apm_cmd { + __u8 command; + __s8 status; + __u16 reserved; + union { + struct { + __u8 parm[MAX_SYSMGMT_SHORTCMD_PARMBUF_LEN]; + } __attribute__ ((packed)) shortreq; + + struct { + __u16 num_sg_entries; + struct { + __u32 size; + __u64 addr; + } __attribute__ ((packed)) + sglist[MAX_SYSMGMT_LONGCMD_SGENTRY_NUM]; + } __attribute__ ((packed)) longreq; + } __attribute__ ((packed)) parameters; +} __attribute__ ((packed)); + +int dcdbas_smi_request(struct smi_cmd *smi_cmd); + +struct smm_eps_table { + char smm_comm_buff_anchor[4]; + u8 length; + u8 checksum; + u8 version; + u64 smm_comm_buff_addr; + u64 num_of_4k_pages; +} __packed; + +#endif /* _DCDBAS_H_ */ + diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c new file mode 100644 index 000000000000..70edc5bb3a14 --- /dev/null +++ b/drivers/platform/x86/dell/dell-laptop.c @@ -0,0 +1,2303 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Dell laptop extras + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-rbtn.h" +#include "dell-smbios.h" + +struct quirk_entry { + bool touchpad_led; + bool kbd_led_not_present; + bool kbd_led_levels_off_1; + bool kbd_missing_ac_tag; + + bool needs_kbd_timeouts; + /* + * Ordered list of timeouts expressed in seconds. + * The list must end with -1 + */ + int kbd_timeouts[]; +}; + +static struct quirk_entry *quirks; + +static struct quirk_entry quirk_dell_vostro_v130 = { + .touchpad_led = true, +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +/* + * These values come from Windows utility provided by Dell. If any other value + * is used then BIOS silently set timeout to 0 without any error message. + */ +static struct quirk_entry quirk_dell_xps13_9333 = { + .needs_kbd_timeouts = true, + .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, +}; + +static struct quirk_entry quirk_dell_xps13_9370 = { + .kbd_missing_ac_tag = true, +}; + +static struct quirk_entry quirk_dell_latitude_e6410 = { + .kbd_led_levels_off_1 = true, +}; + +static struct quirk_entry quirk_dell_inspiron_1012 = { + .kbd_led_not_present = true, +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-laptop", + } +}; + +static struct platform_device *platform_device; +static struct backlight_device *dell_backlight_device; +static struct rfkill *wifi_rfkill; +static struct rfkill *bluetooth_rfkill; +static struct rfkill *wwan_rfkill; +static bool force_rfkill; + +module_param(force_rfkill, bool, 0444); +MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); + +static const struct dmi_system_id dell_device_table[] __initconst = { + { + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "30"), /*Tablet*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /*Convertible*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "32"), /*Detachable*/ + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +static const struct dmi_system_id dell_quirks[] __initconst = { + { + .callback = dmi_matched, + .ident = "Dell Vostro V130", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V130"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro V131", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3350", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3350"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3555", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3555"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron N311z", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron N311z"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3460", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3460"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3560", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro 3560"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro 3450", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Dell System Vostro 3450"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5420"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5520"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 5720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5720"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7420", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7420"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7520"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 7720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), + }, + .driver_data = &quirk_dell_vostro_v130, + }, + { + .callback = dmi_matched, + .ident = "Dell XPS13 9333", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), + }, + .driver_data = &quirk_dell_xps13_9333, + }, + { + .callback = dmi_matched, + .ident = "Dell XPS 13 9370", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "XPS 13 9370"), + }, + .driver_data = &quirk_dell_xps13_9370, + }, + { + .callback = dmi_matched, + .ident = "Dell Latitude E6410", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6410"), + }, + .driver_data = &quirk_dell_latitude_e6410, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 1012", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), + }, + .driver_data = &quirk_dell_inspiron_1012, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 1018", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1018"), + }, + .driver_data = &quirk_dell_inspiron_1012, + }, + { } +}; + +static void dell_fill_request(struct calling_interface_buffer *buffer, + u32 arg0, u32 arg1, u32 arg2, u32 arg3) +{ + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = arg0; + buffer->input[1] = arg1; + buffer->input[2] = arg2; + buffer->input[3] = arg3; +} + +static int dell_send_request(struct calling_interface_buffer *buffer, + u16 class, u16 select) +{ + int ret; + + buffer->cmd_class = class; + buffer->cmd_select = select; + ret = dell_smbios_call(buffer); + if (ret != 0) + return ret; + return dell_smbios_error(buffer->output[0]); +} + +/* + * Derived from information in smbios-wireless-ctl: + * + * cbSelect 17, Value 11 + * + * Return Wireless Info + * cbArg1, byte0 = 0x00 + * + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 Info bit flags: + * + * 0 Hardware switch supported (1) + * 1 WiFi locator supported (1) + * 2 WLAN supported (1) + * 3 Bluetooth (BT) supported (1) + * 4 WWAN supported (1) + * 5 Wireless KBD supported (1) + * 6 Uw b supported (1) + * 7 WiGig supported (1) + * 8 WLAN installed (1) + * 9 BT installed (1) + * 10 WWAN installed (1) + * 11 Uw b installed (1) + * 12 WiGig installed (1) + * 13-15 Reserved (0) + * 16 Hardware (HW) switch is On (1) + * 17 WLAN disabled (1) + * 18 BT disabled (1) + * 19 WWAN disabled (1) + * 20 Uw b disabled (1) + * 21 WiGig disabled (1) + * 20-31 Reserved (0) + * + * cbRes3 NVRAM size in bytes + * cbRes4, byte 0 NVRAM format version number + * + * + * Set QuickSet Radio Disable Flag + * cbArg1, byte0 = 0x01 + * cbArg1, byte1 + * Radio ID value: + * 0 Radio Status + * 1 WLAN ID + * 2 BT ID + * 3 WWAN ID + * 4 UWB ID + * 5 WIGIG ID + * cbArg1, byte2 Flag bits: + * 0 QuickSet disables radio (1) + * 1-7 Reserved (0) + * + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 QuickSet (QS) radio disable bit map: + * 0 QS disables WLAN + * 1 QS disables BT + * 2 QS disables WWAN + * 3 QS disables UWB + * 4 QS disables WIGIG + * 5-31 Reserved (0) + * + * Wireless Switch Configuration + * cbArg1, byte0 = 0x02 + * + * cbArg1, byte1 + * Subcommand: + * 0 Get config + * 1 Set config + * 2 Set WiFi locator enable/disable + * cbArg1,byte2 + * Switch settings (if byte 1==1): + * 0 WLAN sw itch control (1) + * 1 BT sw itch control (1) + * 2 WWAN sw itch control (1) + * 3 UWB sw itch control (1) + * 4 WiGig sw itch control (1) + * 5-7 Reserved (0) + * cbArg1, byte2 Enable bits (if byte 1==2): + * 0 Enable WiFi locator (1) + * + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 QuickSet radio disable bit map: + * 0 WLAN controlled by sw itch (1) + * 1 BT controlled by sw itch (1) + * 2 WWAN controlled by sw itch (1) + * 3 UWB controlled by sw itch (1) + * 4 WiGig controlled by sw itch (1) + * 5-6 Reserved (0) + * 7 Wireless sw itch config locked (1) + * 8 WiFi locator enabled (1) + * 9-14 Reserved (0) + * 15 WiFi locator setting locked (1) + * 16-31 Reserved (0) + * + * Read Local Config Data (LCD) + * cbArg1, byte0 = 0x10 + * cbArg1, byte1 NVRAM index low byte + * cbArg1, byte2 NVRAM index high byte + * cbRes1 Standard return codes (0, -1, -2) + * cbRes2 4 bytes read from LCD[index] + * cbRes3 4 bytes read from LCD[index+4] + * cbRes4 4 bytes read from LCD[index+8] + * + * Write Local Config Data (LCD) + * cbArg1, byte0 = 0x11 + * cbArg1, byte1 NVRAM index low byte + * cbArg1, byte2 NVRAM index high byte + * cbArg2 4 bytes to w rite at LCD[index] + * cbArg3 4 bytes to w rite at LCD[index+4] + * cbArg4 4 bytes to w rite at LCD[index+8] + * cbRes1 Standard return codes (0, -1, -2) + * + * Populate Local Config Data from NVRAM + * cbArg1, byte0 = 0x12 + * cbRes1 Standard return codes (0, -1, -2) + * + * Commit Local Config Data to NVRAM + * cbArg1, byte0 = 0x13 + * cbRes1 Standard return codes (0, -1, -2) + */ + +static int dell_rfkill_set(void *data, bool blocked) +{ + int disable = blocked ? 1 : 0; + unsigned long radio = (unsigned long)data; + int hwswitch_bit = (unsigned long)data - 1; + struct calling_interface_buffer buffer; + int hwswitch; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; + status = buffer.output[1]; + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; + hwswitch = buffer.output[1]; + + /* If the hardware switch controls this radio, and the hardware + switch is disabled, always disable the radio */ + if (ret == 0 && (hwswitch & BIT(hwswitch_bit)) && + (status & BIT(0)) && !(status & BIT(16))) + disable = 1; + + dell_fill_request(&buffer, 1 | (radio<<8) | (disable << 16), 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + return ret; +} + +static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, + int status) +{ + if (status & BIT(0)) { + /* Has hw-switch, sync sw_state to BIOS */ + struct calling_interface_buffer buffer; + int block = rfkill_blocked(rfkill); + dell_fill_request(&buffer, + 1 | (radio << 8) | (block << 16), 0, 0, 0); + dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + } else { + /* No hw-switch, sync BIOS state to sw_state */ + rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); + } +} + +static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, + int status, int hwswitch) +{ + if (hwswitch & (BIT(radio - 1))) + rfkill_set_hw_state(rfkill, !(status & BIT(16))); +} + +static void dell_rfkill_query(struct rfkill *rfkill, void *data) +{ + int radio = ((unsigned long)data & 0xF); + struct calling_interface_buffer buffer; + int hwswitch; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; + + if (ret != 0 || !(status & BIT(0))) { + return; + } + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + hwswitch = buffer.output[1]; + + if (ret != 0) + return; + + dell_rfkill_update_hw_state(rfkill, radio, status, hwswitch); +} + +static const struct rfkill_ops dell_rfkill_ops = { + .set_block = dell_rfkill_set, + .query = dell_rfkill_query, +}; + +static struct dentry *dell_laptop_dir; + +static int dell_debugfs_show(struct seq_file *s, void *data) +{ + struct calling_interface_buffer buffer; + int hwswitch_state; + int hwswitch_ret; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (ret) + return ret; + status = buffer.output[1]; + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + hwswitch_ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + if (hwswitch_ret) + return hwswitch_ret; + hwswitch_state = buffer.output[1]; + + seq_printf(s, "return:\t%d\n", ret); + seq_printf(s, "status:\t0x%X\n", status); + seq_printf(s, "Bit 0 : Hardware switch supported: %lu\n", + status & BIT(0)); + seq_printf(s, "Bit 1 : Wifi locator supported: %lu\n", + (status & BIT(1)) >> 1); + seq_printf(s, "Bit 2 : Wifi is supported: %lu\n", + (status & BIT(2)) >> 2); + seq_printf(s, "Bit 3 : Bluetooth is supported: %lu\n", + (status & BIT(3)) >> 3); + seq_printf(s, "Bit 4 : WWAN is supported: %lu\n", + (status & BIT(4)) >> 4); + seq_printf(s, "Bit 5 : Wireless keyboard supported: %lu\n", + (status & BIT(5)) >> 5); + seq_printf(s, "Bit 6 : UWB supported: %lu\n", + (status & BIT(6)) >> 6); + seq_printf(s, "Bit 7 : WiGig supported: %lu\n", + (status & BIT(7)) >> 7); + seq_printf(s, "Bit 8 : Wifi is installed: %lu\n", + (status & BIT(8)) >> 8); + seq_printf(s, "Bit 9 : Bluetooth is installed: %lu\n", + (status & BIT(9)) >> 9); + seq_printf(s, "Bit 10: WWAN is installed: %lu\n", + (status & BIT(10)) >> 10); + seq_printf(s, "Bit 11: UWB installed: %lu\n", + (status & BIT(11)) >> 11); + seq_printf(s, "Bit 12: WiGig installed: %lu\n", + (status & BIT(12)) >> 12); + + seq_printf(s, "Bit 16: Hardware switch is on: %lu\n", + (status & BIT(16)) >> 16); + seq_printf(s, "Bit 17: Wifi is blocked: %lu\n", + (status & BIT(17)) >> 17); + seq_printf(s, "Bit 18: Bluetooth is blocked: %lu\n", + (status & BIT(18)) >> 18); + seq_printf(s, "Bit 19: WWAN is blocked: %lu\n", + (status & BIT(19)) >> 19); + seq_printf(s, "Bit 20: UWB is blocked: %lu\n", + (status & BIT(20)) >> 20); + seq_printf(s, "Bit 21: WiGig is blocked: %lu\n", + (status & BIT(21)) >> 21); + + seq_printf(s, "\nhwswitch_return:\t%d\n", hwswitch_ret); + seq_printf(s, "hwswitch_state:\t0x%X\n", hwswitch_state); + seq_printf(s, "Bit 0 : Wifi controlled by switch: %lu\n", + hwswitch_state & BIT(0)); + seq_printf(s, "Bit 1 : Bluetooth controlled by switch: %lu\n", + (hwswitch_state & BIT(1)) >> 1); + seq_printf(s, "Bit 2 : WWAN controlled by switch: %lu\n", + (hwswitch_state & BIT(2)) >> 2); + seq_printf(s, "Bit 3 : UWB controlled by switch: %lu\n", + (hwswitch_state & BIT(3)) >> 3); + seq_printf(s, "Bit 4 : WiGig controlled by switch: %lu\n", + (hwswitch_state & BIT(4)) >> 4); + seq_printf(s, "Bit 7 : Wireless switch config locked: %lu\n", + (hwswitch_state & BIT(7)) >> 7); + seq_printf(s, "Bit 8 : Wifi locator enabled: %lu\n", + (hwswitch_state & BIT(8)) >> 8); + seq_printf(s, "Bit 15: Wifi locator setting locked: %lu\n", + (hwswitch_state & BIT(15)) >> 15); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(dell_debugfs); + +static void dell_update_rfkill(struct work_struct *ignored) +{ + struct calling_interface_buffer buffer; + int hwswitch = 0; + int status; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; + + if (ret != 0) + return; + + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + + if (ret == 0 && (status & BIT(0))) + hwswitch = buffer.output[1]; + + if (wifi_rfkill) { + dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch); + dell_rfkill_update_sw_state(wifi_rfkill, 1, status); + } + if (bluetooth_rfkill) { + dell_rfkill_update_hw_state(bluetooth_rfkill, 2, status, + hwswitch); + dell_rfkill_update_sw_state(bluetooth_rfkill, 2, status); + } + if (wwan_rfkill) { + dell_rfkill_update_hw_state(wwan_rfkill, 3, status, hwswitch); + dell_rfkill_update_sw_state(wwan_rfkill, 3, status); + } +} +static DECLARE_DELAYED_WORK(dell_rfkill_work, dell_update_rfkill); + +static bool dell_laptop_i8042_filter(unsigned char data, unsigned char str, + struct serio *port) +{ + static bool extended; + + if (str & I8042_STR_AUXDATA) + return false; + + if (unlikely(data == 0xe0)) { + extended = true; + return false; + } else if (unlikely(extended)) { + switch (data) { + case 0x8: + schedule_delayed_work(&dell_rfkill_work, + round_jiffies_relative(HZ / 4)); + break; + } + extended = false; + } + + return false; +} + +static int (*dell_rbtn_notifier_register_func)(struct notifier_block *); +static int (*dell_rbtn_notifier_unregister_func)(struct notifier_block *); + +static int dell_laptop_rbtn_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + schedule_delayed_work(&dell_rfkill_work, 0); + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_rbtn_notifier = { + .notifier_call = dell_laptop_rbtn_notifier_call, +}; + +static int __init dell_setup_rfkill(void) +{ + struct calling_interface_buffer buffer; + int status, ret, whitelisted; + const char *product; + + /* + * rfkill support causes trouble on various models, mostly Inspirons. + * So we whitelist certain series, and don't support rfkill on others. + */ + whitelisted = 0; + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product && (strncmp(product, "Latitude", 8) == 0 || + strncmp(product, "Precision", 9) == 0)) + whitelisted = 1; + if (!force_rfkill && !whitelisted) + return 0; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; + + /* dell wireless info smbios call is not supported */ + if (ret != 0) + return 0; + + /* rfkill is only tested on laptops with a hwswitch */ + if (!(status & BIT(0)) && !force_rfkill) + return 0; + + if ((status & (1<<2|1<<8)) == (1<<2|1<<8)) { + wifi_rfkill = rfkill_alloc("dell-wifi", &platform_device->dev, + RFKILL_TYPE_WLAN, + &dell_rfkill_ops, (void *) 1); + if (!wifi_rfkill) { + ret = -ENOMEM; + goto err_wifi; + } + ret = rfkill_register(wifi_rfkill); + if (ret) + goto err_wifi; + } + + if ((status & (1<<3|1<<9)) == (1<<3|1<<9)) { + bluetooth_rfkill = rfkill_alloc("dell-bluetooth", + &platform_device->dev, + RFKILL_TYPE_BLUETOOTH, + &dell_rfkill_ops, (void *) 2); + if (!bluetooth_rfkill) { + ret = -ENOMEM; + goto err_bluetooth; + } + ret = rfkill_register(bluetooth_rfkill); + if (ret) + goto err_bluetooth; + } + + if ((status & (1<<4|1<<10)) == (1<<4|1<<10)) { + wwan_rfkill = rfkill_alloc("dell-wwan", + &platform_device->dev, + RFKILL_TYPE_WWAN, + &dell_rfkill_ops, (void *) 3); + if (!wwan_rfkill) { + ret = -ENOMEM; + goto err_wwan; + } + ret = rfkill_register(wwan_rfkill); + if (ret) + goto err_wwan; + } + + /* + * Dell Airplane Mode Switch driver (dell-rbtn) supports ACPI devices + * which can receive events from HW slider switch. + * + * Dell SMBIOS on whitelisted models supports controlling radio devices + * but does not support receiving HW button switch events. We can use + * i8042 filter hook function to receive keyboard data and handle + * keycode for HW button. + * + * So if it is possible we will use Dell Airplane Mode Switch ACPI + * driver for receiving HW events and Dell SMBIOS for setting rfkill + * states. If ACPI driver or device is not available we will fallback to + * i8042 filter hook function. + * + * To prevent duplicate rfkill devices which control and do same thing, + * dell-rbtn driver will automatically remove its own rfkill devices + * once function dell_rbtn_notifier_register() is called. + */ + + dell_rbtn_notifier_register_func = + symbol_request(dell_rbtn_notifier_register); + if (dell_rbtn_notifier_register_func) { + dell_rbtn_notifier_unregister_func = + symbol_request(dell_rbtn_notifier_unregister); + if (!dell_rbtn_notifier_unregister_func) { + symbol_put(dell_rbtn_notifier_register); + dell_rbtn_notifier_register_func = NULL; + } + } + + if (dell_rbtn_notifier_register_func) { + ret = dell_rbtn_notifier_register_func( + &dell_laptop_rbtn_notifier); + symbol_put(dell_rbtn_notifier_register); + dell_rbtn_notifier_register_func = NULL; + if (ret != 0) { + symbol_put(dell_rbtn_notifier_unregister); + dell_rbtn_notifier_unregister_func = NULL; + } + } else { + pr_info("Symbols from dell-rbtn acpi driver are not available\n"); + ret = -ENODEV; + } + + if (ret == 0) { + pr_info("Using dell-rbtn acpi driver for receiving events\n"); + } else if (ret != -ENODEV) { + pr_warn("Unable to register dell rbtn notifier\n"); + goto err_filter; + } else { + ret = i8042_install_filter(dell_laptop_i8042_filter); + if (ret) { + pr_warn("Unable to install key filter\n"); + goto err_filter; + } + pr_info("Using i8042 filter function for receiving events\n"); + } + + return 0; +err_filter: + if (wwan_rfkill) + rfkill_unregister(wwan_rfkill); +err_wwan: + rfkill_destroy(wwan_rfkill); + if (bluetooth_rfkill) + rfkill_unregister(bluetooth_rfkill); +err_bluetooth: + rfkill_destroy(bluetooth_rfkill); + if (wifi_rfkill) + rfkill_unregister(wifi_rfkill); +err_wifi: + rfkill_destroy(wifi_rfkill); + + return ret; +} + +static void dell_cleanup_rfkill(void) +{ + if (dell_rbtn_notifier_unregister_func) { + dell_rbtn_notifier_unregister_func(&dell_laptop_rbtn_notifier); + symbol_put(dell_rbtn_notifier_unregister); + dell_rbtn_notifier_unregister_func = NULL; + } else { + i8042_remove_filter(dell_laptop_i8042_filter); + } + cancel_delayed_work_sync(&dell_rfkill_work); + if (wifi_rfkill) { + rfkill_unregister(wifi_rfkill); + rfkill_destroy(wifi_rfkill); + } + if (bluetooth_rfkill) { + rfkill_unregister(bluetooth_rfkill); + rfkill_destroy(bluetooth_rfkill); + } + if (wwan_rfkill) { + rfkill_unregister(wwan_rfkill); + rfkill_destroy(wwan_rfkill); + } +} + +static int dell_send_intensity(struct backlight_device *bd) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + + token = dell_smbios_find_token(BRIGHTNESS_TOKEN); + if (!token) + return -ENODEV; + + dell_fill_request(&buffer, + token->location, bd->props.brightness, 0, 0); + if (power_supply_is_system_supplied() > 0) + ret = dell_send_request(&buffer, + CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); + else + ret = dell_send_request(&buffer, + CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); + + return ret; +} + +static int dell_get_intensity(struct backlight_device *bd) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + + token = dell_smbios_find_token(BRIGHTNESS_TOKEN); + if (!token) + return -ENODEV; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + if (power_supply_is_system_supplied() > 0) + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_AC); + else + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_BAT); + + if (ret == 0) + ret = buffer.output[1]; + + return ret; +} + +static const struct backlight_ops dell_ops = { + .get_brightness = dell_get_intensity, + .update_status = dell_send_intensity, +}; + +static void touchpad_led_on(void) +{ + int command = 0x97; + char data = 1; + i8042_command(&data, command | 1 << 12); +} + +static void touchpad_led_off(void) +{ + int command = 0x97; + char data = 2; + i8042_command(&data, command | 1 << 12); +} + +static void touchpad_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value > 0) + touchpad_led_on(); + else + touchpad_led_off(); +} + +static struct led_classdev touchpad_led = { + .name = "dell-laptop::touchpad", + .brightness_set = touchpad_led_set, + .flags = LED_CORE_SUSPENDRESUME, +}; + +static int __init touchpad_led_init(struct device *dev) +{ + return led_classdev_register(dev, &touchpad_led); +} + +static void touchpad_led_exit(void) +{ + led_classdev_unregister(&touchpad_led); +} + +/* + * Derived from information in smbios-keyboard-ctl: + * + * cbClass 4 + * cbSelect 11 + * Keyboard illumination + * cbArg1 determines the function to be performed + * + * cbArg1 0x0 = Get Feature Information + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of user-selectable modes + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * cbRES2, byte2 Reserved for future use + * cbRES2, byte3 Keyboard illumination type + * 0 Reserved + * 1 Tasklight + * 2 Backlight + * 3-255 Reserved for future use + * cbRES3, byte0 Supported auto keyboard illumination trigger bitmap. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES3, byte1 Supported timeout unit bitmap + * bit 0 Seconds + * bit 1 Minutes + * bit 2 Hours + * bit 3 Days + * bits 4-7 Reserved for future use + * cbRES3, byte2 Number of keyboard light brightness levels + * cbRES4, byte0 Maximum acceptable seconds value (0 if seconds not supported). + * cbRES4, byte1 Maximum acceptable minutes value (0 if minutes not supported). + * cbRES4, byte2 Maximum acceptable hours value (0 if hours not supported). + * cbRES4, byte3 Maximum acceptable days value (0 if days not supported) + * + * cbArg1 0x1 = Get Current State + * cbRES1 Standard return codes (0, -1, -2) + * cbRES2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbRES2, byte2 Currently active auto keyboard illumination triggers. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbRES2, byte3 Current Timeout on battery + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte + * are set upon return from the [Get feature information] call. + * cbRES3, byte0 Current setting of ALS value that turns the light on or off. + * cbRES3, byte1 Current ALS reading + * cbRES3, byte2 Current keyboard light level. + * cbRES3, byte3 Current timeout on AC Power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * Bits 5:0 Timeout value (0-63) in sec/min/hr/day + * NOTE: A value of 0 means always on (no timeout) if any bits of RES3 byte2 + * are set upon return from the upon return from the [Get Feature information] call. + * + * cbArg1 0x2 = Set New State + * cbRES1 Standard return codes (0, -1, -2) + * cbArg2, word0 Bitmap of current mode state + * bit 0 Always off (All systems) + * bit 1 Always on (Travis ATG, Siberia) + * bit 2 Auto: ALS-based On; ALS-based Off (Travis ATG) + * bit 3 Auto: ALS- and input-activity-based On; input-activity based Off + * bit 4 Auto: Input-activity-based On; input-activity based Off + * bit 5 Auto: Input-activity-based On (illumination level 25%); input-activity based Off + * bit 6 Auto: Input-activity-based On (illumination level 50%); input-activity based Off + * bit 7 Auto: Input-activity-based On (illumination level 75%); input-activity based Off + * bit 8 Auto: Input-activity-based On (illumination level 100%); input-activity based Off + * bits 9-15 Reserved for future use + * Note: Only One bit can be set + * cbArg2, byte2 Desired auto keyboard illumination triggers. Must remain inactive to allow + * keyboard to turn off automatically. + * bit 0 Any keystroke + * bit 1 Touchpad activity + * bit 2 Pointing stick + * bit 3 Any mouse + * bits 4-7 Reserved for future use + * cbArg2, byte3 Desired Timeout on battery + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + * cbArg3, byte0 Desired setting of ALS value that turns the light on or off. + * cbArg3, byte2 Desired keyboard light level. + * cbArg3, byte3 Desired Timeout on AC power + * bits 7:6 Timeout units indicator: + * 00b Seconds + * 01b Minutes + * 10b Hours + * 11b Days + * bits 5:0 Timeout value (0-63) in sec/min/hr/day + */ + + +enum kbd_timeout_unit { + KBD_TIMEOUT_SECONDS = 0, + KBD_TIMEOUT_MINUTES, + KBD_TIMEOUT_HOURS, + KBD_TIMEOUT_DAYS, +}; + +enum kbd_mode_bit { + KBD_MODE_BIT_OFF = 0, + KBD_MODE_BIT_ON, + KBD_MODE_BIT_ALS, + KBD_MODE_BIT_TRIGGER_ALS, + KBD_MODE_BIT_TRIGGER, + KBD_MODE_BIT_TRIGGER_25, + KBD_MODE_BIT_TRIGGER_50, + KBD_MODE_BIT_TRIGGER_75, + KBD_MODE_BIT_TRIGGER_100, +}; + +#define kbd_is_als_mode_bit(bit) \ + ((bit) == KBD_MODE_BIT_ALS || (bit) == KBD_MODE_BIT_TRIGGER_ALS) +#define kbd_is_trigger_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_ALS && (bit) <= KBD_MODE_BIT_TRIGGER_100) +#define kbd_is_level_mode_bit(bit) \ + ((bit) >= KBD_MODE_BIT_TRIGGER_25 && (bit) <= KBD_MODE_BIT_TRIGGER_100) + +struct kbd_info { + u16 modes; + u8 type; + u8 triggers; + u8 levels; + u8 seconds; + u8 minutes; + u8 hours; + u8 days; +}; + +struct kbd_state { + u8 mode_bit; + u8 triggers; + u8 timeout_value; + u8 timeout_unit; + u8 timeout_value_ac; + u8 timeout_unit_ac; + u8 als_setting; + u8 als_value; + u8 level; +}; + +static const int kbd_tokens[] = { + KBD_LED_OFF_TOKEN, + KBD_LED_AUTO_25_TOKEN, + KBD_LED_AUTO_50_TOKEN, + KBD_LED_AUTO_75_TOKEN, + KBD_LED_AUTO_100_TOKEN, + KBD_LED_ON_TOKEN, +}; + +static u16 kbd_token_bits; + +static struct kbd_info kbd_info; +static bool kbd_als_supported; +static bool kbd_triggers_supported; +static bool kbd_timeout_ac_supported; + +static u8 kbd_mode_levels[16]; +static int kbd_mode_levels_count; + +static u8 kbd_previous_level; +static u8 kbd_previous_mode_bit; + +static bool kbd_led_present; +static DEFINE_MUTEX(kbd_led_mutex); +static enum led_brightness kbd_led_level; + +/* + * NOTE: there are three ways to set the keyboard backlight level. + * First, via kbd_state.mode_bit (assigning KBD_MODE_BIT_TRIGGER_* value). + * Second, via kbd_state.level (assigning numerical value <= kbd_info.levels). + * Third, via SMBIOS tokens (KBD_LED_* in kbd_tokens) + * + * There are laptops which support only one of these methods. If we want to + * support as many machines as possible we need to implement all three methods. + * The first two methods use the kbd_state structure. The third uses SMBIOS + * tokens. If kbd_info.levels == 0, the machine does not support setting the + * keyboard backlight level via kbd_state.level. + */ + +static int kbd_get_info(struct kbd_info *info) +{ + struct calling_interface_buffer buffer; + u8 units; + int ret; + + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + if (ret) + return ret; + + info->modes = buffer.output[1] & 0xFFFF; + info->type = (buffer.output[1] >> 24) & 0xFF; + info->triggers = buffer.output[2] & 0xFF; + units = (buffer.output[2] >> 8) & 0xFF; + info->levels = (buffer.output[2] >> 16) & 0xFF; + + if (quirks && quirks->kbd_led_levels_off_1 && info->levels) + info->levels--; + + if (units & BIT(0)) + info->seconds = (buffer.output[3] >> 0) & 0xFF; + if (units & BIT(1)) + info->minutes = (buffer.output[3] >> 8) & 0xFF; + if (units & BIT(2)) + info->hours = (buffer.output[3] >> 16) & 0xFF; + if (units & BIT(3)) + info->days = (buffer.output[3] >> 24) & 0xFF; + + return ret; +} + +static unsigned int kbd_get_max_level(void) +{ + if (kbd_info.levels != 0) + return kbd_info.levels; + if (kbd_mode_levels_count > 0) + return kbd_mode_levels_count - 1; + return 0; +} + +static int kbd_get_level(struct kbd_state *state) +{ + int i; + + if (kbd_info.levels != 0) + return state->level; + + if (kbd_mode_levels_count > 0) { + for (i = 0; i < kbd_mode_levels_count; ++i) + if (kbd_mode_levels[i] == state->mode_bit) + return i; + return 0; + } + + return -EINVAL; +} + +static int kbd_set_level(struct kbd_state *state, u8 level) +{ + if (kbd_info.levels != 0) { + if (level != 0) + kbd_previous_level = level; + if (state->level == level) + return 0; + state->level = level; + if (level != 0 && state->mode_bit == KBD_MODE_BIT_OFF) + state->mode_bit = kbd_previous_mode_bit; + else if (level == 0 && state->mode_bit != KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = state->mode_bit; + state->mode_bit = KBD_MODE_BIT_OFF; + } + return 0; + } + + if (kbd_mode_levels_count > 0 && level < kbd_mode_levels_count) { + if (level != 0) + kbd_previous_level = level; + state->mode_bit = kbd_mode_levels[level]; + return 0; + } + + return -EINVAL; +} + +static int kbd_get_state(struct kbd_state *state) +{ + struct calling_interface_buffer buffer; + int ret; + + dell_fill_request(&buffer, 0x1, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + if (ret) + return ret; + + state->mode_bit = ffs(buffer.output[1] & 0xFFFF); + if (state->mode_bit != 0) + state->mode_bit--; + + state->triggers = (buffer.output[1] >> 16) & 0xFF; + state->timeout_value = (buffer.output[1] >> 24) & 0x3F; + state->timeout_unit = (buffer.output[1] >> 30) & 0x3; + state->als_setting = buffer.output[2] & 0xFF; + state->als_value = (buffer.output[2] >> 8) & 0xFF; + state->level = (buffer.output[2] >> 16) & 0xFF; + state->timeout_value_ac = (buffer.output[2] >> 24) & 0x3F; + state->timeout_unit_ac = (buffer.output[2] >> 30) & 0x3; + + return ret; +} + +static int kbd_set_state(struct kbd_state *state) +{ + struct calling_interface_buffer buffer; + int ret; + u32 input1; + u32 input2; + + input1 = BIT(state->mode_bit) & 0xFFFF; + input1 |= (state->triggers & 0xFF) << 16; + input1 |= (state->timeout_value & 0x3F) << 24; + input1 |= (state->timeout_unit & 0x3) << 30; + input2 = state->als_setting & 0xFF; + input2 |= (state->level & 0xFF) << 16; + input2 |= (state->timeout_value_ac & 0x3F) << 24; + input2 |= (state->timeout_unit_ac & 0x3) << 30; + dell_fill_request(&buffer, 0x2, input1, input2, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + + return ret; +} + +static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) +{ + int ret; + + ret = kbd_set_state(state); + if (ret == 0) + return 0; + + /* + * When setting the new state fails,try to restore the previous one. + * This is needed on some machines where BIOS sets a default state when + * setting a new state fails. This default state could be all off. + */ + + if (kbd_set_state(old)) + pr_err("Setting old previous keyboard state failed\n"); + + return ret; +} + +static int kbd_set_token_bit(u8 bit) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + token = dell_smbios_find_token(kbd_tokens[bit]); + if (!token) + return -EINVAL; + + dell_fill_request(&buffer, token->location, token->value, 0, 0); + ret = dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + + return ret; +} + +static int kbd_get_token_bit(u8 bit) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int ret; + int val; + + if (bit >= ARRAY_SIZE(kbd_tokens)) + return -EINVAL; + + token = dell_smbios_find_token(kbd_tokens[bit]); + if (!token) + return -EINVAL; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_TOKEN_READ, SELECT_TOKEN_STD); + val = buffer.output[1]; + + if (ret) + return ret; + + return (val == token->value); +} + +static int kbd_get_first_active_token_bit(void) +{ + int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) { + ret = kbd_get_token_bit(i); + if (ret == 1) + return i; + } + + return ret; +} + +static int kbd_get_valid_token_counts(void) +{ + return hweight16(kbd_token_bits); +} + +static inline int kbd_init_info(void) +{ + struct kbd_state state; + int ret; + int i; + + ret = kbd_get_info(&kbd_info); + if (ret) + return ret; + + /* NOTE: Old models without KBD_LED_AC_TOKEN token supports only one + * timeout value which is shared for both battery and AC power + * settings. So do not try to set AC values on old models. + */ + if ((quirks && quirks->kbd_missing_ac_tag) || + dell_smbios_find_token(KBD_LED_AC_TOKEN)) + kbd_timeout_ac_supported = true; + + kbd_get_state(&state); + + /* NOTE: timeout value is stored in 6 bits so max value is 63 */ + if (kbd_info.seconds > 63) + kbd_info.seconds = 63; + if (kbd_info.minutes > 63) + kbd_info.minutes = 63; + if (kbd_info.hours > 63) + kbd_info.hours = 63; + if (kbd_info.days > 63) + kbd_info.days = 63; + + /* NOTE: On tested machines ON mode did not work and caused + * problems (turned backlight off) so do not use it + */ + kbd_info.modes &= ~BIT(KBD_MODE_BIT_ON); + + kbd_previous_level = kbd_get_level(&state); + kbd_previous_mode_bit = state.mode_bit; + + if (kbd_previous_level == 0 && kbd_get_max_level() != 0) + kbd_previous_level = 1; + + if (kbd_previous_mode_bit == KBD_MODE_BIT_OFF) { + kbd_previous_mode_bit = + ffs(kbd_info.modes & ~BIT(KBD_MODE_BIT_OFF)); + if (kbd_previous_mode_bit != 0) + kbd_previous_mode_bit--; + } + + if (kbd_info.modes & (BIT(KBD_MODE_BIT_ALS) | + BIT(KBD_MODE_BIT_TRIGGER_ALS))) + kbd_als_supported = true; + + if (kbd_info.modes & ( + BIT(KBD_MODE_BIT_TRIGGER_ALS) | BIT(KBD_MODE_BIT_TRIGGER) | + BIT(KBD_MODE_BIT_TRIGGER_25) | BIT(KBD_MODE_BIT_TRIGGER_50) | + BIT(KBD_MODE_BIT_TRIGGER_75) | BIT(KBD_MODE_BIT_TRIGGER_100) + )) + kbd_triggers_supported = true; + + /* kbd_mode_levels[0] is reserved, see below */ + for (i = 0; i < 16; ++i) + if (kbd_is_level_mode_bit(i) && (BIT(i) & kbd_info.modes)) + kbd_mode_levels[1 + kbd_mode_levels_count++] = i; + + /* + * Find the first supported mode and assign to kbd_mode_levels[0]. + * This should be 0 (off), but we cannot depend on the BIOS to + * support 0. + */ + if (kbd_mode_levels_count > 0) { + for (i = 0; i < 16; ++i) { + if (BIT(i) & kbd_info.modes) { + kbd_mode_levels[0] = i; + break; + } + } + kbd_mode_levels_count++; + } + + return 0; + +} + +static inline void kbd_init_tokens(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(kbd_tokens); ++i) + if (dell_smbios_find_token(kbd_tokens[i])) + kbd_token_bits |= BIT(i); +} + +static void kbd_init(void) +{ + int ret; + + if (quirks && quirks->kbd_led_not_present) + return; + + ret = kbd_init_info(); + kbd_init_tokens(); + + /* + * Only supports keyboard backlight when it has at least two modes. + */ + if ((ret == 0 && (kbd_info.levels != 0 || kbd_mode_levels_count >= 2)) + || kbd_get_valid_token_counts() >= 2) + kbd_led_present = true; +} + +static ssize_t kbd_led_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool convert; + int value; + int ret; + char ch; + u8 unit; + int i; + + ret = sscanf(buf, "%d %c", &value, &ch); + if (ret < 1) + return -EINVAL; + else if (ret == 1) + ch = 's'; + + if (value < 0) + return -EINVAL; + + convert = false; + + switch (ch) { + case 's': + if (value > kbd_info.seconds) + convert = true; + unit = KBD_TIMEOUT_SECONDS; + break; + case 'm': + if (value > kbd_info.minutes) + convert = true; + unit = KBD_TIMEOUT_MINUTES; + break; + case 'h': + if (value > kbd_info.hours) + convert = true; + unit = KBD_TIMEOUT_HOURS; + break; + case 'd': + if (value > kbd_info.days) + convert = true; + unit = KBD_TIMEOUT_DAYS; + break; + default: + return -EINVAL; + } + + if (quirks && quirks->needs_kbd_timeouts) + convert = true; + + if (convert) { + /* Convert value from current units to seconds */ + switch (unit) { + case KBD_TIMEOUT_DAYS: + value *= 24; + fallthrough; + case KBD_TIMEOUT_HOURS: + value *= 60; + fallthrough; + case KBD_TIMEOUT_MINUTES: + value *= 60; + unit = KBD_TIMEOUT_SECONDS; + } + + if (quirks && quirks->needs_kbd_timeouts) { + for (i = 0; quirks->kbd_timeouts[i] != -1; i++) { + if (value <= quirks->kbd_timeouts[i]) { + value = quirks->kbd_timeouts[i]; + break; + } + } + } + + if (value <= kbd_info.seconds && kbd_info.seconds) { + unit = KBD_TIMEOUT_SECONDS; + } else if (value / 60 <= kbd_info.minutes && kbd_info.minutes) { + value /= 60; + unit = KBD_TIMEOUT_MINUTES; + } else if (value / (60 * 60) <= kbd_info.hours && kbd_info.hours) { + value /= (60 * 60); + unit = KBD_TIMEOUT_HOURS; + } else if (value / (60 * 60 * 24) <= kbd_info.days && kbd_info.days) { + value /= (60 * 60 * 24); + unit = KBD_TIMEOUT_DAYS; + } else { + return -EINVAL; + } + } + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + new_state = state; + + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + new_state.timeout_value_ac = value; + new_state.timeout_unit_ac = unit; + } else { + new_state.timeout_value = value; + new_state.timeout_unit = unit; + } + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + int value; + int ret; + int len; + u8 unit; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + if (kbd_timeout_ac_supported && power_supply_is_system_supplied() > 0) { + value = state.timeout_value_ac; + unit = state.timeout_unit_ac; + } else { + value = state.timeout_value; + unit = state.timeout_unit; + } + + len = sprintf(buf, "%d", value); + + switch (unit) { + case KBD_TIMEOUT_SECONDS: + return len + sprintf(buf+len, "s\n"); + case KBD_TIMEOUT_MINUTES: + return len + sprintf(buf+len, "m\n"); + case KBD_TIMEOUT_HOURS: + return len + sprintf(buf+len, "h\n"); + case KBD_TIMEOUT_DAYS: + return len + sprintf(buf+len, "d\n"); + default: + return -EINVAL; + } + + return len; +} + +static DEVICE_ATTR(stop_timeout, S_IRUGO | S_IWUSR, + kbd_led_timeout_show, kbd_led_timeout_store); + +static const char * const kbd_led_triggers[] = { + "keyboard", + "touchpad", + /*"trackstick"*/ NULL, /* NOTE: trackstick is just alias for touchpad */ + "mouse", +}; + +static ssize_t kbd_led_triggers_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int trigger_bit = -1; + char trigger[21]; + int i, ret; + + ret = sscanf(buf, "%20s", trigger); + if (ret != 1) + return -EINVAL; + + if (trigger[0] != '+' && trigger[0] != '-') + return -EINVAL; + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (kbd_triggers_supported) { + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if (strcmp(trigger+1, kbd_led_triggers[i]) != 0) + continue; + if (trigger[0] == '+' && + triggers_enabled && (state.triggers & BIT(i))) { + ret = count; + goto out; + } + if (trigger[0] == '-' && + (!triggers_enabled || !(state.triggers & BIT(i)))) { + ret = count; + goto out; + } + trigger_bit = i; + break; + } + } + + if (trigger_bit == -1) { + ret = -EINVAL; + goto out; + } + + new_state = state; + if (trigger[0] == '+') + new_state.triggers |= BIT(trigger_bit); + else { + new_state.triggers &= ~BIT(trigger_bit); + /* + * NOTE: trackstick bit (2) must be disabled when + * disabling touchpad bit (1), otherwise touchpad + * bit (1) will not be disabled + */ + if (trigger_bit == 1) + new_state.triggers &= ~BIT(2); + } + if ((kbd_info.triggers & new_state.triggers) != + new_state.triggers) { + ret = -EINVAL; + goto out; + } + if (new_state.triggers && !triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else if (new_state.triggers == 0) { + kbd_set_level(&new_state, 0); + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + if (new_state.mode_bit != KBD_MODE_BIT_OFF) + kbd_previous_mode_bit = new_state.mode_bit; + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_triggers_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kbd_state state; + bool triggers_enabled; + int level, i, ret; + int len = 0; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + len = 0; + + if (kbd_triggers_supported) { + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + level = kbd_get_level(&state); + for (i = 0; i < ARRAY_SIZE(kbd_led_triggers); ++i) { + if (!(kbd_info.triggers & BIT(i))) + continue; + if (!kbd_led_triggers[i]) + continue; + if ((triggers_enabled || level <= 0) && + (state.triggers & BIT(i))) + buf[len++] = '+'; + else + buf[len++] = '-'; + len += sprintf(buf+len, "%s ", kbd_led_triggers[i]); + } + } + + if (len) + buf[len - 1] = '\n'; + + return len; +} + +static DEVICE_ATTR(start_triggers, S_IRUGO | S_IWUSR, + kbd_led_triggers_show, kbd_led_triggers_store); + +static ssize_t kbd_led_als_enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state new_state; + struct kbd_state state; + bool triggers_enabled = false; + int enable; + int ret; + + ret = kstrtoint(buf, 0, &enable); + if (ret) + return ret; + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + if (enable == kbd_is_als_mode_bit(state.mode_bit)) { + ret = count; + goto out; + } + + new_state = state; + + if (kbd_triggers_supported) + triggers_enabled = kbd_is_trigger_mode_bit(state.mode_bit); + + if (enable) { + if (triggers_enabled) + new_state.mode_bit = KBD_MODE_BIT_TRIGGER_ALS; + else + new_state.mode_bit = KBD_MODE_BIT_ALS; + } else { + if (triggers_enabled) { + new_state.mode_bit = KBD_MODE_BIT_TRIGGER; + kbd_set_level(&new_state, kbd_previous_level); + } else { + new_state.mode_bit = KBD_MODE_BIT_ON; + } + } + if (!(kbd_info.modes & BIT(new_state.mode_bit))) { + ret = -EINVAL; + goto out; + } + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + kbd_previous_mode_bit = new_state.mode_bit; + + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_als_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + bool enabled = false; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + enabled = kbd_is_als_mode_bit(state.mode_bit); + + return sprintf(buf, "%d\n", enabled ? 1 : 0); +} + +static DEVICE_ATTR(als_enabled, S_IRUGO | S_IWUSR, + kbd_led_als_enabled_show, kbd_led_als_enabled_store); + +static ssize_t kbd_led_als_setting_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct kbd_state state; + struct kbd_state new_state; + u8 setting; + int ret; + + ret = kstrtou8(buf, 10, &setting); + if (ret) + return ret; + + mutex_lock(&kbd_led_mutex); + + ret = kbd_get_state(&state); + if (ret) + goto out; + + new_state = state; + new_state.als_setting = setting; + + ret = kbd_set_state_safe(&new_state, &state); + if (ret) + goto out; + + ret = count; +out: + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static ssize_t kbd_led_als_setting_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct kbd_state state; + int ret; + + ret = kbd_get_state(&state); + if (ret) + return ret; + + return sprintf(buf, "%d\n", state.als_setting); +} + +static DEVICE_ATTR(als_setting, S_IRUGO | S_IWUSR, + kbd_led_als_setting_show, kbd_led_als_setting_store); + +static struct attribute *kbd_led_attrs[] = { + &dev_attr_stop_timeout.attr, + &dev_attr_start_triggers.attr, + NULL, +}; + +static const struct attribute_group kbd_led_group = { + .attrs = kbd_led_attrs, +}; + +static struct attribute *kbd_led_als_attrs[] = { + &dev_attr_als_enabled.attr, + &dev_attr_als_setting.attr, + NULL, +}; + +static const struct attribute_group kbd_led_als_group = { + .attrs = kbd_led_als_attrs, +}; + +static const struct attribute_group *kbd_led_groups[] = { + &kbd_led_group, + &kbd_led_als_group, + NULL, +}; + +static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) +{ + int ret; + u16 num; + struct kbd_state state; + + if (kbd_get_max_level()) { + ret = kbd_get_state(&state); + if (ret) + return 0; + ret = kbd_get_level(&state); + if (ret < 0) + return 0; + return ret; + } + + if (kbd_get_valid_token_counts()) { + ret = kbd_get_first_active_token_bit(); + if (ret < 0) + return 0; + for (num = kbd_token_bits; num != 0 && ret > 0; --ret) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + return 0; + return ffs(num) - 1; + } + + pr_warn("Keyboard brightness level control not supported\n"); + return 0; +} + +static int kbd_led_level_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + enum led_brightness new_value = value; + struct kbd_state state; + struct kbd_state new_state; + u16 num; + int ret; + + mutex_lock(&kbd_led_mutex); + + if (kbd_get_max_level()) { + ret = kbd_get_state(&state); + if (ret) + goto out; + new_state = state; + ret = kbd_set_level(&new_state, value); + if (ret) + goto out; + ret = kbd_set_state_safe(&new_state, &state); + } else if (kbd_get_valid_token_counts()) { + for (num = kbd_token_bits; num != 0 && value > 0; --value) + num &= num - 1; /* clear the first bit set */ + if (num == 0) + ret = 0; + else + ret = kbd_set_token_bit(ffs(num) - 1); + } else { + pr_warn("Keyboard brightness level control not supported\n"); + ret = -ENXIO; + } + +out: + if (ret == 0) + kbd_led_level = new_value; + + mutex_unlock(&kbd_led_mutex); + return ret; +} + +static struct led_classdev kbd_led = { + .name = "dell::kbd_backlight", + .flags = LED_BRIGHT_HW_CHANGED, + .brightness_set_blocking = kbd_led_level_set, + .brightness_get = kbd_led_level_get, + .groups = kbd_led_groups, +}; + +static int __init kbd_led_init(struct device *dev) +{ + int ret; + + kbd_init(); + if (!kbd_led_present) + return -ENODEV; + if (!kbd_als_supported) + kbd_led_groups[1] = NULL; + kbd_led.max_brightness = kbd_get_max_level(); + if (!kbd_led.max_brightness) { + kbd_led.max_brightness = kbd_get_valid_token_counts(); + if (kbd_led.max_brightness) + kbd_led.max_brightness--; + } + + kbd_led_level = kbd_led_level_get(NULL); + + ret = led_classdev_register(dev, &kbd_led); + if (ret) + kbd_led_present = false; + + return ret; +} + +static void brightness_set_exit(struct led_classdev *led_cdev, + enum led_brightness value) +{ + /* Don't change backlight level on exit */ +}; + +static void kbd_led_exit(void) +{ + if (!kbd_led_present) + return; + kbd_led.brightness_set = brightness_set_exit; + led_classdev_unregister(&kbd_led); +} + +static int dell_laptop_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + bool changed = false; + enum led_brightness new_kbd_led_level; + + switch (action) { + case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: + if (!kbd_led_present) + break; + + mutex_lock(&kbd_led_mutex); + new_kbd_led_level = kbd_led_level_get(&kbd_led); + if (kbd_led_level != new_kbd_led_level) { + kbd_led_level = new_kbd_led_level; + changed = true; + } + mutex_unlock(&kbd_led_mutex); + + if (changed) + led_classdev_notify_brightness_hw_changed(&kbd_led, + kbd_led_level); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block dell_laptop_notifier = { + .notifier_call = dell_laptop_notifier_call, +}; + +static int micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct calling_interface_buffer buffer; + struct calling_interface_token *token; + int state = brightness != LED_OFF; + + if (state == 0) + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE); + else + token = dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE); + + if (!token) + return -ENODEV; + + dell_fill_request(&buffer, token->location, token->value, 0, 0); + dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + + return 0; +} + +static struct led_classdev micmute_led_cdev = { + .name = "platform::micmute", + .max_brightness = 1, + .brightness_set_blocking = micmute_led_set, + .default_trigger = "audio-micmute", +}; + +static int __init dell_init(void) +{ + struct calling_interface_token *token; + int max_intensity = 0; + int ret; + + if (!dmi_check_system(dell_device_table)) + return -ENODEV; + + quirks = NULL; + /* find if this machine support other functions */ + dmi_check_system(dell_quirks); + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + platform_device = platform_device_alloc("dell-laptop", -1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device1; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device2; + + ret = dell_setup_rfkill(); + + if (ret) { + pr_warn("Unable to setup rfkill\n"); + goto fail_rfkill; + } + + if (quirks && quirks->touchpad_led) + touchpad_led_init(&platform_device->dev); + + kbd_led_init(&platform_device->dev); + + dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); + debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, + &dell_debugfs_fops); + + dell_laptop_register_notifier(&dell_laptop_notifier); + + if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && + dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) { + micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); + ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); + if (ret < 0) + goto fail_led; + } + + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) + return 0; + + token = dell_smbios_find_token(BRIGHTNESS_TOKEN); + if (token) { + struct calling_interface_buffer buffer; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_AC); + if (ret == 0) + max_intensity = buffer.output[3]; + } + + if (max_intensity) { + struct backlight_properties props; + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_PLATFORM; + props.max_brightness = max_intensity; + dell_backlight_device = backlight_device_register("dell_backlight", + &platform_device->dev, + NULL, + &dell_ops, + &props); + + if (IS_ERR(dell_backlight_device)) { + ret = PTR_ERR(dell_backlight_device); + dell_backlight_device = NULL; + goto fail_backlight; + } + + dell_backlight_device->props.brightness = + dell_get_intensity(dell_backlight_device); + if (dell_backlight_device->props.brightness < 0) { + ret = dell_backlight_device->props.brightness; + goto fail_get_brightness; + } + backlight_update_status(dell_backlight_device); + } + + return 0; + +fail_get_brightness: + backlight_device_unregister(dell_backlight_device); +fail_backlight: + led_classdev_unregister(&micmute_led_cdev); +fail_led: + dell_cleanup_rfkill(); +fail_rfkill: + platform_device_del(platform_device); +fail_platform_device2: + platform_device_put(platform_device); +fail_platform_device1: + platform_driver_unregister(&platform_driver); +fail_platform_driver: + return ret; +} + +static void __exit dell_exit(void) +{ + dell_laptop_unregister_notifier(&dell_laptop_notifier); + debugfs_remove_recursive(dell_laptop_dir); + if (quirks && quirks->touchpad_led) + touchpad_led_exit(); + kbd_led_exit(); + backlight_device_unregister(dell_backlight_device); + led_classdev_unregister(&micmute_led_cdev); + dell_cleanup_rfkill(); + if (platform_device) { + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } +} + +/* dell-rbtn.c driver export functions which will not work correctly (and could + * cause kernel crash) if they are called before dell-rbtn.c init code. This is + * not problem when dell-rbtn.c is compiled as external module. When both files + * (dell-rbtn.c and dell-laptop.c) are compiled statically into kernel, then we + * need to ensure that dell_init() will be called after initializing dell-rbtn. + * This can be achieved by late_initcall() instead module_init(). + */ +late_initcall(dell_init); +module_exit(dell_exit); + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Gabriele Mazzotta "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Dell laptop driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-rbtn.c b/drivers/platform/x86/dell/dell-rbtn.c new file mode 100644 index 000000000000..a89fad47ff13 --- /dev/null +++ b/drivers/platform/x86/dell/dell-rbtn.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár + +*/ + +#include +#include +#include +#include + +#include "dell-rbtn.h" + +enum rbtn_type { + RBTN_UNKNOWN, + RBTN_TOGGLE, + RBTN_SLIDER, +}; + +struct rbtn_data { + enum rbtn_type type; + struct rfkill *rfkill; + struct input_dev *input_dev; + bool suspended; +}; + + +/* + * acpi functions + */ + +static enum rbtn_type rbtn_check(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "CRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return RBTN_UNKNOWN; + + switch (output) { + case 0: + case 1: + return RBTN_TOGGLE; + case 2: + case 3: + return RBTN_SLIDER; + default: + return RBTN_UNKNOWN; + } +} + +static int rbtn_get(struct acpi_device *device) +{ + unsigned long long output; + acpi_status status; + + status = acpi_evaluate_integer(device->handle, "GRBT", NULL, &output); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return !output; +} + +static int rbtn_acquire(struct acpi_device *device, bool enable) +{ + struct acpi_object_list input; + union acpi_object param; + acpi_status status; + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = enable; + input.count = 1; + input.pointer = ¶m; + + status = acpi_evaluate_object(device->handle, "ARBT", &input, NULL); + if (ACPI_FAILURE(status)) + return -EINVAL; + + return 0; +} + + +/* + * rfkill device + */ + +static void rbtn_rfkill_query(struct rfkill *rfkill, void *data) +{ + struct acpi_device *device = data; + int state; + + state = rbtn_get(device); + if (state < 0) + return; + + rfkill_set_states(rfkill, state, state); +} + +static int rbtn_rfkill_set_block(void *data, bool blocked) +{ + /* NOTE: setting soft rfkill state is not supported */ + return -EINVAL; +} + +static const struct rfkill_ops rbtn_ops = { + .query = rbtn_rfkill_query, + .set_block = rbtn_rfkill_set_block, +}; + +static int rbtn_rfkill_init(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + int ret; + + if (rbtn_data->rfkill) + return 0; + + /* + * NOTE: rbtn controls all radio devices, not only WLAN + * but rfkill interface does not support "ANY" type + * so "WLAN" type is used + */ + rbtn_data->rfkill = rfkill_alloc("dell-rbtn", &device->dev, + RFKILL_TYPE_WLAN, &rbtn_ops, device); + if (!rbtn_data->rfkill) + return -ENOMEM; + + ret = rfkill_register(rbtn_data->rfkill); + if (ret) { + rfkill_destroy(rbtn_data->rfkill); + rbtn_data->rfkill = NULL; + return ret; + } + + return 0; +} + +static void rbtn_rfkill_exit(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (!rbtn_data->rfkill) + return; + + rfkill_unregister(rbtn_data->rfkill); + rfkill_destroy(rbtn_data->rfkill); + rbtn_data->rfkill = NULL; +} + +static void rbtn_rfkill_event(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + if (rbtn_data->rfkill) + rbtn_rfkill_query(rbtn_data->rfkill, device); +} + + +/* + * input device + */ + +static int rbtn_input_init(struct rbtn_data *rbtn_data) +{ + int ret; + + rbtn_data->input_dev = input_allocate_device(); + if (!rbtn_data->input_dev) + return -ENOMEM; + + rbtn_data->input_dev->name = "DELL Wireless hotkeys"; + rbtn_data->input_dev->phys = "dellabce/input0"; + rbtn_data->input_dev->id.bustype = BUS_HOST; + rbtn_data->input_dev->evbit[0] = BIT(EV_KEY); + set_bit(KEY_RFKILL, rbtn_data->input_dev->keybit); + + ret = input_register_device(rbtn_data->input_dev); + if (ret) { + input_free_device(rbtn_data->input_dev); + rbtn_data->input_dev = NULL; + return ret; + } + + return 0; +} + +static void rbtn_input_exit(struct rbtn_data *rbtn_data) +{ + input_unregister_device(rbtn_data->input_dev); + rbtn_data->input_dev = NULL; +} + +static void rbtn_input_event(struct rbtn_data *rbtn_data) +{ + input_report_key(rbtn_data->input_dev, KEY_RFKILL, 1); + input_sync(rbtn_data->input_dev); + input_report_key(rbtn_data->input_dev, KEY_RFKILL, 0); + input_sync(rbtn_data->input_dev); +} + + +/* + * acpi driver + */ + +static int rbtn_add(struct acpi_device *device); +static int rbtn_remove(struct acpi_device *device); +static void rbtn_notify(struct acpi_device *device, u32 event); + +static const struct acpi_device_id rbtn_ids[] = { + { "DELRBTN", 0 }, + { "DELLABCE", 0 }, + + /* + * This driver can also handle the "DELLABC6" device that + * appears on the XPS 13 9350, but that device is disabled by + * the DSDT unless booted with acpi_osi="!Windows 2012" + * acpi_osi="!Windows 2013". + * + * According to Mario at Dell: + * + * DELLABC6 is a custom interface that was created solely to + * have airplane mode support for Windows 7. For Windows 10 + * the proper interface is to use that which is handled by + * intel-hid. A OEM airplane mode driver is not used. + * + * Since the kernel doesn't identify as Windows 7 it would be + * incorrect to do attempt to use that interface. + * + * Even if we override _OSI and bind to DELLABC6, we end up with + * inconsistent behavior in which userspace can get out of sync + * with the rfkill state as it conflicts with events from + * intel-hid. + * + * The upshot is that it is better to just ignore DELLABC6 + * devices. + */ + + { "", 0 }, +}; + +#ifdef CONFIG_PM_SLEEP +static void ACPI_SYSTEM_XFACE rbtn_clear_suspended_flag(void *context) +{ + struct rbtn_data *rbtn_data = context; + + rbtn_data->suspended = false; +} + +static int rbtn_suspend(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = acpi_driver_data(device); + + rbtn_data->suspended = true; + + return 0; +} + +static int rbtn_resume(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = acpi_driver_data(device); + acpi_status status; + + /* + * Upon resume, some BIOSes send an ACPI notification thet triggers + * an unwanted input event. In order to ignore it, we use a flag + * that we set at suspend and clear once we have received the extra + * ACPI notification. Since ACPI notifications are delivered + * asynchronously to drivers, we clear the flag from the workqueue + * used to deliver the notifications. This should be enough + * to have the flag cleared only after we received the extra + * notification, if any. + */ + status = acpi_os_execute(OSL_NOTIFY_HANDLER, + rbtn_clear_suspended_flag, rbtn_data); + if (ACPI_FAILURE(status)) + rbtn_clear_suspended_flag(rbtn_data); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(rbtn_pm_ops, rbtn_suspend, rbtn_resume); + +static struct acpi_driver rbtn_driver = { + .name = "dell-rbtn", + .ids = rbtn_ids, + .drv.pm = &rbtn_pm_ops, + .ops = { + .add = rbtn_add, + .remove = rbtn_remove, + .notify = rbtn_notify, + }, + .owner = THIS_MODULE, +}; + + +/* + * notifier export functions + */ + +static bool auto_remove_rfkill = true; + +static ATOMIC_NOTIFIER_HEAD(rbtn_chain_head); + +static int rbtn_inc_count(struct device *dev, void *data) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = device->driver_data; + int *count = data; + + if (rbtn_data->type == RBTN_SLIDER) + (*count)++; + + return 0; +} + +static int rbtn_switch_dev(struct device *dev, void *data) +{ + struct acpi_device *device = to_acpi_device(dev); + struct rbtn_data *rbtn_data = device->driver_data; + bool enable = data; + + if (rbtn_data->type != RBTN_SLIDER) + return 0; + + if (enable) + rbtn_rfkill_init(device); + else + rbtn_rfkill_exit(device); + + return 0; +} + +int dell_rbtn_notifier_register(struct notifier_block *nb) +{ + bool first; + int count; + int ret; + + count = 0; + ret = driver_for_each_device(&rbtn_driver.drv, NULL, &count, + rbtn_inc_count); + if (ret || count == 0) + return -ENODEV; + + first = !rbtn_chain_head.head; + + ret = atomic_notifier_chain_register(&rbtn_chain_head, nb); + if (ret != 0) + return ret; + + if (auto_remove_rfkill && first) + ret = driver_for_each_device(&rbtn_driver.drv, NULL, + (void *)false, rbtn_switch_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dell_rbtn_notifier_register); + +int dell_rbtn_notifier_unregister(struct notifier_block *nb) +{ + int ret; + + ret = atomic_notifier_chain_unregister(&rbtn_chain_head, nb); + if (ret != 0) + return ret; + + if (auto_remove_rfkill && !rbtn_chain_head.head) + ret = driver_for_each_device(&rbtn_driver.drv, NULL, + (void *)true, rbtn_switch_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dell_rbtn_notifier_unregister); + + +/* + * acpi driver functions + */ + +static int rbtn_add(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data; + enum rbtn_type type; + int ret = 0; + + type = rbtn_check(device); + if (type == RBTN_UNKNOWN) { + dev_info(&device->dev, "Unknown device type\n"); + return -EINVAL; + } + + ret = rbtn_acquire(device, true); + if (ret < 0) { + dev_err(&device->dev, "Cannot enable device\n"); + return ret; + } + + rbtn_data = devm_kzalloc(&device->dev, sizeof(*rbtn_data), GFP_KERNEL); + if (!rbtn_data) + return -ENOMEM; + + rbtn_data->type = type; + device->driver_data = rbtn_data; + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + ret = rbtn_input_init(rbtn_data); + break; + case RBTN_SLIDER: + if (auto_remove_rfkill && rbtn_chain_head.head) + ret = 0; + else + ret = rbtn_rfkill_init(device); + break; + default: + ret = -EINVAL; + } + + return ret; + +} + +static int rbtn_remove(struct acpi_device *device) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_exit(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_exit(device); + break; + default: + break; + } + + rbtn_acquire(device, false); + device->driver_data = NULL; + + return 0; +} + +static void rbtn_notify(struct acpi_device *device, u32 event) +{ + struct rbtn_data *rbtn_data = device->driver_data; + + /* + * Some BIOSes send a notification at resume. + * Ignore it to prevent unwanted input events. + */ + if (rbtn_data->suspended) { + dev_dbg(&device->dev, "ACPI notification ignored\n"); + return; + } + + if (event != 0x80) { + dev_info(&device->dev, "Received unknown event (0x%x)\n", + event); + return; + } + + switch (rbtn_data->type) { + case RBTN_TOGGLE: + rbtn_input_event(rbtn_data); + break; + case RBTN_SLIDER: + rbtn_rfkill_event(device); + atomic_notifier_call_chain(&rbtn_chain_head, event, device); + break; + default: + break; + } +} + + +/* + * module functions + */ + +module_acpi_driver(rbtn_driver); + +module_param(auto_remove_rfkill, bool, 0444); + +MODULE_PARM_DESC(auto_remove_rfkill, "Automatically remove rfkill devices when " + "other modules start receiving events " + "from this module and re-add them when " + "the last module stops receiving events " + "(default true)"); +MODULE_DEVICE_TABLE(acpi, rbtn_ids); +MODULE_DESCRIPTION("Dell Airplane Mode Switch driver"); +MODULE_AUTHOR("Pali Rohár "); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-rbtn.h b/drivers/platform/x86/dell/dell-rbtn.h new file mode 100644 index 000000000000..5e030f926c58 --- /dev/null +++ b/drivers/platform/x86/dell/dell-rbtn.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + Dell Airplane Mode Switch driver + Copyright (C) 2014-2015 Pali Rohár + +*/ + +#ifndef _DELL_RBTN_H_ +#define _DELL_RBTN_H_ + +struct notifier_block; + +int dell_rbtn_notifier_register(struct notifier_block *nb); +int dell_rbtn_notifier_unregister(struct notifier_block *nb); + +#endif diff --git a/drivers/platform/x86/dell/dell-smbios-base.c b/drivers/platform/x86/dell/dell-smbios-base.c new file mode 100644 index 000000000000..3a1dbf199441 --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios-base.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Common functions for kernel modules using Dell SMBIOS + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" + +static u32 da_supported_commands; +static int da_num_tokens; +static struct platform_device *platform_device; +static struct calling_interface_token *da_tokens; +static struct device_attribute *token_location_attrs; +static struct device_attribute *token_value_attrs; +static struct attribute **token_attrs; +static DEFINE_MUTEX(smbios_mutex); + +struct smbios_device { + struct list_head list; + struct device *device; + int (*call_fn)(struct calling_interface_buffer *arg); +}; + +struct smbios_call { + u32 need_capability; + int cmd_class; + int cmd_select; +}; + +/* calls that are whitelisted for given capabilities */ +static struct smbios_call call_whitelist[] = { + /* generally tokens are allowed, but may be further filtered or + * restricted by token blacklist or whitelist + */ + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, + {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, + {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, + /* used by userspace: fwupdate */ + {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, + /* used by userspace: fwupd */ + {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, + {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, +}; + +/* calls that are explicitly blacklisted */ +static struct smbios_call call_blacklist[] = { + {0x0000, 1, 7}, /* manufacturing use */ + {0x0000, 6, 5}, /* manufacturing use */ + {0x0000, 11, 3}, /* write once */ + {0x0000, 11, 7}, /* write once */ + {0x0000, 11, 11}, /* write once */ + {0x0000, 19, -1}, /* diagnostics */ + /* handled by kernel: dell-laptop */ + {0x0000, CLASS_INFO, SELECT_RFKILL}, + {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, +}; + +struct token_range { + u32 need_capability; + u16 min; + u16 max; +}; + +/* tokens that are whitelisted for given capabilities */ +static struct token_range token_whitelist[] = { + /* used by userspace: fwupdate */ + {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, + /* can indicate to userspace that WMI is needed */ + {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} +}; + +/* tokens that are explicitly blacklisted */ +static struct token_range token_blacklist[] = { + {0x0000, 0x0058, 0x0059}, /* ME use */ + {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ + {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ + {0x0000, 0x0175, 0x0176}, /* write once */ + {0x0000, 0x0195, 0x0197}, /* diagnostics */ + {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ + {0x0000, 0x027D, 0x0284}, /* diagnostics */ + {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ + {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ + {0x0000, 0x0300, 0x0302}, /* manufacturing use */ + {0x0000, 0x0325, 0x0326}, /* manufacturing use */ + {0x0000, 0x0332, 0x0335}, /* fan control */ + {0x0000, 0x0350, 0x0350}, /* manufacturing use */ + {0x0000, 0x0363, 0x0363}, /* manufacturing use */ + {0x0000, 0x0368, 0x0368}, /* manufacturing use */ + {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ + {0x0000, 0x049E, 0x049F}, /* manufacturing use */ + {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ + {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ + {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ + {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ + {0x0000, 0xA000, 0xBFFF}, /* write only */ + {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ + /* handled by kernel: dell-laptop */ + {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, + {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, + {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, + {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, + {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, + {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, +}; + +static LIST_HEAD(smbios_device_list); + +int dell_smbios_error(int value) +{ + switch (value) { + case 0: /* Completed successfully */ + return 0; + case -1: /* Completed with error */ + return -EIO; + case -2: /* Function not supported */ + return -ENXIO; + default: /* Unknown error */ + return -EINVAL; + } +} +EXPORT_SYMBOL_GPL(dell_smbios_error); + +int dell_smbios_register_device(struct device *d, void *call_fn) +{ + struct smbios_device *priv; + + priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); + if (!priv) + return -ENOMEM; + get_device(d); + priv->device = d; + priv->call_fn = call_fn; + mutex_lock(&smbios_mutex); + list_add_tail(&priv->list, &smbios_device_list); + mutex_unlock(&smbios_mutex); + dev_dbg(d, "Added device: %s\n", d->driver->name); + return 0; +} +EXPORT_SYMBOL_GPL(dell_smbios_register_device); + +void dell_smbios_unregister_device(struct device *d) +{ + struct smbios_device *priv; + + mutex_lock(&smbios_mutex); + list_for_each_entry(priv, &smbios_device_list, list) { + if (priv->device == d) { + list_del(&priv->list); + put_device(d); + break; + } + } + mutex_unlock(&smbios_mutex); + dev_dbg(d, "Remove device: %s\n", d->driver->name); +} +EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); + +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer) +{ + u16 t = 0; + int i; + + /* can't make calls over 30 */ + if (buffer->cmd_class > 30) { + dev_dbg(d, "class too big: %u\n", buffer->cmd_class); + return -EINVAL; + } + + /* supported calls on the particular system */ + if (!(da_supported_commands & (1 << buffer->cmd_class))) { + dev_dbg(d, "invalid command, supported commands: 0x%8x\n", + da_supported_commands); + return -EINVAL; + } + + /* match against call blacklist */ + for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { + if (buffer->cmd_class != call_blacklist[i].cmd_class) + continue; + if (buffer->cmd_select != call_blacklist[i].cmd_select && + call_blacklist[i].cmd_select != -1) + continue; + dev_dbg(d, "blacklisted command: %u/%u\n", + buffer->cmd_class, buffer->cmd_select); + return -EINVAL; + } + + /* if a token call, find token ID */ + + if ((buffer->cmd_class == CLASS_TOKEN_READ || + buffer->cmd_class == CLASS_TOKEN_WRITE) && + buffer->cmd_select < 3) { + /* tokens enabled ? */ + if (!da_tokens) { + dev_dbg(d, "no token support on this system\n"); + return -EINVAL; + } + + /* find the matching token ID */ + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].location != buffer->input[0]) + continue; + t = da_tokens[i].tokenID; + break; + } + + /* token call; but token didn't exist */ + if (!t) { + dev_dbg(d, "token at location %04x doesn't exist\n", + buffer->input[0]); + return -EINVAL; + } + + /* match against token blacklist */ + for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { + if (!token_blacklist[i].min || !token_blacklist[i].max) + continue; + if (t >= token_blacklist[i].min && + t <= token_blacklist[i].max) + return -EINVAL; + } + + /* match against token whitelist */ + for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { + if (!token_whitelist[i].min || !token_whitelist[i].max) + continue; + if (t < token_whitelist[i].min || + t > token_whitelist[i].max) + continue; + if (!token_whitelist[i].need_capability || + capable(token_whitelist[i].need_capability)) { + dev_dbg(d, "whitelisted token: %x\n", t); + return 0; + } + + } + } + /* match against call whitelist */ + for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { + if (buffer->cmd_class != call_whitelist[i].cmd_class) + continue; + if (buffer->cmd_select != call_whitelist[i].cmd_select) + continue; + if (!call_whitelist[i].need_capability || + capable(call_whitelist[i].need_capability)) { + dev_dbg(d, "whitelisted capable command: %u/%u\n", + buffer->cmd_class, buffer->cmd_select); + return 0; + } + dev_dbg(d, "missing capability %d for %u/%u\n", + call_whitelist[i].need_capability, + buffer->cmd_class, buffer->cmd_select); + + } + + /* not in a whitelist, only allow processes with capabilities */ + if (capable(CAP_SYS_RAWIO)) { + dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", + buffer->cmd_class, buffer->cmd_select); + return 0; + } + + return -EACCES; +} +EXPORT_SYMBOL_GPL(dell_smbios_call_filter); + +int dell_smbios_call(struct calling_interface_buffer *buffer) +{ + int (*call_fn)(struct calling_interface_buffer *) = NULL; + struct device *selected_dev = NULL; + struct smbios_device *priv; + int ret; + + mutex_lock(&smbios_mutex); + list_for_each_entry(priv, &smbios_device_list, list) { + if (!selected_dev || priv->device->id >= selected_dev->id) { + dev_dbg(priv->device, "Trying device ID: %d\n", + priv->device->id); + call_fn = priv->call_fn; + selected_dev = priv->device; + } + } + + if (!selected_dev) { + ret = -ENODEV; + pr_err("No dell-smbios drivers are loaded\n"); + goto out_smbios_call; + } + + ret = call_fn(buffer); + +out_smbios_call: + mutex_unlock(&smbios_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_smbios_call); + +struct calling_interface_token *dell_smbios_find_token(int tokenid) +{ + int i; + + if (!da_tokens) + return NULL; + + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].tokenID == tokenid) + return &da_tokens[i]; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(dell_smbios_find_token); + +static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); + +int dell_laptop_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); +} +EXPORT_SYMBOL_GPL(dell_laptop_register_notifier); + +int dell_laptop_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); +} +EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); + +void dell_laptop_call_notifier(unsigned long action, void *data) +{ + blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); +} +EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); + +static void __init parse_da_table(const struct dmi_header *dm) +{ + /* Final token is a terminator, so we don't want to copy it */ + int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; + struct calling_interface_token *new_da_tokens; + struct calling_interface_structure *table = + container_of(dm, struct calling_interface_structure, header); + + /* + * 4 bytes of table header, plus 7 bytes of Dell header + * plus at least 6 bytes of entry + */ + + if (dm->length < 17) + return; + + da_supported_commands = table->supportedCmds; + + new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * + sizeof(struct calling_interface_token), + GFP_KERNEL); + + if (!new_da_tokens) + return; + da_tokens = new_da_tokens; + + memcpy(da_tokens+da_num_tokens, table->tokens, + sizeof(struct calling_interface_token) * tokens); + + da_num_tokens += tokens; +} + +static void zero_duplicates(struct device *dev) +{ + int i, j; + + for (i = 0; i < da_num_tokens; i++) { + if (da_tokens[i].tokenID == 0) + continue; + for (j = i+1; j < da_num_tokens; j++) { + if (da_tokens[j].tokenID == 0) + continue; + if (da_tokens[i].tokenID == da_tokens[j].tokenID) { + dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", + da_tokens[j].tokenID, + da_tokens[j].location, + da_tokens[j].value); + da_tokens[j].tokenID = 0; + } + } + } +} + +static void __init find_tokens(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xd4: /* Indexed IO */ + case 0xd5: /* Protected Area Type 1 */ + case 0xd6: /* Protected Area Type 2 */ + break; + case 0xda: /* Calling interface */ + parse_da_table(dm); + break; + } +} + +static int match_attribute(struct device *dev, + struct device_attribute *attr) +{ + int i; + + for (i = 0; i < da_num_tokens * 2; i++) { + if (!token_attrs[i]) + continue; + if (strcmp(token_attrs[i]->name, attr->attr.name) == 0) + return i/2; + } + dev_dbg(dev, "couldn't match: %s\n", attr->attr.name); + return -EINVAL; +} + +static ssize_t location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location); + return 0; +} + +static ssize_t value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + i = match_attribute(dev, attr); + if (i > 0) + return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value); + return 0; +} + +static struct attribute_group smbios_attribute_group = { + .name = "tokens" +}; + +static struct platform_driver platform_driver = { + .driver = { + .name = "dell-smbios", + }, +}; + +static int build_tokens_sysfs(struct platform_device *dev) +{ + char *location_name; + char *value_name; + size_t size; + int ret; + int i, j; + + /* (number of tokens + 1 for null terminated */ + size = sizeof(struct device_attribute) * (da_num_tokens + 1); + token_location_attrs = kzalloc(size, GFP_KERNEL); + if (!token_location_attrs) + return -ENOMEM; + token_value_attrs = kzalloc(size, GFP_KERNEL); + if (!token_value_attrs) + goto out_allocate_value; + + /* need to store both location and value + terminator*/ + size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1); + token_attrs = kzalloc(size, GFP_KERNEL); + if (!token_attrs) + goto out_allocate_attrs; + + for (i = 0, j = 0; i < da_num_tokens; i++) { + /* skip empty */ + if (da_tokens[i].tokenID == 0) + continue; + /* add location */ + location_name = kasprintf(GFP_KERNEL, "%04x_location", + da_tokens[i].tokenID); + if (location_name == NULL) + goto out_unwind_strings; + sysfs_attr_init(&token_location_attrs[i].attr); + token_location_attrs[i].attr.name = location_name; + token_location_attrs[i].attr.mode = 0444; + token_location_attrs[i].show = location_show; + token_attrs[j++] = &token_location_attrs[i].attr; + + /* add value */ + value_name = kasprintf(GFP_KERNEL, "%04x_value", + da_tokens[i].tokenID); + if (value_name == NULL) + goto loop_fail_create_value; + sysfs_attr_init(&token_value_attrs[i].attr); + token_value_attrs[i].attr.name = value_name; + token_value_attrs[i].attr.mode = 0444; + token_value_attrs[i].show = value_show; + token_attrs[j++] = &token_value_attrs[i].attr; + continue; + +loop_fail_create_value: + kfree(location_name); + goto out_unwind_strings; + } + smbios_attribute_group.attrs = token_attrs; + + ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); + if (ret) + goto out_unwind_strings; + return 0; + +out_unwind_strings: + while (i--) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); +out_allocate_attrs: + kfree(token_value_attrs); +out_allocate_value: + kfree(token_location_attrs); + + return -ENOMEM; +} + +static void free_group(struct platform_device *pdev) +{ + int i; + + sysfs_remove_group(&pdev->dev.kobj, + &smbios_attribute_group); + for (i = 0; i < da_num_tokens; i++) { + kfree(token_location_attrs[i].attr.name); + kfree(token_value_attrs[i].attr.name); + } + kfree(token_attrs); + kfree(token_value_attrs); + kfree(token_location_attrs); +} + +static int __init dell_smbios_init(void) +{ + int ret, wmi, smm; + + if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { + pr_err("Unable to run on non-Dell system\n"); + return -ENODEV; + } + + dmi_walk(find_tokens, NULL); + + ret = platform_driver_register(&platform_driver); + if (ret) + goto fail_platform_driver; + + platform_device = platform_device_alloc("dell-smbios", 0); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device_alloc; + } + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + /* register backends */ + wmi = init_dell_smbios_wmi(); + if (wmi) + pr_debug("Failed to initialize WMI backend: %d\n", wmi); + smm = init_dell_smbios_smm(); + if (smm) + pr_debug("Failed to initialize SMM backend: %d\n", smm); + if (wmi && smm) { + pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", + wmi, smm); + ret = -ENODEV; + goto fail_create_group; + } + + if (da_tokens) { + /* duplicate tokens will cause problems building sysfs files */ + zero_duplicates(&platform_device->dev); + + ret = build_tokens_sysfs(platform_device); + if (ret) + goto fail_sysfs; + } + + return 0; + +fail_sysfs: + free_group(platform_device); + +fail_create_group: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_platform_device_alloc: + platform_driver_unregister(&platform_driver); + +fail_platform_driver: + kfree(da_tokens); + return ret; +} + +static void __exit dell_smbios_exit(void) +{ + exit_dell_smbios_wmi(); + exit_dell_smbios_smm(); + mutex_lock(&smbios_mutex); + if (platform_device) { + if (da_tokens) + free_group(platform_device); + platform_device_unregister(platform_device); + platform_driver_unregister(&platform_driver); + } + kfree(da_tokens); + mutex_unlock(&smbios_mutex); +} + +module_init(dell_smbios_init); +module_exit(dell_smbios_exit); + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Gabriele Mazzotta "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-smbios-smm.c b/drivers/platform/x86/dell/dell-smbios-smm.c new file mode 100644 index 000000000000..97c52a839a3e --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios-smm.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SMI methods for use with dell-smbios + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * Copyright (c) 2017 Dell Inc. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "dcdbas.h" +#include "dell-smbios.h" + +static int da_command_address; +static int da_command_code; +static struct calling_interface_buffer *buffer; +static struct platform_device *platform_device; +static DEFINE_MUTEX(smm_mutex); + +static const struct dmi_system_id dell_device_table[] __initconst = { + { + .ident = "Dell laptop", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "9"), /*Laptop*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "10"), /*Notebook*/ + }, + }, + { + .ident = "Dell Computer Corporation", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), + DMI_MATCH(DMI_CHASSIS_TYPE, "8"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, dell_device_table); + +static void parse_da_table(const struct dmi_header *dm) +{ + struct calling_interface_structure *table = + container_of(dm, struct calling_interface_structure, header); + + /* 4 bytes of table header, plus 7 bytes of Dell header, plus at least + * 6 bytes of entry + */ + if (dm->length < 17) + return; + + da_command_address = table->cmdIOAddress; + da_command_code = table->cmdIOCode; +} + +static void find_cmd_address(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xda: /* Calling interface */ + parse_da_table(dm); + break; + } +} + +static int dell_smbios_smm_call(struct calling_interface_buffer *input) +{ + struct smi_cmd command; + size_t size; + + size = sizeof(struct calling_interface_buffer); + command.magic = SMI_CMD_MAGIC; + command.command_address = da_command_address; + command.command_code = da_command_code; + command.ebx = virt_to_phys(buffer); + command.ecx = 0x42534931; + + mutex_lock(&smm_mutex); + memcpy(buffer, input, size); + dcdbas_smi_request(&command); + memcpy(input, buffer, size); + mutex_unlock(&smm_mutex); + return 0; +} + +/* When enabled this indicates that SMM won't work */ +static bool test_wsmt_enabled(void) +{ + struct calling_interface_token *wsmt; + + /* if token doesn't exist, SMM will work */ + wsmt = dell_smbios_find_token(WSMT_EN_TOKEN); + if (!wsmt) + return false; + + /* If token exists, try to access over SMM but set a dummy return. + * - If WSMT disabled it will be overwritten by SMM + * - If WSMT enabled then dummy value will remain + */ + buffer->cmd_class = CLASS_TOKEN_READ; + buffer->cmd_select = SELECT_TOKEN_STD; + memset(buffer, 0, sizeof(struct calling_interface_buffer)); + buffer->input[0] = wsmt->location; + buffer->output[0] = 99; + dell_smbios_smm_call(buffer); + if (buffer->output[0] == 99) + return true; + + return false; +} + +int init_dell_smbios_smm(void) +{ + int ret; + /* + * Allocate buffer below 4GB for SMI data--only 32-bit physical addr + * is passed to SMI handler. + */ + buffer = (void *)__get_free_page(GFP_KERNEL | GFP_DMA32); + if (!buffer) + return -ENOMEM; + + dmi_walk(find_cmd_address, NULL); + + if (test_wsmt_enabled()) { + pr_debug("Disabling due to WSMT enabled\n"); + ret = -ENODEV; + goto fail_wsmt; + } + + platform_device = platform_device_alloc("dell-smbios", 1); + if (!platform_device) { + ret = -ENOMEM; + goto fail_platform_device_alloc; + } + + ret = platform_device_add(platform_device); + if (ret) + goto fail_platform_device_add; + + ret = dell_smbios_register_device(&platform_device->dev, + &dell_smbios_smm_call); + if (ret) + goto fail_register; + + return 0; + +fail_register: + platform_device_del(platform_device); + +fail_platform_device_add: + platform_device_put(platform_device); + +fail_wsmt: +fail_platform_device_alloc: + free_page((unsigned long)buffer); + return ret; +} + +void exit_dell_smbios_smm(void) +{ + if (platform_device) { + dell_smbios_unregister_device(&platform_device->dev); + platform_device_unregister(platform_device); + free_page((unsigned long)buffer); + } +} diff --git a/drivers/platform/x86/dell/dell-smbios-wmi.c b/drivers/platform/x86/dell/dell-smbios-wmi.c new file mode 100644 index 000000000000..27a298b7c541 --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios-wmi.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * WMI methods for use with dell-smbios + * + * Copyright (c) 2017 Dell Inc. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" +#include "dell-wmi-descriptor.h" + +static DEFINE_MUTEX(call_mutex); +static DEFINE_MUTEX(list_mutex); +static int wmi_supported; + +struct misc_bios_flags_structure { + struct dmi_header header; + u16 flags0; +} __packed; +#define FLAG_HAS_ACPI_WMI 0x02 + +#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" + +struct wmi_smbios_priv { + struct dell_wmi_smbios_buffer *buf; + struct list_head list; + struct wmi_device *wdev; + struct device *child; + u32 req_buf_size; +}; +static LIST_HEAD(wmi_list); + +static inline struct wmi_smbios_priv *get_first_smbios_priv(void) +{ + return list_first_entry_or_null(&wmi_list, + struct wmi_smbios_priv, + list); +} + +static int run_smbios_call(struct wmi_device *wdev) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct wmi_smbios_priv *priv; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + + priv = dev_get_drvdata(&wdev->dev); + input.length = priv->req_buf_size - sizeof(u64); + input.pointer = &priv->buf->std; + + dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n", + priv->buf->std.cmd_class, priv->buf->std.cmd_select, + priv->buf->std.input[0], priv->buf->std.input[1], + priv->buf->std.input[2], priv->buf->std.input[3]); + + status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type != ACPI_TYPE_BUFFER) { + dev_dbg(&wdev->dev, "received type: %d\n", obj->type); + if (obj->type == ACPI_TYPE_INTEGER) + dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n", + obj->integer.value); + return -EIO; + } + memcpy(&priv->buf->std, obj->buffer.pointer, obj->buffer.length); + dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n", + priv->buf->std.output[0], priv->buf->std.output[1], + priv->buf->std.output[2], priv->buf->std.output[3]); + kfree(output.pointer); + + return 0; +} + +static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer) +{ + struct wmi_smbios_priv *priv; + size_t difference; + size_t size; + int ret; + + mutex_lock(&call_mutex); + priv = get_first_smbios_priv(); + if (!priv) { + ret = -ENODEV; + goto out_wmi_call; + } + + size = sizeof(struct calling_interface_buffer); + difference = priv->req_buf_size - sizeof(u64) - size; + + memset(&priv->buf->ext, 0, difference); + memcpy(&priv->buf->std, buffer, size); + ret = run_smbios_call(priv->wdev); + memcpy(buffer, &priv->buf->std, size); +out_wmi_call: + mutex_unlock(&call_mutex); + + return ret; +} + +static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd, + struct wmi_ioctl_buffer *arg) +{ + struct wmi_smbios_priv *priv; + int ret = 0; + + switch (cmd) { + case DELL_WMI_SMBIOS_CMD: + mutex_lock(&call_mutex); + priv = dev_get_drvdata(&wdev->dev); + if (!priv) { + ret = -ENODEV; + goto fail_smbios_cmd; + } + memcpy(priv->buf, arg, priv->req_buf_size); + if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) { + dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n", + priv->buf->std.cmd_class, + priv->buf->std.cmd_select, + priv->buf->std.input[0]); + ret = -EFAULT; + goto fail_smbios_cmd; + } + ret = run_smbios_call(priv->wdev); + if (ret) + goto fail_smbios_cmd; + memcpy(arg, priv->buf, priv->req_buf_size); +fail_smbios_cmd: + mutex_unlock(&call_mutex); + break; + default: + ret = -ENOIOCTLCMD; + } + return ret; +} + +static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct wmi_driver *wdriver = + container_of(wdev->dev.driver, struct wmi_driver, driver); + struct wmi_smbios_priv *priv; + u32 hotfix; + int count; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; + + priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* WMI buffer size will be either 4k or 32k depending on machine */ + if (!dell_wmi_get_size(&priv->req_buf_size)) + return -EPROBE_DEFER; + + /* some SMBIOS calls fail unless BIOS contains hotfix */ + if (!dell_wmi_get_hotfix(&hotfix)) + return -EPROBE_DEFER; + if (!hotfix) { + dev_warn(&wdev->dev, + "WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n", + hotfix); + wdriver->filter_callback = NULL; + } + + /* add in the length object we will use internally with ioctl */ + priv->req_buf_size += sizeof(u64); + ret = set_required_buffer_size(wdev, priv->req_buf_size); + if (ret) + return ret; + + count = get_order(priv->req_buf_size); + priv->buf = (void *)__get_free_pages(GFP_KERNEL, count); + if (!priv->buf) + return -ENOMEM; + + /* ID is used by dell-smbios to set priority of drivers */ + wdev->dev.id = 1; + ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call); + if (ret) + goto fail_register; + + priv->wdev = wdev; + dev_set_drvdata(&wdev->dev, priv); + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + + return 0; + +fail_register: + free_pages((unsigned long)priv->buf, count); + return ret; +} + +static int dell_smbios_wmi_remove(struct wmi_device *wdev) +{ + struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev); + int count; + + mutex_lock(&call_mutex); + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); + dell_smbios_unregister_device(&wdev->dev); + count = get_order(priv->req_buf_size); + free_pages((unsigned long)priv->buf, count); + mutex_unlock(&call_mutex); + return 0; +} + +static const struct wmi_device_id dell_smbios_wmi_id_table[] = { + { .guid_string = DELL_WMI_SMBIOS_GUID }, + { }, +}; + +static void parse_b1_table(const struct dmi_header *dm) +{ + struct misc_bios_flags_structure *flags = + container_of(dm, struct misc_bios_flags_structure, header); + + /* 4 bytes header, 8 bytes flags */ + if (dm->length < 12) + return; + if (dm->handle != 0xb100) + return; + if ((flags->flags0 & FLAG_HAS_ACPI_WMI)) + wmi_supported = 1; +} + +static void find_b1(const struct dmi_header *dm, void *dummy) +{ + switch (dm->type) { + case 0xb1: /* misc bios flags */ + parse_b1_table(dm); + break; + } +} + +static struct wmi_driver dell_smbios_wmi_driver = { + .driver = { + .name = "dell-smbios", + }, + .probe = dell_smbios_wmi_probe, + .remove = dell_smbios_wmi_remove, + .id_table = dell_smbios_wmi_id_table, + .filter_callback = dell_smbios_wmi_filter, +}; + +int init_dell_smbios_wmi(void) +{ + dmi_walk(find_b1, NULL); + + if (!wmi_supported) + return -ENODEV; + + return wmi_driver_register(&dell_smbios_wmi_driver); +} + +void exit_dell_smbios_wmi(void) +{ + wmi_driver_unregister(&dell_smbios_wmi_driver); +} + +MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table); diff --git a/drivers/platform/x86/dell/dell-smbios.h b/drivers/platform/x86/dell/dell-smbios.h new file mode 100644 index 000000000000..75fa8ea0476d --- /dev/null +++ b/drivers/platform/x86/dell/dell-smbios.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Common functions for kernel modules using Dell SMBIOS + * + * Copyright (c) Red Hat + * Copyright (c) 2014 Gabriele Mazzotta + * Copyright (c) 2014 Pali Rohár + * + * Based on documentation in the libsmbios package: + * Copyright (C) 2005-2014 Dell Inc. + */ + +#ifndef _DELL_SMBIOS_H_ +#define _DELL_SMBIOS_H_ + +#include +#include + +/* Classes and selects used only in kernel drivers */ +#define CLASS_KBD_BACKLIGHT 4 +#define SELECT_KBD_BACKLIGHT 11 + +/* Tokens used in kernel drivers, any of these + * should be filtered from userspace access + */ +#define BRIGHTNESS_TOKEN 0x007d +#define KBD_LED_AC_TOKEN 0x0451 +#define KBD_LED_OFF_TOKEN 0x01E1 +#define KBD_LED_ON_TOKEN 0x01E2 +#define KBD_LED_AUTO_TOKEN 0x01E3 +#define KBD_LED_AUTO_25_TOKEN 0x02EA +#define KBD_LED_AUTO_50_TOKEN 0x02EB +#define KBD_LED_AUTO_75_TOKEN 0x02EC +#define KBD_LED_AUTO_100_TOKEN 0x02F6 +#define GLOBAL_MIC_MUTE_ENABLE 0x0364 +#define GLOBAL_MIC_MUTE_DISABLE 0x0365 + +struct notifier_block; + +struct calling_interface_token { + u16 tokenID; + u16 location; + union { + u16 value; + u16 stringlength; + }; +}; + +struct calling_interface_structure { + struct dmi_header header; + u16 cmdIOAddress; + u8 cmdIOCode; + u32 supportedCmds; + struct calling_interface_token tokens[]; +} __packed; + +int dell_smbios_register_device(struct device *d, void *call_fn); +void dell_smbios_unregister_device(struct device *d); + +int dell_smbios_error(int value); +int dell_smbios_call_filter(struct device *d, + struct calling_interface_buffer *buffer); +int dell_smbios_call(struct calling_interface_buffer *buffer); + +struct calling_interface_token *dell_smbios_find_token(int tokenid); + +enum dell_laptop_notifier_actions { + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, +}; + +int dell_laptop_register_notifier(struct notifier_block *nb); +int dell_laptop_unregister_notifier(struct notifier_block *nb); +void dell_laptop_call_notifier(unsigned long action, void *data); + +/* for the supported backends */ +#ifdef CONFIG_DELL_SMBIOS_WMI +int init_dell_smbios_wmi(void); +void exit_dell_smbios_wmi(void); +#else /* CONFIG_DELL_SMBIOS_WMI */ +static inline int init_dell_smbios_wmi(void) +{ + return -ENODEV; +} +static inline void exit_dell_smbios_wmi(void) +{} +#endif /* CONFIG_DELL_SMBIOS_WMI */ + +#ifdef CONFIG_DELL_SMBIOS_SMM +int init_dell_smbios_smm(void); +void exit_dell_smbios_smm(void); +#else /* CONFIG_DELL_SMBIOS_SMM */ +static inline int init_dell_smbios_smm(void) +{ + return -ENODEV; +} +static inline void exit_dell_smbios_smm(void) +{} +#endif /* CONFIG_DELL_SMBIOS_SMM */ + +#endif /* _DELL_SMBIOS_H_ */ diff --git a/drivers/platform/x86/dell/dell-smo8800.c b/drivers/platform/x86/dell/dell-smo8800.c new file mode 100644 index 000000000000..5d9304a7de1b --- /dev/null +++ b/drivers/platform/x86/dell/dell-smo8800.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver + * + * Copyright (C) 2012 Sonal Santan + * Copyright (C) 2014 Pali Rohár + * + * This is loosely based on lis3lv02d driver. + */ + +#define DRIVER_NAME "smo8800" + +#include +#include +#include +#include +#include +#include +#include + +struct smo8800_device { + u32 irq; /* acpi device irq */ + atomic_t counter; /* count after last read */ + struct miscdevice miscdev; /* for /dev/freefall */ + unsigned long misc_opened; /* whether the device is open */ + wait_queue_head_t misc_wait; /* Wait queue for the misc dev */ + struct device *dev; /* acpi device */ +}; + +static irqreturn_t smo8800_interrupt_quick(int irq, void *data) +{ + struct smo8800_device *smo8800 = data; + + atomic_inc(&smo8800->counter); + wake_up_interruptible(&smo8800->misc_wait); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t smo8800_interrupt_thread(int irq, void *data) +{ + struct smo8800_device *smo8800 = data; + + dev_info(smo8800->dev, "detected free fall\n"); + return IRQ_HANDLED; +} + +static acpi_status smo8800_get_resource(struct acpi_resource *resource, + void *context) +{ + struct acpi_resource_extended_irq *irq; + + if (resource->type != ACPI_RESOURCE_TYPE_EXTENDED_IRQ) + return AE_OK; + + irq = &resource->data.extended_irq; + if (!irq || !irq->interrupt_count) + return AE_OK; + + *((u32 *)context) = irq->interrupts[0]; + return AE_CTRL_TERMINATE; +} + +static u32 smo8800_get_irq(struct acpi_device *device) +{ + u32 irq = 0; + acpi_status status; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + smo8800_get_resource, &irq); + if (ACPI_FAILURE(status)) { + dev_err(&device->dev, "acpi_walk_resources failed\n"); + return 0; + } + + return irq; +} + +static ssize_t smo8800_misc_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + u32 data = 0; + unsigned char byte_data; + ssize_t retval = 1; + + if (count < 1) + return -EINVAL; + + atomic_set(&smo8800->counter, 0); + retval = wait_event_interruptible(smo8800->misc_wait, + (data = atomic_xchg(&smo8800->counter, 0))); + + if (retval) + return retval; + + retval = 1; + + if (data < 255) + byte_data = data; + else + byte_data = 255; + + if (put_user(byte_data, buf)) + retval = -EFAULT; + + return retval; +} + +static int smo8800_misc_open(struct inode *inode, struct file *file) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + if (test_and_set_bit(0, &smo8800->misc_opened)) + return -EBUSY; /* already open */ + + atomic_set(&smo8800->counter, 0); + return 0; +} + +static int smo8800_misc_release(struct inode *inode, struct file *file) +{ + struct smo8800_device *smo8800 = container_of(file->private_data, + struct smo8800_device, miscdev); + + clear_bit(0, &smo8800->misc_opened); /* release the device */ + return 0; +} + +static const struct file_operations smo8800_misc_fops = { + .owner = THIS_MODULE, + .read = smo8800_misc_read, + .open = smo8800_misc_open, + .release = smo8800_misc_release, +}; + +static int smo8800_add(struct acpi_device *device) +{ + int err; + struct smo8800_device *smo8800; + + smo8800 = devm_kzalloc(&device->dev, sizeof(*smo8800), GFP_KERNEL); + if (!smo8800) { + dev_err(&device->dev, "failed to allocate device data\n"); + return -ENOMEM; + } + + smo8800->dev = &device->dev; + smo8800->miscdev.minor = MISC_DYNAMIC_MINOR; + smo8800->miscdev.name = "freefall"; + smo8800->miscdev.fops = &smo8800_misc_fops; + + init_waitqueue_head(&smo8800->misc_wait); + + err = misc_register(&smo8800->miscdev); + if (err) { + dev_err(&device->dev, "failed to register misc dev: %d\n", err); + return err; + } + + device->driver_data = smo8800; + + smo8800->irq = smo8800_get_irq(device); + if (!smo8800->irq) { + dev_err(&device->dev, "failed to obtain IRQ\n"); + err = -EINVAL; + goto error; + } + + err = request_threaded_irq(smo8800->irq, smo8800_interrupt_quick, + smo8800_interrupt_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + DRIVER_NAME, smo8800); + if (err) { + dev_err(&device->dev, + "failed to request thread for IRQ %d: %d\n", + smo8800->irq, err); + goto error; + } + + dev_dbg(&device->dev, "device /dev/freefall registered with IRQ %d\n", + smo8800->irq); + return 0; + +error: + misc_deregister(&smo8800->miscdev); + return err; +} + +static int smo8800_remove(struct acpi_device *device) +{ + struct smo8800_device *smo8800 = device->driver_data; + + free_irq(smo8800->irq, smo8800); + misc_deregister(&smo8800->miscdev); + dev_dbg(&device->dev, "device /dev/freefall unregistered\n"); + return 0; +} + +/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ +static const struct acpi_device_id smo8800_ids[] = { + { "SMO8800", 0 }, + { "SMO8801", 0 }, + { "SMO8810", 0 }, + { "SMO8811", 0 }, + { "SMO8820", 0 }, + { "SMO8821", 0 }, + { "SMO8830", 0 }, + { "SMO8831", 0 }, + { "", 0 }, +}; + +MODULE_DEVICE_TABLE(acpi, smo8800_ids); + +static struct acpi_driver smo8800_driver = { + .name = DRIVER_NAME, + .class = "Latitude", + .ids = smo8800_ids, + .ops = { + .add = smo8800_add, + .remove = smo8800_remove, + }, + .owner = THIS_MODULE, +}; + +module_acpi_driver(smo8800_driver); + +MODULE_DESCRIPTION("Dell Latitude freefall driver (ACPI SMO88XX)"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sonal Santan, Pali Rohár"); diff --git a/drivers/platform/x86/dell/dell-wmi-aio.c b/drivers/platform/x86/dell/dell-wmi-aio.c new file mode 100644 index 000000000000..c7b7f1e403fb --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-aio.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * WMI hotkeys support for Dell All-In-One series + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("WMI hotkeys driver for Dell All-In-One series"); +MODULE_LICENSE("GPL"); + +#define EVENT_GUID1 "284A0E6B-380E-472A-921F-E52786257FB4" +#define EVENT_GUID2 "02314822-307C-4F66-BF0E-48AEAEB26CC8" + +struct dell_wmi_event { + u16 length; + /* 0x000: A hot key pressed or an event occurred + * 0x00F: A sequence of hot keys are pressed */ + u16 type; + u16 event[]; +}; + +static const char *dell_wmi_aio_guids[] = { + EVENT_GUID1, + EVENT_GUID2, + NULL +}; + +MODULE_ALIAS("wmi:"EVENT_GUID1); +MODULE_ALIAS("wmi:"EVENT_GUID2); + +static const struct key_entry dell_wmi_aio_keymap[] = { + { KE_KEY, 0xc0, { KEY_VOLUMEUP } }, + { KE_KEY, 0xc1, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0xe030, { KEY_VOLUMEUP } }, + { KE_KEY, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_KEY, 0xe020, { KEY_MUTE } }, + { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, + { KE_END, 0 } +}; + +static struct input_dev *dell_wmi_aio_input_dev; + +/* + * The new WMI event data format will follow the dell_wmi_event structure + * So, we will check if the buffer matches the format + */ +static bool dell_wmi_aio_event_check(u8 *buffer, int length) +{ + struct dell_wmi_event *event = (struct dell_wmi_event *)buffer; + + if (event == NULL || length < 6) + return false; + + if ((event->type == 0 || event->type == 0xf) && + event->length >= 2) + return true; + + return false; +} + +static void dell_wmi_aio_notify(u32 value, void *context) +{ + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + struct dell_wmi_event *event; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (status != AE_OK) { + pr_info("bad event status 0x%x\n", status); + return; + } + + obj = (union acpi_object *)response.pointer; + if (obj) { + unsigned int scancode = 0; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + /* Most All-In-One correctly return integer scancode */ + scancode = obj->integer.value; + sparse_keymap_report_event(dell_wmi_aio_input_dev, + scancode, 1, true); + break; + case ACPI_TYPE_BUFFER: + if (dell_wmi_aio_event_check(obj->buffer.pointer, + obj->buffer.length)) { + event = (struct dell_wmi_event *) + obj->buffer.pointer; + scancode = event->event[0]; + } else { + /* Broken machines return the scancode in a + buffer */ + if (obj->buffer.pointer && + obj->buffer.length > 0) + scancode = obj->buffer.pointer[0]; + } + if (scancode) + sparse_keymap_report_event( + dell_wmi_aio_input_dev, + scancode, 1, true); + break; + } + } + kfree(obj); +} + +static int __init dell_wmi_aio_input_setup(void) +{ + int err; + + dell_wmi_aio_input_dev = input_allocate_device(); + + if (!dell_wmi_aio_input_dev) + return -ENOMEM; + + dell_wmi_aio_input_dev->name = "Dell AIO WMI hotkeys"; + dell_wmi_aio_input_dev->phys = "wmi/input0"; + dell_wmi_aio_input_dev->id.bustype = BUS_HOST; + + err = sparse_keymap_setup(dell_wmi_aio_input_dev, + dell_wmi_aio_keymap, NULL); + if (err) { + pr_err("Unable to setup input device keymap\n"); + goto err_free_dev; + } + err = input_register_device(dell_wmi_aio_input_dev); + if (err) { + pr_info("Unable to register input device\n"); + goto err_free_dev; + } + return 0; + +err_free_dev: + input_free_device(dell_wmi_aio_input_dev); + return err; +} + +static const char *dell_wmi_aio_find(void) +{ + int i; + + for (i = 0; dell_wmi_aio_guids[i] != NULL; i++) + if (wmi_has_guid(dell_wmi_aio_guids[i])) + return dell_wmi_aio_guids[i]; + + return NULL; +} + +static int __init dell_wmi_aio_init(void) +{ + int err; + const char *guid; + + guid = dell_wmi_aio_find(); + if (!guid) { + pr_warn("No known WMI GUID found\n"); + return -ENXIO; + } + + err = dell_wmi_aio_input_setup(); + if (err) + return err; + + err = wmi_install_notify_handler(guid, dell_wmi_aio_notify, NULL); + if (err) { + pr_err("Unable to register notify handler - %d\n", err); + input_unregister_device(dell_wmi_aio_input_dev); + return err; + } + + return 0; +} + +static void __exit dell_wmi_aio_exit(void) +{ + const char *guid; + + guid = dell_wmi_aio_find(); + wmi_remove_notify_handler(guid); + input_unregister_device(dell_wmi_aio_input_dev); +} + +module_init(dell_wmi_aio_init); +module_exit(dell_wmi_aio_exit); diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.c b/drivers/platform/x86/dell/dell-wmi-descriptor.c new file mode 100644 index 000000000000..a068900ae8a1 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-descriptor.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Dell WMI descriptor driver + * + * Copyright (C) 2017 Dell Inc. All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include "dell-wmi-descriptor.h" + +#define DELL_WMI_DESCRIPTOR_GUID "8D9DDCBC-A997-11DA-B012-B622A1EF5492" + +struct descriptor_priv { + struct list_head list; + u32 interface_version; + u32 size; + u32 hotfix; +}; +static int descriptor_valid = -EPROBE_DEFER; +static LIST_HEAD(wmi_list); +static DEFINE_MUTEX(list_mutex); + +int dell_wmi_get_descriptor_valid(void) +{ + if (!wmi_has_guid(DELL_WMI_DESCRIPTOR_GUID)) + return -ENODEV; + + return descriptor_valid; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_descriptor_valid); + +bool dell_wmi_get_interface_version(u32 *version) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *version = priv->interface_version; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_interface_version); + +bool dell_wmi_get_size(u32 *size) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *size = priv->size; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_size); + +bool dell_wmi_get_hotfix(u32 *hotfix) +{ + struct descriptor_priv *priv; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct descriptor_priv, + list); + if (priv) { + *hotfix = priv->hotfix; + ret = true; + } + mutex_unlock(&list_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); + +/* + * Descriptor buffer is 128 byte long and contains: + * + * Name Offset Length Value + * Vendor Signature 0 4 "DELL" + * Object Signature 4 4 " WMI" + * WMI Interface Version 8 4 + * WMI buffer length 12 4 + * WMI hotfix number 16 4 + */ +static int dell_wmi_descriptor_probe(struct wmi_device *wdev, + const void *context) +{ + union acpi_object *obj = NULL; + struct descriptor_priv *priv; + u32 *buffer; + int ret; + + obj = wmidev_block_query(wdev, 0); + if (!obj) { + dev_err(&wdev->dev, "failed to read Dell WMI descriptor\n"); + ret = -EIO; + goto out; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Dell descriptor has wrong type\n"); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + + /* Although it's not technically a failure, this would lead to + * unexpected behavior + */ + if (obj->buffer.length != 128) { + dev_err(&wdev->dev, + "Dell descriptor buffer has unexpected length (%d)\n", + obj->buffer.length); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + + buffer = (u32 *)obj->buffer.pointer; + + if (strncmp(obj->string.pointer, "DELL WMI", 8) != 0) { + dev_err(&wdev->dev, "Dell descriptor buffer has invalid signature (%8ph)\n", + buffer); + ret = -EINVAL; + descriptor_valid = ret; + goto out; + } + descriptor_valid = 0; + + if (buffer[2] != 0 && buffer[2] != 1) + dev_warn(&wdev->dev, "Dell descriptor buffer has unknown version (%lu)\n", + (unsigned long) buffer[2]); + + priv = devm_kzalloc(&wdev->dev, sizeof(struct descriptor_priv), + GFP_KERNEL); + + if (!priv) { + ret = -ENOMEM; + goto out; + } + + priv->interface_version = buffer[2]; + priv->size = buffer[3]; + priv->hotfix = buffer[4]; + ret = 0; + dev_set_drvdata(&wdev->dev, priv); + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + + dev_dbg(&wdev->dev, "Detected Dell WMI interface version %lu, buffer size %lu, hotfix %lu\n", + (unsigned long) priv->interface_version, + (unsigned long) priv->size, + (unsigned long) priv->hotfix); + +out: + kfree(obj); + return ret; +} + +static int dell_wmi_descriptor_remove(struct wmi_device *wdev) +{ + struct descriptor_priv *priv = dev_get_drvdata(&wdev->dev); + + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); + return 0; +} + +static const struct wmi_device_id dell_wmi_descriptor_id_table[] = { + { .guid_string = DELL_WMI_DESCRIPTOR_GUID }, + { }, +}; + +static struct wmi_driver dell_wmi_descriptor_driver = { + .driver = { + .name = "dell-wmi-descriptor", + }, + .probe = dell_wmi_descriptor_probe, + .remove = dell_wmi_descriptor_remove, + .id_table = dell_wmi_descriptor_id_table, +}; + +module_wmi_driver(dell_wmi_descriptor_driver); + +MODULE_DEVICE_TABLE(wmi, dell_wmi_descriptor_id_table); +MODULE_AUTHOR("Mario Limonciello "); +MODULE_DESCRIPTION("Dell WMI descriptor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-wmi-descriptor.h b/drivers/platform/x86/dell/dell-wmi-descriptor.h new file mode 100644 index 000000000000..1f469fef1535 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-descriptor.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Dell WMI descriptor driver + * + * Copyright (c) 2017 Dell Inc. + */ + +#ifndef _DELL_WMI_DESCRIPTOR_H_ +#define _DELL_WMI_DESCRIPTOR_H_ + +#include + +/* possible return values: + * -ENODEV: Descriptor GUID missing from WMI bus + * -EPROBE_DEFER: probing for dell-wmi-descriptor not yet run + * 0: valid descriptor, successfully probed + * < 0: invalid descriptor, don't probe dependent devices + */ +int dell_wmi_get_descriptor_valid(void); + +bool dell_wmi_get_interface_version(u32 *version); +bool dell_wmi_get_size(u32 *size); +bool dell_wmi_get_hotfix(u32 *hotfix); + +#endif diff --git a/drivers/platform/x86/dell/dell-wmi-led.c b/drivers/platform/x86/dell/dell-wmi-led.c new file mode 100644 index 000000000000..5bedaf7f0633 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-led.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2010 Dell Inc. + * Louis Davis + * Jim Dailey + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +MODULE_AUTHOR("Louis Davis/Jim Dailey"); +MODULE_DESCRIPTION("Dell LED Control Driver"); +MODULE_LICENSE("GPL"); + +#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396" +MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); + +/* Error Result Codes: */ +#define INVALID_DEVICE_ID 250 +#define INVALID_PARAMETER 251 +#define INVALID_BUFFER 252 +#define INTERFACE_ERROR 253 +#define UNSUPPORTED_COMMAND 254 +#define UNSPECIFIED_ERROR 255 + +/* Device ID */ +#define DEVICE_ID_PANEL_BACK 1 + +/* LED Commands */ +#define CMD_LED_ON 16 +#define CMD_LED_OFF 17 +#define CMD_LED_BLINK 18 + +struct bios_args { + unsigned char length; + unsigned char result_code; + unsigned char device_id; + unsigned char command; + unsigned char on_time; + unsigned char off_time; +}; + +static int dell_led_perform_fn(u8 length, u8 result_code, u8 device_id, + u8 command, u8 on_time, u8 off_time) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct bios_args *bios_return; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + u8 return_code; + + struct bios_args args = { + .length = length, + .result_code = result_code, + .device_id = device_id, + .command = command, + .on_time = on_time, + .off_time = off_time + }; + + input.length = sizeof(struct bios_args); + input.pointer = &args; + + status = wmi_evaluate_method(DELL_LED_BIOS_GUID, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return status; + + obj = output.pointer; + + if (!obj) + return -EINVAL; + if (obj->type != ACPI_TYPE_BUFFER) { + kfree(obj); + return -EINVAL; + } + + bios_return = ((struct bios_args *)obj->buffer.pointer); + return_code = bios_return->result_code; + + kfree(obj); + + return return_code; +} + +static int led_on(void) +{ + return dell_led_perform_fn(3, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_ON, /* Command */ + 0, /* not used */ + 0); /* not used */ +} + +static int led_off(void) +{ + return dell_led_perform_fn(3, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_OFF, /* Command */ + 0, /* not used */ + 0); /* not used */ +} + +static int led_blink(unsigned char on_eighths, unsigned char off_eighths) +{ + return dell_led_perform_fn(5, /* Length of command */ + INTERFACE_ERROR, /* Init to INTERFACE_ERROR */ + DEVICE_ID_PANEL_BACK, /* Device ID */ + CMD_LED_BLINK, /* Command */ + on_eighths, /* blink on in eigths of a second */ + off_eighths); /* blink off in eights of a second */ +} + +static void dell_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value == LED_OFF) + led_off(); + else + led_on(); +} + +static int dell_led_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + unsigned long on_eighths; + unsigned long off_eighths; + + /* + * The Dell LED delay is based on 125ms intervals. + * Need to round up to next interval. + */ + + on_eighths = DIV_ROUND_UP(*delay_on, 125); + on_eighths = clamp_t(unsigned long, on_eighths, 1, 255); + *delay_on = on_eighths * 125; + + off_eighths = DIV_ROUND_UP(*delay_off, 125); + off_eighths = clamp_t(unsigned long, off_eighths, 1, 255); + *delay_off = off_eighths * 125; + + led_blink(on_eighths, off_eighths); + + return 0; +} + +static struct led_classdev dell_led = { + .name = "dell::lid", + .brightness = LED_OFF, + .max_brightness = 1, + .brightness_set = dell_led_set, + .blink_set = dell_led_blink, + .flags = LED_CORE_SUSPENDRESUME, +}; + +static int __init dell_led_init(void) +{ + int error = 0; + + if (!wmi_has_guid(DELL_LED_BIOS_GUID)) + return -ENODEV; + + error = led_off(); + if (error != 0) + return -ENODEV; + + return led_classdev_register(NULL, &dell_led); +} + +static void __exit dell_led_exit(void) +{ + led_classdev_unregister(&dell_led); + + led_off(); +} + +module_init(dell_led_init); +module_exit(dell_led_exit); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile new file mode 100644 index 000000000000..825fb2fbeea8 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o +dell-wmi-sysman-objs := sysman.o \ + enum-attributes.o \ + int-attributes.o \ + string-attributes.o \ + passobj-attributes.o \ + biosattr-interface.o \ + passwordattr-interface.o diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c new file mode 100644 index 000000000000..f95d8ddace5a --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/biosattr-interface.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET methods under BIOS attributes interface GUID for use + * with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include +#include "dell-wmi-sysman.h" + +#define SETDEFAULTVALUES_METHOD_ID 0x02 +#define SETBIOSDEFAULTS_METHOD_ID 0x03 +#define SETATTRIBUTE_METHOD_ID 0x04 + +static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, + int method_id) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + if (wmi_priv.pending_changes == 0) { + wmi_priv.pending_changes = 1; + /* let userland know it may need to check reboot pending again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + } + kfree(output.pointer); + return map_wmi_error(ret); +} + +/** + * set_attribute() - Update an attribute value + * @a_name: The attribute name + * @a_value: The attribute value + * + * Sets an attribute to new value + */ +int set_attribute(const char *a_name, const char *a_value) +{ + size_t security_area_size, buffer_size; + size_t a_name_size, a_value_size; + char *buffer = NULL, *start; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + a_name_size = calculate_string_buffer(a_name); + a_value_size = calculate_string_buffer(a_value); + buffer_size = security_area_size + a_name_size + a_value_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, a_name_size, a_name); + if (ret < 0) + goto out; + start += ret; + ret = populate_string_buffer(start, a_value_size, a_value); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, + buffer, buffer_size, + SETATTRIBUTE_METHOD_ID); + if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +/** + * set_bios_defaults() - Resets BIOS defaults + * @deftype: the type of BIOS value reset to issue. + * + * Resets BIOS defaults + */ +int set_bios_defaults(u8 deftype) +{ + size_t security_area_size, buffer_size; + size_t integer_area_size = sizeof(u8); + char *buffer = NULL; + u8 *defaultType; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + buffer_size = security_area_size + integer_area_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + defaultType = buffer + security_area_size; + *defaultType = deftype; + + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, + SETBIOSDEFAULTS_METHOD_ID); + if (ret) + dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret); + + kfree(buffer); +out: + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_set_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_set_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_set_interface_driver = { + .driver = { + .name = DRIVER_NAME + }, + .probe = bios_attr_set_interface_probe, + .remove = bios_attr_set_interface_remove, + .id_table = bios_attr_set_interface_id_table, +}; + +int init_bios_attr_set_interface(void) +{ + return wmi_driver_register(&bios_attr_set_interface_driver); +} + +void exit_bios_attr_set_interface(void) +{ + wmi_driver_unregister(&bios_attr_set_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h new file mode 100644 index 000000000000..b80f2a62ea3f --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Definitions for kernel modules using Dell WMI System Management Driver + * + * Copyright (c) 2020 Dell Inc. + */ + +#ifndef _DELL_WMI_BIOS_ATTR_H_ +#define _DELL_WMI_BIOS_ATTR_H_ + +#include +#include +#include +#include +#include + +#define DRIVER_NAME "dell-wmi-sysman" +#define MAX_BUFF 512 + +#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5" +#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA" +#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9" +#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4" +#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4" +#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A" + +struct enumeration_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char dell_value_modifier[MAX_BUFF]; + char possible_values[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; +}; + +struct integer_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; + int scalar_increment; + int default_value; + int min_value; + int max_value; +}; + +struct str_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char display_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + int min_length; + int max_length; +}; + +struct po_data { + struct kobject *attr_name_kobj; + char attribute_name[MAX_BUFF]; + int min_password_length; + int max_password_length; +}; + +struct wmi_sysman_priv { + char current_admin_password[MAX_BUFF]; + char current_system_password[MAX_BUFF]; + struct wmi_device *password_attr_wdev; + struct wmi_device *bios_attr_wdev; + struct kset *authentication_dir_kset; + struct kset *main_dir_kset; + struct device *class_dev; + struct enumeration_data *enumeration_data; + int enumeration_instances_count; + struct integer_data *integer_data; + int integer_instances_count; + struct str_data *str_data; + int str_instances_count; + struct po_data *po_data; + int po_instances_count; + bool pending_changes; + struct mutex mutex; +}; + +/* global structure used by multiple WMI interfaces */ +extern struct wmi_sysman_priv wmi_priv; + +enum { ENUM, INT, STR, PO }; + +enum { + ATTR_NAME, + DISPL_NAME_LANG_CODE, + DISPLAY_NAME, + DEFAULT_VAL, + CURRENT_VAL, + MODIFIER +}; + +#define get_instance_id(type) \ +static int get_##type##_instance_id(struct kobject *kobj) \ +{ \ + int i; \ + for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ + return -EIO; \ +} + +#define attribute_s_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_n_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_property_store(curr_val, type) \ +static ssize_t curr_val##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + char *p, *buf_cp; \ + int i, ret = -EIO; \ + buf_cp = kstrdup(buf, GFP_KERNEL); \ + if (!buf_cp) \ + return -ENOMEM; \ + p = memchr(buf_cp, '\n', count); \ + \ + if (p != NULL) \ + *p = '\0'; \ + i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + ret = validate_##type##_input(i, buf_cp); \ + if (!ret) \ + ret = set_attribute(kobj->name, buf_cp); \ + kfree(buf_cp); \ + return ret ? ret : count; \ +} + +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); +int get_instance_count(const char *guid_string); +void strlcpy_attr(char *dest, char *src); + +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_enum_data(void); +void exit_enum_attributes(void); + +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_int_data(void); +void exit_int_attributes(void); + +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_str_data(void); +void exit_str_attributes(void); + +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_po_data(void); +void exit_po_attributes(void); + +int set_attribute(const char *a_name, const char *a_value); +int set_bios_defaults(u8 defType); + +void exit_bios_attr_set_interface(void); +int init_bios_attr_set_interface(void); +int map_wmi_error(int error_code); +size_t calculate_string_buffer(const char *str); +size_t calculate_security_buffer(char *authentication); +void populate_security_buffer(char *buffer, char *authentication); +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); +int set_new_password(const char *password_type, const char *new); +int init_bios_attr_pass_interface(void); +void exit_bios_attr_pass_interface(void); + +#endif diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c new file mode 100644 index 000000000000..80f4b7785c6c --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to enumeration type attributes under + * BIOS Enumeration GUID for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +get_instance_id(enumeration); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_enumeration_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_enumeration_input() - Validate input of current_value against possible values + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_enumeration_input(int instance_id, const char *buf) +{ + char *options, *tmp, *p; + int ret = -EINVAL; + + options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values, + GFP_KERNEL); + if (!options) + return -ENOMEM; + + while ((p = strsep(&options, ";")) != NULL) { + if (!*p) + continue; + if (!strcasecmp(p, buf)) { + ret = 0; + break; + } + } + + kfree(tmp); + return ret; +} + +attribute_s_property_show(display_name_language_code, enumeration); +static struct kobj_attribute displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, enumeration); +static struct kobj_attribute displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, enumeration); +static struct kobj_attribute default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, enumeration); +static struct kobj_attribute current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, enumeration); +static struct kobj_attribute modifier = + __ATTR_RO(dell_modifier); + +attribute_s_property_show(dell_value_modifier, enumeration); +static struct kobj_attribute value_modfr = + __ATTR_RO(dell_value_modifier); + +attribute_s_property_show(possible_values, enumeration); +static struct kobj_attribute poss_val = + __ATTR_RO(possible_values); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "enumeration\n"); +} +static struct kobj_attribute type = + __ATTR_RO(type); + +static struct attribute *enumeration_attrs[] = { + &displ_langcode.attr, + &displ_name.attr, + &default_val.attr, + ¤t_val.attr, + &modifier.attr, + &value_modfr.attr, + &poss_val.attr, + &type.attr, + NULL, +}; + +static const struct attribute_group enumeration_attr_group = { + .attrs = enumeration_attrs, +}; + +int alloc_enum_data(void) +{ + int ret = 0; + + wmi_priv.enumeration_instances_count = + get_instance_count(DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + wmi_priv.enumeration_data = kcalloc(wmi_priv.enumeration_instances_count, + sizeof(struct enumeration_data), GFP_KERNEL); + if (!wmi_priv.enumeration_data) { + wmi_priv.enumeration_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_enum_data() - Populate all properties of an instance under enumeration attribute + * @enumeration_obj: ACPI object with enumeration data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + int i, next_obj, value_modifier_count, possible_values_count; + + wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, + enumeration_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, + enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, + enumeration_obj[DISPLAY_NAME].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, + enumeration_obj[DEFAULT_VAL].string.pointer); + strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, + enumeration_obj[MODIFIER].string.pointer); + + next_obj = MODIFIER + 1; + + value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; + + for (i = 0; i < value_modifier_count; i++) { + strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, + enumeration_obj[++next_obj].string.pointer); + strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); + } + + possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; + + for (i = 0; i < possible_values_count; i++) { + strcat(wmi_priv.enumeration_data[instance_id].possible_values, + enumeration_obj[++next_obj].string.pointer); + strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); + } + + return sysfs_create_group(attr_name_kobj, &enumeration_attr_group); +} + +/** + * exit_enum_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_enum_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.enumeration_instances_count; instance_id++) { + if (wmi_priv.enumeration_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.enumeration_data[instance_id].attr_name_kobj, + &enumeration_attr_group); + } + kfree(wmi_priv.enumeration_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c new file mode 100644 index 000000000000..75aedbb733be --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to integer type attributes under BIOS Integer GUID for use with + * dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum int_properties {MIN_VALUE = 6, MAX_VALUE, SCALAR_INCR}; + +get_instance_id(integer); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_integer_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_INTEGER) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[CURRENT_VAL].integer.value); + kfree(obj); + return ret; +} + +/** + * validate_integer_input() - Validate input of current_value against lower and upper bound + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_integer_input(int instance_id, char *buf) +{ + int in_val; + int ret; + + ret = kstrtoint(buf, 0, &in_val); + if (ret) + return ret; + if (in_val < wmi_priv.integer_data[instance_id].min_value || + in_val > wmi_priv.integer_data[instance_id].max_value) + return -EINVAL; + + /* workaround for BIOS error. + * validate input to avoid setting 0 when integer input passed with + sign + */ + if (*buf == '+') + memmove(buf, (buf + 1), strlen(buf + 1) + 1); + + return ret; +} + +attribute_s_property_show(display_name_language_code, integer); +static struct kobj_attribute integer_displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, integer); +static struct kobj_attribute integer_displ_name = + __ATTR_RO(display_name); + +attribute_n_property_show(default_value, integer); +static struct kobj_attribute integer_default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, integer); +static struct kobj_attribute integer_current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, integer); +static struct kobj_attribute integer_modifier = + __ATTR_RO(dell_modifier); + +attribute_n_property_show(min_value, integer); +static struct kobj_attribute integer_lower_bound = + __ATTR_RO(min_value); + +attribute_n_property_show(max_value, integer); +static struct kobj_attribute integer_upper_bound = + __ATTR_RO(max_value); + +attribute_n_property_show(scalar_increment, integer); +static struct kobj_attribute integer_scalar_increment = + __ATTR_RO(scalar_increment); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "integer\n"); +} +static struct kobj_attribute integer_type = + __ATTR_RO(type); + +static struct attribute *integer_attrs[] = { + &integer_displ_langcode.attr, + &integer_displ_name.attr, + &integer_default_val.attr, + &integer_current_val.attr, + &integer_modifier.attr, + &integer_lower_bound.attr, + &integer_upper_bound.attr, + &integer_scalar_increment.attr, + &integer_type.attr, + NULL, +}; + +static const struct attribute_group integer_attr_group = { + .attrs = integer_attrs, +}; + +int alloc_int_data(void) +{ + int ret = 0; + + wmi_priv.integer_instances_count = get_instance_count(DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + wmi_priv.integer_data = kcalloc(wmi_priv.integer_instances_count, + sizeof(struct integer_data), GFP_KERNEL); + if (!wmi_priv.integer_data) { + wmi_priv.integer_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_int_data() - Populate all properties of an instance under integer attribute + * @integer_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj) +{ + wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, + integer_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, + integer_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, + integer_obj[DISPLAY_NAME].string.pointer); + wmi_priv.integer_data[instance_id].default_value = + (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; + strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, + integer_obj[MODIFIER].string.pointer); + wmi_priv.integer_data[instance_id].min_value = + (uintptr_t)integer_obj[MIN_VALUE].string.pointer; + wmi_priv.integer_data[instance_id].max_value = + (uintptr_t)integer_obj[MAX_VALUE].string.pointer; + wmi_priv.integer_data[instance_id].scalar_increment = + (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; + + return sysfs_create_group(attr_name_kobj, &integer_attr_group); +} + +/** + * exit_int_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_int_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.integer_instances_count; instance_id++) { + if (wmi_priv.integer_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.integer_data[instance_id].attr_name_kobj, + &integer_attr_group); + } + kfree(wmi_priv.integer_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c new file mode 100644 index 000000000000..3abcd95477c0 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to password object type attributes under BIOS Password Object GUID for + * use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum po_properties {IS_PASS_SET = 1, MIN_PASS_LEN, MAX_PASS_LEN}; + +get_instance_id(po); + +static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int instance_id = get_po_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[IS_PASS_SET].type != ACPI_TYPE_INTEGER) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%lld\n", obj->package.elements[IS_PASS_SET].integer.value); + kfree(obj); + return ret; +} + +static struct kobj_attribute po_is_pass_set = __ATTR_RO(is_enabled); + +static ssize_t current_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *target = NULL; + int length; + + length = strlen(buf); + if (buf[length-1] == '\n') + length--; + + /* firmware does verifiation of min/max password length, + * hence only check for not exceeding MAX_BUFF here. + */ + if (length >= MAX_BUFF) + return -EINVAL; + + if (strcmp(kobj->name, "Admin") == 0) + target = wmi_priv.current_admin_password; + else if (strcmp(kobj->name, "System") == 0) + target = wmi_priv.current_system_password; + if (!target) + return -EIO; + memcpy(target, buf, length); + target[length] = '\0'; + + return count; +} + +static struct kobj_attribute po_current_password = __ATTR_WO(current_password); + +static ssize_t new_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + char *p, *buf_cp; + int ret; + + buf_cp = kstrdup(buf, GFP_KERNEL); + if (!buf_cp) + return -ENOMEM; + p = memchr(buf_cp, '\n', count); + + if (p != NULL) + *p = '\0'; + if (strlen(buf_cp) > MAX_BUFF) { + ret = -EINVAL; + goto out; + } + + ret = set_new_password(kobj->name, buf_cp); + +out: + kfree(buf_cp); + return ret ? ret : count; +} + +static struct kobj_attribute po_new_password = __ATTR_WO(new_password); + +attribute_n_property_show(min_password_length, po); +static struct kobj_attribute po_min_pass_length = __ATTR_RO(min_password_length); + +attribute_n_property_show(max_password_length, po); +static struct kobj_attribute po_max_pass_length = __ATTR_RO(max_password_length); + +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "password\n"); +} + +static struct kobj_attribute po_mechanism = __ATTR_RO(mechanism); + +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + if (strcmp(kobj->name, "Admin") == 0) + return sprintf(buf, "bios-admin\n"); + else if (strcmp(kobj->name, "System") == 0) + return sprintf(buf, "power-on\n"); + return -EIO; +} + +static struct kobj_attribute po_role = __ATTR_RO(role); + +static struct attribute *po_attrs[] = { + &po_is_pass_set.attr, + &po_min_pass_length.attr, + &po_max_pass_length.attr, + &po_current_password.attr, + &po_new_password.attr, + &po_role.attr, + &po_mechanism.attr, + NULL, +}; + +static const struct attribute_group po_attr_group = { + .attrs = po_attrs, +}; + +int alloc_po_data(void) +{ + int ret = 0; + + wmi_priv.po_instances_count = get_instance_count(DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + wmi_priv.po_data = kcalloc(wmi_priv.po_instances_count, sizeof(struct po_data), GFP_KERNEL); + if (!wmi_priv.po_data) { + wmi_priv.po_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_po_data() - Populate all properties of an instance under password object attribute + * @po_obj: ACPI object with password object data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) +{ + wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, + po_obj[ATTR_NAME].string.pointer); + wmi_priv.po_data[instance_id].min_password_length = + (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; + wmi_priv.po_data[instance_id].max_password_length = + (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; + + return sysfs_create_group(attr_name_kobj, &po_attr_group); +} + +/** + * exit_po_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_po_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.po_instances_count; instance_id++) { + if (wmi_priv.po_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.po_data[instance_id].attr_name_kobj, + &po_attr_group); + } + kfree(wmi_priv.po_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c new file mode 100644 index 000000000000..5780b4d94759 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET password methods under BIOS attributes interface GUID + * + * Copyright (c) 2020 Dell Inc. + */ + +#include +#include "dell-wmi-sysman.h" + +static int call_password_interface(struct wmi_device *wdev, char *in_args, size_t size) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, 1, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + kfree(output.pointer); + /* let userland know it may need to check is_password_set again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + return map_wmi_error(ret); +} + +/** + * set_new_password() - Sets a system admin password + * @password_type: The type of password to set + * @new: The new password + * + * Sets the password using plaintext interface + */ +int set_new_password(const char *password_type, const char *new) +{ + size_t password_type_size, current_password_size, new_size; + size_t security_area_size, buffer_size; + char *buffer = NULL, *start; + char *current_password; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.password_attr_wdev) { + ret = -ENODEV; + goto out; + } + if (strcmp(password_type, "Admin") == 0) { + current_password = wmi_priv.current_admin_password; + } else if (strcmp(password_type, "System") == 0) { + current_password = wmi_priv.current_system_password; + } else { + ret = -EINVAL; + dev_err(&wmi_priv.password_attr_wdev->dev, "unknown password type %s\n", + password_type); + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + password_type_size = calculate_string_buffer(password_type); + current_password_size = calculate_string_buffer(current_password); + new_size = calculate_string_buffer(new); + buffer_size = security_area_size + password_type_size + current_password_size + new_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, password_type_size, password_type); + if (ret < 0) + goto out; + + start += ret; + ret = populate_string_buffer(start, current_password_size, current_password); + if (ret < 0) + goto out; + + start += ret; + ret = populate_string_buffer(start, new_size, new); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); + /* clear current_password here and use user input from wmi_priv.current_password */ + if (!ret) + memset(current_password, 0, MAX_BUFF); + /* explain to user the detailed failure reason */ + else if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.password_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + + return ret; +} + +static int bios_attr_pass_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.password_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_pass_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.password_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_pass_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_pass_interface_driver = { + .driver = { + .name = DRIVER_NAME"-password" + }, + .probe = bios_attr_pass_interface_probe, + .remove = bios_attr_pass_interface_remove, + .id_table = bios_attr_pass_interface_id_table, +}; + +int init_bios_attr_pass_interface(void) +{ + return wmi_driver_register(&bios_attr_pass_interface_driver); +} + +void exit_bios_attr_pass_interface(void) +{ + wmi_driver_unregister(&bios_attr_pass_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_pass_interface_id_table); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c new file mode 100644 index 000000000000..ac75dce88a4c --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to string type attributes under BIOS String GUID for use with + * dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +enum string_properties {MIN_LEN = 6, MAX_LEN}; + +get_instance_id(str); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_str_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return -EIO; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_str_input() - Validate input of current_value against min and max lengths + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_str_input(int instance_id, const char *buf) +{ + int in_len = strlen(buf); + + if ((in_len < wmi_priv.str_data[instance_id].min_length) || + (in_len > wmi_priv.str_data[instance_id].max_length)) + return -EINVAL; + + return 0; +} + +attribute_s_property_show(display_name_language_code, str); +static struct kobj_attribute str_displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, str); +static struct kobj_attribute str_displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, str); +static struct kobj_attribute str_default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, str); +static struct kobj_attribute str_current_val = + __ATTR_RW_MODE(current_value, 0600); + +attribute_s_property_show(dell_modifier, str); +static struct kobj_attribute str_modifier = + __ATTR_RO(dell_modifier); + +attribute_n_property_show(min_length, str); +static struct kobj_attribute str_min_length = + __ATTR_RO(min_length); + +attribute_n_property_show(max_length, str); +static struct kobj_attribute str_max_length = + __ATTR_RO(max_length); + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "string\n"); +} +static struct kobj_attribute str_type = + __ATTR_RO(type); + +static struct attribute *str_attrs[] = { + &str_displ_langcode.attr, + &str_displ_name.attr, + &str_default_val.attr, + &str_current_val.attr, + &str_modifier.attr, + &str_min_length.attr, + &str_max_length.attr, + &str_type.attr, + NULL, +}; + +static const struct attribute_group str_attr_group = { + .attrs = str_attrs, +}; + +int alloc_str_data(void) +{ + int ret = 0; + + wmi_priv.str_instances_count = get_instance_count(DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + wmi_priv.str_data = kcalloc(wmi_priv.str_instances_count, + sizeof(struct str_data), GFP_KERNEL); + if (!wmi_priv.str_data) { + wmi_priv.str_instances_count = 0; + ret = -ENOMEM; + } + return ret; +} + +/** + * populate_str_data() - Populate all properties of an instance under string attribute + * @str_obj: ACPI object with integer data + * @instance_id: The instance to enumerate + * @attr_name_kobj: The parent kernel object + */ +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) +{ + wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; + strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, + str_obj[ATTR_NAME].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, + str_obj[DISPL_NAME_LANG_CODE].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].display_name, + str_obj[DISPLAY_NAME].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].default_value, + str_obj[DEFAULT_VAL].string.pointer); + strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, + str_obj[MODIFIER].string.pointer); + wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; + wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; + + return sysfs_create_group(attr_name_kobj, &str_attr_group); +} + +/** + * exit_str_attributes() - Clear all attribute data + * + * Clears all data allocated for this group of attributes + */ +void exit_str_attributes(void) +{ + int instance_id; + + for (instance_id = 0; instance_id < wmi_priv.str_instances_count; instance_id++) { + if (wmi_priv.str_data[instance_id].attr_name_kobj) + sysfs_remove_group(wmi_priv.str_data[instance_id].attr_name_kobj, + &str_attr_group); + } + kfree(wmi_priv.str_data); +} diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c new file mode 100644 index 000000000000..cb81010ba1a2 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common methods for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "dell-wmi-sysman.h" + +#define MAX_TYPES 4 +#include + +static struct class firmware_attributes_class = { + .name = "firmware-attributes", +}; + +struct wmi_sysman_priv wmi_priv = { + .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), +}; + +/* reset bios to defaults */ +static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; +static int reset_option = -1; + + +/** + * populate_string_buffer() - populates a string buffer + * @buffer: the start of the destination buffer + * @buffer_len: length of the destination buffer + * @str: the string to insert into buffer + */ +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str) +{ + u16 *length = (u16 *)buffer; + u16 *target = length + 1; + int ret; + + ret = utf8s_to_utf16s(str, strlen(str), UTF16_HOST_ENDIAN, + target, buffer_len - sizeof(u16)); + if (ret < 0) { + dev_err(wmi_priv.class_dev, "UTF16 conversion failed\n"); + return ret; + } + + if ((ret * sizeof(u16)) > U16_MAX) { + dev_err(wmi_priv.class_dev, "Error string too long\n"); + return -ERANGE; + } + + *length = ret * sizeof(u16); + return sizeof(u16) + *length; +} + +/** + * calculate_string_buffer() - determines size of string buffer for use with BIOS communication + * @str: the string to calculate based upon + * + */ +size_t calculate_string_buffer(const char *str) +{ + /* u16 length field + one UTF16 char for each input char */ + return sizeof(u16) + strlen(str) * sizeof(u16); +} + +/** + * calculate_security_buffer() - determines size of security buffer for authentication scheme + * @authentication: the authentication content + * + * Currently only supported type is Admin password + */ +size_t calculate_security_buffer(char *authentication) +{ + if (strlen(authentication) > 0) { + return (sizeof(u32) * 2) + strlen(authentication) + + strlen(authentication) % 2; + } + return sizeof(u32) * 2; +} + +/** + * populate_security_buffer() - builds a security buffer for authentication scheme + * @buffer: the buffer to populate + * @authentication: the authentication content + * + * Currently only supported type is PLAIN TEXT + */ +void populate_security_buffer(char *buffer, char *authentication) +{ + char *auth = buffer + sizeof(u32) * 2; + u32 *sectype = (u32 *) buffer; + u32 *seclen = sectype + 1; + + *sectype = strlen(authentication) > 0 ? 1 : 0; + *seclen = strlen(authentication); + + /* plain text */ + if (strlen(authentication) > 0) + memcpy(auth, authentication, *seclen); +} + +/** + * map_wmi_error() - map errors from WMI methods to kernel error codes + * @error_code: integer error code returned from Dell's firmware + */ +int map_wmi_error(int error_code) +{ + switch (error_code) { + case 0: + /* success */ + return 0; + case 1: + /* failed */ + return -EIO; + case 2: + /* invalid parameter */ + return -EINVAL; + case 3: + /* access denied */ + return -EACCES; + case 4: + /* not supported */ + return -EOPNOTSUPP; + case 5: + /* memory error */ + return -ENOMEM; + case 6: + /* protocol error */ + return -EPROTO; + } + /* unspecified error */ + return -EIO; +} + +/** + * reset_bios_show() - sysfs implementaton for read reset_bios + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer to display to userspace + */ +static ssize_t reset_bios_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + char *start = buf; + int i; + + for (i = 0; i < MAX_TYPES; i++) { + if (i == reset_option) + buf += sprintf(buf, "[%s] ", reset_types[i]); + else + buf += sprintf(buf, "%s ", reset_types[i]); + } + buf += sprintf(buf, "\n"); + return buf-start; +} + +/** + * reset_bios_store() - sysfs implementaton for write reset_bios + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer from userspace + * @count: the size of the buffer from userspace + */ +static ssize_t reset_bios_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int type = sysfs_match_string(reset_types, buf); + int ret; + + if (type < 0) + return type; + + ret = set_bios_defaults(type); + pr_debug("reset all attributes request type %d: %d\n", type, ret); + if (!ret) { + reset_option = type; + ret = count; + } + + return ret; +} + +/** + * pending_reboot_show() - sysfs implementaton for read pending_reboot + * @kobj: Kernel object for this attribute + * @attr: Kernel object attribute + * @buf: The buffer to display to userspace + * + * Stores default value as 0 + * When current_value is changed this attribute is set to 1 to notify reboot may be required + */ +static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", wmi_priv.pending_changes); +} + +static struct kobj_attribute reset_bios = __ATTR_RW(reset_bios); +static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); + + +/** + * create_attributes_level_sysfs_files() - Creates reset_bios and + * pending_reboot attributes + */ +static int create_attributes_level_sysfs_files(void) +{ + int ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + + if (ret) { + pr_debug("could not create reset_bios file\n"); + return ret; + } + + ret = sysfs_create_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); + if (ret) { + pr_debug("could not create changing_pending_reboot file\n"); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + } + return ret; +} + +static void release_reset_bios_data(void) +{ + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &reset_bios.attr); + sysfs_remove_file(&wmi_priv.main_dir_kset->kobj, &pending_reboot.attr); +} + +static ssize_t wmi_sysman_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct kobj_attribute *kattr; + ssize_t ret = -EIO; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->show) + ret = kattr->show(kobj, kattr, buf); + return ret; +} + +static ssize_t wmi_sysman_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct kobj_attribute *kattr; + ssize_t ret = -EIO; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->store) + ret = kattr->store(kobj, kattr, buf, count); + return ret; +} + +static const struct sysfs_ops wmi_sysman_kobj_sysfs_ops = { + .show = wmi_sysman_attr_show, + .store = wmi_sysman_attr_store, +}; + +static void attr_name_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static struct kobj_type attr_name_ktype = { + .release = attr_name_release, + .sysfs_ops = &wmi_sysman_kobj_sysfs_ops, +}; + +/** + * strlcpy_attr - Copy a length-limited, NULL-terminated string with bound checks + * @dest: Where to copy the string to + * @src: Where to copy the string from + */ +void strlcpy_attr(char *dest, char *src) +{ + size_t len = strlen(src) + 1; + + if (len > 1 && len <= MAX_BUFF) + strlcpy(dest, src, len); + + /*len can be zero because any property not-applicable to attribute can + * be empty so check only for too long buffers and log error + */ + if (len > MAX_BUFF) + pr_err("Source string returned from BIOS is out of bound!\n"); +} + +/** + * get_wmiobj_pointer() - Get Content of WMI block for particular instance + * @instance_id: WMI instance ID + * @guid_string: WMI GUID (in str form) + * + * Fetches the content for WMI block (instance_id) under GUID (guid_string) + * Caller must kfree the return + */ +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string) +{ + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + + status = wmi_query_block(guid_string, instance_id, &out); + + return ACPI_SUCCESS(status) ? (union acpi_object *)out.pointer : NULL; +} + +/** + * get_instance_count() - Compute total number of instances under guid_string + * @guid_string: WMI GUID (in string form) + */ +int get_instance_count(const char *guid_string) +{ + union acpi_object *wmi_obj = NULL; + int i = 0; + + do { + kfree(wmi_obj); + wmi_obj = get_wmiobj_pointer(i, guid_string); + i++; + } while (wmi_obj); + + return (i-1); +} + +/** + * alloc_attributes_data() - Allocate attributes data for a particular type + * @attr_type: Attribute type to allocate + */ +static int alloc_attributes_data(int attr_type) +{ + int retval = 0; + + switch (attr_type) { + case ENUM: + retval = alloc_enum_data(); + break; + case INT: + retval = alloc_int_data(); + break; + case STR: + retval = alloc_str_data(); + break; + case PO: + retval = alloc_po_data(); + break; + default: + break; + } + + return retval; +} + +/** + * destroy_attribute_objs() - Free a kset of kobjects + * @kset: The kset to destroy + * + * Fress kobjects created for each attribute_name under attribute type kset + */ +static void destroy_attribute_objs(struct kset *kset) +{ + struct kobject *pos, *next; + + list_for_each_entry_safe(pos, next, &kset->list, entry) { + kobject_put(pos); + } +} + +/** + * release_attributes_data() - Clean-up all sysfs directories and files created + */ +static void release_attributes_data(void) +{ + release_reset_bios_data(); + + mutex_lock(&wmi_priv.mutex); + exit_enum_attributes(); + exit_int_attributes(); + exit_str_attributes(); + exit_po_attributes(); + if (wmi_priv.authentication_dir_kset) { + destroy_attribute_objs(wmi_priv.authentication_dir_kset); + kset_unregister(wmi_priv.authentication_dir_kset); + wmi_priv.authentication_dir_kset = NULL; + } + if (wmi_priv.main_dir_kset) { + destroy_attribute_objs(wmi_priv.main_dir_kset); + kset_unregister(wmi_priv.main_dir_kset); + } + mutex_unlock(&wmi_priv.mutex); + +} + +/** + * init_bios_attributes() - Initialize all attributes for a type + * @attr_type: The attribute type to initialize + * @guid: The WMI GUID associated with this type to initialize + * + * Initialiaze all 4 types of attributes enumeration, integer, string and password object. + * Populates each attrbute typ's respective properties under sysfs files + */ +static int init_bios_attributes(int attr_type, const char *guid) +{ + struct kobject *attr_name_kobj; //individual attribute names + union acpi_object *obj = NULL; + union acpi_object *elements; + struct kset *tmp_set; + + /* instance_id needs to be reset for each type GUID + * also, instance IDs are unique within GUID but not across + */ + int instance_id = 0; + int retval = 0; + + retval = alloc_attributes_data(attr_type); + if (retval) + return retval; + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, guid); + if (!obj || obj->type != ACPI_TYPE_PACKAGE) + return -ENODEV; + elements = obj->package.elements; + + mutex_lock(&wmi_priv.mutex); + while (elements) { + /* sanity checking */ + if (elements[ATTR_NAME].type != ACPI_TYPE_STRING) { + pr_debug("incorrect element type\n"); + goto nextobj; + } + if (strlen(elements[ATTR_NAME].string.pointer) == 0) { + pr_debug("empty attribute found\n"); + goto nextobj; + } + if (attr_type == PO) + tmp_set = wmi_priv.authentication_dir_kset; + else + tmp_set = wmi_priv.main_dir_kset; + + if (kset_find_obj(tmp_set, elements[ATTR_NAME].string.pointer)) { + pr_debug("duplicate attribute name found - %s\n", + elements[ATTR_NAME].string.pointer); + goto nextobj; + } + + /* build attribute */ + attr_name_kobj = kzalloc(sizeof(*attr_name_kobj), GFP_KERNEL); + if (!attr_name_kobj) { + retval = -ENOMEM; + goto err_attr_init; + } + + attr_name_kobj->kset = tmp_set; + + retval = kobject_init_and_add(attr_name_kobj, &attr_name_ktype, NULL, "%s", + elements[ATTR_NAME].string.pointer); + if (retval) { + kobject_put(attr_name_kobj); + goto err_attr_init; + } + + /* enumerate all of this attribute */ + switch (attr_type) { + case ENUM: + retval = populate_enum_data(elements, instance_id, attr_name_kobj); + break; + case INT: + retval = populate_int_data(elements, instance_id, attr_name_kobj); + break; + case STR: + retval = populate_str_data(elements, instance_id, attr_name_kobj); + break; + case PO: + retval = populate_po_data(elements, instance_id, attr_name_kobj); + break; + default: + break; + } + + if (retval) { + pr_debug("failed to populate %s\n", + elements[ATTR_NAME].string.pointer); + goto err_attr_init; + } + +nextobj: + kfree(obj); + instance_id++; + obj = get_wmiobj_pointer(instance_id, guid); + elements = obj ? obj->package.elements : NULL; + } + + mutex_unlock(&wmi_priv.mutex); + return 0; + +err_attr_init: + mutex_unlock(&wmi_priv.mutex); + release_attributes_data(); + kfree(obj); + return retval; +} + +static int __init sysman_init(void) +{ + int ret = 0; + + if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { + pr_err("Unable to run on non-Dell system\n"); + return -ENODEV; + } + + ret = init_bios_attr_set_interface(); + if (ret || !wmi_priv.bios_attr_wdev) { + pr_debug("failed to initialize set interface\n"); + goto fail_set_interface; + } + + ret = init_bios_attr_pass_interface(); + if (ret || !wmi_priv.password_attr_wdev) { + pr_debug("failed to initialize pass interface\n"); + goto fail_pass_interface; + } + + ret = class_register(&firmware_attributes_class); + if (ret) + goto fail_class; + + wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(wmi_priv.class_dev)) { + ret = PTR_ERR(wmi_priv.class_dev); + goto fail_classdev; + } + + wmi_priv.main_dir_kset = kset_create_and_add("attributes", NULL, + &wmi_priv.class_dev->kobj); + if (!wmi_priv.main_dir_kset) { + ret = -ENOMEM; + goto fail_main_kset; + } + + wmi_priv.authentication_dir_kset = kset_create_and_add("authentication", NULL, + &wmi_priv.class_dev->kobj); + if (!wmi_priv.authentication_dir_kset) { + ret = -ENOMEM; + goto fail_authentication_kset; + } + + ret = create_attributes_level_sysfs_files(); + if (ret) { + pr_debug("could not create reset BIOS attribute\n"); + goto fail_reset_bios; + } + + ret = init_bios_attributes(ENUM, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate enumeration type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(INT, DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate integer type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(STR, DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate string type attributes\n"); + goto fail_create_group; + } + + ret = init_bios_attributes(PO, DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID); + if (ret) { + pr_debug("failed to populate pass object type attributes\n"); + goto fail_create_group; + } + + return 0; + +fail_create_group: + release_attributes_data(); + +fail_reset_bios: + if (wmi_priv.authentication_dir_kset) { + kset_unregister(wmi_priv.authentication_dir_kset); + wmi_priv.authentication_dir_kset = NULL; + } + +fail_authentication_kset: + if (wmi_priv.main_dir_kset) { + kset_unregister(wmi_priv.main_dir_kset); + wmi_priv.main_dir_kset = NULL; + } + +fail_main_kset: + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + +fail_classdev: + class_unregister(&firmware_attributes_class); + +fail_class: + exit_bios_attr_pass_interface(); + +fail_pass_interface: + exit_bios_attr_set_interface(); + +fail_set_interface: + return ret; +} + +static void __exit sysman_exit(void) +{ + release_attributes_data(); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + class_unregister(&firmware_attributes_class); + exit_bios_attr_set_interface(); + exit_bios_attr_pass_interface(); +} + +module_init(sysman_init); +module_exit(sysman_exit); + +MODULE_AUTHOR("Mario Limonciello "); +MODULE_AUTHOR("Prasanth Ksr "); +MODULE_AUTHOR("Divya Bharathi "); +MODULE_DESCRIPTION("Dell platform setting control interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/dell/dell-wmi.c b/drivers/platform/x86/dell/dell-wmi.c new file mode 100644 index 000000000000..bbdb3e860892 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dell WMI hotkeys + * + * Copyright (C) 2008 Red Hat + * Copyright (C) 2014-2015 Pali Rohár + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac + * Copyright (C) 2005 Bernhard Rosenkraenzer + * Copyright (C) 2005 Dmitry Torokhov + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" +#include "dell-wmi-descriptor.h" + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" + +static bool wmi_requires_smbios_request; + +struct dell_wmi_priv { + struct input_dev *input_dev; + u32 interface_version; +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + wmi_requires_smbios_request = 1; + return 1; +} + +static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro V131", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), + }, + }, + { } +}; + +/* + * Keymap for WMI events of type 0x0000 + * + * Certain keys are flagged as KE_IGNORE. All of these are either + * notifications (rather than requests for change) or are also sent + * via the keyboard controller so should not be sent again. + */ +static const struct key_entry dell_wmi_keymap_type_0000[] = { + { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, + + /* Key code is followed by brightness level */ + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + + /* Battery health status button */ + { KE_KEY, 0xe007, { KEY_BATTERY } }, + + /* Radio devices state change, key code is followed by other values */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, + + { KE_KEY, 0xe009, { KEY_EJECTCD } }, + + /* Key code is followed by: next, active and attached devices */ + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, + + /* Key code is followed by keyboard illumination level */ + { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, + + /* BIOS error detected */ + { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, + + /* Battery was removed or inserted */ + { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, + + /* Wifi Catcher */ + { KE_KEY, 0xe011, { KEY_WLAN } }, + + /* Ambient light sensor toggle */ + { KE_IGNORE, 0xe013, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe020, { KEY_MUTE } }, + + /* Unknown, defined in ACPI DSDT */ + /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ + + /* Untested, Dell Instant Launch key on Inspiron 7520 */ + /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ + + /* Dell Instant Launch key */ + { KE_KEY, 0xe025, { KEY_PROG4 } }, + + /* Audio panel key */ + { KE_IGNORE, 0xe026, { KEY_RESERVED } }, + + /* LCD Display On/Off Control key */ + { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, + + /* Untested, Multimedia key on Dell Vostro 3560 */ + /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ + + /* Dell Instant Launch key */ + { KE_KEY, 0xe029, { KEY_PROG4 } }, + + /* Untested, Windows Mobility Center button on Inspiron 7520 */ + /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ + + /* Unknown, defined in ACPI DSDT */ + /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ + + /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ + /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ + + { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, + { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, + { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, + { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, + + /* NIC Link is Up */ + { KE_IGNORE, 0xe043, { KEY_RESERVED } }, + + /* NIC Link is Down */ + { KE_IGNORE, 0xe044, { KEY_RESERVED } }, + + /* + * This entry is very suspicious! + * Originally Matthew Garrett created this dell-wmi driver specially for + * "button with a picture of a battery" which has event code 0xe045. + * Later Mario Limonciello from Dell told us that event code 0xe045 is + * reported by Num Lock and should be ignored because key is send also + * by keyboard controller. + * So for now we will ignore this event to prevent potential double + * Num Lock key press. + */ + { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, + + /* Scroll lock and also going to tablet mode on portable devices */ + { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, + + /* Untested, going from tablet mode on portable devices */ + /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ + + /* Dell Support Center key */ + { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, + { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, +}; + +struct dell_bios_keymap_entry { + u16 scancode; + u16 keycode; +}; + +struct dell_bios_hotkey_table { + struct dmi_header header; + struct dell_bios_keymap_entry keymap[]; + +}; + +struct dell_dmi_results { + int err; + int keymap_size; + struct key_entry *keymap; +}; + +/* Uninitialized entries here are KEY_RESERVED == 0. */ +static const u16 bios_to_linux_keycode[256] = { + [0] = KEY_MEDIA, + [1] = KEY_NEXTSONG, + [2] = KEY_PLAYPAUSE, + [3] = KEY_PREVIOUSSONG, + [4] = KEY_STOPCD, + [5] = KEY_UNKNOWN, + [6] = KEY_UNKNOWN, + [7] = KEY_UNKNOWN, + [8] = KEY_WWW, + [9] = KEY_UNKNOWN, + [10] = KEY_VOLUMEDOWN, + [11] = KEY_MUTE, + [12] = KEY_VOLUMEUP, + [13] = KEY_UNKNOWN, + [14] = KEY_BATTERY, + [15] = KEY_EJECTCD, + [16] = KEY_UNKNOWN, + [17] = KEY_SLEEP, + [18] = KEY_PROG1, + [19] = KEY_BRIGHTNESSDOWN, + [20] = KEY_BRIGHTNESSUP, + [21] = KEY_BRIGHTNESS_AUTO, + [22] = KEY_KBDILLUMTOGGLE, + [23] = KEY_UNKNOWN, + [24] = KEY_SWITCHVIDEOMODE, + [25] = KEY_UNKNOWN, + [26] = KEY_UNKNOWN, + [27] = KEY_SWITCHVIDEOMODE, + [28] = KEY_UNKNOWN, + [29] = KEY_UNKNOWN, + [30] = KEY_PROG2, + [31] = KEY_UNKNOWN, + [32] = KEY_UNKNOWN, + [33] = KEY_UNKNOWN, + [34] = KEY_UNKNOWN, + [35] = KEY_UNKNOWN, + [36] = KEY_UNKNOWN, + [37] = KEY_UNKNOWN, + [38] = KEY_MICMUTE, + [255] = KEY_PROG3, +}; + +/* + * Keymap for WMI events of type 0x0010 + * + * These are applied if the 0xB2 DMI hotkey table is present and doesn't + * override them. + */ +static const struct key_entry dell_wmi_keymap_type_0010[] = { + /* Fn-lock switched to function keys */ + { KE_IGNORE, 0x0, { KEY_RESERVED } }, + + /* Fn-lock switched to multimedia keys */ + { KE_IGNORE, 0x1, { KEY_RESERVED } }, + + /* Keyboard backlight change notification */ + { KE_IGNORE, 0x3f, { KEY_RESERVED } }, + + /* Backlight brightness level */ + { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, + + /* Mic mute */ + { KE_KEY, 0x150, { KEY_MICMUTE } }, + + /* Fn-lock */ + { KE_IGNORE, 0x151, { KEY_RESERVED } }, + + /* Change keyboard illumination */ + { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, + + /* + * Radio disable (notify only -- there is no model for which the + * WMI event is supposed to trigger an action). + */ + { KE_IGNORE, 0x153, { KEY_RFKILL } }, + + /* RGB keyboard backlight control */ + { KE_IGNORE, 0x154, { KEY_RESERVED } }, + + /* + * Stealth mode toggle. This will "disable all lights and sounds". + * The action is performed by the BIOS and EC; the WMI event is just + * a notification. On the XPS 13 9350, this is Fn+F7, and there's + * a BIOS setting to enable and disable the hotkey. + */ + { KE_IGNORE, 0x155, { KEY_RESERVED } }, + + /* Rugged magnetic dock attach/detach events */ + { KE_IGNORE, 0x156, { KEY_RESERVED } }, + { KE_IGNORE, 0x157, { KEY_RESERVED } }, + + /* Rugged programmable (P1/P2/P3 keys) */ + { KE_KEY, 0x850, { KEY_PROG1 } }, + { KE_KEY, 0x851, { KEY_PROG2 } }, + { KE_KEY, 0x852, { KEY_PROG3 } }, + + /* + * Radio disable (notify only -- there is no model for which the + * WMI event is supposed to trigger an action). + */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, + + /* Fn-lock */ + { KE_IGNORE, 0xe035, { KEY_RESERVED } }, +}; + +/* + * Keymap for WMI events of type 0x0011 + */ +static const struct key_entry dell_wmi_keymap_type_0011[] = { + /* Battery unplugged */ + { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, + + /* Battery inserted */ + { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, + + /* + * Detachable keyboard detached / undocked + * Note SW_TABLET_MODE is already reported through the intel_vbtn + * driver for this, so we ignore it. + */ + { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, + + /* Detachable keyboard attached / docked */ + { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, + + /* Keyboard backlight level changed */ + { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, +}; + +/* + * Keymap for WMI events of type 0x0012 + * They are events with extended data + */ +static const struct key_entry dell_wmi_keymap_type_0012[] = { + /* Fn-lock button pressed */ + { KE_IGNORE, 0xe035, { KEY_RESERVED } }, +}; + +static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + const struct key_entry *key; + + key = sparse_keymap_entry_from_scancode(priv->input_dev, + (type << 16) | code); + if (!key) { + pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", + type, code); + return; + } + + pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); + + /* Don't report brightness notifications that will also come via ACPI */ + if ((key->keycode == KEY_BRIGHTNESSUP || + key->keycode == KEY_BRIGHTNESSDOWN) && + acpi_video_handles_brightness_key_presses()) + return; + + if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) + return; + + if (key->keycode == KEY_KBDILLUMTOGGLE) + dell_laptop_call_notifier( + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); + + sparse_keymap_report_entry(priv->input_dev, key, 1, true); +} + +static void dell_wmi_notify(struct wmi_device *wdev, + union acpi_object *obj) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + u16 *buffer_entry, *buffer_end; + acpi_size buffer_size; + int len, i; + + if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("bad response type %x\n", obj->type); + return; + } + + pr_debug("Received WMI event (%*ph)\n", + obj->buffer.length, obj->buffer.pointer); + + buffer_entry = (u16 *)obj->buffer.pointer; + buffer_size = obj->buffer.length/2; + buffer_end = buffer_entry + buffer_size; + + /* + * BIOS/ACPI on devices with WMI interface version 0 does not clear + * buffer before filling it. So next time when BIOS/ACPI send WMI event + * which is smaller as previous then it contains garbage in buffer from + * previous event. + * + * BIOS/ACPI on devices with WMI interface version 1 clears buffer and + * sometimes send more events in buffer at one call. + * + * So to prevent reading garbage from buffer we will process only first + * one event on devices with WMI interface version 0. + */ + if (priv->interface_version == 0 && buffer_entry < buffer_end) + if (buffer_end > buffer_entry + buffer_entry[0] + 1) + buffer_end = buffer_entry + buffer_entry[0] + 1; + + while (buffer_entry < buffer_end) { + + len = buffer_entry[0]; + if (len == 0) + break; + + len++; + + if (buffer_entry + len > buffer_end) { + pr_warn("Invalid length of WMI event\n"); + break; + } + + pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); + + switch (buffer_entry[1]) { + case 0x0000: /* One key pressed or event occurred */ + case 0x0012: /* Event with extended data occurred */ + if (len > 2) + dell_wmi_process_key(wdev, buffer_entry[1], + buffer_entry[2]); + /* Extended data is currently ignored */ + break; + case 0x0010: /* Sequence of keys pressed */ + case 0x0011: /* Sequence of events occurred */ + for (i = 2; i < len; ++i) + dell_wmi_process_key(wdev, buffer_entry[1], + buffer_entry[i]); + break; + default: /* Unknown event */ + pr_info("Unknown WMI event type 0x%x\n", + (int)buffer_entry[1]); + break; + } + + buffer_entry += len; + + } + +} + +static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) +{ + int i; + + for (i = 0; i < len; i++) + if (keymap[i].code == scancode) + return true; + + return false; +} + +static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) +{ + struct dell_dmi_results *results = opaque; + struct dell_bios_hotkey_table *table; + int hotkey_num, i, pos = 0; + struct key_entry *keymap; + + if (results->err || results->keymap) + return; /* We already found the hotkey table. */ + + /* The Dell hotkey table is type 0xB2. Scan until we find it. */ + if (dm->type != 0xb2) + return; + + table = container_of(dm, struct dell_bios_hotkey_table, header); + + hotkey_num = (table->header.length - + sizeof(struct dell_bios_hotkey_table)) / + sizeof(struct dell_bios_keymap_entry); + if (hotkey_num < 1) { + /* + * Historically, dell-wmi would ignore a DMI entry of + * fewer than 7 bytes. Sizes between 4 and 8 bytes are + * nonsensical (both the header and all entries are 4 + * bytes), so we approximate the old behavior by + * ignoring tables with fewer than one entry. + */ + return; + } + + keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) { + results->err = -ENOMEM; + return; + } + + for (i = 0; i < hotkey_num; i++) { + const struct dell_bios_keymap_entry *bios_entry = + &table->keymap[i]; + + /* Uninitialized entries are 0 aka KEY_RESERVED. */ + u16 keycode = (bios_entry->keycode < + ARRAY_SIZE(bios_to_linux_keycode)) ? + bios_to_linux_keycode[bios_entry->keycode] : + (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); + + /* + * Log if we find an entry in the DMI table that we don't + * understand. If this happens, we should figure out what + * the entry means and add it to bios_to_linux_keycode. + */ + if (keycode == KEY_RESERVED) { + pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", + bios_entry->scancode, bios_entry->keycode); + continue; + } + + if (keycode == KEY_KBDILLUMTOGGLE) + keymap[pos].type = KE_IGNORE; + else + keymap[pos].type = KE_KEY; + keymap[pos].code = bios_entry->scancode; + keymap[pos].keycode = keycode; + + pos++; + } + + results->keymap = keymap; + results->keymap_size = pos; +} + +static int dell_wmi_input_setup(struct wmi_device *wdev) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + struct dell_dmi_results dmi_results = {}; + struct key_entry *keymap; + int err, i, pos = 0; + + priv->input_dev = input_allocate_device(); + if (!priv->input_dev) + return -ENOMEM; + + priv->input_dev->name = "Dell WMI hotkeys"; + priv->input_dev->id.bustype = BUS_HOST; + priv->input_dev->dev.parent = &wdev->dev; + + if (dmi_walk(handle_dmi_entry, &dmi_results)) { + /* + * Historically, dell-wmi ignored dmi_walk errors. A failure + * is certainly surprising, but it probably just indicates + * a very old laptop. + */ + pr_warn("no DMI; using the old-style hotkey interface\n"); + } + + if (dmi_results.err) { + err = dmi_results.err; + goto err_free_dev; + } + + keymap = kcalloc(dmi_results.keymap_size + + ARRAY_SIZE(dell_wmi_keymap_type_0000) + + ARRAY_SIZE(dell_wmi_keymap_type_0010) + + ARRAY_SIZE(dell_wmi_keymap_type_0011) + + ARRAY_SIZE(dell_wmi_keymap_type_0012) + + 1, + sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) { + kfree(dmi_results.keymap); + err = -ENOMEM; + goto err_free_dev; + } + + /* Append table with events of type 0x0010 which comes from DMI */ + for (i = 0; i < dmi_results.keymap_size; i++) { + keymap[pos] = dmi_results.keymap[i]; + keymap[pos].code |= (0x0010 << 16); + pos++; + } + + kfree(dmi_results.keymap); + + /* Append table with extra events of type 0x0010 which are not in DMI */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { + const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; + + /* + * Check if we've already found this scancode. This takes + * quadratic time, but it doesn't matter unless the list + * of extra keys gets very long. + */ + if (dmi_results.keymap_size && + have_scancode(entry->code | (0x0010 << 16), + keymap, dmi_results.keymap_size) + ) + continue; + + keymap[pos] = *entry; + keymap[pos].code |= (0x0010 << 16); + pos++; + } + + /* Append table with events of type 0x0011 */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { + keymap[pos] = dell_wmi_keymap_type_0011[i]; + keymap[pos].code |= (0x0011 << 16); + pos++; + } + + /* Append table with events of type 0x0012 */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { + keymap[pos] = dell_wmi_keymap_type_0012[i]; + keymap[pos].code |= (0x0012 << 16); + pos++; + } + + /* + * Now append also table with "legacy" events of type 0x0000. Some of + * them are reported also on laptops which have scancodes in DMI. + */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { + keymap[pos] = dell_wmi_keymap_type_0000[i]; + pos++; + } + + keymap[pos].type = KE_END; + + err = sparse_keymap_setup(priv->input_dev, keymap, NULL); + /* + * Sparse keymap library makes a copy of keymap so we don't need the + * original one that was allocated. + */ + kfree(keymap); + if (err) + goto err_free_dev; + + err = input_register_device(priv->input_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(priv->input_dev); + return err; +} + +static void dell_wmi_input_destroy(struct wmi_device *wdev) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + + input_unregister_device(priv->input_dev); +} + +/* + * According to Dell SMBIOS documentation: + * + * 17 3 Application Program Registration + * + * cbArg1 Application ID 1 = 0x00010000 + * cbArg2 Application ID 2 + * QUICKSET/DCP = 0x51534554 "QSET" + * ALS Driver = 0x416c7353 "AlsS" + * Latitude ON = 0x4c6f6e52 "LonR" + * cbArg3 Application version or revision number + * cbArg4 0 = Unregister application + * 1 = Register application + * cbRes1 Standard return codes (0, -1, -2) + */ + +static int dell_wmi_events_set_enabled(bool enable) +{ + struct calling_interface_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + buffer->cmd_class = CLASS_INFO; + buffer->cmd_select = SELECT_APP_REGISTRATION; + buffer->input[0] = 0x10000; + buffer->input[1] = 0x51534554; + buffer->input[3] = enable; + ret = dell_smbios_call(buffer); + if (ret == 0) + ret = buffer->output[0]; + kfree(buffer); + + return dell_smbios_error(ret); +} + +static int dell_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct dell_wmi_priv *priv; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; + + priv = devm_kzalloc( + &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(&wdev->dev, priv); + + if (!dell_wmi_get_interface_version(&priv->interface_version)) + return -EPROBE_DEFER; + + return dell_wmi_input_setup(wdev); +} + +static int dell_wmi_remove(struct wmi_device *wdev) +{ + dell_wmi_input_destroy(wdev); + return 0; +} +static const struct wmi_device_id dell_wmi_id_table[] = { + { .guid_string = DELL_EVENT_GUID }, + { }, +}; + +static struct wmi_driver dell_wmi_driver = { + .driver = { + .name = "dell-wmi", + }, + .id_table = dell_wmi_id_table, + .probe = dell_wmi_probe, + .remove = dell_wmi_remove, + .notify = dell_wmi_notify, +}; + +static int __init dell_wmi_init(void) +{ + int err; + + dmi_check_system(dell_wmi_smbios_list); + + if (wmi_requires_smbios_request) { + err = dell_wmi_events_set_enabled(true); + if (err) { + pr_err("Failed to enable WMI events\n"); + return err; + } + } + + return wmi_driver_register(&dell_wmi_driver); +} +late_initcall(dell_wmi_init); + +static void __exit dell_wmi_exit(void) +{ + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + wmi_driver_unregister(&dell_wmi_driver); +} +module_exit(dell_wmi_exit); + +MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); diff --git a/drivers/platform/x86/dell/dell_rbu.c b/drivers/platform/x86/dell/dell_rbu.c new file mode 100644 index 000000000000..03c3ff34bcf5 --- /dev/null +++ b/drivers/platform/x86/dell/dell_rbu.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dell_rbu.c + * Bios Update driver for Dell systems + * Author: Dell Inc + * Abhay Salunke + * + * Copyright (C) 2005 Dell Inc. + * + * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by + * creating entries in the /sys file systems on Linux 2.6 and higher + * kernels. The driver supports two mechanism to update the BIOS namely + * contiguous and packetized. Both these methods still require having some + * application to set the CMOS bit indicating the BIOS to update itself + * after a reboot. + * + * Contiguous method: + * This driver writes the incoming data in a monolithic image by allocating + * contiguous physical pages large enough to accommodate the incoming BIOS + * image size. + * + * Packetized method: + * The driver writes the incoming packet image by allocating a new packet + * on every time the packet data is written. This driver requires an + * application to break the BIOS image in to fixed sized packet chunks. + * + * See Documentation/admin-guide/dell_rbu.rst for more info. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Abhay Salunke "); +MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("3.2"); + +#define BIOS_SCAN_LIMIT 0xffffffff +#define MAX_IMAGE_LENGTH 16 +static struct _rbu_data { + void *image_update_buffer; + unsigned long image_update_buffer_size; + unsigned long bios_image_size; + int image_update_ordernum; + spinlock_t lock; + unsigned long packet_read_count; + unsigned long num_packets; + unsigned long packetsize; + unsigned long imagesize; + int entry_created; +} rbu_data; + +static char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; +module_param_string(image_type, image_type, sizeof (image_type), 0); +MODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet or init"); + +static unsigned long allocation_floor = 0x100000; +module_param(allocation_floor, ulong, 0644); +MODULE_PARM_DESC(allocation_floor, "Minimum address for allocations when using Packet mode"); + +struct packet_data { + struct list_head list; + size_t length; + void *data; + int ordernum; +}; + +static struct packet_data packet_data_head; + +static struct platform_device *rbu_device; +static int context; + +static void init_packet_head(void) +{ + INIT_LIST_HEAD(&packet_data_head.list); + rbu_data.packet_read_count = 0; + rbu_data.num_packets = 0; + rbu_data.packetsize = 0; + rbu_data.imagesize = 0; +} + +static int create_packet(void *data, size_t length) +{ + struct packet_data *newpacket; + int ordernum = 0; + int retval = 0; + unsigned int packet_array_size = 0; + void **invalid_addr_packet_array = NULL; + void *packet_data_temp_buf = NULL; + unsigned int idx = 0; + + pr_debug("entry\n"); + + if (!rbu_data.packetsize) { + pr_debug("packetsize not specified\n"); + retval = -EINVAL; + goto out_noalloc; + } + + spin_unlock(&rbu_data.lock); + + newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); + + if (!newpacket) { + pr_warn("failed to allocate new packet\n"); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_noalloc; + } + + ordernum = get_order(length); + + /* + * BIOS errata mean we cannot allocate packets below 1MB or they will + * be overwritten by BIOS. + * + * array to temporarily hold packets + * that are below the allocation floor + * + * NOTE: very simplistic because we only need the floor to be at 1MB + * due to BIOS errata. This shouldn't be used for higher floors + * or you will run out of mem trying to allocate the array. + */ + packet_array_size = max_t(unsigned int, allocation_floor / rbu_data.packetsize, 1); + invalid_addr_packet_array = kcalloc(packet_array_size, sizeof(void *), + GFP_KERNEL); + + if (!invalid_addr_packet_array) { + pr_warn("failed to allocate invalid_addr_packet_array\n"); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_alloc_packet; + } + + while (!packet_data_temp_buf) { + packet_data_temp_buf = (unsigned char *) + __get_free_pages(GFP_KERNEL, ordernum); + if (!packet_data_temp_buf) { + pr_warn("failed to allocate new packet\n"); + retval = -ENOMEM; + spin_lock(&rbu_data.lock); + goto out_alloc_packet_array; + } + + if ((unsigned long)virt_to_phys(packet_data_temp_buf) + < allocation_floor) { + pr_debug("packet 0x%lx below floor at 0x%lx\n", + (unsigned long)virt_to_phys( + packet_data_temp_buf), + allocation_floor); + invalid_addr_packet_array[idx++] = packet_data_temp_buf; + packet_data_temp_buf = NULL; + } + } + /* + * set to uncachable or it may never get written back before reboot + */ + set_memory_uc((unsigned long)packet_data_temp_buf, 1 << ordernum); + + spin_lock(&rbu_data.lock); + + newpacket->data = packet_data_temp_buf; + + pr_debug("newpacket at physical addr %lx\n", + (unsigned long)virt_to_phys(newpacket->data)); + + /* packets may not have fixed size */ + newpacket->length = length; + newpacket->ordernum = ordernum; + ++rbu_data.num_packets; + + /* initialize the newly created packet headers */ + INIT_LIST_HEAD(&newpacket->list); + list_add_tail(&newpacket->list, &packet_data_head.list); + + memcpy(newpacket->data, data, length); + + pr_debug("exit\n"); + +out_alloc_packet_array: + /* always free packet array */ + while (idx--) { + pr_debug("freeing unused packet below floor 0x%lx\n", + (unsigned long)virt_to_phys(invalid_addr_packet_array[idx])); + free_pages((unsigned long)invalid_addr_packet_array[idx], ordernum); + } + kfree(invalid_addr_packet_array); + +out_alloc_packet: + /* if error, free data */ + if (retval) + kfree(newpacket); + +out_noalloc: + return retval; +} + +static int packetize_data(const u8 *data, size_t length) +{ + int rc = 0; + int done = 0; + int packet_length; + u8 *temp; + u8 *end = (u8 *) data + length; + pr_debug("data length %zd\n", length); + if (!rbu_data.packetsize) { + pr_warn("packetsize not specified\n"); + return -EIO; + } + + temp = (u8 *) data; + + /* packetize the hunk */ + while (!done) { + if ((temp + rbu_data.packetsize) < end) + packet_length = rbu_data.packetsize; + else { + /* this is the last packet */ + packet_length = end - temp; + done = 1; + } + + if ((rc = create_packet(temp, packet_length))) + return rc; + + pr_debug("%p:%td\n", temp, (end - temp)); + temp += packet_length; + } + + rbu_data.imagesize = length; + + return rc; +} + +static int do_packet_read(char *data, struct packet_data *newpacket, + int length, int bytes_read, int *list_read_count) +{ + void *ptemp_buf; + int bytes_copied = 0; + int j = 0; + + *list_read_count += newpacket->length; + + if (*list_read_count > bytes_read) { + /* point to the start of unread data */ + j = newpacket->length - (*list_read_count - bytes_read); + /* point to the offset in the packet buffer */ + ptemp_buf = (u8 *) newpacket->data + j; + /* + * check if there is enough room in + * * the incoming buffer + */ + if (length > (*list_read_count - bytes_read)) + /* + * copy what ever is there in this + * packet and move on + */ + bytes_copied = (*list_read_count - bytes_read); + else + /* copy the remaining */ + bytes_copied = length; + memcpy(data, ptemp_buf, bytes_copied); + } + return bytes_copied; +} + +static int packet_read_list(char *data, size_t * pread_length) +{ + struct packet_data *newpacket; + int temp_count = 0; + int bytes_copied = 0; + int bytes_read = 0; + int remaining_bytes = 0; + char *pdest = data; + + /* check if we have any packets */ + if (0 == rbu_data.num_packets) + return -ENOMEM; + + remaining_bytes = *pread_length; + bytes_read = rbu_data.packet_read_count; + + list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) { + bytes_copied = do_packet_read(pdest, newpacket, + remaining_bytes, bytes_read, &temp_count); + remaining_bytes -= bytes_copied; + bytes_read += bytes_copied; + pdest += bytes_copied; + /* + * check if we reached end of buffer before reaching the + * last packet + */ + if (remaining_bytes == 0) + break; + } + /*finally set the bytes read */ + *pread_length = bytes_read - rbu_data.packet_read_count; + rbu_data.packet_read_count = bytes_read; + return 0; +} + +static void packet_empty_list(void) +{ + struct packet_data *newpacket, *tmp; + + list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) { + list_del(&newpacket->list); + + /* + * zero out the RBU packet memory before freeing + * to make sure there are no stale RBU packets left in memory + */ + memset(newpacket->data, 0, rbu_data.packetsize); + set_memory_wb((unsigned long)newpacket->data, + 1 << newpacket->ordernum); + free_pages((unsigned long) newpacket->data, + newpacket->ordernum); + kfree(newpacket); + } + rbu_data.packet_read_count = 0; + rbu_data.num_packets = 0; + rbu_data.imagesize = 0; +} + +/* + * img_update_free: Frees the buffer allocated for storing BIOS image + * Always called with lock held and returned with lock held + */ +static void img_update_free(void) +{ + if (!rbu_data.image_update_buffer) + return; + /* + * zero out this buffer before freeing it to get rid of any stale + * BIOS image copied in memory. + */ + memset(rbu_data.image_update_buffer, 0, + rbu_data.image_update_buffer_size); + free_pages((unsigned long) rbu_data.image_update_buffer, + rbu_data.image_update_ordernum); + + /* + * Re-initialize the rbu_data variables after a free + */ + rbu_data.image_update_ordernum = -1; + rbu_data.image_update_buffer = NULL; + rbu_data.image_update_buffer_size = 0; + rbu_data.bios_image_size = 0; +} + +/* + * img_update_realloc: This function allocates the contiguous pages to + * accommodate the requested size of data. The memory address and size + * values are stored globally and on every call to this function the new + * size is checked to see if more data is required than the existing size. + * If true the previous memory is freed and new allocation is done to + * accommodate the new size. If the incoming size is less then than the + * already allocated size, then that memory is reused. This function is + * called with lock held and returns with lock held. + */ +static int img_update_realloc(unsigned long size) +{ + unsigned char *image_update_buffer = NULL; + unsigned long img_buf_phys_addr; + int ordernum; + + /* + * check if the buffer of sufficient size has been + * already allocated + */ + if (rbu_data.image_update_buffer_size >= size) { + /* + * check for corruption + */ + if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { + pr_err("corruption check failed\n"); + return -EINVAL; + } + /* + * we have a valid pre-allocated buffer with + * sufficient size + */ + return 0; + } + + /* + * free any previously allocated buffer + */ + img_update_free(); + + spin_unlock(&rbu_data.lock); + + ordernum = get_order(size); + image_update_buffer = + (unsigned char *)__get_free_pages(GFP_DMA32, ordernum); + spin_lock(&rbu_data.lock); + if (!image_update_buffer) { + pr_debug("Not enough memory for image update: size = %ld\n", size); + return -ENOMEM; + } + + img_buf_phys_addr = (unsigned long)virt_to_phys(image_update_buffer); + if (WARN_ON_ONCE(img_buf_phys_addr > BIOS_SCAN_LIMIT)) + return -EINVAL; /* can't happen per definition */ + + rbu_data.image_update_buffer = image_update_buffer; + rbu_data.image_update_buffer_size = size; + rbu_data.bios_image_size = rbu_data.image_update_buffer_size; + rbu_data.image_update_ordernum = ordernum; + return 0; +} + +static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) +{ + int retval; + size_t bytes_left; + size_t data_length; + char *ptempBuf = buffer; + + /* check to see if we have something to return */ + if (rbu_data.num_packets == 0) { + pr_debug("no packets written\n"); + retval = -ENOMEM; + goto read_rbu_data_exit; + } + + if (pos > rbu_data.imagesize) { + retval = 0; + pr_warn("data underrun\n"); + goto read_rbu_data_exit; + } + + bytes_left = rbu_data.imagesize - pos; + data_length = min(bytes_left, count); + + if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) + goto read_rbu_data_exit; + + if ((pos + count) > rbu_data.imagesize) { + rbu_data.packet_read_count = 0; + /* this was the last copy */ + retval = bytes_left; + } else + retval = count; + + read_rbu_data_exit: + return retval; +} + +static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) +{ + /* check to see if we have something to return */ + if ((rbu_data.image_update_buffer == NULL) || + (rbu_data.bios_image_size == 0)) { + pr_debug("image_update_buffer %p, bios_image_size %lu\n", + rbu_data.image_update_buffer, + rbu_data.bios_image_size); + return -ENOMEM; + } + + return memory_read_from_buffer(buffer, count, &pos, + rbu_data.image_update_buffer, rbu_data.bios_image_size); +} + +static ssize_t data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + ssize_t ret_count = 0; + + spin_lock(&rbu_data.lock); + + if (!strcmp(image_type, "mono")) + ret_count = read_rbu_mono_data(buffer, pos, count); + else if (!strcmp(image_type, "packet")) + ret_count = read_packet_data(buffer, pos, count); + else + pr_debug("invalid image type specified\n"); + + spin_unlock(&rbu_data.lock); + return ret_count; +} +static BIN_ATTR_RO(data, 0); + +static void callbackfn_rbu(const struct firmware *fw, void *context) +{ + rbu_data.entry_created = 0; + + if (!fw) + return; + + if (!fw->size) + goto out; + + spin_lock(&rbu_data.lock); + if (!strcmp(image_type, "mono")) { + if (!img_update_realloc(fw->size)) + memcpy(rbu_data.image_update_buffer, + fw->data, fw->size); + } else if (!strcmp(image_type, "packet")) { + /* + * we need to free previous packets if a + * new hunk of packets needs to be downloaded + */ + packet_empty_list(); + if (packetize_data(fw->data, fw->size)) + /* Incase something goes wrong when we are + * in middle of packetizing the data, we + * need to free up whatever packets might + * have been created before we quit. + */ + packet_empty_list(); + } else + pr_debug("invalid image type specified\n"); + spin_unlock(&rbu_data.lock); + out: + release_firmware(fw); +} + +static ssize_t image_type_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int size = 0; + if (!pos) + size = scnprintf(buffer, count, "%s\n", image_type); + return size; +} + +static ssize_t image_type_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int rc = count; + int req_firm_rc = 0; + int i; + spin_lock(&rbu_data.lock); + /* + * Find the first newline or space + */ + for (i = 0; i < count; ++i) + if (buffer[i] == '\n' || buffer[i] == ' ') { + buffer[i] = '\0'; + break; + } + if (i == count) + buffer[count] = '\0'; + + if (strstr(buffer, "mono")) + strcpy(image_type, "mono"); + else if (strstr(buffer, "packet")) + strcpy(image_type, "packet"); + else if (strstr(buffer, "init")) { + /* + * If due to the user error the driver gets in a bad + * state where even though it is loaded , the + * /sys/class/firmware/dell_rbu entries are missing. + * to cover this situation the user can recreate entries + * by writing init to image_type. + */ + if (!rbu_data.entry_created) { + spin_unlock(&rbu_data.lock); + req_firm_rc = request_firmware_nowait(THIS_MODULE, + FW_ACTION_NOHOTPLUG, "dell_rbu", + &rbu_device->dev, GFP_KERNEL, &context, + callbackfn_rbu); + if (req_firm_rc) { + pr_err("request_firmware_nowait failed %d\n", rc); + rc = -EIO; + } else + rbu_data.entry_created = 1; + + spin_lock(&rbu_data.lock); + } + } else { + pr_warn("image_type is invalid\n"); + spin_unlock(&rbu_data.lock); + return -EINVAL; + } + + /* we must free all previous allocations */ + packet_empty_list(); + img_update_free(); + spin_unlock(&rbu_data.lock); + + return rc; +} +static BIN_ATTR_RW(image_type, 0); + +static ssize_t packet_size_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + int size = 0; + if (!pos) { + spin_lock(&rbu_data.lock); + size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); + spin_unlock(&rbu_data.lock); + } + return size; +} + +static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t pos, size_t count) +{ + unsigned long temp; + spin_lock(&rbu_data.lock); + packet_empty_list(); + sscanf(buffer, "%lu", &temp); + if (temp < 0xffffffff) + rbu_data.packetsize = temp; + + spin_unlock(&rbu_data.lock); + return count; +} +static BIN_ATTR_RW(packet_size, 0); + +static struct bin_attribute *rbu_bin_attrs[] = { + &bin_attr_data, + &bin_attr_image_type, + &bin_attr_packet_size, + NULL +}; + +static const struct attribute_group rbu_group = { + .bin_attrs = rbu_bin_attrs, +}; + +static int __init dcdrbu_init(void) +{ + int rc; + spin_lock_init(&rbu_data.lock); + + init_packet_head(); + rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0); + if (IS_ERR(rbu_device)) { + pr_err("platform_device_register_simple failed\n"); + return PTR_ERR(rbu_device); + } + + rc = sysfs_create_group(&rbu_device->dev.kobj, &rbu_group); + if (rc) + goto out_devreg; + + rbu_data.entry_created = 0; + return 0; + +out_devreg: + platform_device_unregister(rbu_device); + return rc; +} + +static __exit void dcdrbu_exit(void) +{ + spin_lock(&rbu_data.lock); + packet_empty_list(); + img_update_free(); + spin_unlock(&rbu_data.lock); + sysfs_remove_group(&rbu_device->dev.kobj, &rbu_group); + platform_device_unregister(rbu_device); +} + +module_exit(dcdrbu_exit); +module_init(dcdrbu_init); + +/* vim:noet:ts=8:sw=8 +*/ diff --git a/drivers/platform/x86/dell_rbu.c b/drivers/platform/x86/dell_rbu.c deleted file mode 100644 index 03c3ff34bcf5..000000000000 --- a/drivers/platform/x86/dell_rbu.c +++ /dev/null @@ -1,680 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * dell_rbu.c - * Bios Update driver for Dell systems - * Author: Dell Inc - * Abhay Salunke - * - * Copyright (C) 2005 Dell Inc. - * - * Remote BIOS Update (rbu) driver is used for updating DELL BIOS by - * creating entries in the /sys file systems on Linux 2.6 and higher - * kernels. The driver supports two mechanism to update the BIOS namely - * contiguous and packetized. Both these methods still require having some - * application to set the CMOS bit indicating the BIOS to update itself - * after a reboot. - * - * Contiguous method: - * This driver writes the incoming data in a monolithic image by allocating - * contiguous physical pages large enough to accommodate the incoming BIOS - * image size. - * - * Packetized method: - * The driver writes the incoming packet image by allocating a new packet - * on every time the packet data is written. This driver requires an - * application to break the BIOS image in to fixed sized packet chunks. - * - * See Documentation/admin-guide/dell_rbu.rst for more info. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_AUTHOR("Abhay Salunke "); -MODULE_DESCRIPTION("Driver for updating BIOS image on DELL systems"); -MODULE_LICENSE("GPL"); -MODULE_VERSION("3.2"); - -#define BIOS_SCAN_LIMIT 0xffffffff -#define MAX_IMAGE_LENGTH 16 -static struct _rbu_data { - void *image_update_buffer; - unsigned long image_update_buffer_size; - unsigned long bios_image_size; - int image_update_ordernum; - spinlock_t lock; - unsigned long packet_read_count; - unsigned long num_packets; - unsigned long packetsize; - unsigned long imagesize; - int entry_created; -} rbu_data; - -static char image_type[MAX_IMAGE_LENGTH + 1] = "mono"; -module_param_string(image_type, image_type, sizeof (image_type), 0); -MODULE_PARM_DESC(image_type, "BIOS image type. choose- mono or packet or init"); - -static unsigned long allocation_floor = 0x100000; -module_param(allocation_floor, ulong, 0644); -MODULE_PARM_DESC(allocation_floor, "Minimum address for allocations when using Packet mode"); - -struct packet_data { - struct list_head list; - size_t length; - void *data; - int ordernum; -}; - -static struct packet_data packet_data_head; - -static struct platform_device *rbu_device; -static int context; - -static void init_packet_head(void) -{ - INIT_LIST_HEAD(&packet_data_head.list); - rbu_data.packet_read_count = 0; - rbu_data.num_packets = 0; - rbu_data.packetsize = 0; - rbu_data.imagesize = 0; -} - -static int create_packet(void *data, size_t length) -{ - struct packet_data *newpacket; - int ordernum = 0; - int retval = 0; - unsigned int packet_array_size = 0; - void **invalid_addr_packet_array = NULL; - void *packet_data_temp_buf = NULL; - unsigned int idx = 0; - - pr_debug("entry\n"); - - if (!rbu_data.packetsize) { - pr_debug("packetsize not specified\n"); - retval = -EINVAL; - goto out_noalloc; - } - - spin_unlock(&rbu_data.lock); - - newpacket = kzalloc(sizeof (struct packet_data), GFP_KERNEL); - - if (!newpacket) { - pr_warn("failed to allocate new packet\n"); - retval = -ENOMEM; - spin_lock(&rbu_data.lock); - goto out_noalloc; - } - - ordernum = get_order(length); - - /* - * BIOS errata mean we cannot allocate packets below 1MB or they will - * be overwritten by BIOS. - * - * array to temporarily hold packets - * that are below the allocation floor - * - * NOTE: very simplistic because we only need the floor to be at 1MB - * due to BIOS errata. This shouldn't be used for higher floors - * or you will run out of mem trying to allocate the array. - */ - packet_array_size = max_t(unsigned int, allocation_floor / rbu_data.packetsize, 1); - invalid_addr_packet_array = kcalloc(packet_array_size, sizeof(void *), - GFP_KERNEL); - - if (!invalid_addr_packet_array) { - pr_warn("failed to allocate invalid_addr_packet_array\n"); - retval = -ENOMEM; - spin_lock(&rbu_data.lock); - goto out_alloc_packet; - } - - while (!packet_data_temp_buf) { - packet_data_temp_buf = (unsigned char *) - __get_free_pages(GFP_KERNEL, ordernum); - if (!packet_data_temp_buf) { - pr_warn("failed to allocate new packet\n"); - retval = -ENOMEM; - spin_lock(&rbu_data.lock); - goto out_alloc_packet_array; - } - - if ((unsigned long)virt_to_phys(packet_data_temp_buf) - < allocation_floor) { - pr_debug("packet 0x%lx below floor at 0x%lx\n", - (unsigned long)virt_to_phys( - packet_data_temp_buf), - allocation_floor); - invalid_addr_packet_array[idx++] = packet_data_temp_buf; - packet_data_temp_buf = NULL; - } - } - /* - * set to uncachable or it may never get written back before reboot - */ - set_memory_uc((unsigned long)packet_data_temp_buf, 1 << ordernum); - - spin_lock(&rbu_data.lock); - - newpacket->data = packet_data_temp_buf; - - pr_debug("newpacket at physical addr %lx\n", - (unsigned long)virt_to_phys(newpacket->data)); - - /* packets may not have fixed size */ - newpacket->length = length; - newpacket->ordernum = ordernum; - ++rbu_data.num_packets; - - /* initialize the newly created packet headers */ - INIT_LIST_HEAD(&newpacket->list); - list_add_tail(&newpacket->list, &packet_data_head.list); - - memcpy(newpacket->data, data, length); - - pr_debug("exit\n"); - -out_alloc_packet_array: - /* always free packet array */ - while (idx--) { - pr_debug("freeing unused packet below floor 0x%lx\n", - (unsigned long)virt_to_phys(invalid_addr_packet_array[idx])); - free_pages((unsigned long)invalid_addr_packet_array[idx], ordernum); - } - kfree(invalid_addr_packet_array); - -out_alloc_packet: - /* if error, free data */ - if (retval) - kfree(newpacket); - -out_noalloc: - return retval; -} - -static int packetize_data(const u8 *data, size_t length) -{ - int rc = 0; - int done = 0; - int packet_length; - u8 *temp; - u8 *end = (u8 *) data + length; - pr_debug("data length %zd\n", length); - if (!rbu_data.packetsize) { - pr_warn("packetsize not specified\n"); - return -EIO; - } - - temp = (u8 *) data; - - /* packetize the hunk */ - while (!done) { - if ((temp + rbu_data.packetsize) < end) - packet_length = rbu_data.packetsize; - else { - /* this is the last packet */ - packet_length = end - temp; - done = 1; - } - - if ((rc = create_packet(temp, packet_length))) - return rc; - - pr_debug("%p:%td\n", temp, (end - temp)); - temp += packet_length; - } - - rbu_data.imagesize = length; - - return rc; -} - -static int do_packet_read(char *data, struct packet_data *newpacket, - int length, int bytes_read, int *list_read_count) -{ - void *ptemp_buf; - int bytes_copied = 0; - int j = 0; - - *list_read_count += newpacket->length; - - if (*list_read_count > bytes_read) { - /* point to the start of unread data */ - j = newpacket->length - (*list_read_count - bytes_read); - /* point to the offset in the packet buffer */ - ptemp_buf = (u8 *) newpacket->data + j; - /* - * check if there is enough room in - * * the incoming buffer - */ - if (length > (*list_read_count - bytes_read)) - /* - * copy what ever is there in this - * packet and move on - */ - bytes_copied = (*list_read_count - bytes_read); - else - /* copy the remaining */ - bytes_copied = length; - memcpy(data, ptemp_buf, bytes_copied); - } - return bytes_copied; -} - -static int packet_read_list(char *data, size_t * pread_length) -{ - struct packet_data *newpacket; - int temp_count = 0; - int bytes_copied = 0; - int bytes_read = 0; - int remaining_bytes = 0; - char *pdest = data; - - /* check if we have any packets */ - if (0 == rbu_data.num_packets) - return -ENOMEM; - - remaining_bytes = *pread_length; - bytes_read = rbu_data.packet_read_count; - - list_for_each_entry(newpacket, (&packet_data_head.list)->next, list) { - bytes_copied = do_packet_read(pdest, newpacket, - remaining_bytes, bytes_read, &temp_count); - remaining_bytes -= bytes_copied; - bytes_read += bytes_copied; - pdest += bytes_copied; - /* - * check if we reached end of buffer before reaching the - * last packet - */ - if (remaining_bytes == 0) - break; - } - /*finally set the bytes read */ - *pread_length = bytes_read - rbu_data.packet_read_count; - rbu_data.packet_read_count = bytes_read; - return 0; -} - -static void packet_empty_list(void) -{ - struct packet_data *newpacket, *tmp; - - list_for_each_entry_safe(newpacket, tmp, (&packet_data_head.list)->next, list) { - list_del(&newpacket->list); - - /* - * zero out the RBU packet memory before freeing - * to make sure there are no stale RBU packets left in memory - */ - memset(newpacket->data, 0, rbu_data.packetsize); - set_memory_wb((unsigned long)newpacket->data, - 1 << newpacket->ordernum); - free_pages((unsigned long) newpacket->data, - newpacket->ordernum); - kfree(newpacket); - } - rbu_data.packet_read_count = 0; - rbu_data.num_packets = 0; - rbu_data.imagesize = 0; -} - -/* - * img_update_free: Frees the buffer allocated for storing BIOS image - * Always called with lock held and returned with lock held - */ -static void img_update_free(void) -{ - if (!rbu_data.image_update_buffer) - return; - /* - * zero out this buffer before freeing it to get rid of any stale - * BIOS image copied in memory. - */ - memset(rbu_data.image_update_buffer, 0, - rbu_data.image_update_buffer_size); - free_pages((unsigned long) rbu_data.image_update_buffer, - rbu_data.image_update_ordernum); - - /* - * Re-initialize the rbu_data variables after a free - */ - rbu_data.image_update_ordernum = -1; - rbu_data.image_update_buffer = NULL; - rbu_data.image_update_buffer_size = 0; - rbu_data.bios_image_size = 0; -} - -/* - * img_update_realloc: This function allocates the contiguous pages to - * accommodate the requested size of data. The memory address and size - * values are stored globally and on every call to this function the new - * size is checked to see if more data is required than the existing size. - * If true the previous memory is freed and new allocation is done to - * accommodate the new size. If the incoming size is less then than the - * already allocated size, then that memory is reused. This function is - * called with lock held and returns with lock held. - */ -static int img_update_realloc(unsigned long size) -{ - unsigned char *image_update_buffer = NULL; - unsigned long img_buf_phys_addr; - int ordernum; - - /* - * check if the buffer of sufficient size has been - * already allocated - */ - if (rbu_data.image_update_buffer_size >= size) { - /* - * check for corruption - */ - if ((size != 0) && (rbu_data.image_update_buffer == NULL)) { - pr_err("corruption check failed\n"); - return -EINVAL; - } - /* - * we have a valid pre-allocated buffer with - * sufficient size - */ - return 0; - } - - /* - * free any previously allocated buffer - */ - img_update_free(); - - spin_unlock(&rbu_data.lock); - - ordernum = get_order(size); - image_update_buffer = - (unsigned char *)__get_free_pages(GFP_DMA32, ordernum); - spin_lock(&rbu_data.lock); - if (!image_update_buffer) { - pr_debug("Not enough memory for image update: size = %ld\n", size); - return -ENOMEM; - } - - img_buf_phys_addr = (unsigned long)virt_to_phys(image_update_buffer); - if (WARN_ON_ONCE(img_buf_phys_addr > BIOS_SCAN_LIMIT)) - return -EINVAL; /* can't happen per definition */ - - rbu_data.image_update_buffer = image_update_buffer; - rbu_data.image_update_buffer_size = size; - rbu_data.bios_image_size = rbu_data.image_update_buffer_size; - rbu_data.image_update_ordernum = ordernum; - return 0; -} - -static ssize_t read_packet_data(char *buffer, loff_t pos, size_t count) -{ - int retval; - size_t bytes_left; - size_t data_length; - char *ptempBuf = buffer; - - /* check to see if we have something to return */ - if (rbu_data.num_packets == 0) { - pr_debug("no packets written\n"); - retval = -ENOMEM; - goto read_rbu_data_exit; - } - - if (pos > rbu_data.imagesize) { - retval = 0; - pr_warn("data underrun\n"); - goto read_rbu_data_exit; - } - - bytes_left = rbu_data.imagesize - pos; - data_length = min(bytes_left, count); - - if ((retval = packet_read_list(ptempBuf, &data_length)) < 0) - goto read_rbu_data_exit; - - if ((pos + count) > rbu_data.imagesize) { - rbu_data.packet_read_count = 0; - /* this was the last copy */ - retval = bytes_left; - } else - retval = count; - - read_rbu_data_exit: - return retval; -} - -static ssize_t read_rbu_mono_data(char *buffer, loff_t pos, size_t count) -{ - /* check to see if we have something to return */ - if ((rbu_data.image_update_buffer == NULL) || - (rbu_data.bios_image_size == 0)) { - pr_debug("image_update_buffer %p, bios_image_size %lu\n", - rbu_data.image_update_buffer, - rbu_data.bios_image_size); - return -ENOMEM; - } - - return memory_read_from_buffer(buffer, count, &pos, - rbu_data.image_update_buffer, rbu_data.bios_image_size); -} - -static ssize_t data_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - ssize_t ret_count = 0; - - spin_lock(&rbu_data.lock); - - if (!strcmp(image_type, "mono")) - ret_count = read_rbu_mono_data(buffer, pos, count); - else if (!strcmp(image_type, "packet")) - ret_count = read_packet_data(buffer, pos, count); - else - pr_debug("invalid image type specified\n"); - - spin_unlock(&rbu_data.lock); - return ret_count; -} -static BIN_ATTR_RO(data, 0); - -static void callbackfn_rbu(const struct firmware *fw, void *context) -{ - rbu_data.entry_created = 0; - - if (!fw) - return; - - if (!fw->size) - goto out; - - spin_lock(&rbu_data.lock); - if (!strcmp(image_type, "mono")) { - if (!img_update_realloc(fw->size)) - memcpy(rbu_data.image_update_buffer, - fw->data, fw->size); - } else if (!strcmp(image_type, "packet")) { - /* - * we need to free previous packets if a - * new hunk of packets needs to be downloaded - */ - packet_empty_list(); - if (packetize_data(fw->data, fw->size)) - /* Incase something goes wrong when we are - * in middle of packetizing the data, we - * need to free up whatever packets might - * have been created before we quit. - */ - packet_empty_list(); - } else - pr_debug("invalid image type specified\n"); - spin_unlock(&rbu_data.lock); - out: - release_firmware(fw); -} - -static ssize_t image_type_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - int size = 0; - if (!pos) - size = scnprintf(buffer, count, "%s\n", image_type); - return size; -} - -static ssize_t image_type_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - int rc = count; - int req_firm_rc = 0; - int i; - spin_lock(&rbu_data.lock); - /* - * Find the first newline or space - */ - for (i = 0; i < count; ++i) - if (buffer[i] == '\n' || buffer[i] == ' ') { - buffer[i] = '\0'; - break; - } - if (i == count) - buffer[count] = '\0'; - - if (strstr(buffer, "mono")) - strcpy(image_type, "mono"); - else if (strstr(buffer, "packet")) - strcpy(image_type, "packet"); - else if (strstr(buffer, "init")) { - /* - * If due to the user error the driver gets in a bad - * state where even though it is loaded , the - * /sys/class/firmware/dell_rbu entries are missing. - * to cover this situation the user can recreate entries - * by writing init to image_type. - */ - if (!rbu_data.entry_created) { - spin_unlock(&rbu_data.lock); - req_firm_rc = request_firmware_nowait(THIS_MODULE, - FW_ACTION_NOHOTPLUG, "dell_rbu", - &rbu_device->dev, GFP_KERNEL, &context, - callbackfn_rbu); - if (req_firm_rc) { - pr_err("request_firmware_nowait failed %d\n", rc); - rc = -EIO; - } else - rbu_data.entry_created = 1; - - spin_lock(&rbu_data.lock); - } - } else { - pr_warn("image_type is invalid\n"); - spin_unlock(&rbu_data.lock); - return -EINVAL; - } - - /* we must free all previous allocations */ - packet_empty_list(); - img_update_free(); - spin_unlock(&rbu_data.lock); - - return rc; -} -static BIN_ATTR_RW(image_type, 0); - -static ssize_t packet_size_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - int size = 0; - if (!pos) { - spin_lock(&rbu_data.lock); - size = scnprintf(buffer, count, "%lu\n", rbu_data.packetsize); - spin_unlock(&rbu_data.lock); - } - return size; -} - -static ssize_t packet_size_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buffer, loff_t pos, size_t count) -{ - unsigned long temp; - spin_lock(&rbu_data.lock); - packet_empty_list(); - sscanf(buffer, "%lu", &temp); - if (temp < 0xffffffff) - rbu_data.packetsize = temp; - - spin_unlock(&rbu_data.lock); - return count; -} -static BIN_ATTR_RW(packet_size, 0); - -static struct bin_attribute *rbu_bin_attrs[] = { - &bin_attr_data, - &bin_attr_image_type, - &bin_attr_packet_size, - NULL -}; - -static const struct attribute_group rbu_group = { - .bin_attrs = rbu_bin_attrs, -}; - -static int __init dcdrbu_init(void) -{ - int rc; - spin_lock_init(&rbu_data.lock); - - init_packet_head(); - rbu_device = platform_device_register_simple("dell_rbu", -1, NULL, 0); - if (IS_ERR(rbu_device)) { - pr_err("platform_device_register_simple failed\n"); - return PTR_ERR(rbu_device); - } - - rc = sysfs_create_group(&rbu_device->dev.kobj, &rbu_group); - if (rc) - goto out_devreg; - - rbu_data.entry_created = 0; - return 0; - -out_devreg: - platform_device_unregister(rbu_device); - return rc; -} - -static __exit void dcdrbu_exit(void) -{ - spin_lock(&rbu_data.lock); - packet_empty_list(); - img_update_free(); - spin_unlock(&rbu_data.lock); - sysfs_remove_group(&rbu_device->dev.kobj, &rbu_group); - platform_device_unregister(rbu_device); -} - -module_exit(dcdrbu_exit); -module_init(dcdrbu_init); - -/* vim:noet:ts=8:sw=8 -*/ -- cgit v1.2.3 From f807f4b7b32db00fc8622289644362e0695989bb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 12:38:48 +0100 Subject: platform/surface: surface3-wmi: Fix variable 'status' set but not used compiler warning Explicitly check the status rather then relying on output.pointer staying NULL on an error. This silences the following compiler warning: drivers/platform/surface/surface3-wmi.c:60:14: warning: variable 'status' set but not used [-Wunused-but-set-variable] Reported-by: kernel test robot Reviewed-by: Maximilian Luz Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210204113848.105994-1-hdegoede@redhat.com --- drivers/platform/surface/surface3-wmi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 130b6f52a600..fcd1d4fb94d5 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -57,12 +57,16 @@ static DEFINE_MUTEX(s3_wmi_lock); static int s3_wmi_query_block(const char *guid, int instance, int *ret) { struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj = NULL; acpi_status status; - union acpi_object *obj; int error = 0; mutex_lock(&s3_wmi_lock); status = wmi_query_block(guid, instance, &output); + if (ACPI_FAILURE(status)) { + error = -EIO; + goto out_free_unlock; + } obj = output.pointer; -- cgit v1.2.3 From 2c15644ebed06949458bf6956ed124164ef51063 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 13:19:31 +0100 Subject: platform/x86: msi-wmi: Fix variable 'status' set but not used compiler warning Explicitly check the status rather then relying on output.pointer staying NULL on an error. Reported-by: Maximilian Luz Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210204121931.131617-1-hdegoede@redhat.com --- drivers/platform/x86/msi-wmi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 64ee7819c9d3..fd318cdfe313 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -96,6 +96,8 @@ static int msi_wmi_query_block(int instance, int *ret) struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; status = wmi_query_block(MSIWMI_BIOS_GUID, instance, &output); + if (ACPI_FAILURE(status)) + return -EIO; obj = output.pointer; -- cgit v1.2.3 From d2386d791cb72e0dcaa5f43d509a4f71c44f47d8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 15:01:57 +0100 Subject: platform/x86: thinkpad_acpi: Fix 'warning: no previous prototype for' warnings Some of the new dytc handling functions are not marked static, even though they are only used internally. Mark these static, fixing the following compiler warnings: drivers/platform/x86/thinkpad_acpi.c:10081:5: warning: no previous prototype for 'dytc_profile_get' [-Wmissing-prototypes] drivers/platform/x86/thinkpad_acpi.c:10095:5: warning: no previous prototype for 'dytc_cql_command' [-Wmissing-prototypes] drivers/platform/x86/thinkpad_acpi.c:10133:5: warning: no previous prototype for 'dytc_profile_set' [-Wmissing-prototypes] Cc: Mark Pearson Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210204140158.268289-1-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 18b390153e7f..42e0a497d69e 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -10078,8 +10078,8 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe * dytc_profile_get: Function to register with platform_profile * handler. Returns current platform profile. */ -int dytc_profile_get(struct platform_profile_handler *pprof, - enum platform_profile_option *profile) +static int dytc_profile_get(struct platform_profile_handler *pprof, + enum platform_profile_option *profile) { *profile = dytc_current_profile; return 0; @@ -10092,7 +10092,7 @@ int dytc_profile_get(struct platform_profile_handler *pprof, * - enable CQL * If not in CQL mode, just run the command */ -int dytc_cql_command(int command, int *output) +static int dytc_cql_command(int command, int *output) { int err, cmd_err, dummy; int cur_funcmode; @@ -10130,8 +10130,8 @@ int dytc_cql_command(int command, int *output) * dytc_profile_set: Function to register with platform_profile * handler. Sets current platform profile. */ -int dytc_profile_set(struct platform_profile_handler *pprof, - enum platform_profile_option profile) +static int dytc_profile_set(struct platform_profile_handler *pprof, + enum platform_profile_option profile) { int output; int err; -- cgit v1.2.3 From 9aa422f676c9bbd6621080924c135707510096bc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 15:01:58 +0100 Subject: platform/x86: thinkpad_acpi: Replace ifdef CONFIG_ACPI_PLATFORM_PROFILE with depends on With the #if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE), we get the following errors when thinkpad_acpi is builtin while CONFIG_ACPI_PLATFORM_PROFILE=m : drivers/platform/x86/thinkpad_acpi.c:10186: undefined reference to `platform_profile_notify' drivers/platform/x86/thinkpad_acpi.c:10226: undefined reference to `platform_profile_register' drivers/platform/x86/thinkpad_acpi.c:10246: undefined reference to `platform_profile_remove' This could be fixed by changing the IS_ENABLED to IS_REACHABLE, but I believe that it is better to just switch to using depends on. Using depends on ensures that platform-profile support is always available when thinkpad_acpi is build, hopefully leading to less confusing bug-reports about it sometimes not working. Cc: Mark Pearson Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210204140158.268289-2-hdegoede@redhat.com --- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/thinkpad_acpi.c | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index cf76e724e8c3..17328d1a5d72 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -482,6 +482,7 @@ config THINKPAD_ACPI depends on RFKILL || RFKILL = n depends on ACPI_VIDEO || ACPI_VIDEO = n depends on BACKLIGHT_CLASS_DEVICE + depends on ACPI_PLATFORM_PROFILE select HWMON select NVRAM select NEW_LEDS diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 42e0a497d69e..b881044b31b0 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -9995,8 +9995,6 @@ static struct ibm_struct proxsensor_driver_data = { .exit = proxsensor_exit, }; -#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE) - /************************************************************************* * DYTC Platform Profile interface */ @@ -10251,7 +10249,6 @@ static struct ibm_struct dytc_profile_driver_data = { .name = "dytc-profile", .exit = dytc_profile_exit, }; -#endif /* CONFIG_ACPI_PLATFORM_PROFILE */ /************************************************************************* * Keyboard language interface @@ -10476,11 +10473,9 @@ static void tpacpi_driver_event(const unsigned int hkey_event) if (hkey_event == TP_HKEY_EV_THM_CSM_COMPLETED) { lapsensor_refresh(); -#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE) /* If we are already accessing DYTC then skip dytc update */ if (!atomic_add_unless(&dytc_ignore_event, -1, 0)) dytc_profile_refresh(); -#endif } } @@ -10924,12 +10919,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_proxsensor_init, .data = &proxsensor_driver_data, }, -#if IS_ENABLED(CONFIG_ACPI_PLATFORM_PROFILE) { .init = tpacpi_dytc_profile_init, .data = &dytc_profile_driver_data, }, -#endif { .init = tpacpi_kbdlang_init, .data = &kbdlang_driver_data, -- cgit v1.2.3 From ef14f0e82c9b225ae19476fa5bed89d55b2a96d5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 4 Feb 2021 15:02:05 +0100 Subject: platform/x86: acer-wmi: Don't use ACPI_EXCEPTION() ACPI_EXCEPTION is only intended for internal use by the ACPICA code. ACPI_EXCEPTION was being used here to change the apci_status code into something human-readable, use acpi_format_exception() for this instead, which is the proper way to do this outside of ACPICA. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210204140205.268344-1-hdegoede@redhat.com --- drivers/platform/x86/acer-wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index c1a5357da885..85db9403cc14 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -30,7 +30,6 @@ #include #include -ACPI_MODULE_NAME(KBUILD_MODNAME); MODULE_AUTHOR("Carlos Corbacho"); MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver"); MODULE_LICENSE("GPL"); @@ -1605,7 +1604,8 @@ static void acer_kbd_dock_get_initial_state(void) status = wmi_evaluate_method(WMID_GUID3, 0, 0x2, &input_buf, &output_buf); if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "Error getting keyboard-dock initial status")); + pr_err("Error getting keyboard-dock initial status: %s\n", + acpi_format_exception(status)); return; } -- cgit v1.2.3 From d8f5c5ea6637270a640c70cd472ee945a60b4106 Mon Sep 17 00:00:00 2001 From: Barnabás Pőcze Date: Thu, 4 Feb 2021 14:20:15 +0000 Subject: platform/x86: Kconfig: add missing selects for ideapad-laptop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LED class support is needed by the ideapad-laptop module to compile after the referenced commit. Add missing NEW_LEDS and LEDS_CLASS to Kconfig. Fixes: 503325f84bc0 ("platform/x86: ideapad-laptop: add keyboard backlight control support") Signed-off-by: Barnabás Pőcze Link: https://lore.kernel.org/r/20210204142010.356675-1-pobrn@protonmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 17328d1a5d72..56353e8c792a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -452,6 +452,8 @@ config IDEAPAD_LAPTOP depends on ACPI_WMI || ACPI_WMI = n depends on ACPI_PLATFORM_PROFILE select INPUT_SPARSEKMAP + select NEW_LEDS + select LEDS_CLASS help This is a driver for Lenovo IdeaPad netbooks contains drivers for rfkill switch, hotkey, fan control and backlight control. -- cgit v1.2.3 From fc4325a1a34a8b2dffbd1b664cb41b9bead09f74 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 4 Feb 2021 17:05:08 +0200 Subject: platform/x86: intel_scu_wdt: Drop mistakenly added const Neither original structure nor platform_data is declared with const. Drop mistakenly added const when assing platform_data. Fixes: a507e5d90f3d ("platform/x86: intel_scu_wdt: Get rid of custom x86 model comparison") Reported-by: Stephen Rothwell Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210204150508.62659-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_scu_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel_scu_wdt.c b/drivers/platform/x86/intel_scu_wdt.c index 85ee85ca2215..c2479777a1d6 100644 --- a/drivers/platform/x86/intel_scu_wdt.c +++ b/drivers/platform/x86/intel_scu_wdt.c @@ -63,7 +63,7 @@ static int __init register_mid_wdt(void) if (!id) return -ENODEV; - wdt_dev.dev.platform_data = (const struct intel_mid_wdt_pdata *)id->driver_data; + wdt_dev.dev.platform_data = (struct intel_mid_wdt_pdata *)id->driver_data; return platform_device_register(&wdt_dev); } arch_initcall(register_mid_wdt); -- cgit v1.2.3 From bd69bcce4aa089435e2891222236b1cb20395bec Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 5 Feb 2021 02:26:57 +0100 Subject: platform/surface: Add Surface Hot-Plug driver Some Surface Book 2 and 3 models have a discrete GPU (dGPU) that is hot-pluggable. On those devices, the dGPU is contained in the base, which can be separated from the tablet part (containing CPU and touchscreen) while the device is running. It (in general) is presented as/behaves like a standard PCIe hot-plug capable device, however, this device can also be put into D3cold. In D3cold, the device itself is turned off and can thus not submit any standard PCIe hot-plug events. To properly detect hot-(un)plugging while the dGPU is in D3cold, out-of-band signaling is required. Without this, the device state will only get updated during the next bus-check, eg. via a manually issued lspci call. This commit adds a driver to handle out-of-band PCIe hot-(un)plug events on Microsoft Surface devices. On those devices, said events can be detected via GPIO interrupts, which are then forwarded to the corresponding ACPI DSM calls by this driver. The DSM then takes care of issuing the appropriate bus-/device-check, causing the PCI core to properly pick up the device change. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210205012657.1951753-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- MAINTAINERS | 6 + drivers/platform/surface/Kconfig | 19 ++ drivers/platform/surface/Makefile | 1 + drivers/platform/surface/surface_hotplug.c | 282 +++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 drivers/platform/surface/surface_hotplug.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 34bfa5c1aec5..4fcf3df517a8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11805,6 +11805,12 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git F: drivers/platform/surface/ +MICROSOFT SURFACE HOT-PLUG DRIVER +M: Maximilian Luz +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/surface/surface_hotplug.c + MICROSOFT SURFACE PRO 3 BUTTON DRIVER M: Chen Yu L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 83b0a4c7b352..0847b2dc97bf 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -86,6 +86,25 @@ config SURFACE_GPE accordingly. It is required on those devices to allow wake-ups from suspend by opening the lid. +config SURFACE_HOTPLUG + tristate "Surface Hot-Plug Driver" + depends on GPIOLIB + help + Driver for out-of-band hot-plug event signaling on Microsoft Surface + devices with hot-pluggable PCIe cards. + + This driver is used on Surface Book (2 and 3) devices with a + hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those + devices can enter D3cold, which prevents in-band (standard) PCIe + hot-plug signaling. Thus, without this driver, detaching the base + containing the dGPU will not correctly update the state of the + corresponding PCIe device if it is in D3cold. This driver adds support + for out-of-band hot-plug notifications, ensuring that the device state + is properly updated even when the device in question is in D3cold. + + Select M or Y here, if you want to (fully) support hot-plugging of + dGPU devices on the Surface Book 2 and/or 3 during D3cold. + config SURFACE_PRO3_BUTTON tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" depends on INPUT diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 3eb971006877..990424c5f0c9 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -11,4 +11,5 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o +obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c new file mode 100644 index 000000000000..cfcc15cfbacb --- /dev/null +++ b/drivers/platform/surface/surface_hotplug.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Book (2 and later) hot-plug driver. + * + * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This + * driver is responsible for out-of-band hot-plug event signaling on these + * devices. It is specifically required when the hot-plug device is in D3cold + * and can thus not generate PCIe hot-plug events itself. + * + * Event signaling is handled via ACPI, which will generate the appropriate + * device-check notifications to be picked up by the PCIe hot-plug driver. + * + * Copyright (C) 2019-2021 Maximilian Luz + */ + +#include +#include +#include +#include +#include +#include +#include + +static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false }; +static const struct acpi_gpio_params shps_base_presence = { 1, 0, false }; +static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false }; +static const struct acpi_gpio_params shps_device_power = { 3, 0, false }; +static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false }; +static const struct acpi_gpio_params shps_device_presence = { 5, 0, false }; + +static const struct acpi_gpio_mapping shps_acpi_gpios[] = { + { "base_presence-int-gpio", &shps_base_presence_int, 1 }, + { "base_presence-gpio", &shps_base_presence, 1 }, + { "device_power-int-gpio", &shps_device_power_int, 1 }, + { "device_power-gpio", &shps_device_power, 1 }, + { "device_presence-int-gpio", &shps_device_presence_int, 1 }, + { "device_presence-gpio", &shps_device_presence, 1 }, + { }, +}; + +/* 5515a847-ed55-4b27-8352-cd320e10360a */ +static const guid_t shps_dsm_guid = + GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); + +#define SHPS_DSM_REVISION 1 + +enum shps_dsm_fn { + SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01, + SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02, + SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03, + SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04, + SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05, +}; + +enum shps_irq_type { + /* NOTE: Must be in order of enum shps_dsm_fn above. */ + SHPS_IRQ_TYPE_BASE_PRESENCE = 0, + SHPS_IRQ_TYPE_DEVICE_POWER = 1, + SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, + SHPS_NUM_IRQS, +}; + +static const char *const shps_gpio_names[] = { + [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence", + [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power", + [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence", +}; + +struct shps_device { + struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ + struct gpio_desc *gpio[SHPS_NUM_IRQS]; + unsigned int irq[SHPS_NUM_IRQS]; +}; + +#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) + +static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) +{ + return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; +} + +static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + union acpi_object *result; + union acpi_object param; + int value; + + mutex_lock(&sdev->lock[type]); + + value = gpiod_get_value_cansleep(sdev->gpio[type]); + if (value < 0) { + mutex_unlock(&sdev->lock[type]); + dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); + return; + } + + dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); + + param.type = ACPI_TYPE_INTEGER; + param.integer.value = value; + + result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, + shps_dsm_fn_for_irq(type), ¶m); + + if (!result) { + dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", + type, value); + + } else if (result->type != ACPI_TYPE_BUFFER) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", + type, value); + + } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { + dev_err(&pdev->dev, + "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", + type, value); + } + + mutex_unlock(&sdev->lock[type]); + + if (result) + ACPI_FREE(result); +} + +static irqreturn_t shps_handle_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct shps_device *sdev = platform_get_drvdata(pdev); + int type; + + /* Figure out which IRQ we're handling. */ + for (type = 0; type < SHPS_NUM_IRQS; type++) + if (irq == sdev->irq[type]) + break; + + /* We should have found our interrupt, if not: this is a bug. */ + if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq)) + return IRQ_HANDLED; + + /* Forward interrupt to ACPI via DSM. */ + shps_dsm_notify_irq(pdev, type); + return IRQ_HANDLED; +} + +static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) +{ + unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; + struct shps_device *sdev = platform_get_drvdata(pdev); + struct gpio_desc *gpiod; + acpi_handle handle = ACPI_HANDLE(&pdev->dev); + const char *irq_name; + const int dsm = shps_dsm_fn_for_irq(type); + int status, irq; + + /* + * Only set up interrupts that we actually need: The Surface Book 3 + * does not have a DSM for base presence, so don't set up an interrupt + * for that. + */ + if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { + dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); + return 0; + } + + gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS); + if (IS_ERR(gpiod)) + return PTR_ERR(gpiod); + + irq = gpiod_to_irq(gpiod); + if (irq < 0) + return irq; + + irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type); + if (!irq_name) + return -ENOMEM; + + status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq, + flags, irq_name, pdev); + if (status) + return status; + + dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type); + + sdev->gpio[type] = gpiod; + sdev->irq[type] = irq; + + return 0; +} + +static int surface_hotplug_remove(struct platform_device *pdev) +{ + struct shps_device *sdev = platform_get_drvdata(pdev); + int i; + + /* Ensure that IRQs have been fully handled and won't trigger any more. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) + disable_irq(sdev->irq[i]); + + mutex_destroy(&sdev->lock[i]); + } + + return 0; +} + +static int surface_hotplug_probe(struct platform_device *pdev) +{ + struct shps_device *sdev; + int status, i; + + /* + * The MSHW0153 device is also present on the Surface Laptop 3, + * however that doesn't have a hot-pluggable PCIe device. It also + * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter + * it out here. + */ + if (gpiod_count(&pdev->dev, NULL) < 0) + return -ENODEV; + + status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); + if (status) + return status; + + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); + if (!sdev) + return -ENOMEM; + + platform_set_drvdata(pdev, sdev); + + /* + * Initialize IRQs so that we can safely call surface_hotplug_remove() + * on errors. + */ + for (i = 0; i < SHPS_NUM_IRQS; i++) + sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; + + /* Set up IRQs. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) { + mutex_init(&sdev->lock[i]); + + status = shps_setup_irq(pdev, i); + if (status) { + dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); + goto err; + } + } + + /* Ensure everything is up-to-date. */ + for (i = 0; i < SHPS_NUM_IRQS; i++) + if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) + shps_dsm_notify_irq(pdev, i); + + return 0; + +err: + surface_hotplug_remove(pdev); + return status; +} + +static const struct acpi_device_id surface_hotplug_acpi_match[] = { + { "MSHW0153", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match); + +static struct platform_driver surface_hotplug_driver = { + .probe = surface_hotplug_probe, + .remove = surface_hotplug_remove, + .driver = { + .name = "surface_hotplug", + .acpi_match_table = surface_hotplug_acpi_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(surface_hotplug_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 0f1f7f22f384f34b2f0800a3734aa0fc83eafff2 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sun, 7 Feb 2021 00:21:52 +0100 Subject: Platform: OLPC: Constify static struct regulator_ops The only usage of it is to assign its address to the ops field in the regulator_desc struct, which is a pointer to const struct regulator_ops. Make it const to allow the compiler to put it in read-only memory. Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20210206232152.58046-1-rikard.falkeborn@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/olpc/olpc-ec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 72dbbea0005c..4ff5c3a12991 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -386,7 +386,7 @@ static int dcon_regulator_is_enabled(struct regulator_dev *rdev) return ec->dcon_enabled ? 1 : 0; } -static struct regulator_ops dcon_regulator_ops = { +static const struct regulator_ops dcon_regulator_ops = { .enable = dcon_regulator_enable, .disable = dcon_regulator_disable, .is_enabled = dcon_regulator_is_enabled, -- cgit v1.2.3 From 86eb98cb4a911631874c43309f39aa0003ad0106 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 11 Feb 2021 13:41:49 +0100 Subject: platform/surface: aggregator: Fix access of unaligned value The raw message frame length is unaligned and explicitly marked as little endian. It should not be accessed without the appropriate accessor functions. Fix this. Note that payload.len already contains the correct length after parsing via sshp_parse_frame(), so we can simply use that instead. Reported-by: kernel-test-robot Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") Signed-off-by: Maximilian Luz Acked-by: Mark Gross Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210211124149.2439007-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 583315db8b02..15d96eac6811 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -1774,7 +1774,7 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) break; } - return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); + return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(payload.len); } static int ssh_ptl_rx_threadfn(void *data) -- cgit v1.2.3 From a7d53dbbc70a81d5781da7fc905b656f41ad2381 Mon Sep 17 00:00:00 2001 From: Casey Bowman Date: Wed, 10 Feb 2021 11:20:41 -0800 Subject: platform/x86: intel_scu_ipc: Increase virtual timeout from 3 to 5 seconds Increasing the virtual timeout time to account for scenarios that may require more time, like DisplayPort Multi-Stream Transport (DP MST), where the disconnect time can be extended longer than usual. The recommended timeout range is 5-10 seconds, of which we will take the lower bound. Signed-off-by: Casey Bowman Acked-by: Heikki Krogerus Acked-by: Mika Westerberg Acked-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210210192041.17022-1-casey.g.bowman@intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_scu_ipc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c index d9cf7f7602b0..9171a46a9e3f 100644 --- a/drivers/platform/x86/intel_scu_ipc.c +++ b/drivers/platform/x86/intel_scu_ipc.c @@ -75,7 +75,7 @@ struct intel_scu_ipc_dev { #define IPC_READ_BUFFER 0x90 /* Timeout in jiffies */ -#define IPC_TIMEOUT (3 * HZ) +#define IPC_TIMEOUT (5 * HZ) static struct intel_scu_ipc_dev *ipcdev; /* Only one for now */ static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */ -- cgit v1.2.3