2021-06-03 23:47:18

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 0/7] platform/surface: aggregator: Extend user-space interface for events

Extend the user-space debug interface so that it can be used to receive
SSAM events in user-space.

Currently, inspecting SSAM events requires writing a custom client
device and corresponding driver. This is not particularly user-friendly
for quick testing and comes with higher iteration times. Since we
already have a user-space interface, we can extend this to forward
events from SSAM via the controller device file to user-space. With this
we can then essentially write user-space SSAM clients for testing and
reverse-engineering, providing us with all the essential functionality
that previously only a kernel driver would have access to. Note that
this is still only intended to be an interface for debugging and
reverse-engineering purposes.

To achieve this, we need to extend the core to decouple events from
notifiers. Right now, enabling an event group requires registering a
notifier for that group. This notifier provides a callback that is
called when the event occurs. For user-space forwarding, we need to run
all events through the same file. In the current implementation, this
presents a problem as, when we don't know the exact events or can't
filter for them, multiple notifiers for the same target category will
lead to duplicate events to be sent through the file, one per notifier.

Decoupling notifier registration from event enable-/disablement (and the
corresponding reference counting) allows us to avoid this issue. We can
then register one notifier for a whole target category and enable or
disable events independently of this notifier. Since events are strictly
separated by their target category, this will not lead to duplicate
events.

With this, we can then provide user-space with two new IOCTLs for
registering notifiers for a specific target category of events they are
interested in. This allows us to forward all events received by those
notifiers to the internal buffer of the device file, from which they can
be read by user-space. In other words, user-space can, via those two
IOCTLs, select which event target categories they are interested in.

Furthermore, we add another two IOCTLs for enabling and disabling events
via the controller. While events can already be enabled and disabled via
generic requests, this does not respect the controller-internal
reference counting mechanism. Due to that, this can lead to an event
group being disabled even though a kernel-driver has requested it to be
enabled. Or in other words: Without this, a user-space client cannot
safely reset the state as it has only two options, keeping the event
group enabled and not attempt cleanup at all, or disable the event group
for all clients and potentially stop them from working properly.

Also update the copyright lines since we're already doing some work on
the core.

Maximilian Luz (7):
platform/surface: aggregator: Allow registering notifiers without
enabling events
platform/surface: aggregator: Allow enabling of events without
notifiers
platform/surface: aggregator: Update copyright
platform/surface: aggregator_cdev: Add support for forwarding events
to user-space
platform/surface: aggregator_cdev: Allow enabling of events from
user-space
platform/surface: aggregator_cdev: Add lockdep support
docs: driver-api: Update Surface Aggregator user-space interface
documentation

.../surface_aggregator/clients/cdev.rst | 127 ++++-
.../userspace-api/ioctl/ioctl-number.rst | 2 +-
drivers/platform/surface/aggregator/Kconfig | 2 +-
drivers/platform/surface/aggregator/Makefile | 2 +-
drivers/platform/surface/aggregator/bus.c | 2 +-
drivers/platform/surface/aggregator/bus.h | 2 +-
.../platform/surface/aggregator/controller.c | 206 ++++++-
.../platform/surface/aggregator/controller.h | 2 +-
drivers/platform/surface/aggregator/core.c | 2 +-
.../platform/surface/aggregator/ssh_msgb.h | 2 +-
.../surface/aggregator/ssh_packet_layer.c | 2 +-
.../surface/aggregator/ssh_packet_layer.h | 2 +-
.../platform/surface/aggregator/ssh_parser.c | 2 +-
.../platform/surface/aggregator/ssh_parser.h | 2 +-
.../surface/aggregator/ssh_request_layer.c | 2 +-
.../surface/aggregator/ssh_request_layer.h | 2 +-
drivers/platform/surface/aggregator/trace.h | 2 +-
.../surface/surface_aggregator_cdev.c | 531 +++++++++++++++++-
include/linux/surface_aggregator/controller.h | 27 +-
include/linux/surface_aggregator/device.h | 2 +-
include/linux/surface_aggregator/serial_hub.h | 2 +-
include/uapi/linux/surface_aggregator/cdev.h | 73 ++-
22 files changed, 921 insertions(+), 77 deletions(-)

--
2.31.1


2021-06-03 23:47:44

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 3/7] platform/surface: aggregator: Update copyright

It's 2021, update the copyright accordingly.

Signed-off-by: Maximilian Luz <[email protected]>
---
drivers/platform/surface/aggregator/Kconfig | 2 +-
drivers/platform/surface/aggregator/Makefile | 2 +-
drivers/platform/surface/aggregator/bus.c | 2 +-
drivers/platform/surface/aggregator/bus.h | 2 +-
drivers/platform/surface/aggregator/controller.c | 2 +-
drivers/platform/surface/aggregator/controller.h | 2 +-
drivers/platform/surface/aggregator/core.c | 2 +-
drivers/platform/surface/aggregator/ssh_msgb.h | 2 +-
drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +-
drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +-
drivers/platform/surface/aggregator/ssh_parser.c | 2 +-
drivers/platform/surface/aggregator/ssh_parser.h | 2 +-
drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +-
drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +-
drivers/platform/surface/aggregator/trace.h | 2 +-
include/linux/surface_aggregator/controller.h | 2 +-
include/linux/surface_aggregator/device.h | 2 +-
include/linux/surface_aggregator/serial_hub.h | 2 +-
18 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig
index 3aaeea9f0433..fd6dc452f3e8 100644
--- a/drivers/platform/surface/aggregator/Kconfig
+++ b/drivers/platform/surface/aggregator/Kconfig
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0+
-# Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+# Copyright (C) 2019-2021 Maximilian Luz <[email protected]>

menuconfig SURFACE_AGGREGATOR
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile
index c112e2c7112b..c8498c41e758 100644
--- a/drivers/platform/surface/aggregator/Makefile
+++ b/drivers/platform/surface/aggregator/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0+
-# Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+# Copyright (C) 2019-2021 Maximilian Luz <[email protected]>

# For include/trace/define_trace.h to include trace.h
CFLAGS_core.o = -I$(src)
diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c
index a9b660af0917..0169677c243e 100644
--- a/drivers/platform/surface/aggregator/bus.c
+++ b/drivers/platform/surface/aggregator/bus.c
@@ -2,7 +2,7 @@
/*
* Surface System Aggregator Module bus and device integration.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#include <linux/device.h>
diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h
index 7712baaed6a5..ed032c2cbdb2 100644
--- a/drivers/platform/surface/aggregator/bus.h
+++ b/drivers/platform/surface/aggregator/bus.h
@@ -2,7 +2,7 @@
/*
* Surface System Aggregator Module bus and device integration.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _SURFACE_AGGREGATOR_BUS_H
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
index 49edddea417e..e91ee7e72c14 100644
--- a/drivers/platform/surface/aggregator/controller.c
+++ b/drivers/platform/surface/aggregator/controller.c
@@ -2,7 +2,7 @@
/*
* Main SSAM/SSH controller structure and functionality.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#include <linux/acpi.h>
diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h
index 8297d34e7489..a0963c3562ff 100644
--- a/drivers/platform/surface/aggregator/controller.h
+++ b/drivers/platform/surface/aggregator/controller.h
@@ -2,7 +2,7 @@
/*
* Main SSAM/SSH controller structure and functionality.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c
index 8dc2c267bcd6..5d780e55f4a1 100644
--- a/drivers/platform/surface/aggregator/core.c
+++ b/drivers/platform/surface/aggregator/core.c
@@ -7,7 +7,7 @@
* Handles communication via requests as well as enabling, disabling, and
* relaying of events.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#include <linux/acpi.h>
diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h
index 1221f642dda1..e562958ffdf0 100644
--- a/drivers/platform/surface/aggregator/ssh_msgb.h
+++ b/drivers/platform/surface/aggregator/ssh_msgb.h
@@ -2,7 +2,7 @@
/*
* SSH message builder functions.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _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
index 15d96eac6811..5e08049fc3ac 100644
--- a/drivers/platform/surface/aggregator/ssh_packet_layer.c
+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c
@@ -2,7 +2,7 @@
/*
* SSH packet transport layer.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#include <asm/unaligned.h>
diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h
index e8757d03f279..2eb329f0b91a 100644
--- a/drivers/platform/surface/aggregator/ssh_packet_layer.h
+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h
@@ -2,7 +2,7 @@
/*
* SSH packet transport layer.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c
index e2dead8de94a..b77912f8f13b 100644
--- a/drivers/platform/surface/aggregator/ssh_parser.c
+++ b/drivers/platform/surface/aggregator/ssh_parser.c
@@ -2,7 +2,7 @@
/*
* SSH message parser.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#include <asm/unaligned.h>
diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h
index 63c38d350988..3bd6e180fd16 100644
--- a/drivers/platform/surface/aggregator/ssh_parser.h
+++ b/drivers/platform/surface/aggregator/ssh_parser.h
@@ -2,7 +2,7 @@
/*
* SSH message parser.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c
index 52a83a8fcf82..bfe1aaf38065 100644
--- a/drivers/platform/surface/aggregator/ssh_request_layer.c
+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c
@@ -2,7 +2,7 @@
/*
* SSH request transport layer.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#include <asm/unaligned.h>
diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h
index cb35815858d1..9c3cbae2d4bd 100644
--- a/drivers/platform/surface/aggregator/ssh_request_layer.h
+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h
@@ -2,7 +2,7 @@
/*
* SSH request transport layer.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h
index eb332bb53ae4..de64cf169060 100644
--- a/drivers/platform/surface/aggregator/trace.h
+++ b/drivers/platform/surface/aggregator/trace.h
@@ -2,7 +2,7 @@
/*
* Trace points for SSAM/SSH.
*
- * Copyright (C) 2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2020-2021 Maximilian Luz <[email protected]>
*/

#undef TRACE_SYSTEM
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
index 7965bdc669c5..068e1982ad37 100644
--- a/include/linux/surface_aggregator/controller.h
+++ b/include/linux/surface_aggregator/controller.h
@@ -6,7 +6,7 @@
* managing access and communication to and from the SSAM EC, as well as main
* communication structures and definitions.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H
diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
index 6ff9c58b3e17..f636c5310321 100644
--- a/include/linux/surface_aggregator/device.h
+++ b/include/linux/surface_aggregator/device.h
@@ -7,7 +7,7 @@
* Provides support for non-platform/non-ACPI SSAM clients via dedicated
* subsystem.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H
diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h
index 64276fbfa1d5..c3de43edcffa 100644
--- a/include/linux/surface_aggregator/serial_hub.h
+++ b/include/linux/surface_aggregator/serial_hub.h
@@ -6,7 +6,7 @@
* Surface System Aggregator Module (SSAM). Provides the interface for basic
* packet- and request-based communication with the SSAM EC via SSH.
*
- * Copyright (C) 2019-2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2019-2021 Maximilian Luz <[email protected]>
*/

