Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,69 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
menuconfig SURFACE_AGGREGATOR
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
depends on SERIAL_DEV_BUS
depends on ACPI && !RISCV
select CRC_ITU_T
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.
config SURFACE_AGGREGATOR_BUS
bool "Surface System Aggregator Module Bus"
depends on SURFACE_AGGREGATOR
default y
help
Expands the Surface System Aggregator Module (SSAM) core driver by
providing a dedicated bus and client-device type.
This bus and device type are intended to provide and simplify support
for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are
not auto-detectable via the conventional means (e.g. ACPI).
config SURFACE_AGGREGATOR_ERROR_INJECTION
bool "Surface System Aggregator Module Error Injection Capabilities"
depends on SURFACE_AGGREGATOR
depends on FUNCTION_ERROR_INJECTION
help
Provides error-injection capabilities for the Surface System
Aggregator Module subsystem and Surface Serial Hub driver.
Specifically, exports error injection hooks to be used with the
kernel's function error injection capabilities to simulate underlying
transport and communication problems, such as invalid data sent to or
received from the EC, dropped data, and communication timeouts.
Intended for development and debugging.
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
# For include/trace/define_trace.h to include trace.h
CFLAGS_core.o = -I$(src)
obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o
surface_aggregator-y := core.o
surface_aggregator-y += ssh_parser.o
surface_aggregator-y += ssh_packet_layer.o
surface_aggregator-y += ssh_request_layer.o
surface_aggregator-$(CONFIG_SURFACE_AGGREGATOR_BUS) += bus.o
surface_aggregator-y += controller.o
@@ -0,0 +1,527 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Surface System Aggregator Module bus and device integration.
*
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/device.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/device.h>
#include "bus.h"
#include "controller.h"
/* -- Device and bus functions. --------------------------------------------- */
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ssam_device *sdev = to_ssam_device(dev);
return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n",
sdev->uid.domain, sdev->uid.category, sdev->uid.target,
sdev->uid.instance, sdev->uid.function);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *ssam_device_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(ssam_device);
static const struct bus_type ssam_bus_type;
static int ssam_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct ssam_device *sdev = to_ssam_device(dev);
return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X",
sdev->uid.domain, sdev->uid.category,
sdev->uid.target, sdev->uid.instance,
sdev->uid.function);
}
static void ssam_device_release(struct device *dev)
{
struct ssam_device *sdev = to_ssam_device(dev);
ssam_controller_put(sdev->ctrl);
fwnode_handle_put(sdev->dev.fwnode);
kfree(sdev);
}
const struct device_type ssam_device_type = {
.name = "surface_aggregator_device",
.groups = ssam_device_groups,
.uevent = ssam_device_uevent,
.release = ssam_device_release,
};
EXPORT_SYMBOL_GPL(ssam_device_type);
/**
* ssam_device_alloc() - Allocate and initialize a SSAM client device.
* @ctrl: The controller under which the device should be added.
* @uid: The UID of the device to be added.
*
* Allocates and initializes a new client device. The parent of the device
* will be set to the controller device and the name will be set based on the
* UID. Note that the device still has to be added via ssam_device_add().
* Refer to that function for more details.
*
* Return: Returns the newly allocated and initialized SSAM client device, or
* %NULL if it could not be allocated.
*/
struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl,
struct ssam_device_uid uid)
{
struct ssam_device *sdev;
sdev = kzalloc_obj(*sdev);
if (!sdev)
return NULL;
device_initialize(&sdev->dev);
sdev->dev.bus = &ssam_bus_type;
sdev->dev.type = &ssam_device_type;
sdev->dev.parent = ssam_controller_device(ctrl);
sdev->ctrl = ssam_controller_get(ctrl);
sdev->uid = uid;
dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x",
sdev->uid.domain, sdev->uid.category, sdev->uid.target,
sdev->uid.instance, sdev->uid.function);
return sdev;
}
EXPORT_SYMBOL_GPL(ssam_device_alloc);
/**
* ssam_device_add() - Add a SSAM client device.
* @sdev: The SSAM client device to be added.
*
* Added client devices must be guaranteed to always have a valid and active
* controller. Thus, this function will fail with %-ENODEV if the controller
* of the device has not been initialized yet, has been suspended, or has been
* shut down.
*
* The caller of this function should ensure that the corresponding call to
* ssam_device_remove() is issued before the controller is shut down. If the
* added device is a direct child of the controller device (default), it will
* be automatically removed when the controller is shut down.
*
* By default, the controller device will become the parent of the newly
* created client device. The parent may be changed before ssam_device_add is
* called, but care must be taken that a) the correct suspend/resume ordering
* is guaranteed and b) the client device does not outlive the controller,
* i.e. that the device is removed before the controller is being shut down.
* In case these guarantees have to be manually enforced, please refer to the
* ssam_client_link() and ssam_client_bind() functions, which are intended to
* set up device-links for this purpose.
*
* Return: Returns zero on success, a negative error code on failure.
*/
int ssam_device_add(struct ssam_device *sdev)
{
int status;
/*
* Ensure that we can only add new devices to a controller if it has
* been started and is not going away soon. This works in combination
* with ssam_controller_remove_clients to ensure driver presence for the
* controller device, i.e. it ensures that the controller (sdev->ctrl)
* is always valid and can be used for requests as long as the client
* device we add here is registered as child under it. This essentially
* guarantees that the client driver can always expect the preconditions
* for functions like ssam_request_do_sync() (controller has to be
* started and is not suspended) to hold and thus does not have to check
* for them.
*
* Note that for this to work, the controller has to be a parent device.
* If it is not a direct parent, care has to be taken that the device is
* removed via ssam_device_remove(), as device_unregister does not
* remove child devices recursively.
*/
ssam_controller_statelock(sdev->ctrl);
if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) {
ssam_controller_stateunlock(sdev->ctrl);
return -ENODEV;
}
status = device_add(&sdev->dev);
ssam_controller_stateunlock(sdev->ctrl);
return status;
}
EXPORT_SYMBOL_GPL(ssam_device_add);
/**
* ssam_device_remove() - Remove a SSAM client device.
* @sdev: The device to remove.
*
* Removes and unregisters the provided SSAM client device.
*/
void ssam_device_remove(struct ssam_device *sdev)
{
device_unregister(&sdev->dev);
}
EXPORT_SYMBOL_GPL(ssam_device_remove);
/**
* ssam_device_id_compatible() - Check if a device ID matches a UID.
* @id: The device ID as potential match.
* @uid: The device UID matching against.
*
* Check if the given ID is a match for the given UID, i.e. if a device with
* the provided UID is compatible to the given ID following the match rules
* described in its &ssam_device_id.match_flags member.
*
* Return: Returns %true if the given UID is compatible to the match rule
* described by the given ID, %false otherwise.
*/
static bool ssam_device_id_compatible(const struct ssam_device_id *id,
struct ssam_device_uid uid)
{
if (id->domain != uid.domain || id->category != uid.category)
return false;
if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target)
return false;
if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance)
return false;
if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function)
return false;
return true;
}
/**
* ssam_device_id_is_null() - Check if a device ID is null.
* @id: The device ID to check.
*
* Check if a given device ID is null, i.e. all zeros. Used to check for the
* end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists.
*
* Return: Returns %true if the given ID represents a null ID, %false
* otherwise.
*/
static bool ssam_device_id_is_null(const struct ssam_device_id *id)
{
return id->match_flags == 0 &&
id->domain == 0 &&
id->category == 0 &&
id->target == 0 &&
id->instance == 0 &&
id->function == 0 &&
id->driver_data == 0;
}
/**
* ssam_device_id_match() - Find the matching ID table entry for the given UID.
* @table: The table to search in.
* @uid: The UID to matched against the individual table entries.
*
* Find the first match for the provided device UID in the provided ID table
* and return it. Returns %NULL if no match could be found.
*/
const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table,
const struct ssam_device_uid uid)
{
const struct ssam_device_id *id;
for (id = table; !ssam_device_id_is_null(id); ++id)
if (ssam_device_id_compatible(id, uid))
return id;
return NULL;
}
EXPORT_SYMBOL_GPL(ssam_device_id_match);
/**
* ssam_device_get_match() - Find and return the ID matching the device in the
* ID table of the bound driver.
* @dev: The device for which to get the matching ID table entry.
*
* Find the fist match for the UID of the device in the ID table of the
* currently bound driver and return it. Returns %NULL if the device does not
* have a driver bound to it, the driver does not have match_table (i.e. it is
* %NULL), or there is no match in the driver's match_table.
*
* This function essentially calls ssam_device_id_match() with the ID table of
* the bound device driver and the UID of the device.
*
* Return: Returns the first match for the UID of the device in the device
* driver's match table, or %NULL if no such match could be found.
*/
const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev)
{
const struct ssam_device_driver *sdrv;
sdrv = to_ssam_device_driver(dev->dev.driver);
if (!sdrv)
return NULL;
if (!sdrv->match_table)
return NULL;
return ssam_device_id_match(sdrv->match_table, dev->uid);
}
EXPORT_SYMBOL_GPL(ssam_device_get_match);
/**
* ssam_device_get_match_data() - Find the ID matching the device in the
* ID table of the bound driver and return its ``driver_data`` member.
* @dev: The device for which to get the match data.
*
* Find the fist match for the UID of the device in the ID table of the
* corresponding driver and return its driver_data. Returns %NULL if the
* device does not have a driver bound to it, the driver does not have
* match_table (i.e. it is %NULL), there is no match in the driver's
* match_table, or the match does not have any driver_data.
*
* This function essentially calls ssam_device_get_match() and, if any match
* could be found, returns its ``struct ssam_device_id.driver_data`` member.
*
* Return: Returns the driver data associated with the first match for the UID
* of the device in the device driver's match table, or %NULL if no such match
* could be found.
*/
const void *ssam_device_get_match_data(const struct ssam_device *dev)
{
const struct ssam_device_id *id;
id = ssam_device_get_match(dev);
if (!id)
return NULL;
return (const void *)id->driver_data;
}
EXPORT_SYMBOL_GPL(ssam_device_get_match_data);
static int ssam_bus_match(struct device *dev, const struct device_driver *drv)
{
const struct ssam_device_driver *sdrv = to_ssam_device_driver(drv);
struct ssam_device *sdev = to_ssam_device(dev);
if (!is_ssam_device(dev))
return 0;
return !!ssam_device_id_match(sdrv->match_table, sdev->uid);
}
static int ssam_bus_probe(struct device *dev)
{
return to_ssam_device_driver(dev->driver)
->probe(to_ssam_device(dev));
}
static void ssam_bus_remove(struct device *dev)
{
struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver);
if (sdrv->remove)
sdrv->remove(to_ssam_device(dev));
}
static const struct bus_type ssam_bus_type = {
.name = "surface_aggregator",
.match = ssam_bus_match,
.probe = ssam_bus_probe,
.remove = ssam_bus_remove,
};
/**
* __ssam_device_driver_register() - Register a SSAM client device driver.
* @sdrv: The driver to register.
* @owner: The module owning the provided driver.
*
* Please refer to the ssam_device_driver_register() macro for the normal way
* to register a driver from inside its owning module.
*/
int __ssam_device_driver_register(struct ssam_device_driver *sdrv,
struct module *owner)
{
sdrv->driver.owner = owner;
sdrv->driver.bus = &ssam_bus_type;
/* force drivers to async probe so I/O is possible in probe */
sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS;
return driver_register(&sdrv->driver);
}
EXPORT_SYMBOL_GPL(__ssam_device_driver_register);
/**
* ssam_device_driver_unregister - Unregister a SSAM device driver.
* @sdrv: The driver to unregister.
*/
void ssam_device_driver_unregister(struct ssam_device_driver *sdrv)
{
driver_unregister(&sdrv->driver);
}
EXPORT_SYMBOL_GPL(ssam_device_driver_unregister);
/* -- Bus registration. ----------------------------------------------------- */
/**
* ssam_bus_register() - Register and set-up the SSAM client device bus.
*/
int ssam_bus_register(void)
{
return bus_register(&ssam_bus_type);
}
/**
* ssam_bus_unregister() - Unregister the SSAM client device bus.
*/
void ssam_bus_unregister(void)
{
return bus_unregister(&ssam_bus_type);
}
/* -- Helpers for controller and hub devices. ------------------------------- */
static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid)
{
u8 d, tc, tid, iid, fn;
int n;
n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
if (n != 5)
return -EINVAL;
uid->domain = d;
uid->category = tc;
uid->target = tid;
uid->instance = iid;
uid->function = fn;
return 0;
}
static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid)
{
const char *str = fwnode_get_name(node);
/*
* To simplify definitions of firmware nodes, we set the device name
* based on the UID of the device, prefixed with "ssam:".
*/
if (strncmp(str, "ssam:", strlen("ssam:")) != 0)
return -ENODEV;
str += strlen("ssam:");
return ssam_device_uid_from_string(str, uid);
}
static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
{
struct ssam_device_uid uid;
struct ssam_device *sdev;
int status;
status = ssam_get_uid_for_node(node, &uid);
if (status)
return status;
sdev = ssam_device_alloc(ctrl, uid);
if (!sdev)
return -ENOMEM;
sdev->dev.parent = parent;
sdev->dev.fwnode = fwnode_handle_get(node);
sdev->dev.of_node = to_of_node(node);
status = ssam_device_add(sdev);
if (status)
ssam_device_put(sdev);
return status;
}
/**
* __ssam_register_clients() - Register client devices defined under the
* given firmware node as children of the given device.
* @parent: The parent device under which clients should be registered.
* @ctrl: The controller with which client should be registered.
* @node: The firmware node holding definitions of the devices to be added.
*
* Register all clients that have been defined as children of the given root
* firmware node as children of the given parent device. The respective child
* firmware nodes will be associated with the correspondingly created child
* devices.
*
* The given controller will be used to instantiate the new devices. See
* ssam_device_add() for details.
*
* Note that, generally, the use of either ssam_device_register_clients() or
* ssam_register_clients() should be preferred as they directly use the
* firmware node and/or controller associated with the given device. This
* function is only intended for use when different device specifications (e.g.
* ACPI and firmware nodes) need to be combined (as is done in the platform hub
* of the device registry).
*
* Return: Returns zero on success, nonzero on failure.
*/
int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
{
struct fwnode_handle *child;
int status;
fwnode_for_each_child_node(node, child) {
/*
* Try to add the device specified in the firmware node. If
* this fails with -ENODEV, the node does not specify any SSAM
* device, so ignore it and continue with the next one.
*/
status = ssam_add_client_device(parent, ctrl, child);
if (status && status != -ENODEV) {
fwnode_handle_put(child);
goto err;
}
}
return 0;
err:
ssam_remove_clients(parent);
return status;
}
EXPORT_SYMBOL_GPL(__ssam_register_clients);
static int ssam_remove_device(struct device *dev, void *_data)
{
struct ssam_device *sdev = to_ssam_device(dev);
if (is_ssam_device(dev))
ssam_device_remove(sdev);
return 0;
}
/**
* ssam_remove_clients() - Remove SSAM client devices registered as direct
* children under the given parent device.
* @dev: The (parent) device to remove all direct clients for.
*
* Remove all SSAM client devices registered as direct children under the given
* device. Note that this only accounts for direct children of the device.
* Refer to ssam_device_add()/ssam_device_remove() for more details.
*/
void ssam_remove_clients(struct device *dev)
{
device_for_each_child_reverse(dev, NULL, ssam_remove_device);
}
EXPORT_SYMBOL_GPL(ssam_remove_clients);
@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Surface System Aggregator Module bus and device integration.
*
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_BUS_H
#define _SURFACE_AGGREGATOR_BUS_H
#include <linux/surface_aggregator/controller.h>
#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
int ssam_bus_register(void);
void ssam_bus_unregister(void);
#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
static inline int ssam_bus_register(void) { return 0; }
static inline void ssam_bus_unregister(void) {}
#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
#endif /* _SURFACE_AGGREGATOR_BUS_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,285 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Main SSAM/SSH controller structure and functionality.
*
* Copyright (C) 2019-2022 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.
* @ops: Instance specific functions.
* @ops.free: Callback for freeing this event item.
* @event: Actual event data.
*/
struct ssam_event_item {
struct list_head node;
u16 rqid;
struct {
void (*free)(struct ssam_event_item *event);
} ops;
struct ssam_event event; /* must be last */
};
/**
* 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
ssize_t ssam_controller_receive_buf(struct ssam_controller *ctrl, const u8 *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);
int ssam_event_item_cache_init(void);
void ssam_event_item_cache_destroy(void);
#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */
@@ -0,0 +1,894 @@
// 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-2022 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/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/serdev.h>
#include <linux/sysfs.h>
#include <linux/units.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/device.h>
#include "bus.h"
#include "controller.h"
#define CREATE_TRACE_POINTS
#include "trace.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 size_t ssam_receive_buf(struct serdev_device *dev, const u8 *buf,
size_t n)
{
struct ssam_controller *ctrl;
int ret;
ctrl = serdev_device_get_drvdata(dev);
ret = ssam_controller_receive_buf(ctrl, buf, n);
return ret < 0 ? 0 : ret;
}
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,
};
/* -- Serial 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_uart_serialbus *uart;
bool flow_control;
int status = 0;
if (!serdev_acpi_get_uart_resource(rsc, &uart))
return AE_OK;
/* 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 int ssam_serdev_setup_via_acpi(struct serdev_device *serdev, acpi_handle handle)
{
acpi_status status;
status = acpi_walk_resources(handle, METHOD_NAME__CRS,
ssam_serdev_setup_via_acpi_crs, serdev);
return status ? -ENXIO : 0;
}
static int ssam_serdev_setup(struct acpi_device *ssh, struct serdev_device *serdev)
{
if (ssh)
return ssam_serdev_setup_via_acpi(serdev, ssh->handle);
/* TODO: these values may differ per board/implementation */
serdev_device_set_baudrate(serdev, 4 * HZ_PER_MHZ);
serdev_device_set_flow_control(serdev, true);
serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
return 0;
}
/* -- Power management. ----------------------------------------------------- */
static void ssam_serial_hub_shutdown(struct serdev_device *serdev)
{
struct ssam_controller *c = dev_get_drvdata(&serdev->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 device *dev = &serdev->dev;
struct acpi_device *ssh = ACPI_COMPANION(dev);
struct ssam_controller *ctrl;
int status;
if (ssh) {
status = gpiod_count(dev, NULL);
if (status < 0)
return dev_err_probe(dev, status, "no GPIO found\n");
status = devm_acpi_dev_add_driver_gpios(dev, ssam_acpi_gpios);
if (status)
return status;
}
/* Allocate controller. */
ctrl = kzalloc_obj(*ctrl);
if (!ctrl)
return -ENOMEM;
/* Initialize controller. */
status = ssam_controller_init(ctrl, serdev);
if (status) {
dev_err_probe(dev, status, "failed to initialize ssam controller\n");
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) {
dev_err_probe(dev, status, "failed to open serdev device\n");
goto err_devopen;
}
status = ssam_serdev_setup(ssh, serdev);
if (status) {
dev_err_probe(dev, status, "failed to setup serdev\n");
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) {
dev_err_probe(dev, status, "failed to get firmware version\n");
goto err_initrq;
}
status = ssam_ctrl_notif_d0_entry(ctrl);
if (status) {
dev_err_probe(dev, status, "D0-entry notification failed\n");
goto err_initrq;
}
status = ssam_ctrl_notif_display_on(ctrl);
if (status) {
dev_err_probe(dev, status, "display-on notification failed\n");
goto err_initrq;
}
status = sysfs_create_group(&dev->kobj, &ssam_sam_group);
if (status)
goto err_initrq;
/* Set up IRQ. */
status = ssam_irq_setup(ctrl);
if (status) {
dev_err_probe(dev, status, "failed to setup IRQ\n");
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(dev, true);
/*
* When using DT, we have to register the platform hub driver manually,
* as it can't be matched based on top-level board compatible (like it
* does the ACPI case).
*/
if (!ssh) {
struct platform_device *ph_pdev =
platform_device_register_simple("surface_aggregator_platform_hub",
0, NULL, 0);
if (IS_ERR(ph_pdev))
return dev_err_probe(dev, PTR_ERR(ph_pdev),
"Failed to register the platform hub driver\n");
}
if (ssh)
acpi_dev_clear_dependencies(ssh);
return 0;
err_mainref:
ssam_irq_free(ctrl);
err_irq:
sysfs_remove_group(&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);
/* Remove all client devices. */
ssam_remove_clients(&serdev->dev);
/* 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_acpi_match[] = {
{ "MSHW0084", 0 },
{ },
};
MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_acpi_match);
#ifdef CONFIG_OF
static const struct of_device_id ssam_serial_hub_of_match[] = {
{ .compatible = "microsoft,surface-sam", },
{ },
};
MODULE_DEVICE_TABLE(of, ssam_serial_hub_of_match);
#endif
static struct serdev_device_driver ssam_serial_hub = {
.probe = ssam_serial_hub_probe,
.remove = ssam_serial_hub_remove,
.shutdown = ssam_serial_hub_shutdown,
.driver = {
.name = "surface_serial_hub",
.acpi_match_table = ACPI_PTR(ssam_serial_hub_acpi_match),
.of_match_table = of_match_ptr(ssam_serial_hub_of_match),
.pm = &ssam_serial_hub_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
/* -- Module setup. --------------------------------------------------------- */
static int __init ssam_core_init(void)
{
int status;
status = ssam_bus_register();
if (status)
goto err_bus;
status = ssh_ctrl_packet_cache_init();
if (status)
goto err_cpkg;
status = ssam_event_item_cache_init();
if (status)
goto err_evitem;
status = serdev_device_driver_register(&ssam_serial_hub);
if (status)
goto err_register;
return 0;
err_register:
ssam_event_item_cache_destroy();
err_evitem:
ssh_ctrl_packet_cache_destroy();
err_cpkg:
ssam_bus_unregister();
err_bus:
return status;
}
subsys_initcall(ssam_core_init);
static void __exit ssam_core_exit(void)
{
serdev_device_driver_unregister(&ssam_serial_hub);
ssam_event_item_cache_destroy();
ssh_ctrl_packet_cache_destroy();
ssam_bus_unregister();
}
module_exit(ssam_core_exit);
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module");
MODULE_LICENSE("GPL");
@@ -0,0 +1,205 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH message builder functions.
*
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
#define _SURFACE_AGGREGATOR_SSH_MSGB_H
#include <linux/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. */
__msgb_push_u8(msgb, SSAM_SSH_TID_HOST); /* Source ID. */
__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
@@ -0,0 +1,190 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH packet transport layer.
*
* Copyright (C) 2019-2022 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);
ssize_t 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);
int ssh_ctrl_packet_cache_init(void);
void ssh_ctrl_packet_cache_destroy(void);
#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* SSH message parser.
*
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/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;
}
@@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH message parser.
*
* Copyright (C) 2019-2022 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
@@ -0,0 +1,143 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH request transport layer.
*
* Copyright (C) 2019-2022 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 */
@@ -0,0 +1,703 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Trace points for SSAM/SSH.
*
* Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM surface_aggregator
#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _SURFACE_AGGREGATOR_TRACE_H
#include <linux/surface_aggregator/serial_hub.h>
#include <linux/unaligned.h>
#include <linux/tracepoint.h>
TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ);
TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ);
TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK);
TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT);
TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK);
TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT);
TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK);
TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS);
#define SSAM_PTR_UID_LEN 9
#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1)
#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1)
#define SSAM_RQID_NOT_APPLICABLE ((u32)-1)
#define SSAM_SSH_TC_NOT_APPLICABLE 0
#define SSAM_SSH_TID_NOT_APPLICABLE ((u8)-1)
#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS
#define _SURFACE_AGGREGATOR_TRACE_HELPERS
/**
* ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string.
* @ptr: The pointer to convert.
* @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored.
*
* Converts the given pointer into a UID string that is safe to be shared
* with userspace and logs, i.e. doesn't give away the real memory location.
*/
static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str)
{
char buf[2 * sizeof(void *) + 1];
BUILD_BUG_ON(ARRAY_SIZE(buf) < SSAM_PTR_UID_LEN);
snprintf(buf, ARRAY_SIZE(buf), "%p", ptr);
memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN],
SSAM_PTR_UID_LEN);
}
/**
* ssam_trace_get_packet_seq() - Read the packet's sequence ID.
* @p: The packet.
*
* Return: Returns the packet's sequence ID (SEQ) field if present, or
* %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet).
*/
static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p)
{
if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0))
return SSAM_SEQ_NOT_APPLICABLE;
return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)];
}
/**
* ssam_trace_get_request_id() - Read the packet's request ID.
* @p: The packet.
*
* Return: Returns the packet's request ID (RQID) field if the packet
* represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not
* (e.g. flush request, control packet).
*/
static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p)
{
if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0))
return SSAM_RQID_NOT_APPLICABLE;
return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]);
}
/**
* ssam_trace_get_request_tid() - Read the packet's request target ID.
* @p: The packet.
*
* Return: Returns the packet's request target ID (TID) field if the packet
* represents a request with command data, or %SSAM_SSH_TID_NOT_APPLICABLE
* if not (e.g. flush request, control packet).
*/
static inline u32 ssam_trace_get_request_tid(const struct ssh_packet *p)
{
if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0))
return SSAM_SSH_TID_NOT_APPLICABLE;
return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tid)]);
}
/**
* ssam_trace_get_request_sid() - Read the packet's request source ID.
* @p: The packet.
*
* Return: Returns the packet's request source ID (SID) field if the packet
* represents a request with command data, or %SSAM_SSH_TID_NOT_APPLICABLE
* if not (e.g. flush request, control packet).
*/
static inline u32 ssam_trace_get_request_sid(const struct ssh_packet *p)
{
if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0))
return SSAM_SSH_TID_NOT_APPLICABLE;
return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(sid)]);
}
/**
* ssam_trace_get_request_tc() - Read the packet's request target category.
* @p: The packet.
*
* Return: Returns the packet's request target category (TC) field if the
* packet represents a request with command data, or %SSAM_SSH_TC_NOT_APPLICABLE
* if not (e.g. flush request, control packet).
*/
static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p)
{
if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0))
return SSAM_SSH_TC_NOT_APPLICABLE;
return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]);
}
#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */
#define ssam_trace_get_command_field_u8(packet, field) \
((!(packet) || (packet)->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \
? 0 : (packet)->data.ptr[SSH_MSGOFFSET_COMMAND(field)])
#define ssam_show_generic_u8_field(value) \
__print_symbolic(value, \
{ SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \
)
#define ssam_show_frame_type(ty) \
__print_symbolic(ty, \
{ SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \
{ SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \
{ SSH_FRAME_TYPE_ACK, "ACK" }, \
{ SSH_FRAME_TYPE_NAK, "NAK" } \
)
#define ssam_show_packet_type(type) \
__print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \
{ BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \
{ BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \
{ BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \
)
#define ssam_show_packet_state(state) \
__print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \
{ BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \
{ BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \
{ BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \
{ BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \
{ BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \
{ BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \
{ BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \
{ BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \
)
#define ssam_show_packet_seq(seq) \
__print_symbolic(seq, \
{ SSAM_SEQ_NOT_APPLICABLE, "N/A" } \
)
#define ssam_show_request_type(flags) \
__print_flags((flags) & SSH_REQUEST_FLAGS_TY_MASK, "", \
{ BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \
{ BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \
)
#define ssam_show_request_state(flags) \
__print_flags((flags) & SSH_REQUEST_FLAGS_SF_MASK, "", \
{ BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \
{ BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \
{ BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \
{ BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \
{ BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \
{ BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \
{ BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \
{ BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \
)
#define ssam_show_request_id(rqid) \
__print_symbolic(rqid, \
{ SSAM_RQID_NOT_APPLICABLE, "N/A" } \
)
#define ssam_show_ssh_tid(tid) \
__print_symbolic(tid, \
{ SSAM_SSH_TID_NOT_APPLICABLE, "N/A" }, \
{ SSAM_SSH_TID_HOST, "Host" }, \
{ SSAM_SSH_TID_SAM, "SAM" }, \
{ SSAM_SSH_TID_KIP, "KIP" }, \
{ SSAM_SSH_TID_DEBUG, "Debug" }, \
{ SSAM_SSH_TID_SURFLINK, "SurfLink" } \
)
#define ssam_show_ssh_tc(tc) \
__print_symbolic(tc, \
{ SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \
{ SSAM_SSH_TC_SAM, "SAM" }, \
{ SSAM_SSH_TC_BAT, "BAT" }, \
{ SSAM_SSH_TC_TMP, "TMP" }, \
{ SSAM_SSH_TC_PMC, "PMC" }, \
{ SSAM_SSH_TC_FAN, "FAN" }, \
{ SSAM_SSH_TC_PoM, "PoM" }, \
{ SSAM_SSH_TC_DBG, "DBG" }, \
{ SSAM_SSH_TC_KBD, "KBD" }, \
{ SSAM_SSH_TC_FWU, "FWU" }, \
{ SSAM_SSH_TC_UNI, "UNI" }, \
{ SSAM_SSH_TC_LPC, "LPC" }, \
{ SSAM_SSH_TC_TCL, "TCL" }, \
{ SSAM_SSH_TC_SFL, "SFL" }, \
{ SSAM_SSH_TC_KIP, "KIP" }, \
{ SSAM_SSH_TC_EXT, "EXT" }, \
{ SSAM_SSH_TC_BLD, "BLD" }, \
{ SSAM_SSH_TC_BAS, "BAS" }, \
{ SSAM_SSH_TC_SEN, "SEN" }, \
{ SSAM_SSH_TC_SRQ, "SRQ" }, \
{ SSAM_SSH_TC_MCU, "MCU" }, \
{ SSAM_SSH_TC_HID, "HID" }, \
{ SSAM_SSH_TC_TCH, "TCH" }, \
{ SSAM_SSH_TC_BKL, "BKL" }, \
{ SSAM_SSH_TC_TAM, "TAM" }, \
{ SSAM_SSH_TC_ACC0, "ACC0" }, \
{ SSAM_SSH_TC_UFI, "UFI" }, \
{ SSAM_SSH_TC_USC, "USC" }, \
{ SSAM_SSH_TC_PEN, "PEN" }, \
{ SSAM_SSH_TC_VID, "VID" }, \
{ SSAM_SSH_TC_AUD, "AUD" }, \
{ SSAM_SSH_TC_SMC, "SMC" }, \
{ SSAM_SSH_TC_KPD, "KPD" }, \
{ SSAM_SSH_TC_REG, "REG" }, \
{ SSAM_SSH_TC_SPT, "SPT" }, \
{ SSAM_SSH_TC_SYS, "SYS" }, \
{ SSAM_SSH_TC_ACC1, "ACC1" }, \
{ SSAM_SSH_TC_SHB, "SMB" }, \
{ SSAM_SSH_TC_POS, "POS" } \
)
DECLARE_EVENT_CLASS(ssam_frame_class,
TP_PROTO(const struct ssh_frame *frame),
TP_ARGS(frame),
TP_STRUCT__entry(
__field(u8, type)
__field(u8, seq)
__field(u16, len)
),
TP_fast_assign(
__entry->type = frame->type;
__entry->seq = frame->seq;
__entry->len = get_unaligned_le16(&frame->len);
),
TP_printk("ty=%s, seq=%#04x, len=%u",
ssam_show_frame_type(__entry->type),
__entry->seq,
__entry->len
)
);
#define DEFINE_SSAM_FRAME_EVENT(name) \
DEFINE_EVENT(ssam_frame_class, ssam_##name, \
TP_PROTO(const struct ssh_frame *frame), \
TP_ARGS(frame) \
)
DECLARE_EVENT_CLASS(ssam_command_class,
TP_PROTO(const struct ssh_command *cmd, u16 len),
TP_ARGS(cmd, len),
TP_STRUCT__entry(
__field(u16, rqid)
__field(u16, len)
__field(u8, tid)
__field(u8, sid)
__field(u8, tc)
__field(u8, cid)
__field(u8, iid)
),
TP_fast_assign(
__entry->rqid = get_unaligned_le16(&cmd->rqid);
__entry->tid = cmd->tid;
__entry->sid = cmd->sid;
__entry->tc = cmd->tc;
__entry->cid = cmd->cid;
__entry->iid = cmd->iid;
__entry->len = len;
),
TP_printk("rqid=%#06x, tid=%s, sid=%s, tc=%s, cid=%#04x, iid=%#04x, len=%u",
__entry->rqid,
ssam_show_ssh_tid(__entry->tid),
ssam_show_ssh_tid(__entry->sid),
ssam_show_ssh_tc(__entry->tc),
__entry->cid,
__entry->iid,
__entry->len
)
);
#define DEFINE_SSAM_COMMAND_EVENT(name) \
DEFINE_EVENT(ssam_command_class, ssam_##name, \
TP_PROTO(const struct ssh_command *cmd, u16 len), \
TP_ARGS(cmd, len) \
)
DECLARE_EVENT_CLASS(ssam_packet_class,
TP_PROTO(const struct ssh_packet *packet),
TP_ARGS(packet),
TP_STRUCT__entry(
__field(unsigned long, state)
__array(char, uid, SSAM_PTR_UID_LEN)
__field(u8, priority)
__field(u16, length)
__field(u16, seq)
),
TP_fast_assign(
__entry->state = READ_ONCE(packet->state);
ssam_trace_ptr_uid(packet, __entry->uid);
__entry->priority = READ_ONCE(packet->priority);
__entry->length = packet->data.len;
__entry->seq = ssam_trace_get_packet_seq(packet);
),
TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s",
__entry->uid,
ssam_show_packet_seq(__entry->seq),
ssam_show_packet_type(__entry->state),
__entry->priority,
__entry->length,
ssam_show_packet_state(__entry->state)
)
);
#define DEFINE_SSAM_PACKET_EVENT(name) \
DEFINE_EVENT(ssam_packet_class, ssam_##name, \
TP_PROTO(const struct ssh_packet *packet), \
TP_ARGS(packet) \
)
DECLARE_EVENT_CLASS(ssam_packet_status_class,
TP_PROTO(const struct ssh_packet *packet, int status),
TP_ARGS(packet, status),
TP_STRUCT__entry(
__field(unsigned long, state)
__field(int, status)
__array(char, uid, SSAM_PTR_UID_LEN)
__field(u8, priority)
__field(u16, length)
__field(u16, seq)
),
TP_fast_assign(
__entry->state = READ_ONCE(packet->state);
__entry->status = status;
ssam_trace_ptr_uid(packet, __entry->uid);
__entry->priority = READ_ONCE(packet->priority);
__entry->length = packet->data.len;
__entry->seq = ssam_trace_get_packet_seq(packet);
),
TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s, status=%d",
__entry->uid,
ssam_show_packet_seq(__entry->seq),
ssam_show_packet_type(__entry->state),
__entry->priority,
__entry->length,
ssam_show_packet_state(__entry->state),
__entry->status
)
);
#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \
DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \
TP_PROTO(const struct ssh_packet *packet, int status), \
TP_ARGS(packet, status) \
)
DECLARE_EVENT_CLASS(ssam_request_class,
TP_PROTO(const struct ssh_request *request),
TP_ARGS(request),
TP_STRUCT__entry(
__field(unsigned long, state)
__field(u32, rqid)
__array(char, uid, SSAM_PTR_UID_LEN)
__field(u8, tc)
__field(u16, cid)
__field(u16, iid)
__field(u8, tid)
__field(u8, sid)
),
TP_fast_assign(
const struct ssh_packet *p = &request->packet;
/* Use packet for UID so we can match requests to packets. */
__entry->state = READ_ONCE(request->state);
__entry->rqid = ssam_trace_get_request_id(p);
ssam_trace_ptr_uid(p, __entry->uid);
__entry->tid = ssam_trace_get_request_tid(p);
__entry->sid = ssam_trace_get_request_sid(p);
__entry->tc = ssam_trace_get_request_tc(p);
__entry->cid = ssam_trace_get_command_field_u8(p, cid);
__entry->iid = ssam_trace_get_command_field_u8(p, iid);
),
TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tid=%s, sid=%s, tc=%s, cid=%s, iid=%s",
__entry->uid,
ssam_show_request_id(__entry->rqid),
ssam_show_request_type(__entry->state),
ssam_show_request_state(__entry->state),
ssam_show_ssh_tid(__entry->tid),
ssam_show_ssh_tid(__entry->sid),
ssam_show_ssh_tc(__entry->tc),
ssam_show_generic_u8_field(__entry->cid),
ssam_show_generic_u8_field(__entry->iid)
)
);
#define DEFINE_SSAM_REQUEST_EVENT(name) \
DEFINE_EVENT(ssam_request_class, ssam_##name, \
TP_PROTO(const struct ssh_request *request), \
TP_ARGS(request) \
)
DECLARE_EVENT_CLASS(ssam_request_status_class,
TP_PROTO(const struct ssh_request *request, int status),
TP_ARGS(request, status),
TP_STRUCT__entry(
__field(unsigned long, state)
__field(u32, rqid)
__field(int, status)
__array(char, uid, SSAM_PTR_UID_LEN)
__field(u8, tc)
__field(u16, cid)
__field(u16, iid)
__field(u8, tid)
__field(u8, sid)
),
TP_fast_assign(
const struct ssh_packet *p = &request->packet;
/* Use packet for UID so we can match requests to packets. */
__entry->state = READ_ONCE(request->state);
__entry->rqid = ssam_trace_get_request_id(p);
__entry->status = status;
ssam_trace_ptr_uid(p, __entry->uid);
__entry->tid = ssam_trace_get_request_tid(p);
__entry->sid = ssam_trace_get_request_sid(p);
__entry->tc = ssam_trace_get_request_tc(p);
__entry->cid = ssam_trace_get_command_field_u8(p, cid);
__entry->iid = ssam_trace_get_command_field_u8(p, iid);
),
TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tid=%s, sid=%s, tc=%s, cid=%s, iid=%s, status=%d",
__entry->uid,
ssam_show_request_id(__entry->rqid),
ssam_show_request_type(__entry->state),
ssam_show_request_state(__entry->state),
ssam_show_ssh_tid(__entry->tid),
ssam_show_ssh_tid(__entry->sid),
ssam_show_ssh_tc(__entry->tc),
ssam_show_generic_u8_field(__entry->cid),
ssam_show_generic_u8_field(__entry->iid),
__entry->status
)
);
#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \
DEFINE_EVENT(ssam_request_status_class, ssam_##name, \
TP_PROTO(const struct ssh_request *request, int status),\
TP_ARGS(request, status) \
)
DECLARE_EVENT_CLASS(ssam_alloc_class,
TP_PROTO(void *ptr, size_t len),
TP_ARGS(ptr, len),
TP_STRUCT__entry(
__field(size_t, len)
__array(char, uid, SSAM_PTR_UID_LEN)
),
TP_fast_assign(
__entry->len = len;
ssam_trace_ptr_uid(ptr, __entry->uid);
),
TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len)
);
#define DEFINE_SSAM_ALLOC_EVENT(name) \
DEFINE_EVENT(ssam_alloc_class, ssam_##name, \
TP_PROTO(void *ptr, size_t len), \
TP_ARGS(ptr, len) \
)
DECLARE_EVENT_CLASS(ssam_free_class,
TP_PROTO(void *ptr),
TP_ARGS(ptr),
TP_STRUCT__entry(
__array(char, uid, SSAM_PTR_UID_LEN)
),
TP_fast_assign(
ssam_trace_ptr_uid(ptr, __entry->uid);
),
TP_printk("uid=%s", __entry->uid)
);
#define DEFINE_SSAM_FREE_EVENT(name) \
DEFINE_EVENT(ssam_free_class, ssam_##name, \
TP_PROTO(void *ptr), \
TP_ARGS(ptr) \
)
DECLARE_EVENT_CLASS(ssam_pending_class,
TP_PROTO(unsigned int pending),
TP_ARGS(pending),
TP_STRUCT__entry(
__field(unsigned int, pending)
),
TP_fast_assign(
__entry->pending = pending;
),
TP_printk("pending=%u", __entry->pending)
);
#define DEFINE_SSAM_PENDING_EVENT(name) \
DEFINE_EVENT(ssam_pending_class, ssam_##name, \
TP_PROTO(unsigned int pending), \
TP_ARGS(pending) \
)
DECLARE_EVENT_CLASS(ssam_data_class,
TP_PROTO(size_t length),
TP_ARGS(length),
TP_STRUCT__entry(
__field(size_t, length)
),
TP_fast_assign(
__entry->length = length;
),
TP_printk("length=%zu", __entry->length)
);
#define DEFINE_SSAM_DATA_EVENT(name) \
DEFINE_EVENT(ssam_data_class, ssam_##name, \
TP_PROTO(size_t length), \
TP_ARGS(length) \
)
DEFINE_SSAM_FRAME_EVENT(rx_frame_received);
DEFINE_SSAM_COMMAND_EVENT(rx_response_received);
DEFINE_SSAM_COMMAND_EVENT(rx_event_received);
DEFINE_SSAM_PACKET_EVENT(packet_release);
DEFINE_SSAM_PACKET_EVENT(packet_submit);
DEFINE_SSAM_PACKET_EVENT(packet_resubmit);
DEFINE_SSAM_PACKET_EVENT(packet_timeout);
DEFINE_SSAM_PACKET_EVENT(packet_cancel);
DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete);
DEFINE_SSAM_PENDING_EVENT(ptl_timeout_reap);
DEFINE_SSAM_REQUEST_EVENT(request_submit);
DEFINE_SSAM_REQUEST_EVENT(request_timeout);
DEFINE_SSAM_REQUEST_EVENT(request_cancel);
DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete);
DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap);
DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet);
DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet);
DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet);
DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write);
DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data);
DEFINE_SSAM_DATA_EVENT(ei_rx_corrupt_syn);
DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data);
DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response);
DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc);
DEFINE_SSAM_FREE_EVENT(ctrl_packet_free);
DEFINE_SSAM_ALLOC_EVENT(event_item_alloc);
DEFINE_SSAM_FREE_EVENT(event_item_free);
#endif /* _SURFACE_AGGREGATOR_TRACE_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>