platform/surface: Add Surface Aggregator subsystem

Add Surface System Aggregator Module core and Surface Serial Hub driver,
required for the embedded controller found on Microsoft Surface devices.

The Surface System Aggregator Module (SSAM, SAM or Surface Aggregator)
is an embedded controller (EC) found on 4th and later generation
Microsoft Surface devices, with the exception of the Surface Go series.
This EC provides various functionality, depending on the device in
question. This can include battery status and thermal reporting (5th and
later generations), but also HID keyboard (6th+) and touchpad input
(7th+) on Surface Laptop and Surface Book 3 series devices.

This patch provides the basic necessities for communication with the SAM
EC on 5th and later generation devices. On these devices, the EC
provides an interface that acts as serial device, called the Surface
Serial Hub (SSH). 4th generation devices, on which the EC interface is
provided via an HID-over-I2C device, are not supported by this patch.

Specifically, this patch adds a driver for the SSH device (device HID
MSHW0084 in ACPI), as well as a controller structure and associated API.
This represents the functional core of the Surface Aggregator kernel
subsystem, introduced with this patch, and will be expanded upon in
subsequent commits.

The SSH driver acts as the main attachment point for this subsystem and
sets-up and manages the controller structure. The controller in turn
provides a basic communication interface, allowing to send requests from
host to EC and receiving the corresponding responses, as well as
managing and receiving events, sent from EC to host. It is structured
into multiple layers, with the top layer presenting the API used by
other kernel drivers and the lower layers modeled after the serial
protocol used for communication.

Said other drivers are then responsible for providing the (Surface model
specific) functionality accessible through the EC (e.g. battery status
reporting, thermal information, ...) via said controller structure and
API, and will be added in future commits.

Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
Link: https://lore.kernel.org/r/20201221183959.1186143-2-luzmaximilian@gmail.com
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
This commit is contained in:
Maximilian Luz 2020-12-21 19:39:51 +01:00 committed by Hans de Goede
parent 5b56930252
commit c167b9c7e3
17 changed files with 8964 additions and 0 deletions

View File

@ -11813,6 +11813,14 @@ L: platform-driver-x86@vger.kernel.org
S: Supported S: Supported
F: drivers/platform/surface/surfacepro3_button.c F: drivers/platform/surface/surfacepro3_button.c
MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM
M: Maximilian Luz <luzmaximilian@gmail.com>
S: Maintained
W: https://github.com/linux-surface/surface-aggregator-module
C: irc://chat.freenode.net/##linux-surface
F: drivers/platform/surface/aggregator/
F: include/linux/surface_aggregator/
MICROTEK X6 SCANNER MICROTEK X6 SCANNER
M: Oliver Neukum <oliver@neukum.org> M: Oliver Neukum <oliver@neukum.org>
S: Maintained S: Maintained

View File

@ -56,4 +56,6 @@ config SURFACE_PRO3_BUTTON
help help
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
source "drivers/platform/surface/aggregator/Kconfig"
endif # SURFACE_PLATFORMS endif # SURFACE_PLATFORMS

View File

@ -7,5 +7,6 @@
obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o

View File

@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
menuconfig SURFACE_AGGREGATOR
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
depends on SERIAL_DEV_BUS
select CRC_CCITT
help
The Surface System Aggregator Module (Surface SAM or SSAM) is an
embedded controller (EC) found on 5th- and later-generation Microsoft
Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop,
and newer, with exception of Surface Go series devices).
Depending on the device in question, this EC provides varying
functionality, including:
- EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation)
- battery status information (all devices)
- thermal sensor access (all devices)
- performance mode / cooling mode control (all devices)
- clipboard detachment system control (Surface Book 2 and 3)
- HID / keyboard input (Surface Laptops, Surface Book 3)
This option controls whether the Surface SAM subsystem core will be
built. This includes a driver for the Surface Serial Hub (SSH), which
is the device responsible for the communication with the EC, and a
basic kernel interface exposing the EC functionality to other client
drivers, i.e. allowing them to make requests to the EC and receive
events from it. Selecting this option alone will not provide any
client drivers and therefore no functionality beyond the in-kernel
interface. Said functionality is the responsibility of the respective
client drivers.
Note: While 4th-generation Surface devices also make use of a SAM EC,
due to a difference in the communication interface of the controller,
only 5th and later generations are currently supported. Specifically,
devices using SAM-over-SSH are supported, whereas devices using
SAM-over-HID, which is used on the 4th generation, are currently not
supported.
Choose m if you want to build the SAM subsystem core and SSH driver as
module, y if you want to build it into the kernel and n if you don't
want it at all.

View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o
surface_aggregator-objs := core.o
surface_aggregator-objs += ssh_parser.o
surface_aggregator-objs += ssh_packet_layer.o
surface_aggregator-objs += ssh_request_layer.o
surface_aggregator-objs += controller.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,276 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Main SSAM/SSH controller structure and functionality.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
#define _SURFACE_AGGREGATOR_CONTROLLER_H
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/rwsem.h>
#include <linux/serdev.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/serial_hub.h>
#include "ssh_request_layer.h"
/* -- Safe counters. -------------------------------------------------------- */
/**
* struct ssh_seq_counter - Safe counter for SSH sequence IDs.
* @value: The current counter value.
*/
struct ssh_seq_counter {
u8 value;
};
/**
* struct ssh_rqid_counter - Safe counter for SSH request IDs.
* @value: The current counter value.
*/
struct ssh_rqid_counter {
u16 value;
};
/* -- Event/notification system. -------------------------------------------- */
/**
* struct ssam_nf_head - Notifier head for SSAM events.
* @srcu: The SRCU struct for synchronization.
* @head: List-head for notifier blocks registered under this head.
*/
struct ssam_nf_head {
struct srcu_struct srcu;
struct list_head head;
};
/**
* struct ssam_nf - Notifier callback- and activation-registry for SSAM events.
* @lock: Lock guarding (de-)registration of notifier blocks. Note: This
* lock does not need to be held for notifier calls, only
* registration and deregistration.
* @refcount: The root of the RB-tree used for reference-counting enabled
* events/notifications.
* @head: The list of notifier heads for event/notification callbacks.
*/
struct ssam_nf {
struct mutex lock;
struct rb_root refcount;
struct ssam_nf_head head[SSH_NUM_EVENTS];
};
/* -- Event/async request completion system. -------------------------------- */
struct ssam_cplt;
/**
* struct ssam_event_item - Struct for event queuing and completion.
* @node: The node in the queue.
* @rqid: The request ID of the event.
* @event: Actual event data.
*/
struct ssam_event_item {
struct list_head node;
u16 rqid;
struct ssam_event event; /* must be last */
};
/**
* struct ssam_event_queue - Queue for completing received events.
* @cplt: Reference to the completion system on which this queue is active.
* @lock: The lock for any operation on the queue.
* @head: The list-head of the queue.
* @work: The &struct work_struct performing completion work for this queue.
*/
struct ssam_event_queue {
struct ssam_cplt *cplt;
spinlock_t lock;
struct list_head head;
struct work_struct work;
};
/**
* struct ssam_event_target - Set of queues for a single SSH target ID.
* @queue: The array of queues, one queue per event ID.
*/
struct ssam_event_target {
struct ssam_event_queue queue[SSH_NUM_EVENTS];
};
/**
* struct ssam_cplt - SSAM event/async request completion system.
* @dev: The device with which this system is associated. Only used
* for logging.
* @wq: The &struct workqueue_struct on which all completion work
* items are queued.
* @event: Event completion management.
* @event.target: Array of &struct ssam_event_target, one for each target.
* @event.notif: Notifier callbacks and event activation reference counting.
*/
struct ssam_cplt {
struct device *dev;
struct workqueue_struct *wq;
struct {
struct ssam_event_target target[SSH_NUM_TARGETS];
struct ssam_nf notif;
} event;
};
/* -- Main SSAM device structures. ------------------------------------------ */
/**
* enum ssam_controller_state - State values for &struct ssam_controller.
* @SSAM_CONTROLLER_UNINITIALIZED:
* The controller has not been initialized yet or has been deinitialized.
* @SSAM_CONTROLLER_INITIALIZED:
* The controller is initialized, but has not been started yet.
* @SSAM_CONTROLLER_STARTED:
* The controller has been started and is ready to use.
* @SSAM_CONTROLLER_STOPPED:
* The controller has been stopped.
* @SSAM_CONTROLLER_SUSPENDED:
* The controller has been suspended.
*/
enum ssam_controller_state {
SSAM_CONTROLLER_UNINITIALIZED,
SSAM_CONTROLLER_INITIALIZED,
SSAM_CONTROLLER_STARTED,
SSAM_CONTROLLER_STOPPED,
SSAM_CONTROLLER_SUSPENDED,
};
/**
* struct ssam_controller_caps - Controller device capabilities.
* @ssh_power_profile: SSH power profile.
* @ssh_buffer_size: SSH driver UART buffer size.
* @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout.
* @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout.
* @d3_closes_handle: SAM closes UART handle in D3.
*
* Controller and SSH device capabilities found in ACPI.
*/
struct ssam_controller_caps {
u32 ssh_power_profile;
u32 ssh_buffer_size;
u32 screen_on_sleep_idle_timeout;
u32 screen_off_sleep_idle_timeout;
u32 d3_closes_handle:1;
};
/**
* struct ssam_controller - SSAM controller device.
* @kref: Reference count of the controller.
* @lock: Main lock for the controller, used to guard state changes.
* @state: Controller state.
* @rtl: Request transport layer for SSH I/O.
* @cplt: Completion system for SSH/SSAM events and asynchronous requests.
* @counter: Safe SSH message ID counters.
* @counter.seq: Sequence ID counter.
* @counter.rqid: Request ID counter.
* @irq: Wakeup IRQ resources.
* @irq.num: The wakeup IRQ number.
* @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend.
* @caps: The controller device capabilities.
*/
struct ssam_controller {
struct kref kref;
struct rw_semaphore lock;
enum ssam_controller_state state;
struct ssh_rtl rtl;
struct ssam_cplt cplt;
struct {
struct ssh_seq_counter seq;
struct ssh_rqid_counter rqid;
} counter;
struct {
int num;
bool wakeup_enabled;
} irq;
struct ssam_controller_caps caps;
};
#define to_ssam_controller(ptr, member) \
container_of(ptr, struct ssam_controller, member)
#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
/**
* ssam_controller_receive_buf() - Provide input-data to the controller.
* @ctrl: The controller.
* @buf: The input buffer.
* @n: The number of bytes in the input buffer.
*
* Provide input data to be evaluated by the controller, which has been
* received via the lower-level transport.
*
* Return: Returns the number of bytes consumed, or, if the packet transport
* layer of the controller has been shut down, %-ESHUTDOWN.
*/
static inline
int ssam_controller_receive_buf(struct ssam_controller *ctrl,
const unsigned char *buf, size_t n)
{
return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n);
}
/**
* ssam_controller_write_wakeup() - Notify the controller that the underlying
* device has space available for data to be written.
* @ctrl: The controller.
*/
static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl)
{
ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl);
}
int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s);
int ssam_controller_start(struct ssam_controller *ctrl);
void ssam_controller_shutdown(struct ssam_controller *ctrl);
void ssam_controller_destroy(struct ssam_controller *ctrl);
int ssam_notifier_disable_registered(struct ssam_controller *ctrl);
void ssam_notifier_restore_registered(struct ssam_controller *ctrl);
int ssam_irq_setup(struct ssam_controller *ctrl);
void ssam_irq_free(struct ssam_controller *ctrl);
int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl);
void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl);
void ssam_controller_lock(struct ssam_controller *c);
void ssam_controller_unlock(struct ssam_controller *c);
int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version);
int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl);
int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl);
int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl);
int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl);
int ssam_controller_suspend(struct ssam_controller *ctrl);
int ssam_controller_resume(struct ssam_controller *ctrl);
#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */

View File