#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H
--
2.31.1

2021-06-03 23:49:11

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 2/7] platform/surface: aggregator: Allow enabling of events without notifiers

We can already enable and disable SAM events via one of two ways: either
via a (non-observer) notifier tied to a specific event group, or a
generic event enable/disable request. In some instances, however,
neither method may be desirable.

The first method will tie the event enable request to a specific
notifier, however, when we want to receive notifications for multiple
event groups of the same target category and forward this to the same
notifier callback, we may receive duplicate events, i.e. one event per
registered notifier. The second method will bypass the internal
reference counting mechanism, meaning that a disable request will
disable the event regardless of any other client driver using it, which
may break the functionality of that driver.

To address this problem, add new functions that allow enabling and
disabling of events via the event reference counting mechanism built
into the controller, without needing to register a notifier.

This can then be used in combination with observer notifiers to process
multiple events of the same target category without duplication in the
same callback function.

Signed-off-by: Maximilian Luz <[email protected]>
---
.../platform/surface/aggregator/controller.c | 135 ++++++++++++++++++
include/linux/surface_aggregator/controller.h | 8 ++
2 files changed, 143 insertions(+)

diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
index cd3a6b77f48d..49edddea417e 100644
--- a/drivers/platform/surface/aggregator/controller.c
+++ b/drivers/platform/surface/aggregator/controller.c
@@ -2287,6 +2287,141 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
}
EXPORT_SYMBOL_GPL(ssam_notifier_unregister);

+/**
+ * ssam_controller_event_enable() - Enable the specified event.
+ * @ctrl: The controller to enable the event for.
+ * @reg: The event registry to use for enabling the event.
+ * @id: The event ID specifying the event to be enabled.
+ * @flags: The SAM event flags used for enabling the event.
+ *
+ * Increment the event reference count of the specified event. If the event has
+ * not been enabled previously, it will be enabled by this call.
+ *
+ * Note: In general, ssam_notifier_register() with a non-observer notifier
+ * should be preferred for enabling/disabling events, as this will guarantee
+ * proper ordering and event forwarding in case of errors during event
+ * enabling/disabling.
+ *
+ * Return: Returns zero on success, %-ENOSPC if the reference count for the
+ * specified event has reached its maximum, %-ENOMEM if the corresponding event
+ * entry could not be allocated. If this is the first time that this event has
+ * been enabled (i.e. the reference count was incremented from zero to one by
+ * this call), returns the status of the event-enable EC-command.
+ */
+int ssam_controller_event_enable(struct ssam_controller *ctrl,
+ struct ssam_event_registry reg,
+ struct ssam_event_id id, u8 flags)
+{
+ u16 rqid = ssh_tc_to_rqid(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, reg, 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",
+ reg.target_category, id.target_category, id.instance,
+ entry->refcount);
+
+ if (entry->refcount == 1) {
+ status = ssam_ssh_event_enable(ctrl, reg, id, flags);
+ if (status) {
+ kfree(ssam_nf_refcount_dec(nf, reg, id));
+ mutex_unlock(&nf->lock);
+ return status;
+ }
+
+ entry->flags = flags;
+
+ } else if (entry->flags != flags) {
+ ssam_warn(ctrl,
+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
+ flags, entry->flags, reg.target_category,
+ id.target_category, id.instance);
+ }
+
+ mutex_unlock(&nf->lock);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
+
+/**
+ * ssam_controller_event_disable() - Disable the specified event.
+ * @ctrl: The controller to disable the event for.
+ * @reg: The event registry to use for disabling the event.
+ * @id: The event ID specifying the event to be disabled.
+ * @flags: The flags used when enabling the event.
+ *
+ * Decrement the reference count of the specified event. If the reference count
+ * reaches zero, the event will be disabled.
+ *
+ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a
+ * non-observer notifier should be preferred for enabling/disabling events, as
+ * this will guarantee proper ordering and event forwarding in case of errors
+ * during event enabling/disabling.
+ *
+ * Return: Returns zero on success, %-ENOENT if the given event has not been
+ * enabled on the controller. If the reference count of the event reaches zero
+ * during this call, returns the status of the event-disable EC-command.
+ */
+int ssam_controller_event_disable(struct ssam_controller *ctrl,
+ struct ssam_event_registry reg,
+ struct ssam_event_id id, u8 flags)
+{
+ u16 rqid = ssh_tc_to_rqid(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);
+
+ entry = ssam_nf_refcount_dec(nf, reg, id);
+ if (WARN_ON(!entry)) {
+ mutex_unlock(&nf->lock);
+ return -ENOENT;
+ }
+
+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
+ reg.target_category, id.target_category, id.instance,
+ entry->refcount);
+
+ if (entry->flags != flags) {
+ ssam_warn(ctrl,
+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
+ flags, entry->flags, reg.target_category,
+ id.target_category, id.instance);
+ }
+
+ if (entry->refcount == 0) {
+ status = ssam_ssh_event_disable(ctrl, reg, id, flags);
+ kfree(entry);
+ }
+
+ mutex_unlock(&nf->lock);
+ return status;
+}
+EXPORT_SYMBOL_GPL(ssam_controller_event_disable);
+
/**
* ssam_notifier_disable_registered() - Disable events for all registered
* notifiers.
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
index cf4bb48a850e..7965bdc669c5 100644
--- a/include/linux/surface_aggregator/controller.h
+++ b/include/linux/surface_aggregator/controller.h
@@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
int ssam_notifier_unregister(struct ssam_controller *ctrl,
struct ssam_event_notifier *n);

+int ssam_controller_event_enable(struct ssam_controller *ctrl,
+ struct ssam_event_registry reg,
+ struct ssam_event_id id, u8 flags);
+
+int ssam_controller_event_disable(struct ssam_controller *ctrl,
+ struct ssam_event_registry reg,
+ struct ssam_event_id id, u8 flags);
+
#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */
--
2.31.1

2021-06-03 23:49:11

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 1/7] platform/surface: aggregator: Allow registering notifiers without enabling events

Currently, each SSAM event notifier is directly tied to one group of
events. This makes sense as registering a notifier will automatically
take care of enabling the corresponding event group and normally drivers
only need notifications for a very limited number of events, associated
with different callbacks for each group.

However, there are rare cases, especially for debugging, when we want to
get notifications for a whole event target category instead of just a
single group of events in that category. Registering multiple notifiers,
i.e. one per group, may be infeasible due to two issues: a) we might not
know every event enable/disable specification as some events are
auto-enabled by the EC and b) forwarding this to the same callback will
lead to duplicate events as we might not know the full event
specification to perform the appropriate filtering.

This commit introduces observer-notifiers, which are notifiers that are
not tied to a specific event group and do not attempt to manage any
events. In other words, they can be registered without enabling any
event group or incrementing the corresponding reference count and just
act as silent observers, listening to all currently/previously enabled
events based on their match-specification.

Essentially, this allows us to register one single notifier for a full
event target category, meaning that we can process all events of that
target category in a single callback without duplication. Specifically,
this will be used in the cdev debug interface to forward events to
user-space via a device file from which the events can be read.

Signed-off-by: Maximilian Luz <[email protected]>
---
.../platform/surface/aggregator/controller.c | 69 +++++++++++--------
include/linux/surface_aggregator/controller.h | 17 +++++
2 files changed, 58 insertions(+), 28 deletions(-)

diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
index a06964aa96e7..cd3a6b77f48d 100644
--- a/drivers/platform/surface/aggregator/controller.c
+++ b/drivers/platform/surface/aggregator/controller.c
@@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
* @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.
+ * Register an event notifier. Increment the usage counter of the associated
+ * SAM event if the notifier is not marked as an observer. If the event is not
+ * marked as an observer and is currently not enabled, it will be enabled
+ * during this call. If the notifier is marked as an observer, no attempt will
+ * be made at enabling any event and no reference count will be modified.
+ *
+ * Notifiers marked as observers do not need to be associated with one specific
+ * event, i.e. as long as no event matching is performed, only the event target
+ * category needs to be set.
*
* Return: Returns zero on success, %-ENOSPC if there have already been
* %INT_MAX notifiers for the event ID/type associated with the notifier block
@@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
* 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)
+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_refcount_entry *entry = NULL;
struct ssam_nf_head *nf_head;
struct ssam_nf *nf;
int status;
@@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl,

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);
- }
+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
+ 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);
+ 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);
+ if (entry) {
+ 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 (entry && 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));
@@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl,

entry->flags = n->event.flags;

- } else if (entry->flags != n->event.flags) {
+ } else if (entry && 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,
@@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register);
* @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.
+ * Unregister an event notifier. Decrement the usage counter of the associated
+ * SAM event if the notifier is not marked as an observer. 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)
+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;
@@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
return -ENOENT;
}

+ /*
+ * If this is an observer notifier, do not attempt to disable the
+ * event, just remove it.
+ */
+ if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)
+ goto remove;
+
entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
if (WARN_ON(!entry)) {
/*
@@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
}

if (entry->refcount == 0) {
- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id,
- n->event.flags);
+ status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags);
kfree(entry);
}

diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
index 0806796eabcb..cf4bb48a850e 100644
--- a/include/linux/surface_aggregator/controller.h
+++ b/include/linux/surface_aggregator/controller.h
@@ -795,6 +795,20 @@ enum ssam_event_mask {
#define SSAM_EVENT_REGISTRY_REG \
SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02)

+/**
+ * enum ssam_event_notifier_flags - Flags for event notifiers.
+ * @SSAM_EVENT_NOTIFIER_OBSERVER:
+ * The corresponding notifier acts as observer. Registering a notifier
+ * with this flag set will not attempt to enable any event. Equally,
+ * unregistering will not attempt to disable any event. Note that a
+ * notifier with this flag may not even correspond to a certain event at
+ * all, only to a specific event target category. Event matching will not
+ * be influenced by this flag.
+ */
+enum ssam_event_notifier_flags {
+ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0),
+};
+
/**
* struct ssam_event_notifier - Notifier block for SSAM events.
* @base: The base notifier block with callback function and priority.
@@ -803,6 +817,7 @@ enum ssam_event_mask {
* @event.id: ID specifying the event.
* @event.mask: Flags determining how events are matched to the notifier.
* @event.flags: Flags used for enabling the event.
+ * @flags: Notifier flags (see &enum ssam_event_notifier_flags).
*/
struct ssam_event_notifier {
struct ssam_notifier_block base;
@@ -813,6 +828,8 @@ struct ssam_event_notifier {
enum ssam_event_mask mask;
u8 flags;
} event;
+
+ unsigned long flags;
};

