summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/platform/surface/Kconfig2
-rw-r--r--drivers/platform/surface/Makefile1
-rw-r--r--drivers/platform/surface/aggregator/Kconfig42
-rw-r--r--drivers/platform/surface/aggregator/Makefile10
-rw-r--r--drivers/platform/surface/aggregator/controller.c2504
-rw-r--r--drivers/platform/surface/aggregator/controller.h276
-rw-r--r--drivers/platform/surface/aggregator/core.c787
-rw-r--r--drivers/platform/surface/aggregator/ssh_msgb.h205
-rw-r--r--drivers/platform/surface/aggregator/ssh_packet_layer.c1710
-rw-r--r--drivers/platform/surface/aggregator/ssh_packet_layer.h187
-rw-r--r--drivers/platform/surface/aggregator/ssh_parser.c228
-rw-r--r--drivers/platform/surface/aggregator/ssh_parser.h154
-rw-r--r--drivers/platform/surface/aggregator/ssh_request_layer.c1211
-rw-r--r--drivers/platform/surface/aggregator/ssh_request_layer.h143
-rw-r--r--include/linux/surface_aggregator/controller.h824
-rw-r--r--include/linux/surface_aggregator/serial_hub.h672
17 files changed, 8964 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 546aa66428c9..845a245f562c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11813,6 +11813,14 @@ L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/surface/surfacepro3_button.c
+MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM
+M: Maximilian Luz <luzmaximilian@gmail.com>
+S: Maintained
+W: https://github.com/linux-surface/surface-aggregator-module
+C: irc://chat.freenode.net/##linux-surface
+F: drivers/platform/surface/aggregator/
+F: include/linux/surface_aggregator/
+
MICROTEK X6 SCANNER
M: Oliver Neukum <oliver@neukum.org>
S: Maintained
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 <luzmaximilian@gmail.com>
+
+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 <luzmaximilian@gmail.com>
+
+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 <luzmaximilian@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/kref.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/rculist.h>
+#include <linux/rbtree.h>
+#include <linux/rwsem.h>
+#include <linux/serdev.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/srcu.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/serial_hub.h>
+
+#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