@ -0,0 +1,787 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface Serial Hub (SSH) driver for communication with the Surface/System
* Aggregator Module (SSAM/SAM).
*
* Provides access to a SAM-over-SSH connected EC via a controller device.
* Handles communication via requests as well as enabling, disabling, and
* relaying of events.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/acpi.h>
#include <linux/atomic.h>
#include <linux/completion.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/serdev.h>
#include <linux/sysfs.h>
#include <linux/surface_aggregator/controller.h>
#include "controller.h"
/* -- Static controller reference. ------------------------------------------ */
/*
* Main controller reference. The corresponding lock must be held while
* accessing (reading/writing) the reference.
*/
static struct ssam_controller *__ssam_controller;
static DEFINE_SPINLOCK(__ssam_controller_lock);
/**
* ssam_get_controller() - Get reference to SSAM controller.
*
* Returns a reference to the SSAM controller of the system or %NULL if there
* is none, it hasn't been set up yet, or it has already been unregistered.
* This function automatically increments the reference count of the
* controller, thus the calling party must ensure that ssam_controller_put()
* is called when it doesn't need the controller any more.
*/
struct ssam_controller *ssam_get_controller(void)
{
struct ssam_controller *ctrl;
spin_lock(&__ssam_controller_lock);
ctrl = __ssam_controller;
if (!ctrl)
goto out;
if (WARN_ON(!kref_get_unless_zero(&ctrl->kref)))
ctrl = NULL;
out:
spin_unlock(&__ssam_controller_lock);
return ctrl;
}
EXPORT_SYMBOL_GPL(ssam_get_controller);
/**
* ssam_try_set_controller() - Try to set the main controller reference.
* @ctrl: The controller to which the reference should point.
*
* Set the main controller reference to the given pointer if the reference
* hasn't been set already.
*
* Return: Returns zero on success or %-EEXIST if the reference has already
* been set.
*/
static int ssam_try_set_controller(struct ssam_controller *ctrl)
{
int status = 0;
spin_lock(&__ssam_controller_lock);
if (!__ssam_controller)
__ssam_controller = ctrl;
else
status = -EEXIST;
spin_unlock(&__ssam_controller_lock);
return status;
}
/**
* ssam_clear_controller() - Remove/clear the main controller reference.
*
* Clears the main controller reference, i.e. sets it to %NULL. This function
* should be called before the controller is shut down.
*/
static void ssam_clear_controller(void)
{
spin_lock(&__ssam_controller_lock);
__ssam_controller = NULL;
spin_unlock(&__ssam_controller_lock);
}
/**
* ssam_client_link() - Link an arbitrary client device to the controller.
* @c: The controller to link to.
* @client: The client device.
*
* Link an arbitrary client device to the controller by creating a device link
* between it as consumer and the controller device as provider. This function
* can be used for non-SSAM devices (or SSAM devices not registered as child
* under the controller) to guarantee that the controller is valid for as long
* as the driver of the client device is bound, and that proper suspend and
* resume ordering is guaranteed.
*
* The device link does not have to be destructed manually. It is removed
* automatically once the driver of the client device unbinds.
*
* Return: Returns zero on success, %-ENODEV if the controller is not ready or
* going to be removed soon, or %-ENOMEM if the device link could not be
* created for other reasons.
*/
int ssam_client_link(struct ssam_controller *c, struct device *client)
{
const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
struct device_link *link;
struct device *ctrldev;
ssam_controller_statelock(c);
if (c->state != SSAM_CONTROLLER_STARTED) {
ssam_controller_stateunlock(c);
return -ENODEV;
}
ctrldev = ssam_controller_device(c);
if (!ctrldev) {
ssam_controller_stateunlock(c);
return -ENODEV;
}
link = device_link_add(client, ctrldev, flags);
if (!link) {
ssam_controller_stateunlock(c);
return -ENOMEM;
}
/*
* Return -ENODEV if supplier driver is on its way to be removed. In
* this case, the controller won't be around for much longer and the
* device link is not going to save us any more, as unbinding is
* already in progress.
*/
if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) {
ssam_controller_stateunlock(c);
return -ENODEV;
}
ssam_controller_stateunlock(c);
return 0;
}
EXPORT_SYMBOL_GPL(ssam_client_link);
/**
* ssam_client_bind() - Bind an arbitrary client device to the controller.
* @client: The client device.
*
* Link an arbitrary client device to the controller by creating a device link
* between it as consumer and the main controller device as provider. This
* function can be used for non-SSAM devices to guarantee that the controller
* returned by this function is valid for as long as the driver of the client
* device is bound, and that proper suspend and resume ordering is guaranteed.
*
* This function does essentially the same as ssam_client_link(), except that
* it first fetches the main controller reference, then creates the link, and
* finally returns this reference. Note that this function does not increment
* the reference counter of the controller, as, due to the link, the
* controller lifetime is assured as long as the driver of the client device
* is bound.
*
* It is not valid to use the controller reference obtained by this method
* outside of the driver bound to the client device at the time of calling
* this function, without first incrementing the reference count of the
* controller via ssam_controller_get(). Even after doing this, care must be
* taken that requests are only submitted and notifiers are only
* (un-)registered when the controller is active and not suspended. In other
* words: The device link only lives as long as the client driver is bound and
* any guarantees enforced by this link (e.g. active controller state) can
* only be relied upon as long as this link exists and may need to be enforced
* in other ways afterwards.
*
* The created device link does not have to be destructed manually. It is
* removed automatically once the driver of the client device unbinds.
*
* Return: Returns the controller on success, an error pointer with %-ENODEV
* if the controller is not present, not ready or going to be removed soon, or
* %-ENOMEM if the device link could not be created for other reasons.
*/
struct ssam_controller *ssam_client_bind(struct device *client)
{
struct ssam_controller *c;
int status;
c = ssam_get_controller();
if (!c)
return ERR_PTR(-ENODEV);
status = ssam_client_link(c, client);
/*
* Note that we can drop our controller reference in both success and
* failure cases: On success, we have bound the controller lifetime
* inherently to the client driver lifetime, i.e. it the controller is
* now guaranteed to outlive the client driver. On failure, we're not
* going to use the controller any more.
*/
ssam_controller_put(c);
return status >= 0 ? c : ERR_PTR(status);
}
EXPORT_SYMBOL_GPL(ssam_client_bind);
/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */
static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf,
size_t n)
{
struct ssam_controller *ctrl;
ctrl = serdev_device_get_drvdata(dev);
return ssam_controller_receive_buf(ctrl, buf, n);
}
static void ssam_write_wakeup(struct serdev_device *dev)
{
ssam_controller_write_wakeup(serdev_device_get_drvdata(dev));
}
static const struct serdev_device_ops ssam_serdev_ops = {
.receive_buf = ssam_receive_buf,
.write_wakeup = ssam_write_wakeup,
};
/* -- SysFS and misc. ------------------------------------------------------- */
static int ssam_log_firmware_version(struct ssam_controller *ctrl)
{
u32 version, a, b, c;
int status;
status = ssam_get_firmware_version(ctrl, &version);
if (status)
return status;
a = (version >> 24) & 0xff;
b = ((version >> 8) & 0xffff);
c = version & 0xff;
ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c);
return 0;
}
static ssize_t firmware_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ssam_controller *ctrl = dev_get_drvdata(dev);
u32 version, a, b, c;
int status;
status = ssam_get_firmware_version(ctrl, &version);
if (status < 0)
return status;
a = (version >> 24) & 0xff;
b = ((version >> 8) & 0xffff);
c = version & 0xff;
return sysfs_emit(buf, "%u.%u.%u\n", a, b, c);
}
static DEVICE_ATTR_RO(firmware_version);
static struct attribute *ssam_sam_attrs[] = {
&dev_attr_firmware_version.attr,
NULL
};
static const struct attribute_group ssam_sam_group = {
.name = "sam",
.attrs = ssam_sam_attrs,
};
/* -- ACPI based device setup. ---------------------------------------------- */
static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc,
void *ctx)
{
struct serdev_device *serdev = ctx;
struct acpi_resource_common_serialbus *serial;
struct acpi_resource_uart_serialbus *uart;
bool flow_control;
int status = 0;
if (rsc->type != ACPI_RESOURCE_TYPE_SERIAL_BUS)
return AE_OK;
serial = &rsc->data.common_serial_bus;
if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART)
return AE_OK;
uart = &rsc->data.uart_serial_bus;
/* Set up serdev device. */
serdev_device_set_baudrate(serdev, uart->default_baud_rate);
/* serdev currently only supports RTSCTS flow control. */
if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) {
dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n",
uart->flow_control);
}
/* Set RTSCTS flow control. */
flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW;
serdev_device_set_flow_control(serdev, flow_control);
/* serdev currently only supports EVEN/ODD parity. */
switch (uart->parity) {
case ACPI_UART_PARITY_NONE:
status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
break;
case ACPI_UART_PARITY_EVEN:
status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN);
break;
case ACPI_UART_PARITY_ODD:
status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD);
break;
default:
dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n",
uart->parity);
break;
}
if (status) {
dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n",
uart->parity, status);
return AE_ERROR;
}
/* We've found the resource and are done. */
return AE_CTRL_TERMINATE;
}
static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle,
struct serdev_device *serdev)
{
return acpi_walk_resources(handle, METHOD_NAME__CRS,
ssam_serdev_setup_via_acpi_crs, serdev);
}
/* -- Power management. ----------------------------------------------------- */
static void ssam_serial_hub_shutdown(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* Try to disable notifiers, signal display-off and D0-exit, ignore any
* errors.
*
* Note: It has not been established yet if this is actually
* necessary/useful for shutdown.
*/
status = ssam_notifier_disable_registered(c);
if (status) {
ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n",
status);
}
status = ssam_ctrl_notif_display_off(c);
if (status)
ssam_err(c, "pm: display-off notification failed: %d\n", status);
status = ssam_ctrl_notif_d0_exit(c);
if (status)
ssam_err(c, "pm: D0-exit notification failed: %d\n", status);
}
#ifdef CONFIG_PM_SLEEP
static int ssam_serial_hub_pm_prepare(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* Try to signal display-off, This will quiesce events.
*
* Note: Signaling display-off/display-on should normally be done from
* some sort of display state notifier. As that is not available,
* signal it here.
*/
status = ssam_ctrl_notif_display_off(c);
if (status)
ssam_err(c, "pm: display-off notification failed: %d\n", status);
return status;
}
static void ssam_serial_hub_pm_complete(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* Try to signal display-on. This will restore events.
*
* Note: Signaling display-off/display-on should normally be done from
* some sort of display state notifier. As that is not available,
* signal it here.
*/
status = ssam_ctrl_notif_display_on(c);
if (status)
ssam_err(c, "pm: display-on notification failed: %d\n", status);
}
static int ssam_serial_hub_pm_suspend(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* Try to signal D0-exit, enable IRQ wakeup if specified. Abort on
* error.
*/
status = ssam_ctrl_notif_d0_exit(c);
if (status) {
ssam_err(c, "pm: D0-exit notification failed: %d\n", status);
goto err_notif;
}
status = ssam_irq_arm_for_wakeup(c);
if (status)
goto err_irq;
WARN_ON(ssam_controller_suspend(c));
return 0;
err_irq:
ssam_ctrl_notif_d0_entry(c);
err_notif:
ssam_ctrl_notif_display_on(c);
return status;
}
static int ssam_serial_hub_pm_resume(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
WARN_ON(ssam_controller_resume(c));
/*
* Try to disable IRQ wakeup (if specified) and signal D0-entry. In
* case of errors, log them and try to restore normal operation state
* as far as possible.
*
* Note: Signaling display-off/display-on should normally be done from
* some sort of display state notifier. As that is not available,
* signal it here.
*/
ssam_irq_disarm_wakeup(c);
status = ssam_ctrl_notif_d0_entry(c);
if (status)
ssam_err(c, "pm: D0-entry notification failed: %d\n", status);
return 0;
}
static int ssam_serial_hub_pm_freeze(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* During hibernation image creation, we only have to ensure that the
* EC doesn't send us any events. This is done via the display-off
* and D0-exit notifications. Note that this sets up the wakeup IRQ
* on the EC side, however, we have disabled it by default on our side
* and won't enable it here.
*
* See ssam_serial_hub_poweroff() for more details on the hibernation
* process.
*/
status = ssam_ctrl_notif_d0_exit(c);
if (status) {
ssam_err(c, "pm: D0-exit notification failed: %d\n", status);
ssam_ctrl_notif_display_on(c);
return status;
}
WARN_ON(ssam_controller_suspend(c));
return 0;
}
static int ssam_serial_hub_pm_thaw(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
WARN_ON(ssam_controller_resume(c));
status = ssam_ctrl_notif_d0_entry(c);
if (status)
ssam_err(c, "pm: D0-exit notification failed: %d\n", status);
return status;
}
static int ssam_serial_hub_pm_poweroff(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* When entering hibernation and powering off the system, the EC, at
* least on some models, may disable events. Without us taking care of
* that, this leads to events not being enabled/restored when the
* system resumes from hibernation, resulting SAM-HID subsystem devices
* (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being
* gone, etc.
*
* To avoid these issues, we disable all registered events here (this is
* likely not actually required) and restore them during the drivers PM
* restore callback.
*
* Wakeup from the EC interrupt is not supported during hibernation,
* so don't arm the IRQ here.
*/
status = ssam_notifier_disable_registered(c);
if (status) {
ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n",
status);
return status;
}
status = ssam_ctrl_notif_d0_exit(c);
if (status) {
ssam_err(c, "pm: D0-exit notification failed: %d\n", status);
ssam_notifier_restore_registered(c);
return status;
}
WARN_ON(ssam_controller_suspend(c));
return 0;
}
static int ssam_serial_hub_pm_restore(struct device *dev)
{
struct ssam_controller *c = dev_get_drvdata(dev);
int status;
/*
* Ignore but log errors, try to restore state as much as possible in
* case of failures. See ssam_serial_hub_poweroff() for more details on
* the hibernation process.
*/
WARN_ON(ssam_controller_resume(c));
status = ssam_ctrl_notif_d0_entry(c);
if (status)
ssam_err(c, "pm: D0-entry notification failed: %d\n", status);
ssam_notifier_restore_registered(c);
return 0;
}
static const struct dev_pm_ops ssam_serial_hub_pm_ops = {
.prepare = ssam_serial_hub_pm_prepare,
.complete = ssam_serial_hub_pm_complete,
.suspend = ssam_serial_hub_pm_suspend,
.resume = ssam_serial_hub_pm_resume,
.freeze = ssam_serial_hub_pm_freeze,
.thaw = ssam_serial_hub_pm_thaw,
.poweroff = ssam_serial_hub_pm_poweroff,
.restore = ssam_serial_hub_pm_restore,
};
#else /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops ssam_serial_hub_pm_ops = { };
#endif /* CONFIG_PM_SLEEP */
/* -- Device/driver setup. -------------------------------------------------- */
static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false };
static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false };
static const struct acpi_gpio_mapping ssam_acpi_gpios[] = {
{ "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 },
{ "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 },
{ },
};
static int ssam_serial_hub_probe(struct serdev_device *serdev)
{
struct ssam_controller *ctrl;
acpi_handle *ssh = ACPI_HANDLE(&serdev->dev);
acpi_status astatus;
int status;
if (gpiod_count(&serdev->dev, NULL) < 0)
return -ENODEV;
status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios);
if (status)
return status;
/* Allocate controller. */
ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
if (!ctrl)
return -ENOMEM;
/* Initialize controller. */
status = ssam_controller_init(ctrl, serdev);
if (status)
goto err_ctrl_init;
ssam_controller_lock(ctrl);
/* Set up serdev device. */
serdev_device_set_drvdata(serdev, ctrl);
serdev_device_set_client_ops(serdev, &ssam_serdev_ops);
status = serdev_device_open(serdev);
if (status)
goto err_devopen;
astatus = ssam_serdev_setup_via_acpi(ssh, serdev);
if (ACPI_FAILURE(astatus)) {
status = -ENXIO;
goto err_devinit;
}
/* Start controller. */
status = ssam_controller_start(ctrl);
if (status)
goto err_devinit;
ssam_controller_unlock(ctrl);
/*
* Initial SAM requests: Log version and notify default/init power
* states.
*/
status = ssam_log_firmware_version(ctrl);
if (status)
goto err_initrq;
status = ssam_ctrl_notif_d0_entry(ctrl);
if (status)
goto err_initrq;
status = ssam_ctrl_notif_display_on(ctrl);
if (status)
goto err_initrq;
status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group);
if (status)
goto err_initrq;
/* Set up IRQ. */
status = ssam_irq_setup(ctrl);
if (status)
goto err_irq;
/* Finally, set main controller reference. */
status = ssam_try_set_controller(ctrl);
if (WARN_ON(status)) /* Currently, we're the only provider. */
goto err_mainref;
/*
* TODO: The EC can wake up the system via the associated GPIO interrupt
* in multiple situations. One of which is the remaining battery
* capacity falling below a certain threshold. Normally, we should
* use the device_init_wakeup function, however, the EC also seems
* to have other reasons for waking up the system and it seems
* that Windows has additional checks whether the system should be
* resumed. In short, this causes some spurious unwanted wake-ups.
* For now let's thus default power/wakeup to false.
*/
device_set_wakeup_capable(&serdev->dev, true);
acpi_walk_dep_device_list(ssh);
return 0;
err_mainref:
ssam_irq_free(ctrl);
err_irq:
sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group);
err_initrq:
ssam_controller_lock(ctrl);
ssam_controller_shutdown(ctrl);
err_devinit:
serdev_device_close(serdev);
err_devopen:
ssam_controller_destroy(ctrl);
ssam_controller_unlock(ctrl);
err_ctrl_init:
kfree(ctrl);
return status;
}
static void ssam_serial_hub_remove(struct serdev_device *serdev)
{
struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev);
int status;
/* Clear static reference so that no one else can get a new one. */
ssam_clear_controller();
/* Disable and free IRQ. */
ssam_irq_free(ctrl);
sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group);
ssam_controller_lock(ctrl);
/* Act as if suspending to silence events. */
status = ssam_ctrl_notif_display_off(ctrl);
if (status) {
dev_err(&serdev->dev, "display-off notification failed: %d\n",
status);
}
status = ssam_ctrl_notif_d0_exit(ctrl);
if (status) {
dev_err(&serdev->dev, "D0-exit notification failed: %d\n",
status);
}
/* Shut down controller and remove serdev device reference from it. */
ssam_controller_shutdown(ctrl);
/* Shut down actual transport. */
serdev_device_wait_until_sent(serdev, 0);
serdev_device_close(serdev);
/* Drop our controller reference. */
ssam_controller_unlock(ctrl);
ssam_controller_put(ctrl);
device_set_wakeup_capable(&serdev->dev, false);
}
static const struct acpi_device_id ssam_serial_hub_match[] = {
{ "MSHW0084", 0 },
{ },
};
MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match);
static struct serdev_device_driver ssam_serial_hub = {
.probe = ssam_serial_hub_probe,
.remove = ssam_serial_hub_remove,
.driver = {
.name = "surface_serial_hub",
.acpi_match_table = ssam_serial_hub_match,
.pm = &ssam_serial_hub_pm_ops,
.shutdown = ssam_serial_hub_shutdown,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
module_serdev_device_driver(ssam_serial_hub);
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,205 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH message builder functions.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
#define _SURFACE_AGGREGATOR_SSH_MSGB_H
#include <asm/unaligned.h>
#include <linux/types.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/serial_hub.h>
/**
* struct msgbuf - Buffer struct to construct SSH messages.
* @begin: Pointer to the beginning of the allocated buffer space.
* @end: Pointer to the end (one past last element) of the allocated buffer
* space.
* @ptr: Pointer to the first free element in the buffer.
*/
struct msgbuf {
u8 *begin;
u8 *end;
u8 *ptr;
};
/**
* msgb_init() - Initialize the given message buffer struct.
* @msgb: The buffer struct to initialize
* @ptr: Pointer to the underlying memory by which the buffer will be backed.
* @cap: Size of the underlying memory.
*
* Initialize the given message buffer struct using the provided memory as
* backing.
*/
static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap)
{
msgb->begin = ptr;
msgb->end = ptr + cap;
msgb->ptr = ptr;
}
/**
* msgb_bytes_used() - Return the current number of bytes used in the buffer.
* @msgb: The message buffer.
*/
static inline size_t msgb_bytes_used(const struct msgbuf *msgb)
{
return msgb->ptr - msgb->begin;
}
static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value)
{
*msgb->ptr = value;
msgb->ptr += sizeof(u8);
}
static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value)
{
put_unaligned_le16(value, msgb->ptr);
msgb->ptr += sizeof(u16);
}
/**
* msgb_push_u16() - Push a u16 value to the buffer.
* @msgb: The message buffer.
* @value: The value to push to the buffer.
*/
static inline void msgb_push_u16(struct msgbuf *msgb, u16 value)
{
if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end))
return;
__msgb_push_u16(msgb, value);
}
/**
* msgb_push_syn() - Push SSH SYN bytes to the buffer.
* @msgb: The message buffer.
*/
static inline void msgb_push_syn(struct msgbuf *msgb)
{
msgb_push_u16(msgb, SSH_MSG_SYN);
}
/**
* msgb_push_buf() - Push raw data to the buffer.
* @msgb: The message buffer.
* @buf: The data to push to the buffer.
* @len: The length of the data to push to the buffer.
*/
static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len)
{
msgb->ptr = memcpy(msgb->ptr, buf, len) + len;
}
/**
* msgb_push_crc() - Compute CRC and push it to the buffer.
* @msgb: The message buffer.
* @buf: The data for which the CRC should be computed.
* @len: The length of the data for which the CRC should be computed.
*/
static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len)
{
msgb_push_u16(msgb, ssh_crc(buf, len));
}
/**
* msgb_push_frame() - Push a SSH message frame header to the buffer.
* @msgb: The message buffer
* @ty: The type of the frame.
* @len: The length of the payload of the frame.
* @seq: The sequence ID of the frame/packet.
*/
static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq)
{
u8 *const begin = msgb->ptr;
if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end))
return;
__msgb_push_u8(msgb, ty); /* Frame type. */
__msgb_push_u16(msgb, len); /* Frame payload length. */
__msgb_push_u8(msgb, seq); /* Frame sequence ID. */
msgb_push_crc(msgb, begin, msgb->ptr - begin);
}
/**
* msgb_push_ack() - Push a SSH ACK frame to the buffer.
* @msgb: The message buffer
* @seq: The sequence ID of the frame/packet to be ACKed.
*/
static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq)
{
/* SYN. */
msgb_push_syn(msgb);
/* ACK-type frame + CRC. */
msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq);
/* Payload CRC (ACK-type frames do not have a payload). */
msgb_push_crc(msgb, msgb->ptr, 0);
}
/**
* msgb_push_nak() - Push a SSH NAK frame to the buffer.
* @msgb: The message buffer
*/
static inline void msgb_push_nak(struct msgbuf *msgb)
{
/* SYN. */
msgb_push_syn(msgb);
/* NAK-type frame + CRC. */
msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00);
/* Payload CRC (ACK-type frames do not have a payload). */
msgb_push_crc(msgb, msgb->ptr, 0);
}
/**
* msgb_push_cmd() - Push a SSH command frame with payload to the buffer.
* @msgb: The message buffer.
* @seq: The sequence ID (SEQ) of the frame/packet.
* @rqid: The request ID (RQID) of the request contained in the frame.
* @rqst: The request to wrap in the frame.
*/
static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid,
const struct ssam_request *rqst)
{
const u8 type = SSH_FRAME_TYPE_DATA_SEQ;
u8 *cmd;
/* SYN. */
msgb_push_syn(msgb);
/* Command frame + CRC. */
msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq);
/* Frame payload: Command struct + payload. */
if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end))
return;
cmd = msgb->ptr;
__msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */
__msgb_push_u8(msgb, rqst->target_category); /* Target category. */
__msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */
__msgb_push_u8(msgb, 0x00); /* Target ID (in). */
__msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */
__msgb_push_u16(msgb, rqid); /* Request ID. */
__msgb_push_u8(msgb, rqst->command_id); /* Command ID. */
/* Command payload. */
msgb_push_buf(msgb, rqst->payload, rqst->length);
/* CRC for command struct + payload. */
msgb_push_crc(msgb, cmd, msgb->ptr - cmd);
}
#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH packet transport layer.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
#include <linux/atomic.h>
#include <linux/kfifo.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/serdev.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/surface_aggregator/serial_hub.h>
#include "ssh_parser.h"
/**
* enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl.
*
* @SSH_PTL_SF_SHUTDOWN_BIT:
* Indicates that the packet transport layer has been shut down or is
* being shut down and should not accept any new packets/data.
*/
enum ssh_ptl_state_flags {
SSH_PTL_SF_SHUTDOWN_BIT,
};
/**
* struct ssh_ptl_ops - Callback operations for packet transport layer.
* @data_received: Function called when a data-packet has been received. Both,
* the packet layer on which the packet has been received and
* the packet's payload data are provided to this function.
*/
struct ssh_ptl_ops {
void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data);
};
/**
* struct ssh_ptl - SSH packet transport layer.
* @serdev: Serial device providing the underlying data transport.
* @state: State(-flags) of the transport layer.
* @queue: Packet submission queue.
* @queue.lock: Lock for modifying the packet submission queue.
* @queue.head: List-head of the packet submission queue.
* @pending: Set/list of pending packets.
* @pending.lock: Lock for modifying the pending set.
* @pending.head: List-head of the pending set/list.
* @pending.count: Number of currently pending packets.
* @tx: Transmitter subsystem.
* @tx.running: Flag indicating (desired) transmitter thread state.
* @tx.thread: Transmitter thread.
* @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer.
* @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets.
* @tx.packet_wq: Waitqueue-head for packet transmit completion.
* @rx: Receiver subsystem.
* @rx.thread: Receiver thread.
* @rx.wq: Waitqueue-head for receiver thread.
* @rx.fifo: Buffer for receiving data/pushing data to receiver thread.
* @rx.buf: Buffer for evaluating data on receiver thread.
* @rx.blocked: List of recent/blocked sequence IDs to detect retransmission.
* @rx.blocked.seqs: Array of blocked sequence IDs.
* @rx.blocked.offset: Offset indicating where a new ID should be inserted.
* @rtx_timeout: Retransmission timeout subsystem.
* @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper.
* @rtx_timeout.timeout: Timeout interval for retransmission.
* @rtx_timeout.expires: Time specifying when the reaper work is next scheduled.
* @rtx_timeout.reaper: Work performing timeout checks and subsequent actions.
* @ops: Packet layer operations.
*/
struct ssh_ptl {
struct serdev_device *serdev;
unsigned long state;
struct {
spinlock_t lock;
struct list_head head;
} queue;
struct {
spinlock_t lock;
struct list_head head;
atomic_t count;
} pending;
struct {
atomic_t running;
struct task_struct *thread;
struct completion thread_cplt_tx;
struct completion thread_cplt_pkt;
struct wait_queue_head packet_wq;
} tx;
struct {
struct task_struct *thread;
struct wait_queue_head wq;
struct kfifo fifo;
struct sshp_buf buf;
struct {
u16 seqs[8];
u16 offset;
} blocked;
} rx;
struct {
spinlock_t lock;
ktime_t timeout;
ktime_t expires;
struct delayed_work reaper;
} rtx_timeout;
struct ssh_ptl_ops ops;
};
#define __ssam_prcond(func, p, fmt, ...) \
do { \
typeof(p) __p = (p); \
\
if (__p) \
func(__p, fmt, ##__VA_ARGS__); \
} while (0)
#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__)
#define to_ssh_ptl(ptr, member) \
container_of(ptr, struct ssh_ptl, member)
int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev,
struct ssh_ptl_ops *ops);
void ssh_ptl_destroy(struct ssh_ptl *ptl);
/**
* ssh_ptl_get_device() - Get device associated with packet transport layer.
* @ptl: The packet transport layer.
*
* Return: Returns the device on which the given packet transport layer builds
* upon.
*/
static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl)
{
return ptl->serdev ? &ptl->serdev->dev : NULL;
}
int ssh_ptl_tx_start(struct ssh_ptl *ptl);
int ssh_ptl_tx_stop(struct ssh_ptl *ptl);
int ssh_ptl_rx_start(struct ssh_ptl *ptl);
int ssh_ptl_rx_stop(struct ssh_ptl *ptl);
void ssh_ptl_shutdown(struct ssh_ptl *ptl);
int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p);
void ssh_ptl_cancel(struct ssh_packet *p);
int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n);
/**
* ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for
* transfer.
* @ptl: The packet transport layer.
*
* Wakes up the packet transmitter thread, notifying it that the underlying
* transport has more space for data to be transmitted. If the packet
* transport layer has been shut down, calls to this function will be ignored.
*/
static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl)
{
if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state))
return;
complete(&ptl->tx.thread_cplt_tx);
}
void ssh_packet_init(struct ssh_packet *packet, unsigned long type,
u8 priority, const struct ssh_packet_ops *ops);
#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */

View File

@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* SSH message parser.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/compiler.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/surface_aggregator/serial_hub.h>
#include "ssh_parser.h"
/**
* sshp_validate_crc() - Validate a CRC in raw message data.
* @src: The span of data over which the CRC should be computed.
* @crc: The pointer to the expected u16 CRC value.
*
* Computes the CRC of the provided data span (@src), compares it to the CRC
* stored at the given address (@crc), and returns the result of this
* comparison, i.e. %true if equal. This function is intended to run on raw
* input/message data.
*
* Return: Returns %true if the computed CRC matches the stored CRC, %false
* otherwise.
*/
static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc)
{
u16 actual = ssh_crc(src->ptr, src->len);
u16 expected = get_unaligned_le16(crc);
return actual == expected;
}
/**
* sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes.
* @src: The data span to check the start of.
*/
static bool sshp_starts_with_syn(const struct ssam_span *src)
{
return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN;
}
/**
* sshp_find_syn() - Find SSH SYN bytes in the given data span.
* @src: The data span to search in.
* @rem: The span (output) indicating the remaining data, starting with SSH
* SYN bytes, if found.
*
* Search for SSH SYN bytes in the given source span. If found, set the @rem
* span to the remaining data, starting with the first SYN bytes and capped by
* the source span length, and return %true. This function does not copy any
* data, but rather only sets pointers to the respective start addresses and
* length values.
*
* If no SSH SYN bytes could be found, set the @rem span to the zero-length
* span at the end of the source span and return %false.
*
* If partial SSH SYN bytes could be found at the end of the source span, set
* the @rem span to cover these partial SYN bytes, capped by the end of the
* source span, and return %false. This function should then be re-run once
* more data is available.
*
* Return: Returns %true if a complete SSH SYN sequence could be found,
* %false otherwise.
*/
bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem)
{
size_t i;
for (i = 0; i < src->len - 1; i++) {
if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) {
rem->ptr = src->ptr + i;
rem->len = src->len - i;
return true;
}
}
if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) {
rem->ptr = src->ptr + src->len - 1;
rem->len = 1;
return false;
}
rem->ptr = src->ptr + src->len;
rem->len = 0;
return false;
}
/**
* sshp_parse_frame() - Parse SSH frame.
* @dev: The device used for logging.
* @source: The source to parse from.
* @frame: The parsed frame (output).
* @payload: The parsed payload (output).
* @maxlen: The maximum supported message length.
*
* Parses and validates a SSH frame, including its payload, from the given
* source. Sets the provided @frame pointer to the start of the frame and
* writes the limits of the frame payload to the provided @payload span
* pointer.
*
* This function does not copy any data, but rather only validates the message
* data and sets pointers (and length values) to indicate the respective parts.
*
* If no complete SSH frame could be found, the frame pointer will be set to
* the %NULL pointer and the payload span will be set to the null span (start
* pointer %NULL, size zero).
*
* Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if
* the start of the message is invalid, %-EBADMSG if any (frame-header or
* payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than
* the maximum message length specified in the @maxlen parameter.
*/
int sshp_parse_frame(const struct device *dev, const struct ssam_span *source,
struct ssh_frame **frame, struct ssam_span *payload,
size_t maxlen)
{
struct ssam_span sf;
struct ssam_span sp;
/* Initialize output. */
*frame = NULL;
payload->ptr = NULL;
payload->len = 0;
if (!sshp_starts_with_syn(source)) {
dev_warn(dev, "rx: parser: invalid start of frame\n");
return -ENOMSG;
}
/* Check for minimum packet length. */
if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) {
dev_dbg(dev, "rx: parser: not enough data for frame\n");
return 0;
}
/* Pin down frame. */
sf.ptr = source->ptr + sizeof(u16);
sf.len = sizeof(struct ssh_frame);
/* Validate frame CRC. */
if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) {
dev_warn(dev, "rx: parser: invalid frame CRC\n");
return -EBADMSG;
}
/* Ensure packet does not exceed maximum length. */
sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len);
if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) {
dev_warn(dev, "rx: parser: frame too large: %llu bytes\n",
SSH_MESSAGE_LENGTH(sp.len));
return -EMSGSIZE;
}
/* Pin down payload. */
sp.ptr = sf.ptr + sf.len + sizeof(u16);
/* Check for frame + payload length. */
if (source->len < SSH_MESSAGE_LENGTH(sp.len)) {
dev_dbg(dev, "rx: parser: not enough data for payload\n");
return 0;
}
/* Validate payload CRC. */
if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) {
dev_warn(dev, "rx: parser: invalid payload CRC\n");
return -EBADMSG;
}
*frame = (struct ssh_frame *)sf.ptr;
*payload = sp;
dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n",
(*frame)->type, (*frame)->len);
return 0;
}
/**
* sshp_parse_command() - Parse SSH command frame payload.
* @dev: The device used for logging.
* @source: The source to parse from.
* @command: The parsed command (output).
* @command_data: The parsed command data/payload (output).
*
* Parses and validates a SSH command frame payload. Sets the @command pointer
* to the command header and the @command_data span to the command data (i.e.
* payload of the command). This will result in a zero-length span if the
* command does not have any associated data/payload. This function does not
* check the frame-payload-type field, which should be checked by the caller
* before calling this function.
*
* The @source parameter should be the complete frame payload, e.g. returned
* by the sshp_parse_frame() command.
*
* This function does not copy any data, but rather only validates the frame
* payload data and sets pointers (and length values) to indicate the
* respective parts.
*
* Return: Returns zero on success or %-ENOMSG if @source does not represent a
* valid command-type frame payload, i.e. is too short.
*/
int sshp_parse_command(const struct device *dev, const struct ssam_span *source,
struct ssh_command **command,
struct ssam_span *command_data)
{
/* Check for minimum length. */
if (unlikely(source->len < sizeof(struct ssh_command))) {
*command = NULL;
command_data->ptr = NULL;
command_data->len = 0;
dev_err(dev, "rx: parser: command payload is too short\n");
return -ENOMSG;
}
*command = (struct ssh_command *)source->ptr;
command_data->ptr = source->ptr + sizeof(struct ssh_command);
command_data->len = source->len - sizeof(struct ssh_command);
dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n",
(*command)->tc, (*command)->cid);
return 0;
}