int ssam_notifier_register(struct ssam_controller *ctrl,
--
2.31.1

2021-06-03 23:49:33

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 7/7] docs: driver-api: Update Surface Aggregator user-space interface documentation

Update the controller-device user-space interface (cdev) documentation
for the newly introduced IOCTLs and event interface.

Signed-off-by: Maximilian Luz <[email protected]>
---
.../surface_aggregator/clients/cdev.rst | 127 +++++++++++++++++-
1 file changed, 122 insertions(+), 5 deletions(-)

diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst
index 248c1372d879..0134a841a079 100644
--- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst
+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst
@@ -1,9 +1,8 @@
.. SPDX-License-Identifier: GPL-2.0+

-.. |u8| replace:: :c:type:`u8 <u8>`
-.. |u16| replace:: :c:type:`u16 <u16>`
.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>`
.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>`
+.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event <ssam_cdev_event>`

==============================
User-Space EC Interface (cdev)
@@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in
A small python library and scripts for accessing this interface can be found
at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam.

+.. contents::
+
+
+Receiving Events
+================
+
+Events can be received by reading from the device-file. The are represented by
+the |ssam_cdev_event| datatype.
+
+Before events are available to be read, however, the desired notifiers must be
+registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in
+essence, callbacks, called when the EC sends an event. They are, in this
+interface, associated with a specific target category and device-file-instance.
+They forward any event of this category to the buffer of the corresponding
+instance, from which it can then be read.
+
+Notifiers themselves do not enable events on the EC. Thus, it may additionally
+be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While
+notifiers work per-client (i.e. per-device-file-instance), events are enabled
+globally, for the EC and all of its clients (regardless of userspace or
+non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE``
+IOCTLs take care of reference counting the events, such that an event is
+enabled as long as there is a client that has requested it.
+
+Note that enabled events are not automatically disabled once the client
+instance is closed. Therefore any client process (or group of processes) should
+balance their event enable calls with the corresponding event disable calls. It
+is, however, perfectly valid to enable and disable events on different client
+instances. For example, it is valid to set up notifiers and read events on
+client instance ``A``, enable those events on instance ``B`` (note that these
+will also be received by A since events are enabled/disabled globally), and
+after no more events are desired, disable the previously enabled events via
+instance ``C``.
+

Controller IOCTLs
=================
@@ -45,9 +78,33 @@ The following IOCTLs are provided:
- ``REQUEST``
- Perform synchronous SAM request.

+ * - ``0xA5``
+ - ``2``
+ - ``W``
+ - ``NOTIF_REGISTER``
+ - Register event notifier.

-``REQUEST``
------------
+ * - ``0xA5``
+ - ``3``
+ - ``W``
+ - ``NOTIF_UNREGISTER``
+ - Unregister event notifier.
+
+ * - ``0xA5``
+ - ``4``
+ - ``W``
+ - ``EVENT_ENABLE``
+ - Enable event source.
+
+ * - ``0xA5``
+ - ``5``
+ - ``W``
+ - ``EVENT_DISABLE``
+ - Disable event source.
+
+
+``SSAM_CDEV_REQUEST``
+---------------------

Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``.

@@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from
inside the IOCTL, but the request ``status`` member may still be negative in
case the actual execution of the request failed after it has been submitted.

-A full definition of the argument struct is provided below:
+A full definition of the argument struct is provided below.
+
+``SSAM_CDEV_NOTIF_REGISTER``
+----------------------------
+
+Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``.
+
+Register a notifier for the event target category specified in the given
+notifier description with the specified priority. Notifiers registration is
+required to receive events, but does not enable events themselves. After a
+notifier for a specific target category has been registered, all events of that
+category will be forwarded to the userspace client and can then be read from
+the device file instance. Note that events may have to be enabled, e.g. via the
+``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them.
+
+Only one notifier can be registered per target category and client instance. If
+a notifier has already been registered, this IOCTL will fail with ``-EEXIST``.
+
+Notifiers will automatically be removed when the device file instance is
+closed.
+
+``SSAM_CDEV_NOTIF_UNREGISTER``
+------------------------------
+
+Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``.
+
+Unregisters the notifier associated with the specified target category. The
+priority field will be ignored by this IOCTL. If no notifier has been
+registered for this client instance and the given category, this IOCTL will
+fail with ``-ENOENT``.
+
+``SSAM_CDEV_EVENT_ENABLE``
+--------------------------
+
+Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``.
+
+Enable the event associated with the given event descriptor.
+
+Note that this call will not register a notifier itself, it will only enable
+events on the controller. If you want to receive events by reading from the
+device file, you will need to register the corresponding notifier(s) on that
+instance.
+
+Events are not automatically disabled when the device file is closed. This must
+be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL.
+
+``SSAM_CDEV_EVENT_DISABLE``
+---------------------------
+
+Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``.
+
+Disable the event associated with the given event descriptor.
+
+Note that this will not unregister any notifiers. Events may still be received
+and forwarded to user-space after this call. The only safe way of stopping
+events from being received is unregistering all previously registered
+notifiers.
+
+
+Structures and Enums
+====================

.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h
--
2.31.1

2021-06-03 23:49:47

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 6/7] platform/surface: aggregator_cdev: Add lockdep support

Mark functions with locking requirements via the corresponding lockdep
calls for debugging and documentary purposes.

Signed-off-by: Maximilian Luz <[email protected]>
---
.../platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)

diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
index e0f3c437b8ee..f3247cd15f6d 100644
--- a/drivers/platform/surface/surface_aggregator_cdev.c
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
@@ -139,6 +139,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, i
struct ssam_cdev_notifier *nf;
int status;

+ lockdep_assert_held_read(&client->cdev->lock);
+
/* Validate notifier target category. */
if (!ssh_rqid_is_event(rqid))
return -EINVAL;
@@ -188,6 +190,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)
const u16 event = ssh_rqid_to_event(rqid);
int status;

+ lockdep_assert_held_read(&client->cdev->lock);
+
/* Validate notifier target category. */
if (!ssh_rqid_is_event(rqid))
return -EINVAL;
@@ -257,6 +261,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_
void __user *rspdata;
int status = 0, ret = 0, tmp;

+ lockdep_assert_held_read(&client->cdev->lock);
+
ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
if (ret)
goto out;
@@ -367,6 +373,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
struct ssam_cdev_notifier_desc desc;
long ret;

+ lockdep_assert_held_read(&client->cdev->lock);
+
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
if (ret)
return ret;
@@ -380,6 +388,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
struct ssam_cdev_notifier_desc desc;
long ret;

+ lockdep_assert_held_read(&client->cdev->lock);
+
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
if (ret)
return ret;
@@ -395,6 +405,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
struct ssam_event_id id;
long ret;

+ lockdep_assert_held_read(&client->cdev->lock);
+
/* Read descriptor from user-space. */
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
if (ret)
@@ -421,6 +433,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
struct ssam_event_id id;
long ret;

+ lockdep_assert_held_read(&client->cdev->lock);
+
/* Read descriptor from user-space. */
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
if (ret)
@@ -510,6 +524,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
unsigned long arg)
{
+ lockdep_assert_held_read(&client->cdev->lock);
+
switch (cmd) {
case SSAM_CDEV_REQUEST:
return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
--
2.31.1

2021-06-03 23:50:03

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 5/7] platform/surface: aggregator_cdev: Allow enabling of events from user-space

While events can already be enabled and disabled via the generic request
IOCTL, this bypasses the internal reference counting mechanism of the
controller. Due to that, disabling an event will turn it off regardless
of any other client having requested said event, which may break
functionality of that client.

To solve this, add IOCTLs wrapping the ssam_controller_event_enable()
and ssam_controller_event_disable() functions, which have been
previously introduced for this specific purpose.

Signed-off-by: Maximilian Luz <[email protected]>
---
.../surface/surface_aggregator_cdev.c | 58 +++++++++++++++++++
include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++
2 files changed, 90 insertions(+)

diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
index 807930144039..e0f3c437b8ee 100644
--- a/drivers/platform/surface/surface_aggregator_cdev.c
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
@@ -387,6 +387,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
return ssam_cdev_notifier_unregister(client, desc.target_category);
}

+static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
+ const struct ssam_cdev_event_desc __user *d)
+{
+ struct ssam_cdev_event_desc desc;
+ struct ssam_event_registry reg;
+ struct ssam_event_id id;
+ long ret;
+
+ /* Read descriptor from user-space. */
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
+ if (ret)
+ return ret;
+
+ /* Translate descriptor. */
+ reg.target_category = desc.reg.target_category;
+ reg.target_id = desc.reg.target_id;
+ reg.cid_enable = desc.reg.cid_enable;
+ reg.cid_disable = desc.reg.cid_disable;
+
+ id.target_category = desc.id.target_category;
+ id.instance = desc.id.instance;
+
+ /* Disable event. */
+ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags);
+}
+
+static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
+ const struct ssam_cdev_event_desc __user *d)
+{
+ struct ssam_cdev_event_desc desc;
+ struct ssam_event_registry reg;
+ struct ssam_event_id id;
+ long ret;
+
+ /* Read descriptor from user-space. */
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
+ if (ret)
+ return ret;
+
+ /* Translate descriptor. */
+ reg.target_category = desc.reg.target_category;
+ reg.target_id = desc.reg.target_id;
+ reg.cid_enable = desc.reg.cid_enable;
+ reg.cid_disable = desc.reg.cid_disable;
+
+ id.target_category = desc.id.target_category;
+ id.instance = desc.id.instance;
+
+ /* Disable event. */
+ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags);
+}
+

/* -- File operations. ------------------------------------------------------ */

@@ -470,6 +522,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i
return ssam_cdev_notif_unregister(client,
(struct ssam_cdev_notifier_desc __user *)arg);

+ case SSAM_CDEV_EVENT_ENABLE:
+ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg);
+
+ case SSAM_CDEV_EVENT_DISABLE:
+ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg);
+
default:
return -ENOTTY;
}
diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
index 4f393fafc235..08f46b60b151 100644
--- a/include/uapi/linux/surface_aggregator/cdev.h
+++ b/include/uapi/linux/surface_aggregator/cdev.h
@@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc {
__u8 target_category;
} __attribute__((__packed__));

+/**
+ * struct ssam_cdev_event_desc - Event descriptor.
+ * @reg: Registry via which the event will be enabled/disabled.
+ * @reg.target_category: Target category for the event registry requests.
+ * @reg.target_id: Target ID for the event registry requests.
+ * @reg.cid_enable: Command ID for the event-enable request.
+ * @reg.cid_disable: Command ID for the event-disable request.
+ * @id: ID specifying the event.
+ * @id.target_category: Target category of the event source.
+ * @id.instance: Instance ID of the event source.
+ * @flags: Flags used for enabling the event.
+ *
+ * Specifies which event should be enabled/disabled and how to do that.
+ */
+struct ssam_cdev_event_desc {
+ struct {
+ __u8 target_category;
+ __u8 target_id;
+ __u8 cid_enable;
+ __u8 cid_disable;
+ } reg;
+
+ struct {
+ __u8 target_category;
+ __u8 instance;
+ } id;
+
+ __u8 flags;
+} __attribute__((__packed__));
+
/**
* struct ssam_cdev_event - SSAM event sent by the EC.
* @target_category: Target category of the event source. See &enum ssam_ssh_tc.
@@ -111,5 +141,7 @@ struct ssam_cdev_event {
#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
+#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc)
+#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc)

#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
--
2.31.1

2021-06-03 23:50:23

by Maximilian Luz

[permalink] [raw]
Subject: [PATCH 4/7] platform/surface: aggregator_cdev: Add support for forwarding events to user-space

Currently, debugging unknown events requires writing a custom driver.
This is somewhat difficult, slow to adapt, and not entirely
user-friendly for quickly trying to figure out things on devices of some
third-party user. We can do better. We already have a user-space
interface intended for debugging SAM EC requests, so let's add support
for receiving events to that.

This commit provides support for receiving events by reading from the
controller file. It additionally introduces two new IOCTLs to control
which event categories will be forwarded. Specifically, a user-space
client can specify which target categories it wants to receive events
from by registering the corresponding notifier(s) via the IOCTLs and
after that, read the received events by reading from the controller
device.

Signed-off-by: Maximilian Luz <[email protected]>
---
.../userspace-api/ioctl/ioctl-number.rst | 2 +-
.../surface/surface_aggregator_cdev.c | 457 +++++++++++++++++-
include/uapi/linux/surface_aggregator/cdev.h | 41 +-
3 files changed, 474 insertions(+), 26 deletions(-)

diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 9bfc2b510c64..1409e40e6345 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -325,7 +325,7 @@ Code Seq# Include File Comments
0xA3 90-9F linux/dtlk.h
0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
0xA4 00-1F uapi/asm/sgx.h <mailto:[email protected]>
-0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
+0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
<mailto:[email protected]>
0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
<mailto:[email protected]>
diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
index 79e28fab7e40..807930144039 100644
--- a/drivers/platform/surface/surface_aggregator_cdev.c
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
@@ -3,29 +3,69 @@
* Provides user-space access to the SSAM EC via the /dev/surface/aggregator
* misc device. Intended for debugging and development.
*
- * Copyright (C) 2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2020-2021 Maximilian Luz <[email protected]>
*/

#include <linux/fs.h>
+#include <linux/ioctl.h>
#include <linux/kernel.h>
+#include <linux/kfifo.h>
#include <linux/kref.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/poll.h>
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
+#include <linux/vmalloc.h>

#include <linux/surface_aggregator/cdev.h>
#include <linux/surface_aggregator/controller.h>
+#include <linux/surface_aggregator/serial_hub.h>

#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"

+
+/* -- Main structures. ------------------------------------------------------ */
+
+enum ssam_cdev_device_state {
+ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0),
+};
+
struct ssam_cdev {
struct kref kref;
struct rw_semaphore lock;
+
+ struct device *dev;
struct ssam_controller *ctrl;
struct miscdevice mdev;
+ unsigned long flags;
+
+ struct rw_semaphore client_lock; /* Guards client list. */
+ struct list_head client_list;
+};
+
+struct ssam_cdev_client;
+
+struct ssam_cdev_notifier {
+ struct ssam_cdev_client *client;
+ struct ssam_event_notifier nf;
+};
+
+struct ssam_cdev_client {
+ struct ssam_cdev *cdev;
+ struct list_head node;
+
+ struct mutex notifier_lock; /* Guards notifier access for registration */
+ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS];
+
+ struct mutex read_lock; /* Guards FIFO buffer read access */
+ struct mutex write_lock; /* Guards FIFO buffer write access */
+ DECLARE_KFIFO(buffer, u8, 4096);
+
+ wait_queue_head_t waitq;
+ struct fasync_struct *fasync;
};

static void __ssam_cdev_release(struct kref *kref)
@@ -47,24 +87,169 @@ static void ssam_cdev_put(struct ssam_cdev *cdev)
kref_put(&cdev->kref, __ssam_cdev_release);
}

-static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
+
+/* -- Notifier handling. ---------------------------------------------------- */
+
+static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
{
- struct miscdevice *mdev = filp->private_data;
- struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
+ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf);
+ struct ssam_cdev_client *client = cdev_nf->client;
+ struct ssam_cdev_event event;
+ size_t n = struct_size(&event, data, in->length);
+
+ /* Translate event. */
+ event.target_category = in->target_category;
+ event.target_id = in->target_id;
+ event.command_id = in->command_id;
+ event.instance_id = in->instance_id;
+ event.length = in->length;
+
+ mutex_lock(&client->write_lock);
+
+ /* Make sure we have enough space. */
+ if (kfifo_avail(&client->buffer) < n) {
+ dev_warn(client->cdev->dev,
+ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
+ in->target_category, in->target_id, in->command_id, in->instance_id);
+ mutex_unlock(&client->write_lock);
+ return 0;
+ }

- filp->private_data = ssam_cdev_get(cdev);
- return stream_open(inode, filp);
+ /* Copy event header and payload. */
+ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0));
+ kfifo_in(&client->buffer, &in->data[0], in->length);
+
+ mutex_unlock(&client->write_lock);
+
+ /* Notify waiting readers. */
+ kill_fasync(&client->fasync, SIGIO, POLL_IN);
+ wake_up_interruptible(&client->waitq);
+
+ /*
+ * Don't mark events as handled, this is the job of a proper driver and
+ * not the debugging interface.
+ */
+ return 0;
}

-static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority)
{
- ssam_cdev_put(filp->private_data);
- return 0;
+ const u16 rqid = ssh_tc_to_rqid(tc);
+ const u16 event = ssh_rqid_to_event(rqid);
+ struct ssam_cdev_notifier *nf;
+ int status;
+
+ /* Validate notifier target category. */
+ if (!ssh_rqid_is_event(rqid))
+ return -EINVAL;
+
+ mutex_lock(&client->notifier_lock);
+
+ /* Check if the notifier has already been registered. */
+ if (client->notifier[event]) {
+ mutex_unlock(&client->notifier_lock);
+ return -EEXIST;
+ }
+
+ /* Allocate new notifier. */
+ nf = kzalloc(sizeof(*nf), GFP_KERNEL);
+ if (!nf) {
+ mutex_unlock(&client->notifier_lock);
+ return -ENOMEM;
+ }
+
+ /*
+ * Create a dummy notifier with the minimal required fields for
+ * observer registration. Note that we can skip fully specifying event
+ * and registry here as we do not need any matching and use silent
+ * registration, which does not enable the corresponding event.
+ */
+ nf->client = client;
+ nf->nf.base.fn = ssam_cdev_notifier;
+ nf->nf.base.priority = priority;
+ nf->nf.event.id.target_category = tc;
+ nf->nf.event.mask = 0; /* Do not do any matching. */
+ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
+
+ /* Register notifier. */
+ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf);
+ if (status)
+ kfree(nf);
+ else
+ client->notifier[event] = nf;
+
+ mutex_unlock(&client->notifier_lock);
+ return status;
}

-static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)
+{
+ const u16 rqid = ssh_tc_to_rqid(tc);
+ const u16 event = ssh_rqid_to_event(rqid);
+ int status;
+
+ /* Validate notifier target category. */
+ if (!ssh_rqid_is_event(rqid))
+ return -EINVAL;
+
+ mutex_lock(&client->notifier_lock);
+
+ /* Check if the notifier is currently registered. */
+ if (!client->notifier[event]) {
+ mutex_unlock(&client->notifier_lock);
+ return -ENOENT;
+ }
+
+ /* Unregister and free notifier. */
+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf);
+ kfree(client->notifier[event]);
+ client->notifier[event] = NULL;
+
+ mutex_unlock(&client->notifier_lock);
+ return status;
+}
+
+static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client)
+{
+ int i;
+
+ down_read(&client->cdev->lock);
+
+ /*
+ * This function may be used during shutdown, thus we need to test for
+ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit.
+ */
+ if (client->cdev->ctrl) {
+ for (i = 0; i < SSH_NUM_EVENTS; i++)
+ ssam_cdev_notifier_unregister(client, i + 1);
+
+ } else {
+ int count = 0;
+
+ /*
+ * Device has been shut down. Any notifier remaining is a bug,
+ * so warn about that as this would otherwise hardly be
+ * noticeable. Nevertheless, free them as well.
+ */
+ mutex_lock(&client->notifier_lock);
+ for (i = 0; i < SSH_NUM_EVENTS; i++) {
+ count += !!(client->notifier[i]);
+ kfree(client->notifier[i]);
+ client->notifier[i] = NULL;
+ }
+ mutex_unlock(&client->notifier_lock);
+
+ WARN_ON(count > 0);
+ }
+
+ up_read(&client->cdev->lock);
+}
+
+
+/* -- IOCTL functions. ------------------------------------------------------ */
+
+static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r)
{
- struct ssam_cdev_request __user *r;
struct ssam_cdev_request rqst;
struct ssam_request spec = {};
struct ssam_response rsp = {};
@@ -72,7 +257,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
void __user *rspdata;
int status = 0, ret = 0, tmp;

- r = (struct ssam_cdev_request __user *)arg;
ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
if (ret)
goto out;
@@ -152,7 +336,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
}

/* Perform request. */
- status = ssam_request_sync(cdev->ctrl, &spec, &rsp);
+ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp);
if (status)
goto out;

@@ -177,48 +361,244 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
return ret;
}

-static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd,
+static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
+ const struct ssam_cdev_notifier_desc __user *d)
+{
+ struct ssam_cdev_notifier_desc desc;
+ long ret;
+
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
+ if (ret)
+ return ret;
+
+ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority);
+}
+
+static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
+ const struct ssam_cdev_notifier_desc __user *d)
+{
+ struct ssam_cdev_notifier_desc desc;
+ long ret;
+
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
+ if (ret)
+ return ret;
+
+ return ssam_cdev_notifier_unregister(client, desc.target_category);
+}
+
+
+/* -- File operations. ------------------------------------------------------ */
+
+static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
+{
+ struct miscdevice *mdev = filp->private_data;
+ struct ssam_cdev_client *client;
+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
+
+ /* Initialize client */
+ client = vzalloc(sizeof(*client));
+ if (!client)
+ return -ENOMEM;
+
+ client->cdev = ssam_cdev_get(cdev);
+
+ INIT_LIST_HEAD(&client->node);
+
+ mutex_init(&client->notifier_lock);
+
+ mutex_init(&client->read_lock);
+ mutex_init(&client->write_lock);
+ INIT_KFIFO(client->buffer);
+ init_waitqueue_head(&client->waitq);
+
+ filp->private_data = client;
+
+ /* Attach client. */
+ down_write(&cdev->client_lock);
+
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
+ up_write(&cdev->client_lock);
+ ssam_cdev_put(client->cdev);
+ vfree(client);
+ return -ENODEV;
+ }
+ list_add_tail(&client->node, &cdev->client_list);
+
+ up_write(&cdev->client_lock);
+
+ stream_open(inode, filp);
+ return 0;
+}
+
+static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
+{
+ struct ssam_cdev_client *client = filp->private_data;
+
+ /* Force-unregister all remaining notifiers of this client. */
+ ssam_cdev_notifier_unregister_all(client);
+
+ /* Detach client. */
+ down_write(&client->cdev->client_lock);
+ list_del(&client->node);
+ up_write(&client->cdev->client_lock);
+
+ /* Free client. */
+ mutex_destroy(&client->write_lock);
+ mutex_destroy(&client->read_lock);
+
+ mutex_destroy(&client->notifier_lock);
+
+ ssam_cdev_put(client->cdev);
+ vfree(client);
+
+ return 0;
+}
+
+static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case SSAM_CDEV_REQUEST:
- return ssam_cdev_request(cdev, arg);
+ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
+
+ case SSAM_CDEV_NOTIF_REGISTER:
+ return ssam_cdev_notif_register(client,
+ (struct ssam_cdev_notifier_desc __user *)arg);
+
+ case SSAM_CDEV_NOTIF_UNREGISTER:
+ return ssam_cdev_notif_unregister(client,
+ (struct ssam_cdev_notifier_desc __user *)arg);

default:
return -ENOTTY;
}
}

-static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd,
- unsigned long arg)
+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
- struct ssam_cdev *cdev = file->private_data;
+ struct ssam_cdev_client *client = file->private_data;
long status;

/* Ensure that controller is valid for as long as we need it. */
+ if (down_read_killable(&client->cdev->lock))
+ return -ERESTARTSYS;
+
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) {
+ up_read(&client->cdev->lock);
+ return -ENODEV;
+ }
+
+ status = __ssam_cdev_device_ioctl(client, cmd, arg);
+
+ up_read(&client->cdev->lock);
+ return status;
+}
+
+static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
+{
+ struct ssam_cdev_client *client = file->private_data;
+ struct ssam_cdev *cdev = client->cdev;
+ unsigned int copied;
+ int status = 0;
+
if (down_read_killable(&cdev->lock))
return -ERESTARTSYS;

- if (!cdev->ctrl) {
+ /* Make sure we're not shut down. */
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
up_read(&cdev->lock);
return -ENODEV;
}

- status = __ssam_cdev_device_ioctl(cdev, cmd, arg);
+ do {
+ /* Check availability, wait if necessary. */
+ if (kfifo_is_empty(&client->buffer)) {
+ up_read(&cdev->lock);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ status = wait_event_interruptible(client->waitq,
+ !kfifo_is_empty(&client->buffer) ||
+ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT,
+ &cdev->flags));
+ if (status < 0)
+ return status;
+
+ if (down_read_killable(&cdev->lock))
+ return -ERESTARTSYS;
+
+ /* Need to check that we're not shut down again. */
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
+ up_read(&cdev->lock);
+ return -ENODEV;
+ }
+ }
+
+ /* Try to read from FIFO. */
+ if (mutex_lock_interruptible(&client->read_lock)) {
+ up_read(&cdev->lock);
+ return -ERESTARTSYS;
+ }
+
+ status = kfifo_to_user(&client->buffer, buf, count, &copied);
+ mutex_unlock(&client->read_lock);
+
+ if (status < 0) {
+ up_read(&cdev->lock);
+ return status;
+ }
+
+ /* We might not have gotten anything, check this here. */
+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
+ up_read(&cdev->lock);
+ return -EAGAIN;
+ }
+ } while (copied == 0);

up_read(&cdev->lock);
- return status;
+ return copied;
+}
+
+static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt)
+{
+ struct ssam_cdev_client *client = file->private_data;
+ __poll_t events = 0;
+
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags))
+ return EPOLLHUP | EPOLLERR;
+
+ poll_wait(file, &client->waitq, pt);
+
+ if (!kfifo_is_empty(&client->buffer))
+ events |= EPOLLIN | EPOLLRDNORM;
+
+ return events;
+}
+
+static int ssam_cdev_fasync(int fd, struct file *file, int on)
+{
+ struct ssam_cdev_client *client = file->private_data;
+
+ return fasync_helper(fd, file, on, &client->fasync);
}

static const struct file_operations ssam_controller_fops = {
.owner = THIS_MODULE,
.open = ssam_cdev_device_open,
.release = ssam_cdev_device_release,
+ .read = ssam_cdev_read,
+ .poll = ssam_cdev_poll,
+ .fasync = ssam_cdev_fasync,
.unlocked_ioctl = ssam_cdev_device_ioctl,
.compat_ioctl = ssam_cdev_device_ioctl,
- .llseek = noop_llseek,
+ .llseek = no_llseek,
};

+
+/* -- Device and driver setup ----------------------------------------------- */
+
static int ssam_dbg_device_probe(struct platform_device *pdev)
{
struct ssam_controller *ctrl;
@@ -236,6 +616,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
kref_init(&cdev->kref);
init_rwsem(&cdev->lock);
cdev->ctrl = ctrl;
+ cdev->dev = &pdev->dev;

cdev->mdev.parent = &pdev->dev;
cdev->mdev.minor = MISC_DYNAMIC_MINOR;
@@ -243,6 +624,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
cdev->mdev.nodename = "surface/aggregator";
cdev->mdev.fops = &ssam_controller_fops;

+ init_rwsem(&cdev->client_lock);
+ INIT_LIST_HEAD(&cdev->client_list);
+
status = misc_register(&cdev->mdev);
if (status) {
kfree(cdev);
@@ -256,8 +640,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
static int ssam_dbg_device_remove(struct platform_device *pdev)
{
struct ssam_cdev *cdev = platform_get_drvdata(pdev);
+ struct ssam_cdev_client *client;

- misc_deregister(&cdev->mdev);
+ /*
+ * Mark device as shut-down. Prevent new clients from being added and
+ * new operations from being executed.
+ */
+ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags);
+
+ down_write(&cdev->client_lock);
+
+ /* Remove all notifiers registered by us. */
+ list_for_each_entry(client, &cdev->client_list, node) {
+ ssam_cdev_notifier_unregister_all(client);
+ }
+
+ /* Wake up async clients. */
+ list_for_each_entry(client, &cdev->client_list, node) {
+ kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+ }
+
+ /* Wake up blocking clients. */
+ list_for_each_entry(client, &cdev->client_list, node) {
+ wake_up_interruptible(&client->waitq);
+ }
+
+ up_write(&cdev->client_lock);

/*
* The controller is only guaranteed to be valid for as long as the
@@ -266,8 +674,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev)
*/
down_write(&cdev->lock);
cdev->ctrl = NULL;
+ cdev->dev = NULL;
up_write(&cdev->lock);

+ misc_deregister(&cdev->mdev);
+
ssam_cdev_put(cdev);
return 0;
}
diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
index fbcce04abfe9..4f393fafc235 100644
--- a/include/uapi/linux/surface_aggregator/cdev.h
+++ b/include/uapi/linux/surface_aggregator/cdev.h
@@ -6,7 +6,7 @@
* device. This device provides direct user-space access to the SSAM EC.
* Intended for debugging and development.
*
- * Copyright (C) 2020 Maximilian Luz <[email protected]>
+ * Copyright (C) 2020-2021 Maximilian Luz <[email protected]>
*/

#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H
@@ -73,6 +73,43 @@ struct ssam_cdev_request {
} response;
} __attribute__((__packed__));

-#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
+/**
+ * struct ssam_cdev_notifier_desc - Notifier descriptor.
+ * @priority: Priority value determining the order in which notifier
+ * callbacks will be called. A higher value means higher
+ * priority, i.e. the associated callback will be executed
+ * earlier than other (lower priority) callbacks.
+ * @target_category: The event target category for which this notifier should
+ * receive events.
+ *
+ * Specifies the notifier that should be registered or unregistered,
+ * specifically with which priority and for which target category of events.
+ */
+struct ssam_cdev_notifier_desc {
+ __s32 priority;
+ __u8 target_category;
+} __attribute__((__packed__));
+
+/**
+ * struct ssam_cdev_event - SSAM event sent by the EC.
+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc.
+ * @target_id: Target ID of the event source.
+ * @command_id: Command ID of the event.
+ * @instance_id: Instance ID of the event source.
+ * @length: Length of the event payload in bytes.
+ * @data: Event payload data.
+ */
+struct ssam_cdev_event {
+ __u8 target_category;
+ __u8 target_id;
+ __u8 command_id;
+ __u8 instance_id;
+ __u16 length;
+ __u8 data[];
+} __attribute__((__packed__));
+
+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
+#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
+#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)

#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
--
2.31.1

2021-06-04 05:59:22

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 2/7] platform/surface: aggregator: Allow enabling of events without notifiers

Hi Maximilian,

I love your patch! Perhaps something to improve:

[auto build test WARNING on linus/master]
[also build test WARNING on v5.13-rc4 next-20210603]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url: https://github.com/0day-ci/linux/commits/Maximilian-Luz/platform-surface-aggregator-Extend-user-space-interface-for-events/20210604-074904
base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git f88cd3fb9df228e5ce4e13ec3dbad671ddb2146e
config: x86_64-randconfig-r016-20210604 (attached as .config)
compiler: clang version 13.0.0 (https://github.com/llvm/llvm-project 5c0d1b2f902aa6a9cf47cc7e42c5b83bb2217cf9)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# install x86_64 cross compiling tool for clang build
# apt-get install binutils-x86-64-linux-gnu
# https://github.com/0day-ci/linux/commit/7b6b5a24e7c8e389e231ee547972e871145f5972
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Maximilian-Luz/platform-surface-aggregator-Extend-user-space-interface-for-events/20210604-074904
git checkout 7b6b5a24e7c8e389e231ee547972e871145f5972
# save the attached .config to linux build tree
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>

All warnings (new ones prefixed by >>):

>> drivers/platform/surface/aggregator/controller.c:2317:23: warning: variable 'nf_head' set but not used [-Wunused-but-set-variable]
struct ssam_nf_head *nf_head;
^
drivers/platform/surface/aggregator/controller.c:2386:23: warning: variable 'nf_head' set but not used [-Wunused-but-set-variable]
struct ssam_nf_head *nf_head;
^
2 warnings generated.


vim +/nf_head +2317 drivers/platform/surface/aggregator/controller.c

2289
2290 /**
2291 * ssam_controller_event_enable() - Enable the specified event.
2292 * @ctrl: The controller to enable the event for.
2293 * @reg: The event registry to use for enabling the event.
2294 * @id: The event ID specifying the event to be enabled.
2295 * @flags: The SAM event flags used for enabling the event.
2296 *
2297 * Increment the event reference count of the specified event. If the event has
2298 * not been enabled previously, it will be enabled by this call.
2299 *
2300 * Note: In general, ssam_notifier_register() with a non-observer notifier
2301 * should be preferred for enabling/disabling events, as this will guarantee
2302 * proper ordering and event forwarding in case of errors during event
2303 * enabling/disabling.
2304 *
2305 * Return: Returns zero on success, %-ENOSPC if the reference count for the
2306 * specified event has reached its maximum, %-ENOMEM if the corresponding event
2307 * entry could not be allocated. If this is the first time that this event has
2308 * been enabled (i.e. the reference count was incremented from zero to one by
2309 * this call), returns the status of the event-enable EC-command.
2310 */
2311 int ssam_controller_event_enable(struct ssam_controller *ctrl,
2312 struct ssam_event_registry reg,
2313 struct ssam_event_id id, u8 flags)
2314 {
2315 u16 rqid = ssh_tc_to_rqid(id.target_category);
2316 struct ssam_nf_refcount_entry *entry;
> 2317 struct ssam_nf_head *nf_head;
2318 struct ssam_nf *nf;
2319 int status;
2320
2321 if (!ssh_rqid_is_event(rqid))
2322 return -EINVAL;
2323
2324 nf = &ctrl->cplt.event.notif;
2325 nf_head = &nf->head[ssh_rqid_to_event(rqid)];
2326
2327 mutex_lock(&nf->lock);
2328
2329 entry = ssam_nf_refcount_inc(nf, reg, id);
2330 if (IS_ERR(entry)) {
2331 mutex_unlock(&nf->lock);
2332 return PTR_ERR(entry);
2333 }
2334
2335 ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
2336 reg.target_category, id.target_category, id.instance,
2337 entry->refcount);
2338
2339 if (entry->refcount == 1) {
2340 status = ssam_ssh_event_enable(ctrl, reg, id, flags);
2341 if (status) {
2342 kfree(ssam_nf_refcount_dec(nf, reg, id));
2343 mutex_unlock(&nf->lock);
2344 return status;
2345 }
2346
2347 entry->flags = flags;
2348
2349 } else if (entry->flags != flags) {
2350 ssam_warn(ctrl,
2351 "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
2352 flags, entry->flags, reg.target_category,
2353 id.target_category, id.instance);
2354 }
2355
2356 mutex_unlock(&nf->lock);
2357 return 0;
2358 }
2359 EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
2360

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]


Attachments:
(No filename) (5.23 kB)
.config.gz (33.06 kB)
Download all attachments

2021-06-04 11:35:24

by Hans de Goede

[permalink] [raw]
Subject: Re: [PATCH 4/7] platform/surface: aggregator_cdev: Add support for forwarding events to user-space

Hi,

I've one review remark inline below.

On 6/4/21 1:45 AM, Maximilian Luz wrote:
> Currently, debugging unknown events requires writing a custom driver.
> This is somewhat difficult, slow to adapt, and not entirely
> user-friendly for quickly trying to figure out things on devices of some
> third-party user. We can do better. We already have a user-space
> interface intended for debugging SAM EC requests, so let's add support
> for receiving events to that.
>
> This commit provides support for receiving events by reading from the
> controller file. It additionally introduces two new IOCTLs to control
> which event categories will be forwarded. Specifically, a user-space
> client can specify which target categories it wants to receive events
> from by registering the corresponding notifier(s) via the IOCTLs and
> after that, read the received events by reading from the controller
> device.
>
> Signed-off-by: Maximilian Luz <[email protected]>
> ---
> .../userspace-api/ioctl/ioctl-number.rst | 2 +-
> .../surface/surface_aggregator_cdev.c | 457 +++++++++++++++++-
> include/uapi/linux/surface_aggregator/cdev.h | 41 +-
> 3 files changed, 474 insertions(+), 26 deletions(-)
>
> diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> index 9bfc2b510c64..1409e40e6345 100644
> --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> @@ -325,7 +325,7 @@ Code Seq# Include File Comments
> 0xA3 90-9F linux/dtlk.h
> 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
> 0xA4 00-1F uapi/asm/sgx.h <mailto:[email protected]>
> -0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
> +0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
> <mailto:[email protected]>
> 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
> <mailto:[email protected]>
> diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
> index 79e28fab7e40..807930144039 100644
> --- a/drivers/platform/surface/surface_aggregator_cdev.c
> +++ b/drivers/platform/surface/surface_aggregator_cdev.c
> @@ -3,29 +3,69 @@
> * Provides user-space access to the SSAM EC via the /dev/surface/aggregator
> * misc device. Intended for debugging and development.
> *
> - * Copyright (C) 2020 Maximilian Luz <[email protected]>
> + * Copyright (C) 2020-2021 Maximilian Luz <[email protected]>
> */
>
> #include <linux/fs.h>
> +#include <linux/ioctl.h>
> #include <linux/kernel.h>
> +#include <linux/kfifo.h>
> #include <linux/kref.h>
> #include <linux/miscdevice.h>
> #include <linux/module.h>
> #include <linux/platform_device.h>
> +#include <linux/poll.h>
> #include <linux/rwsem.h>
> #include <linux/slab.h>
> #include <linux/uaccess.h>
> +#include <linux/vmalloc.h>
>
> #include <linux/surface_aggregator/cdev.h>
> #include <linux/surface_aggregator/controller.h>
> +#include <linux/surface_aggregator/serial_hub.h>
>
> #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"
>
> +
> +/* -- Main structures. ------------------------------------------------------ */
> +
> +enum ssam_cdev_device_state {
> + SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0),
> +};
> +
> struct ssam_cdev {
> struct kref kref;
> struct rw_semaphore lock;
> +
> + struct device *dev;
> struct ssam_controller *ctrl;
> struct miscdevice mdev;
> + unsigned long flags;
> +
> + struct rw_semaphore client_lock; /* Guards client list. */
> + struct list_head client_list;
> +};
> +
> +struct ssam_cdev_client;
> +
> +struct ssam_cdev_notifier {
> + struct ssam_cdev_client *client;
> + struct ssam_event_notifier nf;
> +};
> +
> +struct ssam_cdev_client {
> + struct ssam_cdev *cdev;
> + struct list_head node;
> +
> + struct mutex notifier_lock; /* Guards notifier access for registration */
> + struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS];
> +
> + struct mutex read_lock; /* Guards FIFO buffer read access */
> + struct mutex write_lock; /* Guards FIFO buffer write access */
> + DECLARE_KFIFO(buffer, u8, 4096);
> +
> + wait_queue_head_t waitq;
> + struct fasync_struct *fasync;
> };
>
> static void __ssam_cdev_release(struct kref *kref)
> @@ -47,24 +87,169 @@ static void ssam_cdev_put(struct ssam_cdev *cdev)
> kref_put(&cdev->kref, __ssam_cdev_release);
> }
>
> -static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
> +
> +/* -- Notifier handling. ---------------------------------------------------- */
> +
> +static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
> {
> - struct miscdevice *mdev = filp->private_data;
> - struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
> + struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf);
> + struct ssam_cdev_client *client = cdev_nf->client;
> + struct ssam_cdev_event event;
> + size_t n = struct_size(&event, data, in->length);
> +
> + /* Translate event. */
> + event.target_category = in->target_category;
> + event.target_id = in->target_id;
> + event.command_id = in->command_id;
> + event.instance_id = in->instance_id;
> + event.length = in->length;
> +
> + mutex_lock(&client->write_lock);
> +
> + /* Make sure we have enough space. */
> + if (kfifo_avail(&client->buffer) < n) {
> + dev_warn(client->cdev->dev,
> + "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
> + in->target_category, in->target_id, in->command_id, in->instance_id);
> + mutex_unlock(&client->write_lock);
> + return 0;
> + }
>
> - filp->private_data = ssam_cdev_get(cdev);
> - return stream_open(inode, filp);
> + /* Copy event header and payload. */
> + kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0));
> + kfifo_in(&client->buffer, &in->data[0], in->length);
> +
> + mutex_unlock(&client->write_lock);
> +
> + /* Notify waiting readers. */
> + kill_fasync(&client->fasync, SIGIO, POLL_IN);
> + wake_up_interruptible(&client->waitq);
> +
> + /*
> + * Don't mark events as handled, this is the job of a proper driver and
> + * not the debugging interface.
> + */
> + return 0;
> }
>
> -static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
> +static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority)
> {
> - ssam_cdev_put(filp->private_data);
> - return 0;
> + const u16 rqid = ssh_tc_to_rqid(tc);
> + const u16 event = ssh_rqid_to_event(rqid);
> + struct ssam_cdev_notifier *nf;
> + int status;
> +
> + /* Validate notifier target category. */
> + if (!ssh_rqid_is_event(rqid))
> + return -EINVAL;
> +
> + mutex_lock(&client->notifier_lock);
> +
> + /* Check if the notifier has already been registered. */
> + if (client->notifier[event]) {
> + mutex_unlock(&client->notifier_lock);
> + return -EEXIST;
> + }
> +
> + /* Allocate new notifier. */
> + nf = kzalloc(sizeof(*nf), GFP_KERNEL);
> + if (!nf) {
> + mutex_unlock(&client->notifier_lock);
> + return -ENOMEM;
> + }
> +
> + /*
> + * Create a dummy notifier with the minimal required fields for
> + * observer registration. Note that we can skip fully specifying event
> + * and registry here as we do not need any matching and use silent
> + * registration, which does not enable the corresponding event.
> + */
> + nf->client = client;
> + nf->nf.base.fn = ssam_cdev_notifier;
> + nf->nf.base.priority = priority;
> + nf->nf.event.id.target_category = tc;
> + nf->nf.event.mask = 0; /* Do not do any matching. */
> + nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
> +
> + /* Register notifier. */
> + status = ssam_notifier_register(client->cdev->ctrl, &nf->nf);
> + if (status)
> + kfree(nf);
> + else
> + client->notifier[event] = nf;
> +
> + mutex_unlock(&client->notifier_lock);
> + return status;
> }
>
> -static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
> +static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)
> +{
> + const u16 rqid = ssh_tc_to_rqid(tc);
> + const u16 event = ssh_rqid_to_event(rqid);
> + int status;
> +
> + /* Validate notifier target category. */
> + if (!ssh_rqid_is_event(rqid))
> + return -EINVAL;
> +
> + mutex_lock(&client->notifier_lock);
> +
> + /* Check if the notifier is currently registered. */
> + if (!client->notifier[event]) {
> + mutex_unlock(&client->notifier_lock);
> + return -ENOENT;
> + }
> +
> + /* Unregister and free notifier. */
> + status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf);
> + kfree(client->notifier[event]);
> + client->notifier[event] = NULL;
> +
> + mutex_unlock(&client->notifier_lock);
> + return status;
> +}
> +
> +static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client)
> +{
> + int i;
> +
> + down_read(&client->cdev->lock);
> +
> + /*
> + * This function may be used during shutdown, thus we need to test for
> + * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit.
> + */
> + if (client->cdev->ctrl) {
> + for (i = 0; i < SSH_NUM_EVENTS; i++)
> + ssam_cdev_notifier_unregister(client, i + 1);
> +
> + } else {
> + int count = 0;
> +
> + /*
> + * Device has been shut down. Any notifier remaining is a bug,
> + * so warn about that as this would otherwise hardly be
> + * noticeable. Nevertheless, free them as well.
> + */
> + mutex_lock(&client->notifier_lock);
> + for (i = 0; i < SSH_NUM_EVENTS; i++) {
> + count += !!(client->notifier[i]);
> + kfree(client->notifier[i]);
> + client->notifier[i] = NULL;
> + }
> + mutex_unlock(&client->notifier_lock);
> +
> + WARN_ON(count > 0);
> + }
> +
> + up_read(&client->cdev->lock);
> +}
> +
> +
> +/* -- IOCTL functions. ------------------------------------------------------ */
> +
> +static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r)
> {
> - struct ssam_cdev_request __user *r;
> struct ssam_cdev_request rqst;
> struct ssam_request spec = {};
> struct ssam_response rsp = {};
> @@ -72,7 +257,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
> void __user *rspdata;
> int status = 0, ret = 0, tmp;
>
> - r = (struct ssam_cdev_request __user *)arg;
> ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
> if (ret)
> goto out;
> @@ -152,7 +336,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
> }
>
> /* Perform request. */
> - status = ssam_request_sync(cdev->ctrl, &spec, &rsp);
> + status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp);
> if (status)
> goto out;
>
> @@ -177,48 +361,244 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
> return ret;
> }
>
> -static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd,
> +static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
> + const struct ssam_cdev_notifier_desc __user *d)
> +{
> + struct ssam_cdev_notifier_desc desc;
> + long ret;
> +
> + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
> + if (ret)
> + return ret;
> +
> + return ssam_cdev_notifier_register(client, desc.target_category, desc.priority);
> +}
> +
> +static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
> + const struct ssam_cdev_notifier_desc __user *d)
> +{
> + struct ssam_cdev_notifier_desc desc;
> + long ret;
> +
> + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
> + if (ret)
> + return ret;
> +
> + return ssam_cdev_notifier_unregister(client, desc.target_category);
> +}
> +
> +
> +/* -- File operations. ------------------------------------------------------ */
> +
> +static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
> +{
> + struct miscdevice *mdev = filp->private_data;
> + struct ssam_cdev_client *client;
> + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
> +
> + /* Initialize client */
> + client = vzalloc(sizeof(*client));
> + if (!client)
> + return -ENOMEM;
> +
> + client->cdev = ssam_cdev_get(cdev);
> +
> + INIT_LIST_HEAD(&client->node);
> +
> + mutex_init(&client->notifier_lock);
> +
> + mutex_init(&client->read_lock);
> + mutex_init(&client->write_lock);
> + INIT_KFIFO(client->buffer);
> + init_waitqueue_head(&client->waitq);
> +
> + filp->private_data = client;
> +
> + /* Attach client. */
> + down_write(&cdev->client_lock);
> +
> + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
> + up_write(&cdev->client_lock);
> + ssam_cdev_put(client->cdev);

You are missing the mutex_destroy() calls here which you are
doing in ssam_cdev_device_release().

Or maybe move the mutex_init calls below this check
(before the up_write()) since I don't think the client can
be accessed by any code until the up_write is done?

Regards,

Hans


> + vfree(client);
> + return -ENODEV;
> + }
> + list_add_tail(&client->node, &cdev->client_list);
> +
> + up_write(&cdev->client_lock);
> +
> + stream_open(inode, filp);
> + return 0;
> +}
> +
> +static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
> +{
> + struct ssam_cdev_client *client = filp->private_data;
> +
> + /* Force-unregister all remaining notifiers of this client. */
> + ssam_cdev_notifier_unregister_all(client);
> +
> + /* Detach client. */
> + down_write(&client->cdev->client_lock);
> + list_del(&client->node);
> + up_write(&client->cdev->client_lock);
> +
> + /* Free client. */
> + mutex_destroy(&client->write_lock);
> + mutex_destroy(&client->read_lock);
> +
> + mutex_destroy(&client->notifier_lock);
> +
> + ssam_cdev_put(client->cdev);
> + vfree(client);
> +
> + return 0;
> +}
> +
> +static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
> unsigned long arg)
> {
> switch (cmd) {
> case SSAM_CDEV_REQUEST:
> - return ssam_cdev_request(cdev, arg);
> + return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
> +
> + case SSAM_CDEV_NOTIF_REGISTER:
> + return ssam_cdev_notif_register(client,
> + (struct ssam_cdev_notifier_desc __user *)arg);
> +
> + case SSAM_CDEV_NOTIF_UNREGISTER:
> + return ssam_cdev_notif_unregister(client,
> + (struct ssam_cdev_notifier_desc __user *)arg);
>
> default:
> return -ENOTTY;
> }
> }
>
> -static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd,
> - unsigned long arg)
> +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> {
> - struct ssam_cdev *cdev = file->private_data;
> + struct ssam_cdev_client *client = file->private_data;
> long status;
>
> /* Ensure that controller is valid for as long as we need it. */
> + if (down_read_killable(&client->cdev->lock))
> + return -ERESTARTSYS;
> +
> + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) {
> + up_read(&client->cdev->lock);
> + return -ENODEV;
> + }
> +
> + status = __ssam_cdev_device_ioctl(client, cmd, arg);
> +
> + up_read(&client->cdev->lock);
> + return status;
> +}
> +
> +static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
> +{
> + struct ssam_cdev_client *client = file->private_data;
> + struct ssam_cdev *cdev = client->cdev;
> + unsigned int copied;
> + int status = 0;
> +
> if (down_read_killable(&cdev->lock))
> return -ERESTARTSYS;
>
> - if (!cdev->ctrl) {
> + /* Make sure we're not shut down. */
> + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
> up_read(&cdev->lock);
> return -ENODEV;
> }
>
> - status = __ssam_cdev_device_ioctl(cdev, cmd, arg);
> + do {
> + /* Check availability, wait if necessary. */
> + if (kfifo_is_empty(&client->buffer)) {
> + up_read(&cdev->lock);
> +
> + if (file->f_flags & O_NONBLOCK)
> + return -EAGAIN;
> +
> + status = wait_event_interruptible(client->waitq,
> + !kfifo_is_empty(&client->buffer) ||
> + test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT,
> + &cdev->flags));
> + if (status < 0)
> + return status;
> +
> + if (down_read_killable(&cdev->lock))
> + return -ERESTARTSYS;
> +
> + /* Need to check that we're not shut down again. */
> + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
> + up_read(&cdev->lock);
> + return -ENODEV;
> + }
> + }
> +
> + /* Try to read from FIFO. */
> + if (mutex_lock_interruptible(&client->read_lock)) {
> + up_read(&cdev->lock);
> + return -ERESTARTSYS;
> + }
> +
> + status = kfifo_to_user(&client->buffer, buf, count, &copied);
> + mutex_unlock(&client->read_lock);
> +
> + if (status < 0) {
> + up_read(&cdev->lock);
> + return status;
> + }
> +
> + /* We might not have gotten anything, check this here. */
> + if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
> + up_read(&cdev->lock);
> + return -EAGAIN;
> + }
> + } while (copied == 0);
>
> up_read(&cdev->lock);
> - return status;
> + return copied;
> +}
> +
> +static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt)
> +{
> + struct ssam_cdev_client *client = file->private_data;
> + __poll_t events = 0;
> +
> + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags))
> + return EPOLLHUP | EPOLLERR;
> +
> + poll_wait(file, &client->waitq, pt);
> +
> + if (!kfifo_is_empty(&client->buffer))
> + events |= EPOLLIN | EPOLLRDNORM;
> +
> + return events;
> +}
> +
> +static int ssam_cdev_fasync(int fd, struct file *file, int on)
> +{
> + struct ssam_cdev_client *client = file->private_data;
> +
> + return fasync_helper(fd, file, on, &client->fasync);
> }
>
> static const struct file_operations ssam_controller_fops = {
> .owner = THIS_MODULE,
> .open = ssam_cdev_device_open,
> .release = ssam_cdev_device_release,
> + .read = ssam_cdev_read,
> + .poll = ssam_cdev_poll,
> + .fasync = ssam_cdev_fasync,
> .unlocked_ioctl = ssam_cdev_device_ioctl,
> .compat_ioctl = ssam_cdev_device_ioctl,
> - .llseek = noop_llseek,
> + .llseek = no_llseek,
> };
>
> +
> +/* -- Device and driver setup ----------------------------------------------- */
> +
> static int ssam_dbg_device_probe(struct platform_device *pdev)
> {
> struct ssam_controller *ctrl;
> @@ -236,6 +616,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
> kref_init(&cdev->kref);
> init_rwsem(&cdev->lock);
> cdev->ctrl = ctrl;
> + cdev->dev = &pdev->dev;
>
> cdev->mdev.parent = &pdev->dev;
> cdev->mdev.minor = MISC_DYNAMIC_MINOR;
> @@ -243,6 +624,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
> cdev->mdev.nodename = "surface/aggregator";
> cdev->mdev.fops = &ssam_controller_fops;
>
> + init_rwsem(&cdev->client_lock);
> + INIT_LIST_HEAD(&cdev->client_list);
> +
> status = misc_register(&cdev->mdev);
> if (status) {
> kfree(cdev);
> @@ -256,8 +640,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
> static int ssam_dbg_device_remove(struct platform_device *pdev)
> {
> struct ssam_cdev *cdev = platform_get_drvdata(pdev);
> + struct ssam_cdev_client *client;
>
> - misc_deregister(&cdev->mdev);
> + /*
> + * Mark device as shut-down. Prevent new clients from being added and
> + * new operations from being executed.
> + */
> + set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags);
> +
> + down_write(&cdev->client_lock);
> +
> + /* Remove all notifiers registered by us. */
> + list_for_each_entry(client, &cdev->client_list, node) {
> + ssam_cdev_notifier_unregister_all(client);
> + }
> +
> + /* Wake up async clients. */
> + list_for_each_entry(client, &cdev->client_list, node) {
> + kill_fasync(&client->fasync, SIGIO, POLL_HUP);
> + }
> +
> + /* Wake up blocking clients. */
> + list_for_each_entry(client, &cdev->client_list, node) {
> + wake_up_interruptible(&client->waitq);
> + }
> +
> + up_write(&cdev->client_lock);
>
> /*
> * The controller is only guaranteed to be valid for as long as the
> @@ -266,8 +674,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev)
> */
> down_write(&cdev->lock);
> cdev->ctrl = NULL;
> + cdev->dev = NULL;
> up_write(&cdev->lock);
>
> + misc_deregister(&cdev->mdev);
> +
> ssam_cdev_put(cdev);
> return 0;
> }
> diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
> index fbcce04abfe9..4f393fafc235 100644
> --- a/include/uapi/linux/surface_aggregator/cdev.h
> +++ b/include/uapi/linux/surface_aggregator/cdev.h
> @@ -6,7 +6,7 @@
> * device. This device provides direct user-space access to the SSAM EC.
> * Intended for debugging and development.
> *
> - * Copyright (C) 2020 Maximilian Luz <[email protected]>
> + * Copyright (C) 2020-2021 Maximilian Luz <[email protected]>
> */
>
> #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H
> @@ -73,6 +73,43 @@ struct ssam_cdev_request {
> } response;
> } __attribute__((__packed__));
>
> -#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
> +/**
> + * struct ssam_cdev_notifier_desc - Notifier descriptor.
> + * @priority: Priority value determining the order in which notifier
> + * callbacks will be called. A higher value means higher
> + * priority, i.e. the associated callback will be executed
> + * earlier than other (lower priority) callbacks.
> + * @target_category: The event target category for which this notifier should
> + * receive events.
> + *
> + * Specifies the notifier that should be registered or unregistered,
> + * specifically with which priority and for which target category of events.
> + */
> +struct ssam_cdev_notifier_desc {
> + __s32 priority;
> + __u8 target_category;
> +} __attribute__((__packed__));
> +
> +/**
> + * struct ssam_cdev_event - SSAM event sent by the EC.
> + * @target_category: Target category of the event source. See &enum ssam_ssh_tc.
> + * @target_id: Target ID of the event source.
> + * @command_id: Command ID of the event.
> + * @instance_id: Instance ID of the event source.
> + * @length: Length of the event payload in bytes.
> + * @data: Event payload data.
> + */
> +struct ssam_cdev_event {
> + __u8 target_category;
> + __u8 target_id;
> + __u8 command_id;
> + __u8 instance_id;
> + __u16 length;
> + __u8 data[];
> +} __attribute__((__packed__));
> +
> +#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
> +#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
> +#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
>
> #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
>

2021-06-04 11:43:28

by Hans de Goede

[permalink] [raw]
Subject: Re: [PATCH 0/7] platform/surface: aggregator: Extend user-space interface for events

Hi Maxime,

On 6/4/21 1:45 AM, Maximilian Luz wrote:
> Extend the user-space debug interface so that it can be used to receive
> SSAM events in user-space.
>
> Currently, inspecting SSAM events requires writing a custom client
> device and corresponding driver. This is not particularly user-friendly
> for quick testing and comes with higher iteration times. Since we
> already have a user-space interface, we can extend this to forward
> events from SSAM via the controller device file to user-space. With this
> we can then essentially write user-space SSAM clients for testing and
> reverse-engineering, providing us with all the essential functionality
> that previously only a kernel driver would have access to. Note that
> this is still only intended to be an interface for debugging and
> reverse-engineering purposes.
>
> To achieve this, we need to extend the core to decouple events from
> notifiers. Right now, enabling an event group requires registering a
> notifier for that group. This notifier provides a callback that is
> called when the event occurs. For user-space forwarding, we need to run
> all events through the same file. In the current implementation, this
> presents a problem as, when we don't know the exact events or can't
> filter for them, multiple notifiers for the same target category will
> lead to duplicate events to be sent through the file, one per notifier.
>
> Decoupling notifier registration from event enable-/disablement (and the
> corresponding reference counting) allows us to avoid this issue. We can
> then register one notifier for a whole target category and enable or
> disable events independently of this notifier. Since events are strictly
> separated by their target category, this will not lead to duplicate
> events.
>
> With this, we can then provide user-space with two new IOCTLs for
> registering notifiers for a specific target category of events they are
> interested in. This allows us to forward all events received by those
> notifiers to the internal buffer of the device file, from which they can
> be read by user-space. In other words, user-space can, via those two
> IOCTLs, select which event target categories they are interested in.
>
> Furthermore, we add another two IOCTLs for enabling and disabling events
> via the controller. While events can already be enabled and disabled via
> generic requests, this does not respect the controller-internal
> reference counting mechanism. Due to that, this can lead to an event
> group being disabled even though a kernel-driver has requested it to be
> enabled. Or in other words: Without this, a user-space client cannot
> safely reset the state as it has only two options, keeping the event
> group enabled and not attempt cleanup at all, or disable the event group
> for all clients and potentially stop them from working properly.
>
> Also update the copyright lines since we're already doing some work on
> the core.

Overall this series looks good to me. I've found one small issue with
PATCH 4/7 (see my reply to that patch) and as the kernel test robot
pointed out there is an used "struct ssam_nf_head *nf_head;" in
PATCH 2/7.

With these 2 small issues fixed you can add my:

Reviewed-by: Hans de Goede <[email protected]>

to v2 of the series.

Regards,

Hans



>
> Maximilian Luz (7):
> platform/surface: aggregator: Allow registering notifiers without
> enabling events
> platform/surface: aggregator: Allow enabling of events without
> notifiers
> platform/surface: aggregator: Update copyright
> platform/surface: aggregator_cdev: Add support for forwarding events
> to user-space
> platform/surface: aggregator_cdev: Allow enabling of events from
> user-space
> platform/surface: aggregator_cdev: Add lockdep support
> docs: driver-api: Update Surface Aggregator user-space interface
> documentation
>
> .../surface_aggregator/clients/cdev.rst | 127 ++++-
> .../userspace-api/ioctl/ioctl-number.rst | 2 +-
> drivers/platform/surface/aggregator/Kconfig | 2 +-
> drivers/platform/surface/aggregator/Makefile | 2 +-
> drivers/platform/surface/aggregator/bus.c | 2 +-
> drivers/platform/surface/aggregator/bus.h | 2 +-
> .../platform/surface/aggregator/controller.c | 206 ++++++-
> .../platform/surface/aggregator/controller.h | 2 +-
> drivers/platform/surface/aggregator/core.c | 2 +-
> .../platform/surface/aggregator/ssh_msgb.h | 2 +-
> .../surface/aggregator/ssh_packet_layer.c | 2 +-
> .../surface/aggregator/ssh_packet_layer.h | 2 +-
> .../platform/surface/aggregator/ssh_parser.c | 2 +-
> .../platform/surface/aggregator/ssh_parser.h | 2 +-
> .../surface/aggregator/ssh_request_layer.c | 2 +-
> .../surface/aggregator/ssh_request_layer.h | 2 +-
> drivers/platform/surface/aggregator/trace.h | 2 +-
> .../surface/surface_aggregator_cdev.c | 531 +++++++++++++++++-
> include/linux/surface_aggregator/controller.h | 27 +-
> include/linux/surface_aggregator/device.h | 2 +-
> include/linux/surface_aggregator/serial_hub.h | 2 +-
> include/uapi/linux/surface_aggregator/cdev.h | 73 ++-
> 22 files changed, 921 insertions(+), 77 deletions(-)
>

2021-06-04 12:50:14

by Maximilian Luz

[permalink] [raw]
Subject: Re: [PATCH 4/7] platform/surface: aggregator_cdev: Add support for forwarding events to user-space

On 6/4/21 1:32 PM, Hans de Goede wrote:
> Hi,
>
> I've one review remark inline below.
>
> On 6/4/21 1:45 AM, Maximilian Luz wrote:

[...]

>> +static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
>> +{
>> + struct miscdevice *mdev = filp->private_data;
>> + struct ssam_cdev_client *client;
>> + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
>> +
>> + /* Initialize client */
>> + client = vzalloc(sizeof(*client));
>> + if (!client)
>> + return -ENOMEM;
>> +
>> + client->cdev = ssam_cdev_get(cdev);
>> +
>> + INIT_LIST_HEAD(&client->node);
>> +
>> + mutex_init(&client->notifier_lock);
>> +
>> + mutex_init(&client->read_lock);
>> + mutex_init(&client->write_lock);
>> + INIT_KFIFO(client->buffer);
>> + init_waitqueue_head(&client->waitq);
>> +
>> + filp->private_data = client;
>> +
>> + /* Attach client. */
>> + down_write(&cdev->client_lock);
>> +
>> + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
>> + up_write(&cdev->client_lock);
>> + ssam_cdev_put(client->cdev);
>
> You are missing the mutex_destroy() calls here which you are
> doing in ssam_cdev_device_release().

Thank you for noticing this! This code is based on Surface DTX code
which has the same problem, I'll send in a fix for that shortly.

> Or maybe move the mutex_init calls below this check
> (before the up_write()) since I don't think the client can
> be accessed by any code until the up_write is done?

Yes, that would also be possible, but I'd prefer adding the
mutex_destroy() calls in the failure path. To me that seems a bit easier
to reason about.

Thanks,
Max

2021-06-04 13:00:11

by Maximilian Luz

[permalink] [raw]
Subject: Re: [PATCH 0/7] platform/surface: aggregator: Extend user-space interface for events

On 6/4/21 1:40 PM, Hans de Goede wrote:
> Hi Maxime,
>
> On 6/4/21 1:45 AM, Maximilian Luz wrote:
>> Extend the user-space debug interface so that it can be used to receive
>> SSAM events in user-space.
>>

[...]

>
> Overall this series looks good to me. I've found one small issue with
> PATCH 4/7 (see my reply to that patch) and as the kernel test robot
> pointed out there is an used "struct ssam_nf_head *nf_head;" in
> PATCH 2/7.
>
> With these 2 small issues fixed you can add my:
>
> Reviewed-by: Hans de Goede <[email protected]>
>
> to v2 of the series.

Thank you for your review.

Based on the issue reported by the kernel test robot I've been
restructuring PATCH 2/7 to remove some code-duplication. I'll add your
R-b to all except that one.

Thanks,
Max