From: Hans de Goede <hdegoede@redhat.com>
To: Maximilian Luz <luzmaximilian@gmail.com>, linux-kernel@vger.kernel.org
Cc: "Mark Gross" <mgross@linux.intel.com>,
"Andy Shevchenko" <andy.shevchenko@gmail.com>,
"Barnabás Pőcze" <pobrn@protonmail.com>,
"Arnd Bergmann" <arnd@arndb.de>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Rob Herring" <robh@kernel.org>,
"Jiri Slaby" <jirislaby@kernel.org>,
"Rafael J . Wysocki" <rjw@rjwysocki.net>,
"Len Brown" <lenb@kernel.org>, "Blaž Hrastnik" <blaz@mxxn.io>,
"Dorian Stoll" <dorian.stoll@tmsp.io>,
platform-driver-x86@vger.kernel.org,
linux-serial@vger.kernel.org, linux-acpi@vger.kernel.org
Subject: Re: [PATCH v2 1/9] platform/surface: Add Surface Aggregator subsystem
Date: Tue, 8 Dec 2020 14:01:06 +0100 [thread overview]
Message-ID: <fd24d99a-e4f4-2030-e9bb-bcd549ce4bb9@redhat.com> (raw)
In-Reply-To: <20201203212640.663931-2-luzmaximilian@gmail.com>
Hi,
Thank you for your patch series.
Full review done, I have a couple of minor review remarks inline below.
Note these are really all suggestions and not must fix for this to
be merged items.
On 12/3/20 10:26 PM, Maximilian Luz wrote:
> 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 <luzmaximilian@gmail.com>
> ---
>
> Changes in v1 (from RFC):
> - add copyright lines
> - change SPDX identifier to GPL-2.0+ (was GPL-2.0-or-later)
> - fix bug in __ssh_ptl_queue_push, potentially leading to a deadlock
> in high-traffic situations
> - remove unnecessary READ_ONCE on multiple occasions
> - add NULL checks for ssh_packet_get, ssh_packet_put
> - add NULL checks for ssh_request_get, ssh_request_put
> - guard dev_pm_ops with CONFIG_PM_SLEEP check
> - mark struct ssam_request pointer as const when possible
> - introduce generic retry macro with defaults
> - retry base controller requests on failure
>
> Changes in v2:
> - add transmission timeout for packets
> - simplify acknowledgment timeout implementation
> - simplify packet ACK handling
> - restructure and simplify transmitter thread function
> - restructure SSH message building
> - switch to explicit RCU list for event notifiers
> - use sysfs_emit for sysfs attributes
> - fix memory leak on event item submission failure
> - fix unaligned length access in parser
> - use printk specifier for hex prefix instead of hard-coding it
> - use numeric limit macros instead of casting around '-1'
> - extract and document magic values
> - try to make return values more consistent
> - spell check comments and strings, fix typos
> - unify comment style
> - run checkpatch --strict, fix these and other style issues
> - improve comments/documentation
>
> ---
> MAINTAINERS | 8 +
> drivers/platform/surface/Kconfig | 2 +
> drivers/platform/surface/Makefile | 1 +
> drivers/platform/surface/aggregator/Kconfig | 43 +
> drivers/platform/surface/aggregator/Makefile | 10 +
> .../platform/surface/aggregator/controller.c | 2468 +++++++++++++++++
> .../platform/surface/aggregator/controller.h | 276 ++
> drivers/platform/surface/aggregator/core.c | 781 ++++++
> .../platform/surface/aggregator/ssh_msgb.h | 205 ++
> .../surface/aggregator/ssh_packet_layer.c | 1699 ++++++++++++
> .../surface/aggregator/ssh_packet_layer.h | 187 ++
> .../platform/surface/aggregator/ssh_parser.c | 228 ++
> .../platform/surface/aggregator/ssh_parser.h | 154 +
> .../surface/aggregator/ssh_request_layer.c | 1203 ++++++++
> .../surface/aggregator/ssh_request_layer.h | 143 +
> include/linux/surface_aggregator/controller.h | 824 ++++++
> include/linux/surface_aggregator/serial_hub.h | 672 +++++
> 17 files changed, 8904 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
> create mode 100644 include/linux/surface_aggregator/controller.h
> create mode 100644 include/linux/surface_aggregator/serial_hub.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index bb4604e64b4a..64730461f4df 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11692,6 +11692,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 33040b0b3b79..5af22f5141ff 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..ce34941ef91b
> --- /dev/null
> +++ b/drivers/platform/surface/aggregator/Kconfig
> @@ -0,0 +1,43 @@
> +# 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
> + depends on ACPI
> + 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..8d9811cc2943
> --- /dev/null
> +++ b/drivers/platform/surface/aggregator/controller.c
> @@ -0,0 +1,2468 @@
> +// 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/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 iff the given event matches the given notifier
s/iff/if/
> + * 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) {
> + 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.
> + *
> + * 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.
> + *
> + * 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. 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: Must be synchronized by the caller with regards to other
> + * ssam_nf_refcount_inc() and ssam_nf_refcount_dec() calls, e.g. via
> + * ``nf->lock``. Note that this lock should also be used to ensure the
> + * corresponding EC requests are sent, if necessary.
You write "e.g. via ``nf->lock``", are any other locking strategies used
in the other patches ? If not then it might be good to change this to just
stating that nf->lock must be held and adding an assert for that. I would
prefer a clear set of locking rules like this over rules like:
"must be synchronized ...".
Note I am naively assuming here that it is possible to come up with such a
clear set of locking rules, I'm not sure if that is the case.
Note in case it is possible to always take nf->lock but you are not doing
so in some places because of performance concerns then I would prefer to
move to a model where the caller simply always takes nf->lock. AFAICT non
of the events/data going through the SSH are high frequency (iow
none of them occur at a high rate of see 50 times / second or more) so
I don't think that we need to focus too much on optimizing things for
speed.
> + *
> + * 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;
> +
> + 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 {
So we hit this only if entry->refcount == INT_MAX, right?
That seems like something which should never happen, right?
So maybe add a:
WARN_ON(1);
here?
> + 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: Must be synchronized by the caller with regards to other
> + * ssam_nf_refcount_inc() and ssam_nf_refcount_dec() calls, e.g. via
> + * ``nf->lock``. Note that this lock should also be used to ensure the
> + * corresponding EC requests are sent, if necessary.
> + *
> + * 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;
> +
> + 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);
> + }
> +
> + if (!(nf_ret & SSAM_NOTIF_HANDLED)) {
Maybe use "else if" here so that on an error we don't emit both the error
and the unhandled event warning ?
> + 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);
> +
> + ssam_controller_destroy(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;
> +
> + if (!acpi_has_method(handle, "_DSM"))
> + return 0;
Is this expected on some models?
If yes then maybe add a comment.
If not then I think this can be dropped as acpi_evaluate_dsm_typed()
will then error out with -EIO which seems better then return 0
(while this is not expected).
> +
> + 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;
This exit path leaves *ret uninitialized, looking at the usage if this
function I see that this is intentional, but it did stood out to me, so maybe
add a comment like this ? :
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 from an exclusive context with regards to the
> + * state, if necessary, by locking the controller via ssam_controller_lock().
Again you are being a bit hand-wavy (I assume you know what I mean by that)
wrt the locking requirements. If possible I would prefer clearly spelled out
locking requirements in the form of "this and that lock must be held when
calling this function". Preferably backed-up by lockdep_assert-s asserting
these conditions.
And maybe if you are a bit stricter with always holding the lock when
calling this, you can also drop the WRITE_ONCE and the comment about it
(in all places where you do this).
Note, that like my remarks around the locking docs for
ssam_nf_refcount_inc() this assumes that it is possible to come
up with such a clear set of locking rules. If that for some reason
is not straight forwardd, then maybe add some docs documenting the
locking expectations somewhere instead ?
> + */
> +int ssam_controller_start(struct ssam_controller *ctrl)
> +{
> + int status;
> +
> + 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 from an exclusive context with regards to the
> + * state, if necessary, by locking the controller via ssam_controller_lock().
Same comment as earlier wrt the locking.
> + */
> +void ssam_controller_shutdown(struct ssam_controller *ctrl)
> +{
> + enum ssam_controller_state s = ctrl->state;
> + int status;
> +
> + 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.
> + *
> + * Must be called from an exclusive context with regards to the controller
> + * state.
Same comment as earlier wrt the locking.
> + */
> +void ssam_controller_destroy(struct ssam_controller *ctrl)
> +{
> + 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().
> + */
> +void ssam_request_sync_init(struct ssam_request_sync *rqst,
> + enum ssam_request_flags flags)
> +{
> + ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops);
> + init_completion(&rqst->comp);
> + rqst->resp = NULL;
> + rqst->status = 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;
> +
> + ssam_request_sync_init(rqst, spec->flags);
> + 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;
> +
> + ssam_request_sync_init(&rqst, spec->flags);
> + 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 <luzmaximilian@gmail.com>
> + */
> +
> +#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
> +#define _SURFACE_AGGREGATOR_CONTROLLER_H
> +
> +#include <linux/kref.h>
> +#include <linux/list.h>
> +#include <linux/mutex.h>
> +#include <linux/rbtree.h>
> +#include <linux/rwsem.h>
> +#include <linux/serdev.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 "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..ec6c7f40ad36
> --- /dev/null
> +++ b/drivers/platform/surface/aggregator/core.c
> @@ -0,0 +1,781 @@
> +// 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 <luzmaximilian@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/atomic.h>
> +#include <linux/completion.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/kernel.h>
> +#include <linux/kref.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/serdev.h>
> +#include <linux/sysfs.h>
> +
> +#include <linux/surface_aggregator/controller.h>
> +#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;
> +
> + /* 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;
> +
> + /*
> + * 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_shutdown(ctrl);
> +err_devinit:
> + serdev_device_close(serdev);
> +err_devopen:
> + ssam_controller_destroy(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 <luzmaximilian@gmail.com>");
> +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 <luzmaximilian@gmail.com>
> + */
> +
> +#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
> +#define _SURFACE_AGGREGATOR_SSH_MSGB_H
> +
> +#include <asm/unaligned.h>
> +#include <linux/types.h>
> +
> +#include <linux/surface_aggregator/controller.h>
> +#include <linux/surface_aggregator/serial_hub.h>
> +
> +/**
> + * 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..237d28c90e4b
> --- /dev/null
> +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c
> @@ -0,0 +1,1699 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * SSH packet transport layer.
> + *
> + * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
> + */
> +
> +#include <asm/unaligned.h>
> +#include <linux/atomic.h>
> +#include <linux/jiffies.h>
> +#include <linux/kfifo.h>
> +#include <linux/kref.h>
> +#include <linux/kthread.h>
> +#include <linux/ktime.h>
> +#include <linux/limits.h>
> +#include <linux/list.h>
> +#include <linux/serdev.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +
> +#include <linux/surface_aggregator/serial_hub.h>
> +
> +#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);
> +
> + 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;
> +
> + /*
> + * 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. */
Maybe add a lockdep_assert for this ?
(and the same for a bunch of similar cases below)
> +static int __ssh_ptl_queue_push(struct ssh_packet *packet)
> +{
> + struct ssh_ptl *ptl = packet->ptl;
> + struct list_head *head;
> +
> + 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;
> +}
<snip about 5000 more lines>
Snipped because nothing stood out in the rest of this patch.
Phew that was one large patch to review. I've run out of review
bandwidth for today, so I will review the rest of the series
later (probably coming Thursday).
Regards,
Hans
next prev parent reply other threads:[~2020-12-08 13:03 UTC|newest]
Thread overview: 47+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-12-03 21:26 [PATCH v2 0/9] Add support for Microsoft Surface System Aggregator Module Maximilian Luz
2020-12-03 21:26 ` [PATCH v2 1/9] platform/surface: Add Surface Aggregator subsystem Maximilian Luz
2020-12-08 13:01 ` Hans de Goede [this message]
2020-12-08 14:37 ` Maximilian Luz
2020-12-08 14:43 ` Hans de Goede
2020-12-08 14:54 ` Maximilian Luz
2020-12-03 21:26 ` [PATCH v2 2/9] platform/surface: aggregator: Add control packet allocation caching Maximilian Luz
2020-12-15 13:42 ` Hans de Goede
2020-12-03 21:26 ` [PATCH v2 3/9] platform/surface: aggregator: Add event item " Maximilian Luz
2020-12-15 13:44 ` Hans de Goede
2020-12-03 21:26 ` [PATCH v2 4/9] platform/surface: aggregator: Add trace points Maximilian Luz
2020-12-15 14:20 ` Hans de Goede
2020-12-03 21:26 ` [PATCH v2 5/9] platform/surface: aggregator: Add error injection capabilities Maximilian Luz
2020-12-15 14:43 ` Hans de Goede
2020-12-03 21:26 ` [PATCH v2 6/9] platform/surface: aggregator: Add dedicated bus and device type Maximilian Luz
2020-12-15 14:49 ` Hans de Goede
2020-12-15 14:50 ` Hans de Goede
2020-12-03 21:26 ` [PATCH v2 7/9] docs: driver-api: Add Surface Aggregator subsystem documentation Maximilian Luz
2020-12-15 16:25 ` Hans de Goede
2020-12-03 21:26 ` [PATCH v2 8/9] platform/surface: Add Surface Aggregator user-space interface Maximilian Luz
2020-12-15 16:35 ` Hans de Goede
2020-12-15 20:00 ` Maximilian Luz
2020-12-03 21:26 ` [PATCH v2 9/9] platform/surface: Add Surface ACPI Notify driver Maximilian Luz
2020-12-15 17:18 ` Hans de Goede
2020-12-06 7:07 ` [PATCH v2 0/9] Add support for Microsoft Surface System Aggregator Module Leon Romanovsky
2020-12-06 8:32 ` Greg Kroah-Hartman
2020-12-06 8:35 ` Leon Romanovsky
2020-12-06 11:13 ` Maximilian Luz
2020-12-06 8:41 ` Hans de Goede
2020-12-06 8:56 ` Leon Romanovsky
2020-12-06 10:04 ` Hans de Goede
2020-12-06 10:33 ` Leon Romanovsky
2020-12-06 10:41 ` Hans de Goede
2020-12-06 11:41 ` Leon Romanovsky
2020-12-06 13:43 ` Maximilian Luz
2020-12-06 10:51 ` Maximilian Luz
2020-12-06 8:58 ` Blaž Hrastnik
2020-12-06 9:06 ` Leon Romanovsky
2020-12-06 10:33 ` Maximilian Luz
2020-12-06 10:43 ` Hans de Goede
2020-12-06 10:56 ` Maximilian Luz
2020-12-06 11:30 ` Leon Romanovsky
2020-12-06 13:27 ` Maximilian Luz
2020-12-06 15:58 ` Maximilian Luz
2020-12-07 6:15 ` Leon Romanovsky
2020-12-07 8:49 ` Hans de Goede
2020-12-07 9:12 ` Greg Kroah-Hartman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=fd24d99a-e4f4-2030-e9bb-bcd549ce4bb9@redhat.com \
--to=hdegoede@redhat.com \
--cc=andy.shevchenko@gmail.com \
--cc=arnd@arndb.de \
--cc=blaz@mxxn.io \
--cc=dorian.stoll@tmsp.io \
--cc=gregkh@linuxfoundation.org \
--cc=jirislaby@kernel.org \
--cc=lenb@kernel.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=luzmaximilian@gmail.com \
--cc=mgross@linux.intel.com \
--cc=platform-driver-x86@vger.kernel.org \
--cc=pobrn@protonmail.com \
--cc=rjw@rjwysocki.net \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.