View File

@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH message parser.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
#define _SURFACE_AGGREGATOR_SSH_PARSER_H
#include <linux/device.h>
#include <linux/kfifo.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/surface_aggregator/serial_hub.h>
/**
* struct sshp_buf - Parser buffer for SSH messages.
* @ptr: Pointer to the beginning of the buffer.
* @len: Number of bytes used in the buffer.
* @cap: Maximum capacity of the buffer.
*/
struct sshp_buf {
u8 *ptr;
size_t len;
size_t cap;
};
/**
* sshp_buf_init() - Initialize a SSH parser buffer.
* @buf: The buffer to initialize.
* @ptr: The memory backing the buffer.
* @cap: The length of the memory backing the buffer, i.e. its capacity.
*
* Initializes the buffer with the given memory as backing and set its used
* length to zero.
*/
static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap)
{
buf->ptr = ptr;
buf->len = 0;
buf->cap = cap;
}
/**
* sshp_buf_alloc() - Allocate and initialize a SSH parser buffer.
* @buf: The buffer to initialize/allocate to.
* @cap: The desired capacity of the buffer.
* @flags: The flags used for allocating the memory.
*
* Allocates @cap bytes and initializes the provided buffer struct with the
* allocated memory.
*
* Return: Returns zero on success and %-ENOMEM if allocation failed.
*/
static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags)
{
u8 *ptr;
ptr = kzalloc(cap, flags);
if (!ptr)
return -ENOMEM;
sshp_buf_init(buf, ptr, cap);
return 0;
}
/**
* sshp_buf_free() - Free a SSH parser buffer.
* @buf: The buffer to free.
*
* Frees a SSH parser buffer by freeing the memory backing it and then
* resetting its pointer to %NULL and length and capacity to zero. Intended to
* free a buffer previously allocated with sshp_buf_alloc().
*/
static inline void sshp_buf_free(struct sshp_buf *buf)
{
kfree(buf->ptr);
buf->ptr = NULL;
buf->len = 0;
buf->cap = 0;
}
/**
* sshp_buf_drop() - Drop data from the beginning of the buffer.
* @buf: The buffer to drop data from.
* @n: The number of bytes to drop.
*
* Drops the first @n bytes from the buffer. Re-aligns any remaining data to
* the beginning of the buffer.
*/
static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n)
{
memmove(buf->ptr, buf->ptr + n, buf->len - n);
buf->len -= n;
}
/**
* sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer.
* @buf: The buffer to write the data into.
* @fifo: The fifo to read the data from.
*
* Transfers the data contained in the fifo to the buffer, removing it from
* the fifo. This function will try to transfer as much data as possible,
* limited either by the remaining space in the buffer or by the number of
* bytes available in the fifo.
*
* Return: Returns the number of bytes transferred.
*/
static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf,
struct kfifo *fifo)
{
size_t n;
n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len);
buf->len += n;
return n;
}
/**
* sshp_buf_span_from() - Initialize a span from the given buffer and offset.
* @buf: The buffer to create the span from.
* @offset: The offset in the buffer at which the span should start.
* @span: The span to initialize (output).
*
* Initializes the provided span to point to the memory at the given offset in
* the buffer, with the length of the span being capped by the number of bytes
* used in the buffer after the offset (i.e. bytes remaining after the
* offset).
*
* Warning: This function does not validate that @offset is less than or equal
* to the number of bytes used in the buffer or the buffer capacity. This must
* be guaranteed by the caller.
*/
static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset,
struct ssam_span *span)
{
span->ptr = buf->ptr + offset;
span->len = buf->len - offset;
}
bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem);
int sshp_parse_frame(const struct device *dev, const struct ssam_span *source,
struct ssh_frame **frame, struct ssam_span *payload,
size_t maxlen);
int sshp_parse_command(const struct device *dev, const struct ssam_span *source,
struct ssh_command **command,
struct ssam_span *command_data);
#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH request transport layer.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
#include <linux/atomic.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/surface_aggregator/serial_hub.h>
#include <linux/surface_aggregator/controller.h>
#include "ssh_packet_layer.h"
/**
* enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl.
*
* @SSH_RTL_SF_SHUTDOWN_BIT:
* Indicates that the request transport layer has been shut down or is
* being shut down and should not accept any new requests.
*/
enum ssh_rtl_state_flags {
SSH_RTL_SF_SHUTDOWN_BIT,
};
/**
* struct ssh_rtl_ops - Callback operations for request transport layer.
* @handle_event: Function called when a SSH event has been received. The
* specified function takes the request layer, received command
* struct, and corresponding payload as arguments. If the event
* has no payload, the payload span is empty (not %NULL).
*/
struct ssh_rtl_ops {
void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd,
const struct ssam_span *data);
};
/**
* struct ssh_rtl - SSH request transport layer.
* @ptl: Underlying packet transport layer.
* @state: State(-flags) of the transport layer.
* @queue: Request submission queue.
* @queue.lock: Lock for modifying the request submission queue.
* @queue.head: List-head of the request submission queue.
* @pending: Set/list of pending requests.
* @pending.lock: Lock for modifying the request set.
* @pending.head: List-head of the pending set/list.
* @pending.count: Number of currently pending requests.
* @tx: Transmitter subsystem.
* @tx.work: Transmitter work item.
* @rtx_timeout: Retransmission timeout subsystem.
* @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper.
* @rtx_timeout.timeout: Timeout interval for retransmission.
* @rtx_timeout.expires: Time specifying when the reaper work is next scheduled.
* @rtx_timeout.reaper: Work performing timeout checks and subsequent actions.
* @ops: Request layer operations.
*/
struct ssh_rtl {
struct ssh_ptl ptl;
unsigned long state;
struct {
spinlock_t lock;
struct list_head head;
} queue;
struct {
spinlock_t lock;
struct list_head head;
atomic_t count;
} pending;
struct {
struct work_struct work;
} tx;
struct {
spinlock_t lock;
ktime_t timeout;
ktime_t expires;
struct delayed_work reaper;
} rtx_timeout;
struct ssh_rtl_ops ops;
};
#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__)
#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__)
#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__)
#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__)
#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__)
#define to_ssh_rtl(ptr, member) \
container_of(ptr, struct ssh_rtl, member)
/**
* ssh_rtl_get_device() - Get device associated with request transport layer.
* @rtl: The request transport layer.
*
* Return: Returns the device on which the given request transport layer
* builds upon.
*/
static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl)
{
return ssh_ptl_get_device(&rtl->ptl);
}
/**
* ssh_request_rtl() - Get request transport layer associated with request.
* @rqst: The request to get the request transport layer reference for.
*
* Return: Returns the &struct ssh_rtl associated with the given SSH request.
*/
static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst)
{
struct ssh_ptl *ptl;
ptl = READ_ONCE(rqst->packet.ptl);
return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL;
}
int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst);
bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending);
int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev,
const struct ssh_rtl_ops *ops);
int ssh_rtl_start(struct ssh_rtl *rtl);
int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout);
void ssh_rtl_shutdown(struct ssh_rtl *rtl);
void ssh_rtl_destroy(struct ssh_rtl *rtl);
int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags,
const struct ssh_request_ops *ops);
#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */

View File

@ -0,0 +1,824 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Surface System Aggregator Module (SSAM) controller interface.
*
* Main communication interface for the SSAM EC. Provides a controller
* managing access and communication to and from the SSAM EC, as well as main
* communication structures and definitions.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H
#define _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/surface_aggregator/serial_hub.h>
/* -- Main data types and definitions --------------------------------------- */
/**
* enum ssam_event_flags - Flags for enabling/disabling SAM events
* @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame.
*/
enum ssam_event_flags {
SSAM_EVENT_SEQUENCED = BIT(0),
};
/**
* struct ssam_event - SAM event sent from the EC to the host.
* @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_event {
u8 target_category;
u8 target_id;
u8 command_id;
u8 instance_id;
u16 length;
u8 data[];
};
/**
* enum ssam_request_flags - Flags for SAM requests.
*
* @SSAM_REQUEST_HAS_RESPONSE:
* Specifies that the request expects a response. If not set, the request
* will be directly completed after its underlying packet has been
* transmitted. If set, the request transport system waits for a response
* of the request.
*
* @SSAM_REQUEST_UNSEQUENCED:
* Specifies that the request should be transmitted via an unsequenced
* packet. If set, the request must not have a response, meaning that this
* flag and the %SSAM_REQUEST_HAS_RESPONSE flag are mutually exclusive.
*/
enum ssam_request_flags {
SSAM_REQUEST_HAS_RESPONSE = BIT(0),
SSAM_REQUEST_UNSEQUENCED = BIT(1),
};
/**
* struct ssam_request - SAM request description.
* @target_category: Category of the request's target. See &enum ssam_ssh_tc.
* @target_id: ID of the request's target.
* @command_id: Command ID of the request.
* @instance_id: Instance ID of the request's target.
* @flags: Flags for the request. See &enum ssam_request_flags.
* @length: Length of the request payload in bytes.
* @payload: Request payload data.
*
* This struct fully describes a SAM request with payload. It is intended to
* help set up the actual transport struct, e.g. &struct ssam_request_sync,
* and specifically its raw message data via ssam_request_write_data().
*/
struct ssam_request {
u8 target_category;
u8 target_id;
u8 command_id;
u8 instance_id;
u16 flags;
u16 length;
const u8 *payload;
};
/**
* struct ssam_response - Response buffer for SAM request.
* @capacity: Capacity of the buffer, in bytes.
* @length: Length of the actual data stored in the memory pointed to by
* @pointer, in bytes. Set by the transport system.
* @pointer: Pointer to the buffer's memory, storing the response payload data.
*/
struct ssam_response {
size_t capacity;
size_t length;
u8 *pointer;
};
struct ssam_controller;
struct ssam_controller *ssam_get_controller(void);
struct ssam_controller *ssam_client_bind(struct device *client);
int ssam_client_link(struct ssam_controller *ctrl, struct device *client);
struct device *ssam_controller_device(struct ssam_controller *c);
struct ssam_controller *ssam_controller_get(struct ssam_controller *c);
void ssam_controller_put(struct ssam_controller *c);
void ssam_controller_statelock(struct ssam_controller *c);
void ssam_controller_stateunlock(struct ssam_controller *c);
ssize_t ssam_request_write_data(struct ssam_span *buf,
struct ssam_controller *ctrl,
const struct ssam_request *spec);
/* -- Synchronous request interface. ---------------------------------------- */
/**
* struct ssam_request_sync - Synchronous SAM request struct.
* @base: Underlying SSH request.
* @comp: Completion used to signal full completion of the request. After the
* request has been submitted, this struct may only be modified or
* deallocated after the completion has been signaled.
* request has been submitted,
* @resp: Buffer to store the response.
* @status: Status of the request, set after the base request has been
* completed or has failed.
*/
struct ssam_request_sync {
struct ssh_request base;
struct completion comp;
struct ssam_response *resp;
int status;
};
int ssam_request_sync_alloc(size_t payload_len, gfp_t flags,
struct ssam_request_sync **rqst,
struct ssam_span *buffer);
void ssam_request_sync_free(struct ssam_request_sync *rqst);
int ssam_request_sync_init(struct ssam_request_sync *rqst,
enum ssam_request_flags flags);
/**
* ssam_request_sync_set_data - Set message data of a synchronous request.
* @rqst: The request.
* @ptr: Pointer to the request message data.
* @len: Length of the request message data.
*
* Set the request message data of a synchronous request. The provided buffer
* needs to live until the request has been completed.
*/
static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst,
u8 *ptr, size_t len)
{
ssh_request_set_data(&rqst->base, ptr, len);
}
/**
* ssam_request_sync_set_resp - Set response buffer of a synchronous request.
* @rqst: The request.
* @resp: The response buffer.
*
* Sets the response buffer of a synchronous request. This buffer will store
* the response of the request after it has been completed. May be %NULL if no
* response is expected.
*/
static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst,
struct ssam_response *resp)
{
rqst->resp = resp;
}
int ssam_request_sync_submit(struct ssam_controller *ctrl,
struct ssam_request_sync *rqst);
/**
* ssam_request_sync_wait - Wait for completion of a synchronous request.
* @rqst: The request to wait for.
*
* Wait for completion and release of a synchronous request. After this
* function terminates, the request is guaranteed to have left the transport
* system. After successful submission of a request, this function must be
* called before accessing the response of the request, freeing the request,
* or freeing any of the buffers associated with the request.
*
* This function must not be called if the request has not been submitted yet
* and may lead to a deadlock/infinite wait if a subsequent request submission
* fails in that case, due to the completion never triggering.
*
* Return: Returns the status of the given request, which is set on completion
* of the packet. This value is zero on success and negative on failure.
*/
static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst)
{
wait_for_completion(&rqst->comp);
return rqst->status;
}
int ssam_request_sync(struct ssam_controller *ctrl,
const struct ssam_request *spec,
struct ssam_response *rsp);
int ssam_request_sync_with_buffer(struct ssam_controller *ctrl,
const struct ssam_request *spec,
struct ssam_response *rsp,
struct ssam_span *buf);
/**
* ssam_request_sync_onstack - Execute a synchronous request on the stack.
* @ctrl: The controller via which the request is submitted.
* @rqst: The request specification.
* @rsp: The response buffer.
* @payload_len: The (maximum) request payload length.
*
* Allocates a synchronous request with specified payload length on the stack,
* fully initializes it via the provided request specification, submits it,
* and finally waits for its completion before returning its status. This
* helper macro essentially allocates the request message buffer on the stack
* and then calls ssam_request_sync_with_buffer().
*
* Note: The @payload_len parameter specifies the maximum payload length, used
* for buffer allocation. The actual payload length may be smaller.
*
* Return: Returns the status of the request or any failure during setup, i.e.
* zero on success and a negative value on failure.
*/
#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \
({ \
u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \
struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \
\
ssam_request_sync_with_buffer(ctrl, rqst, rsp, &__buf); \
})
/**
* __ssam_retry - Retry request in case of I/O errors or timeouts.
* @request: The request function to execute. Must return an integer.
* @n: Number of tries.
* @args: Arguments for the request function.
*
* Executes the given request function, i.e. calls @request. In case the
* request returns %-EREMOTEIO (indicates I/O error) or %-ETIMEDOUT (request
* or underlying packet timed out), @request will be re-executed again, up to
* @n times in total.
*
* Return: Returns the return value of the last execution of @request.
*/
#define __ssam_retry(request, n, args...) \
({ \
int __i, __s = 0; \
\
for (__i = (n); __i > 0; __i--) { \
__s = request(args); \
if (__s != -ETIMEDOUT && __s != -EREMOTEIO) \
break; \
} \
__s; \
})
/**
* ssam_retry - Retry request in case of I/O errors or timeouts up to three
* times in total.
* @request: The request function to execute. Must return an integer.
* @args: Arguments for the request function.
*
* Executes the given request function, i.e. calls @request. In case the
* request returns %-EREMOTEIO (indicates I/O error) or -%ETIMEDOUT (request
* or underlying packet timed out), @request will be re-executed again, up to
* three times in total.
*
* See __ssam_retry() for a more generic macro for this purpose.
*
* Return: Returns the return value of the last execution of @request.
*/
#define ssam_retry(request, args...) \
__ssam_retry(request, 3, args)
/**
* struct ssam_request_spec - Blue-print specification of SAM request.
* @target_category: Category of the request's target. See &enum ssam_ssh_tc.
* @target_id: ID of the request's target.
* @command_id: Command ID of the request.
* @instance_id: Instance ID of the request's target.
* @flags: Flags for the request. See &enum ssam_request_flags.
*
* Blue-print specification for a SAM request. This struct describes the
* unique static parameters of a request (i.e. type) without specifying any of
* its instance-specific data (e.g. payload). It is intended to be used as base
* for defining simple request functions via the
* ``SSAM_DEFINE_SYNC_REQUEST_x()`` family of macros.
*/
struct ssam_request_spec {
u8 target_category;
u8 target_id;
u8 command_id;
u8 instance_id;
u8 flags;
};
/**
* struct ssam_request_spec_md - Blue-print specification for multi-device SAM
* request.
* @target_category: Category of the request's target. See &enum ssam_ssh_tc.
* @command_id: Command ID of the request.
* @flags: Flags for the request. See &enum ssam_request_flags.
*
* Blue-print specification for a multi-device SAM request, i.e. a request
* that is applicable to multiple device instances, described by their
* individual target and instance IDs. This struct describes the unique static
* parameters of a request (i.e. type) without specifying any of its
* instance-specific data (e.g. payload) and without specifying any of its
* device specific IDs (i.e. target and instance ID). It is intended to be
* used as base for defining simple multi-device request functions via the
* ``SSAM_DEFINE_SYNC_REQUEST_MD_x()`` and ``SSAM_DEFINE_SYNC_REQUEST_CL_x()``
* families of macros.
*/
struct ssam_request_spec_md {
u8 target_category;
u8 command_id;
u8 flags;
};
/**
* SSAM_DEFINE_SYNC_REQUEST_N() - Define synchronous SAM request function
* with neither argument nor return value.
* @name: Name of the generated function.
* @spec: Specification (&struct ssam_request_spec) defining the request.
*
* Defines a function executing the synchronous SAM request specified by
* @spec, with the request having neither argument nor return value. The
* generated function takes care of setting up the request struct and buffer
* allocation, as well as execution of the request itself, returning once the
* request has been fully completed. The required transport buffer will be
* allocated on the stack.
*
* The generated function is defined as ``int name(struct ssam_controller
* *ctrl)``, returning the status of the request, which is zero on success and
* negative on failure. The ``ctrl`` parameter is the controller via which the
* request is being sent.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \
int name(struct ssam_controller *ctrl) \
{ \
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
struct ssam_request rqst; \
\
rqst.target_category = s.target_category; \
rqst.target_id = s.target_id; \
rqst.command_id = s.command_id; \
rqst.instance_id = s.instance_id; \
rqst.flags = s.flags; \
rqst.length = 0; \
rqst.payload = NULL; \
\
return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \
}
/**
* SSAM_DEFINE_SYNC_REQUEST_W() - Define synchronous SAM request function with
* argument.
* @name: Name of the generated function.
* @atype: Type of the request's argument.
* @spec: Specification (&struct ssam_request_spec) defining the request.
*
* Defines a function executing the synchronous SAM request specified by
* @spec, with the request taking an argument of type @atype and having no
* return value. The generated function takes care of setting up the request
* struct, buffer allocation, as well as execution of the request itself,
* returning once the request has been fully completed. The required transport
* buffer will be allocated on the stack.
*
* The generated function is defined as ``int name(struct ssam_controller
* *ctrl, const atype *arg)``, returning the status of the request, which is
* zero on success and negative on failure. The ``ctrl`` parameter is the
* controller via which the request is sent. The request argument is specified
* via the ``arg`` pointer.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \
int name(struct ssam_controller *ctrl, const atype *arg) \
{ \
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
struct ssam_request rqst; \
\
rqst.target_category = s.target_category; \
rqst.target_id = s.target_id; \
rqst.command_id = s.command_id; \
rqst.instance_id = s.instance_id; \
rqst.flags = s.flags; \
rqst.length = sizeof(atype); \
rqst.payload = (u8 *)arg; \
\
return ssam_request_sync_onstack(ctrl, &rqst, NULL, \
sizeof(atype)); \
}
/**
* SSAM_DEFINE_SYNC_REQUEST_R() - Define synchronous SAM request function with
* return value.
* @name: Name of the generated function.
* @rtype: Type of the request's return value.
* @spec: Specification (&struct ssam_request_spec) defining the request.
*
* Defines a function executing the synchronous SAM request specified by
* @spec, with the request taking no argument but having a return value of
* type @rtype. The generated function takes care of setting up the request
* and response structs, buffer allocation, as well as execution of the
* request itself, returning once the request has been fully completed. The
* required transport buffer will be allocated on the stack.
*
* The generated function is defined as ``int name(struct ssam_controller
* *ctrl, rtype *ret)``, returning the status of the request, which is zero on
* success and negative on failure. The ``ctrl`` parameter is the controller
* via which the request is sent. The request's return value is written to the
* memory pointed to by the ``ret`` parameter.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \
int name(struct ssam_controller *ctrl, rtype *ret) \
{ \
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
struct ssam_request rqst; \
struct ssam_response rsp; \
int status; \
\
rqst.target_category = s.target_category; \
rqst.target_id = s.target_id; \
rqst.command_id = s.command_id; \
rqst.instance_id = s.instance_id; \
rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \
rqst.length = 0; \
rqst.payload = NULL; \
\
rsp.capacity = sizeof(rtype); \
rsp.length = 0; \
rsp.pointer = (u8 *)ret; \
\
status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \
if (status) \
return status; \
\
if (rsp.length != sizeof(rtype)) { \
struct device *dev = ssam_controller_device(ctrl); \
dev_err(dev, \
"rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \
sizeof(rtype), rsp.length, rqst.target_category,\
rqst.command_id); \
return -EIO; \
} \
\
return 0; \
}
/**
* SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM
* request function with neither argument nor return value.
* @name: Name of the generated function.
* @spec: Specification (&struct ssam_request_spec_md) defining the request.
*
* Defines a function executing the synchronous SAM request specified by
* @spec, with the request having neither argument nor return value. Device
* specifying parameters are not hard-coded, but instead must be provided to
* the function. The generated function takes care of setting up the request
* struct, buffer allocation, as well as execution of the request itself,
* returning once the request has been fully completed. The required transport
* buffer will be allocated on the stack.
*
* The generated function is defined as ``int name(struct ssam_controller
* *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is
* zero on success and negative on failure. The ``ctrl`` parameter is the
* controller via which the request is sent, ``tid`` the target ID for the
* request, and ``iid`` the instance ID.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \
int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
{ \
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
struct ssam_request rqst; \
\
rqst.target_category = s.target_category; \
rqst.target_id = tid; \
rqst.command_id = s.command_id; \
rqst.instance_id = iid; \
rqst.flags = s.flags; \
rqst.length = 0; \
rqst.payload = NULL; \
\
return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \
}
/**
* SSAM_DEFINE_SYNC_REQUEST_MD_W() - Define synchronous multi-device SAM
* request function with argument.
* @name: Name of the generated function.
* @atype: Type of the request's argument.
* @spec: Specification (&struct ssam_request_spec_md) defining the request.
*
* Defines a function executing the synchronous SAM request specified by
* @spec, with the request taking an argument of type @atype and having no
* return value. Device specifying parameters are not hard-coded, but instead
* must be provided to the function. The generated function takes care of
* setting up the request struct, buffer allocation, as well as execution of
* the request itself, returning once the request has been fully completed.
* The required transport buffer will be allocated on the stack.
*
* The generated function is defined as ``int name(struct ssam_controller
* *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the
* request, which is zero on success and negative on failure. The ``ctrl``
* parameter is the controller via which the request is sent, ``tid`` the
* target ID for the request, and ``iid`` the instance ID. The request argument
* is specified via the ``arg`` pointer.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \
int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\
{ \
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
struct ssam_request rqst; \
\
rqst.target_category = s.target_category; \
rqst.target_id = tid; \
rqst.command_id = s.command_id; \
rqst.instance_id = iid; \
rqst.flags = s.flags; \
rqst.length = sizeof(atype); \
rqst.payload = (u8 *)arg; \
\
return ssam_request_sync_onstack(ctrl, &rqst, NULL, \
sizeof(atype)); \
}
/**
* SSAM_DEFINE_SYNC_REQUEST_MD_R() - Define synchronous multi-device SAM
* request function with return value.
* @name: Name of the generated function.
* @rtype: Type of the request's return value.
* @spec: Specification (&struct ssam_request_spec_md) defining the request.
*
* Defines a function executing the synchronous SAM request specified by
* @spec, with the request taking no argument but having a return value of
* type @rtype. Device specifying parameters are not hard-coded, but instead
* must be provided to the function. The generated function takes care of
* setting up the request and response structs, buffer allocation, as well as
* execution of the request itself, returning once the request has been fully
* completed. The required transport buffer will be allocated on the stack.
*
* The generated function is defined as ``int name(struct ssam_controller
* *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request,
* which is zero on success and negative on failure. The ``ctrl`` parameter is
* the controller via which the request is sent, ``tid`` the target ID for the
* request, and ``iid`` the instance ID. The request's return value is written
* to the memory pointed to by the ``ret`` parameter.
*
* Refer to ssam_request_sync_onstack() for more details on the behavior of
* the generated function.
*/
#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \
int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
{ \
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
struct ssam_request rqst; \
struct ssam_response rsp; \
int status; \
\
rqst.target_category = s.target_category; \
rqst.target_id = tid; \
rqst.command_id = s.command_id; \
rqst.instance_id = iid; \
rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \
rqst.length = 0; \
rqst.payload = NULL; \
\
rsp.capacity = sizeof(rtype); \
rsp.length = 0; \
rsp.pointer = (u8 *)ret; \
\
status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \
if (status) \
return status; \
\
if (rsp.length != sizeof(rtype)) { \
struct device *dev = ssam_controller_device(ctrl); \
dev_err(dev, \
"rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \
sizeof(rtype), rsp.length, rqst.target_category,\
rqst.command_id); \
return -EIO; \
} \
\
return 0; \
}
/* -- Event notifier/callbacks. --------------------------------------------- */
#define SSAM_NOTIF_STATE_SHIFT 2
#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1)
/**
* enum ssam_notif_flags - Flags used in return values from SSAM notifier
* callback functions.
*
* @SSAM_NOTIF_HANDLED:
* Indicates that the notification has been handled. This flag should be
* set by the handler if the handler can act/has acted upon the event
* provided to it. This flag should not be set if the handler is not a
* primary handler intended for the provided event.
*
* If this flag has not been set by any handler after the notifier chain
* has been traversed, a warning will be emitted, stating that the event
* has not been handled.
*
* @SSAM_NOTIF_STOP:
* Indicates that the notifier traversal should stop. If this flag is
* returned from a notifier callback, notifier chain traversal will
* immediately stop and any remaining notifiers will not be called. This
* flag is automatically set when ssam_notifier_from_errno() is called
* with a negative error value.
*/
enum ssam_notif_flags {
SSAM_NOTIF_HANDLED = BIT(0),
SSAM_NOTIF_STOP = BIT(1),
};
struct ssam_event_notifier;
typedef u32 (*ssam_notifier_fn_t)(struct ssam_event_notifier *nf,
const struct ssam_event *event);
/**
* struct ssam_notifier_block - Base notifier block for SSAM event
* notifications.
* @node: The node for the list of notifiers.
* @fn: The callback function of this notifier. This function takes the
* respective notifier block and event as input and should return
* a notifier value, which can either be obtained from the flags
* provided in &enum ssam_notif_flags, converted from a standard
* error value via ssam_notifier_from_errno(), or a combination of
* both (e.g. ``ssam_notifier_from_errno(e) | SSAM_NOTIF_HANDLED``).
* @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.
*/
struct ssam_notifier_block {
struct list_head node;
ssam_notifier_fn_t fn;
int priority;
};
/**
* ssam_notifier_from_errno() - Convert standard error value to notifier
* return code.
* @err: The error code to convert, must be negative (in case of failure) or
* zero (in case of success).
*
* Return: Returns the notifier return value obtained by converting the
* specified @err value. In case @err is negative, the %SSAM_NOTIF_STOP flag
* will be set, causing notifier call chain traversal to abort.
*/
static inline u32 ssam_notifier_from_errno(int err)
{
if (WARN_ON(err > 0) || err == 0)
return 0;
else
return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP;
}
/**
* ssam_notifier_to_errno() - Convert notifier return code to standard error
* value.
* @ret: The notifier return value to convert.
*
* Return: Returns the negative error value encoded in @ret or zero if @ret
* indicates success.
*/
static inline int ssam_notifier_to_errno(u32 ret)
{
return -(ret >> SSAM_NOTIF_STATE_SHIFT);
}
/* -- Event/notification registry. ------------------------------------------ */
/**
* struct ssam_event_registry - Registry specification used for enabling events.
* @target_category: Target category for the event registry requests.
* @target_id: Target ID for the event registry requests.
* @cid_enable: Command ID for the event-enable request.
* @cid_disable: Command ID for the event-disable request.
*
* This struct describes a SAM event registry via the minimal collection of
* SAM IDs specifying the requests to use for enabling and disabling an event.
* The individual event to be enabled/disabled itself is specified via &struct
* ssam_event_id.
*/
struct ssam_event_registry {
u8 target_category;
u8 target_id;
u8 cid_enable;
u8 cid_disable;
};
/**
* struct ssam_event_id - Unique event ID used for enabling events.
* @target_category: Target category of the event source.
* @instance: Instance ID of the event source.
*
* This struct specifies the event to be enabled/disabled via an externally
* provided registry. It does not specify the registry to be used itself, this
* is done via &struct ssam_event_registry.
*/
struct ssam_event_id {
u8 target_category;
u8 instance;
};
/**
* enum ssam_event_mask - Flags specifying how events are matched to notifiers.
*
* @SSAM_EVENT_MASK_NONE:
* Run the callback for any event with matching target category. Do not
* do any additional filtering.
*
* @SSAM_EVENT_MASK_TARGET:
* In addition to filtering by target category, only execute the notifier
* callback for events with a target ID matching to the one of the
* registry used for enabling/disabling the event.
*
* @SSAM_EVENT_MASK_INSTANCE:
* In addition to filtering by target category, only execute the notifier
* callback for events with an instance ID matching to the instance ID
* used when enabling the event.
*
* @SSAM_EVENT_MASK_STRICT:
* Do all the filtering above.
*/
enum ssam_event_mask {
SSAM_EVENT_MASK_TARGET = BIT(0),
SSAM_EVENT_MASK_INSTANCE = BIT(1),
SSAM_EVENT_MASK_NONE = 0,
SSAM_EVENT_MASK_STRICT =
SSAM_EVENT_MASK_TARGET
| SSAM_EVENT_MASK_INSTANCE,
};
/**
* SSAM_EVENT_REGISTRY() - Define a new event registry.
* @tc: Target category for the event registry requests.
* @tid: Target ID for the event registry requests.
* @cid_en: Command ID for the event-enable request.
* @cid_dis: Command ID for the event-disable request.
*
* Return: Returns the &struct ssam_event_registry specified by the given
* parameters.
*/
#define SSAM_EVENT_REGISTRY(tc, tid, cid_en, cid_dis) \
((struct ssam_event_registry) { \
.target_category = (tc), \
.target_id = (tid), \
.cid_enable = (cid_en), \
.cid_disable = (cid_dis), \
})
#define SSAM_EVENT_REGISTRY_SAM \
SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c)
#define SSAM_EVENT_REGISTRY_KIP \
SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28)
#define SSAM_EVENT_REGISTRY_REG \
SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02)
/**
* struct ssam_event_notifier - Notifier block for SSAM events.
* @base: The base notifier block with callback function and priority.
* @event: The event for which this block will receive notifications.
* @event.reg: Registry via which the event will be enabled/disabled.
* @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.
*/
struct ssam_event_notifier {
struct ssam_notifier_block base;
struct {
struct ssam_event_registry reg;
struct ssam_event_id id;
enum ssam_event_mask mask;
u8 flags;
} event;
};
int ssam_notifier_register(struct ssam_controller *ctrl,
struct ssam_event_notifier *n);
int ssam_notifier_unregister(struct ssam_controller *ctrl,
struct ssam_event_notifier *n);
#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */

View File

@ -0,0 +1,672 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Surface Serial Hub (SSH) protocol and communication interface.
*
* Lower-level communication layers and SSH protocol definitions for the
* 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 <luzmaximilian@gmail.com>
*/
#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H
#define _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H
#include <linux/crc-ccitt.h>
#include <linux/kref.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/types.h>
/* -- Data structures for SAM-over-SSH communication. ----------------------- */
/**
* enum ssh_frame_type - Frame types for SSH frames.
*
* @SSH_FRAME_TYPE_DATA_SEQ:
* Indicates a data frame, followed by a payload with the length specified
* in the ``struct ssh_frame.len`` field. This frame is sequenced, meaning
* that an ACK is required.
*
* @SSH_FRAME_TYPE_DATA_NSQ:
* Same as %SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, meaning that the
* message does not have to be ACKed.
*
* @SSH_FRAME_TYPE_ACK:
* Indicates an ACK message.
*
* @SSH_FRAME_TYPE_NAK:
* Indicates an error response for previously sent frame. In general, this
* means that the frame and/or payload is malformed, e.g. a CRC is wrong.
* For command-type payloads, this can also mean that the command is
* invalid.
*/
enum ssh_frame_type {
SSH_FRAME_TYPE_DATA_SEQ = 0x80,
SSH_FRAME_TYPE_DATA_NSQ = 0x00,
SSH_FRAME_TYPE_ACK = 0x40,
SSH_FRAME_TYPE_NAK = 0x04,
};
/**
* struct ssh_frame - SSH communication frame.
* @type: The type of the frame. See &enum ssh_frame_type.
* @len: The length of the frame payload directly following the CRC for this
* frame. Does not include the final CRC for that payload.
* @seq: The sequence number for this message/exchange.
*/
struct ssh_frame {
u8 type;
__le16 len;
u8 seq;
} __packed;
static_assert(sizeof(struct ssh_frame) == 4);
/*
* SSH_FRAME_MAX_PAYLOAD_SIZE - Maximum SSH frame payload length in bytes.
*
* This is the physical maximum length of the protocol. Implementations may
* set a more constrained limit.
*/
#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX
/**
* enum ssh_payload_type - Type indicator for the SSH payload.
* @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command
* payload.
*/
enum ssh_payload_type {
SSH_PLD_TYPE_CMD = 0x80,
};
/**
* struct ssh_command - Payload of a command-type frame.
* @type: The type of the payload. See &enum ssh_payload_type. Should be
* SSH_PLD_TYPE_CMD for this struct.
* @tc: Command target category.
* @tid_out: Output target ID. Should be zero if this an incoming (EC to host)
* message.
* @tid_in: Input target ID. Should be zero if this is an outgoing (host to
* EC) message.
* @iid: Instance ID.
* @rqid: Request ID. Used to match requests with responses and differentiate
* between responses and events.
* @cid: Command ID.
*/
struct ssh_command {
u8 type;
u8 tc;
u8 tid_out;
u8 tid_in;
u8 iid;
__le16 rqid;
u8 cid;
} __packed;
static_assert(sizeof(struct ssh_command) == 8);
/*
* SSH_COMMAND_MAX_PAYLOAD_SIZE - Maximum SSH command payload length in bytes.
*
* This is the physical maximum length of the protocol. Implementations may
* set a more constrained limit.
*/
#define SSH_COMMAND_MAX_PAYLOAD_SIZE \
(SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command))
/*
* SSH_MSG_LEN_BASE - Base-length of a SSH message.
*
* This is the minimum number of bytes required to form a message. The actual
* message length is SSH_MSG_LEN_BASE plus the length of the frame payload.
*/
#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16))
/*
* SSH_MSG_LEN_CTRL - Length of a SSH control message.
*
* This is the length of a SSH control message, which is equal to a SSH
* message without any payload.
*/
#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE
/**
* SSH_MESSAGE_LENGTH() - Compute length of SSH message.
* @payload_size: Length of the payload inside the SSH frame.
*
* Return: Returns the length of a SSH message with payload of specified size.
*/
#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + (payload_size))
/**
* SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message.
* @payload_size: Length of the command payload.
*
* Return: Returns the length of a SSH command message with command payload of
* specified size.
*/
#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \
SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + (payload_size))
/**
* SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in
* frame.
* @field: The field for which the offset should be computed.
*
* Return: Returns the offset of the specified &struct ssh_frame field in the
* raw SSH message data as. Takes SYN bytes (u16) preceding the frame into
* account.
*/
#define SSH_MSGOFFSET_FRAME(field) \
(sizeof(u16) + offsetof(struct ssh_frame, field))
/**
* SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field
* in command.
* @field: The field for which the offset should be computed.
*
* Return: Returns the offset of the specified &struct ssh_command field in
* the raw SSH message data. Takes SYN bytes (u16) preceding the frame and the
* frame CRC (u16) between frame and command into account.
*/
#define SSH_MSGOFFSET_COMMAND(field) \
(2ull * sizeof(u16) + sizeof(struct ssh_frame) \
+ offsetof(struct ssh_command, field))
/*
* SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16.
*/
#define SSH_MSG_SYN ((u16)0x55aa)
/**
* ssh_crc() - Compute CRC for SSH messages.
* @buf: The pointer pointing to the data for which the CRC should be computed.
* @len: The length of the data for which the CRC should be computed.
*
* Return: Returns the CRC computed on the provided data, as used for SSH
* messages.
*/
static inline u16 ssh_crc(const u8 *buf, size_t len)
{
return crc_ccitt_false(0xffff, buf, len);
}
/*
* SSH_NUM_EVENTS - The number of reserved event IDs.
*
* The number of reserved event IDs, used for registering an SSH event
* handler. Valid event IDs are numbers below or equal to this value, with
* exception of zero, which is not an event ID. Thus, this is also the
* absolute maximum number of event handlers that can be registered.
*/
#define SSH_NUM_EVENTS 34
/*
* SSH_NUM_TARGETS - The number of communication targets used in the protocol.
*/
#define SSH_NUM_TARGETS 2
/**
* ssh_rqid_next_valid() - Return the next valid request ID.
* @rqid: The current request ID.
*
* Return: Returns the next valid request ID, following the current request ID
* provided to this function. This function skips any request IDs reserved for
* events.
*/
static inline u16 ssh_rqid_next_valid(u16 rqid)
{
return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u;
}
/**
* ssh_rqid_to_event() - Convert request ID to its corresponding event ID.
* @rqid: The request ID to convert.
*/
static inline u16 ssh_rqid_to_event(u16 rqid)
{
return rqid - 1u;
}
/**
* ssh_rqid_is_event() - Check if given request ID is a valid event ID.
* @rqid: The request ID to check.
*/
static inline bool ssh_rqid_is_event(u16 rqid)
{
return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS;
}
/**
* ssh_tc_to_rqid() - Convert target category to its corresponding request ID.
* @tc: The target category to convert.
*/
static inline u16 ssh_tc_to_rqid(u8 tc)
{
return tc;
}
/**
* ssh_tid_to_index() - Convert target ID to its corresponding target index.
* @tid: The target ID to convert.
*/
static inline u8 ssh_tid_to_index(u8 tid)
{
return tid - 1u;
}
/**
* ssh_tid_is_valid() - Check if target ID is valid/supported.
* @tid: The target ID to check.
*/
static inline bool ssh_tid_is_valid(u8 tid)
{
return ssh_tid_to_index(tid) < SSH_NUM_TARGETS;
}
/**
* struct ssam_span - Reference to a buffer region.
* @ptr: Pointer to the buffer region.
* @len: Length of the buffer region.
*
* A reference to a (non-owned) buffer segment, consisting of pointer and
* length. Use of this struct indicates non-owned data, i.e. data of which the
* life-time is managed (i.e. it is allocated/freed) via another pointer.
*/
struct ssam_span {
u8 *ptr;
size_t len;
};
/*
* Known SSH/EC target categories.
*
* List of currently known target category values; "Known" as in we know they
* exist and are valid on at least some device/model. Detailed functionality
* or the full category name is only known for some of these categories and
* is detailed in the respective comment below.
*
* These values and abbreviations have been extracted from strings inside the
* Windows driver.
*/
enum ssam_ssh_tc {
/* Category 0x00 is invalid for EC use. */
SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */
SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */
SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */
SSAM_SSH_TC_PMC = 0x04,
SSAM_SSH_TC_FAN = 0x05,
SSAM_SSH_TC_PoM = 0x06,
SSAM_SSH_TC_DBG = 0x07,
SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */
SSAM_SSH_TC_FWU = 0x09,
SSAM_SSH_TC_UNI = 0x0a,
SSAM_SSH_TC_LPC = 0x0b,
SSAM_SSH_TC_TCL = 0x0c,
SSAM_SSH_TC_SFL = 0x0d,
SSAM_SSH_TC_KIP = 0x0e,
SSAM_SSH_TC_EXT = 0x0f,
SSAM_SSH_TC_BLD = 0x10,
SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */
SSAM_SSH_TC_SEN = 0x12,
SSAM_SSH_TC_SRQ = 0x13,
SSAM_SSH_TC_MCU = 0x14,
SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */
SSAM_SSH_TC_TCH = 0x16,
SSAM_SSH_TC_BKL = 0x17,
SSAM_SSH_TC_TAM = 0x18,
SSAM_SSH_TC_ACC = 0x19,
SSAM_SSH_TC_UFI = 0x1a,
SSAM_SSH_TC_USC = 0x1b,
SSAM_SSH_TC_PEN = 0x1c,
SSAM_SSH_TC_VID = 0x1d,
SSAM_SSH_TC_AUD = 0x1e,
SSAM_SSH_TC_SMC = 0x1f,
SSAM_SSH_TC_KPD = 0x20,
SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */
};
/* -- Packet transport layer (ptl). ----------------------------------------- */
/**
* enum ssh_packet_base_priority - Base priorities for &struct ssh_packet.
* @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets.
* @SSH_PACKET_PRIORITY_DATA: Base priority for normal data packets.
* @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets.
* @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets.
*/
enum ssh_packet_base_priority {
SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */
SSH_PACKET_PRIORITY_DATA = 0,
SSH_PACKET_PRIORITY_NAK = 1,
SSH_PACKET_PRIORITY_ACK = 2,
};
/*
* Same as SSH_PACKET_PRIORITY() below, only with actual values.
*/
#define __SSH_PACKET_PRIORITY(base, try) \
(((base) << 4) | ((try) & 0x0f))
/**
* SSH_PACKET_PRIORITY() - Compute packet priority from base priority and
* number of tries.
* @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g.
* ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``.
* @try: The number of tries (must be less than 16).
*
* Compute the combined packet priority. The combined priority is dominated by
* the base priority, whereas the number of (re-)tries decides the precedence
* of packets with the same base priority, giving higher priority to packets
* that already have more tries.
*
* Return: Returns the computed priority as value fitting inside a &u8. A
* higher number means a higher priority.
*/
#define SSH_PACKET_PRIORITY(base, try) \
__SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try))
/**
* ssh_packet_priority_get_try() - Get number of tries from packet priority.
* @priority: The packet priority.
*
* Return: Returns the number of tries encoded in the specified packet
* priority.
*/
static inline u8 ssh_packet_priority_get_try(u8 priority)
{
return priority & 0x0f;
}
/**
* ssh_packet_priority_get_base - Get base priority from packet priority.
* @priority: The packet priority.
*
* Return: Returns the base priority encoded in the given packet priority.
*/
static inline u8 ssh_packet_priority_get_base(u8 priority)
{
return (priority & 0xf0) >> 4;
}
enum ssh_packet_flags {
/* state flags */
SSH_PACKET_SF_LOCKED_BIT,
SSH_PACKET_SF_QUEUED_BIT,
SSH_PACKET_SF_PENDING_BIT,
SSH_PACKET_SF_TRANSMITTING_BIT,
SSH_PACKET_SF_TRANSMITTED_BIT,
SSH_PACKET_SF_ACKED_BIT,
SSH_PACKET_SF_CANCELED_BIT,
SSH_PACKET_SF_COMPLETED_BIT,
/* type flags */
SSH_PACKET_TY_FLUSH_BIT,
SSH_PACKET_TY_SEQUENCED_BIT,
SSH_PACKET_TY_BLOCKING_BIT,
/* mask for state flags */
SSH_PACKET_FLAGS_SF_MASK =
BIT(SSH_PACKET_SF_LOCKED_BIT)
| BIT(SSH_PACKET_SF_QUEUED_BIT)
| BIT(SSH_PACKET_SF_PENDING_BIT)
| BIT(SSH_PACKET_SF_TRANSMITTING_BIT)
| BIT(SSH_PACKET_SF_TRANSMITTED_BIT)
| BIT(SSH_PACKET_SF_ACKED_BIT)
| BIT(SSH_PACKET_SF_CANCELED_BIT)
| BIT(SSH_PACKET_SF_COMPLETED_BIT),
/* mask for type flags */
SSH_PACKET_FLAGS_TY_MASK =
BIT(SSH_PACKET_TY_FLUSH_BIT)
| BIT(SSH_PACKET_TY_SEQUENCED_BIT)
| BIT(SSH_PACKET_TY_BLOCKING_BIT),
};
struct ssh_ptl;
struct ssh_packet;
/**
* struct ssh_packet_ops - Callback operations for a SSH packet.
* @release: Function called when the packet reference count reaches zero.
* This callback must be relied upon to ensure that the packet has
* left the transport system(s).
* @complete: Function called when the packet is completed, either with
* success or failure. In case of failure, the reason for the
* failure is indicated by the value of the provided status code
* argument. This value will be zero in case of success. Note that
* a call to this callback does not guarantee that the packet is
* not in use by the transport system any more.
*/
struct ssh_packet_ops {
void (*release)(struct ssh_packet *p);
void (*complete)(struct ssh_packet *p, int status);
};
/**
* struct ssh_packet - SSH transport packet.
* @ptl: Pointer to the packet transport layer. May be %NULL if the packet
* (or enclosing request) has not been submitted yet.
* @refcnt: Reference count of the packet.
* @priority: Priority of the packet. Must be computed via
* SSH_PACKET_PRIORITY(). Must only be accessed while holding the
* queue lock after first submission.
* @data: Raw message data.
* @data.len: Length of the raw message data.
* @data.ptr: Pointer to the raw message data buffer.
* @state: State and type flags describing current packet state (dynamic)
* and type (static). See &enum ssh_packet_flags for possible
* options.
* @timestamp: Timestamp specifying when the latest transmission of a
* currently pending packet has been started. May be %KTIME_MAX
* before or in-between transmission attempts. Used for the packet
* timeout implementation. Must only be accessed while holding the
* pending lock after first submission.
* @queue_node: The list node for the packet queue.
* @pending_node: The list node for the set of pending packets.
* @ops: Packet operations.
*/
struct ssh_packet {
struct ssh_ptl *ptl;
struct kref refcnt;
u8 priority;
struct {
size_t len;
u8 *ptr;
} data;
unsigned long state;
ktime_t timestamp;
struct list_head queue_node;
struct list_head pending_node;
const struct ssh_packet_ops *ops;
};
struct ssh_packet *ssh_packet_get(struct ssh_packet *p);
void ssh_packet_put(struct ssh_packet *p);
/**
* ssh_packet_set_data() - Set raw message data of packet.
* @p: The packet for which the message data should be set.
* @ptr: Pointer to the memory holding the message data.
* @len: Length of the message data.
*
* Sets the raw message data buffer of the packet to the provided memory. The
* memory is not copied. Instead, the caller is responsible for management
* (i.e. allocation and deallocation) of the memory. The caller must ensure
* that the provided memory is valid and contains a valid SSH message,
* starting from the time of submission of the packet until the ``release``
* callback has been called. During this time, the memory may not be altered
* in any way.
*/
static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len)
{
p->data.ptr = ptr;
p->data.len = len;
}
/* -- Request transport layer (rtl). ---------------------------------------- */
enum ssh_request_flags {
/* state flags */
SSH_REQUEST_SF_LOCKED_BIT,
SSH_REQUEST_SF_QUEUED_BIT,
SSH_REQUEST_SF_PENDING_BIT,
SSH_REQUEST_SF_TRANSMITTING_BIT,
SSH_REQUEST_SF_TRANSMITTED_BIT,
SSH_REQUEST_SF_RSPRCVD_BIT,
SSH_REQUEST_SF_CANCELED_BIT,
SSH_REQUEST_SF_COMPLETED_BIT,
/* type flags */
SSH_REQUEST_TY_FLUSH_BIT,
SSH_REQUEST_TY_HAS_RESPONSE_BIT,
/* mask for state flags */
SSH_REQUEST_FLAGS_SF_MASK =
BIT(SSH_REQUEST_SF_LOCKED_BIT)
| BIT(SSH_REQUEST_SF_QUEUED_BIT)
| BIT(SSH_REQUEST_SF_PENDING_BIT)
| BIT(SSH_REQUEST_SF_TRANSMITTING_BIT)
| BIT(SSH_REQUEST_SF_TRANSMITTED_BIT)
| BIT(SSH_REQUEST_SF_RSPRCVD_BIT)
| BIT(SSH_REQUEST_SF_CANCELED_BIT)
| BIT(SSH_REQUEST_SF_COMPLETED_BIT),
/* mask for type flags */
SSH_REQUEST_FLAGS_TY_MASK =
BIT(SSH_REQUEST_TY_FLUSH_BIT)
| BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT),
};
struct ssh_rtl;
struct ssh_request;
/**
* struct ssh_request_ops - Callback operations for a SSH request.
* @release: Function called when the request's reference count reaches zero.
* This callback must be relied upon to ensure that the request has
* left the transport systems (both, packet an request systems).
* @complete: Function called when the request is completed, either with
* success or failure. The command data for the request response
* is provided via the &struct ssh_command parameter (``cmd``),
* the command payload of the request response via the &struct
* ssh_span parameter (``data``).
*
* If the request does not have any response or has not been
* completed with success, both ``cmd`` and ``data`` parameters will
* be NULL. If the request response does not have any command
* payload, the ``data`` span will be an empty (zero-length) span.
*
* In case of failure, the reason for the failure is indicated by
* the value of the provided status code argument (``status``). This
* value will be zero in case of success and a regular errno
* otherwise.
*
* Note that a call to this callback does not guarantee that the
* request is not in use by the transport systems any more.
*/
struct ssh_request_ops {
void (*release)(struct ssh_request *rqst);
void (*complete)(struct ssh_request *rqst,
const struct ssh_command *cmd,
const struct ssam_span *data, int status);
};
/**
* struct ssh_request - SSH transport request.
* @packet: The underlying SSH transport packet.
* @node: List node for the request queue and pending set.
* @state: State and type flags describing current request state (dynamic)
* and type (static). See &enum ssh_request_flags for possible
* options.
* @timestamp: Timestamp specifying when we start waiting on the response of
* the request. This is set once the underlying packet has been
* completed and may be %KTIME_MAX before that, or when the request
* does not expect a response. Used for the request timeout
* implementation.
* @ops: Request Operations.
*/
struct ssh_request {
struct ssh_packet packet;
struct list_head node;
unsigned long state;
ktime_t timestamp;
const struct ssh_request_ops *ops;
};
/**
* to_ssh_request() - Cast a SSH packet to its enclosing SSH request.
* @p: The packet to cast.
*
* Casts the given &struct ssh_packet to its enclosing &struct ssh_request.
* The caller is responsible for making sure that the packet is actually
* wrapped in a &struct ssh_request.
*
* Return: Returns the &struct ssh_request wrapping the provided packet.
*/
static inline struct ssh_request *to_ssh_request(struct ssh_packet *p)
{
return container_of(p, struct ssh_request, packet);
}
/**
* ssh_request_get() - Increment reference count of request.
* @r: The request to increment the reference count of.
*
* Increments the reference count of the given request by incrementing the
* reference count of the underlying &struct ssh_packet, enclosed in it.
*
* See also ssh_request_put(), ssh_packet_get().
*
* Return: Returns the request provided as input.
*/
static inline struct ssh_request *ssh_request_get(struct ssh_request *r)
{
return r ? to_ssh_request(ssh_packet_get(&r->packet)) : NULL;
}
/**
* ssh_request_put() - Decrement reference count of request.
* @r: The request to decrement the reference count of.
*
* Decrements the reference count of the given request by decrementing the
* reference count of the underlying &struct ssh_packet, enclosed in it. If
* the reference count reaches zero, the ``release`` callback specified in the
* request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be
* called.
*
* See also ssh_request_get(), ssh_packet_put().
*/
static inline void ssh_request_put(struct ssh_request *r)
{
if (r)
ssh_packet_put(&r->packet);
}
/**
* ssh_request_set_data() - Set raw message data of request.
* @r: The request for which the message data should be set.
* @ptr: Pointer to the memory holding the message data.
* @len: Length of the message data.
*
* Sets the raw message data buffer of the underlying packet to the specified
* buffer. Does not copy the actual message data, just sets the buffer pointer
* and length. Refer to ssh_packet_set_data() for more details.
*/
static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len)
{
ssh_packet_set_data(&r->packet, ptr, len);
}
#endif /* _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H */