Commit c167b9c7 authored by Maximilian Luz's avatar Maximilian Luz Committed by Hans de Goede

platform/surface: Add Surface Aggregator subsystem

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

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

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

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

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

Said other drivers are then responsible for providing the (Surface model
specific) functionality accessible through the EC (e.g. battery status
reporting, thermal information, ...) via said controller structure and
API, and will be added in future commits.
Signed-off-by: default avatarMaximilian Luz <luzmaximilian@gmail.com>
Link: https://lore.kernel.org/r/20201221183959.1186143-2-luzmaximilian@gmail.comSigned-off-by: default avatarHans de Goede <hdegoede@redhat.com>
parent 5b569302
......@@ -11813,6 +11813,14 @@ L: platform-driver-x86@vger.kernel.org
S: Supported
F: drivers/platform/surface/surfacepro3_button.c
MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM
M: Maximilian Luz <luzmaximilian@gmail.com>
S: Maintained
W: https://github.com/linux-surface/surface-aggregator-module
C: irc://chat.freenode.net/##linux-surface
F: drivers/platform/surface/aggregator/
F: include/linux/surface_aggregator/
MICROTEK X6 SCANNER
M: Oliver Neukum <oliver@neukum.org>
S: Maintained
......
......@@ -56,4 +56,6 @@ config SURFACE_PRO3_BUTTON
help
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
source "drivers/platform/surface/aggregator/Kconfig"
endif # SURFACE_PLATFORMS
......@@ -7,5 +7,6 @@
obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
menuconfig SURFACE_AGGREGATOR
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
depends on SERIAL_DEV_BUS
select CRC_CCITT
help
The Surface System Aggregator Module (Surface SAM or SSAM) is an
embedded controller (EC) found on 5th- and later-generation Microsoft
Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop,
and newer, with exception of Surface Go series devices).
Depending on the device in question, this EC provides varying
functionality, including:
- EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation)
- battery status information (all devices)
- thermal sensor access (all devices)
- performance mode / cooling mode control (all devices)
- clipboard detachment system control (Surface Book 2 and 3)
- HID / keyboard input (Surface Laptops, Surface Book 3)
This option controls whether the Surface SAM subsystem core will be
built. This includes a driver for the Surface Serial Hub (SSH), which
is the device responsible for the communication with the EC, and a
basic kernel interface exposing the EC functionality to other client
drivers, i.e. allowing them to make requests to the EC and receive
events from it. Selecting this option alone will not provide any
client drivers and therefore no functionality beyond the in-kernel
interface. Said functionality is the responsibility of the respective
client drivers.
Note: While 4th-generation Surface devices also make use of a SAM EC,
due to a difference in the communication interface of the controller,
only 5th and later generations are currently supported. Specifically,
devices using SAM-over-SSH are supported, whereas devices using
SAM-over-HID, which is used on the 4th generation, are currently not
supported.
Choose m if you want to build the SAM subsystem core and SSH driver as
module, y if you want to build it into the kernel and n if you don't
want it at all.
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o
surface_aggregator-objs := core.o
surface_aggregator-objs += ssh_parser.o
surface_aggregator-objs += ssh_packet_layer.o
surface_aggregator-objs += ssh_request_layer.o
surface_aggregator-objs += controller.o
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Main SSAM/SSH controller structure and functionality.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
#define _SURFACE_AGGREGATOR_CONTROLLER_H
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/rwsem.h>
#include <linux/serdev.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/serial_hub.h>
#include "ssh_request_layer.h"
/* -- Safe counters. -------------------------------------------------------- */
/**
* struct ssh_seq_counter - Safe counter for SSH sequence IDs.
* @value: The current counter value.
*/
struct ssh_seq_counter {
u8 value;
};
/**
* struct ssh_rqid_counter - Safe counter for SSH request IDs.
* @value: The current counter value.
*/
struct ssh_rqid_counter {
u16 value;
};
/* -- Event/notification system. -------------------------------------------- */
/**
* struct ssam_nf_head - Notifier head for SSAM events.
* @srcu: The SRCU struct for synchronization.
* @head: List-head for notifier blocks registered under this head.
*/
struct ssam_nf_head {
struct srcu_struct srcu;
struct list_head head;
};
/**
* struct ssam_nf - Notifier callback- and activation-registry for SSAM events.
* @lock: Lock guarding (de-)registration of notifier blocks. Note: This
* lock does not need to be held for notifier calls, only
* registration and deregistration.
* @refcount: The root of the RB-tree used for reference-counting enabled
* events/notifications.
* @head: The list of notifier heads for event/notification callbacks.
*/
struct ssam_nf {
struct mutex lock;
struct rb_root refcount;
struct ssam_nf_head head[SSH_NUM_EVENTS];
};
/* -- Event/async request completion system. -------------------------------- */
struct ssam_cplt;
/**
* struct ssam_event_item - Struct for event queuing and completion.
* @node: The node in the queue.
* @rqid: The request ID of the event.
* @event: Actual event data.
*/
struct ssam_event_item {
struct list_head node;
u16 rqid;
struct ssam_event event; /* must be last */
};
/**
* struct ssam_event_queue - Queue for completing received events.
* @cplt: Reference to the completion system on which this queue is active.
* @lock: The lock for any operation on the queue.
* @head: The list-head of the queue.
* @work: The &struct work_struct performing completion work for this queue.
*/
struct ssam_event_queue {
struct ssam_cplt *cplt;
spinlock_t lock;
struct list_head head;
struct work_struct work;
};
/**
* struct ssam_event_target - Set of queues for a single SSH target ID.
* @queue: The array of queues, one queue per event ID.
*/
struct ssam_event_target {
struct ssam_event_queue queue[SSH_NUM_EVENTS];
};
/**
* struct ssam_cplt - SSAM event/async request completion system.
* @dev: The device with which this system is associated. Only used
* for logging.
* @wq: The &struct workqueue_struct on which all completion work
* items are queued.
* @event: Event completion management.
* @event.target: Array of &struct ssam_event_target, one for each target.
* @event.notif: Notifier callbacks and event activation reference counting.
*/
struct ssam_cplt {
struct device *dev;
struct workqueue_struct *wq;
struct {
struct ssam_event_target target[SSH_NUM_TARGETS];
struct ssam_nf notif;
} event;
};
/* -- Main SSAM device structures. ------------------------------------------ */
/**
* enum ssam_controller_state - State values for &struct ssam_controller.
* @SSAM_CONTROLLER_UNINITIALIZED:
* The controller has not been initialized yet or has been deinitialized.
* @SSAM_CONTROLLER_INITIALIZED:
* The controller is initialized, but has not been started yet.
* @SSAM_CONTROLLER_STARTED:
* The controller has been started and is ready to use.
* @SSAM_CONTROLLER_STOPPED:
* The controller has been stopped.
* @SSAM_CONTROLLER_SUSPENDED:
* The controller has been suspended.
*/
enum ssam_controller_state {
SSAM_CONTROLLER_UNINITIALIZED,
SSAM_CONTROLLER_INITIALIZED,
SSAM_CONTROLLER_STARTED,
SSAM_CONTROLLER_STOPPED,
SSAM_CONTROLLER_SUSPENDED,
};
/**
* struct ssam_controller_caps - Controller device capabilities.
* @ssh_power_profile: SSH power profile.
* @ssh_buffer_size: SSH driver UART buffer size.
* @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout.
* @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout.
* @d3_closes_handle: SAM closes UART handle in D3.
*
* Controller and SSH device capabilities found in ACPI.
*/
struct ssam_controller_caps {
u32 ssh_power_profile;
u32 ssh_buffer_size;
u32 screen_on_sleep_idle_timeout;
u32 screen_off_sleep_idle_timeout;
u32 d3_closes_handle:1;
};
/**
* struct ssam_controller - SSAM controller device.
* @kref: Reference count of the controller.
* @lock: Main lock for the controller, used to guard state changes.
* @state: Controller state.
* @rtl: Request transport layer for SSH I/O.
* @cplt: Completion system for SSH/SSAM events and asynchronous requests.
* @counter: Safe SSH message ID counters.
* @counter.seq: Sequence ID counter.
* @counter.rqid: Request ID counter.
* @irq: Wakeup IRQ resources.
* @irq.num: The wakeup IRQ number.
* @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend.
* @caps: The controller device capabilities.
*/
struct ssam_controller {
struct kref kref;
struct rw_semaphore lock;
enum ssam_controller_state state;
struct ssh_rtl rtl;
struct ssam_cplt cplt;
struct {
struct ssh_seq_counter seq;
struct ssh_rqid_counter rqid;
} counter;
struct {
int num;
bool wakeup_enabled;
} irq;
struct ssam_controller_caps caps;
};
#define to_ssam_controller(ptr, member) \
container_of(ptr, struct ssam_controller, member)
#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__)
/**
* ssam_controller_receive_buf() - Provide input-data to the controller.
* @ctrl: The controller.
* @buf: The input buffer.
* @n: The number of bytes in the input buffer.
*
* Provide input data to be evaluated by the controller, which has been
* received via the lower-level transport.
*
* Return: Returns the number of bytes consumed, or, if the packet transport
* layer of the controller has been shut down, %-ESHUTDOWN.
*/
static inline
int ssam_controller_receive_buf(struct ssam_controller *ctrl,
const unsigned char *buf, size_t n)
{
return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n);
}
/**
* ssam_controller_write_wakeup() - Notify the controller that the underlying
* device has space available for data to be written.
* @ctrl: The controller.
*/
static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl)
{
ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl);
}
int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s);
int ssam_controller_start(struct ssam_controller *ctrl);
void ssam_controller_shutdown(struct ssam_controller *ctrl);
void ssam_controller_destroy(struct ssam_controller *ctrl);
int ssam_notifier_disable_registered(struct ssam_controller *ctrl);
void ssam_notifier_restore_registered(struct ssam_controller *ctrl);
int ssam_irq_setup(struct ssam_controller *ctrl);
void ssam_irq_free(struct ssam_controller *ctrl);
int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl);
void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl);
void ssam_controller_lock(struct ssam_controller *c);
void ssam_controller_unlock(struct ssam_controller *c);
int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version);
int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl);
int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl);
int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl);
int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl);
int ssam_controller_suspend(struct ssam_controller *ctrl);
int ssam_controller_resume(struct ssam_controller *ctrl);
#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH message builder functions.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
#define _SURFACE_AGGREGATOR_SSH_MSGB_H
#include <asm/unaligned.h>
#include <linux/types.h>
#include <linux/surface_aggregator/controller.h>
#include <linux/surface_aggregator/serial_hub.h>
/**
* struct msgbuf - Buffer struct to construct SSH messages.
* @begin: Pointer to the beginning of the allocated buffer space.
* @end: Pointer to the end (one past last element) of the allocated buffer
* space.
* @ptr: Pointer to the first free element in the buffer.
*/
struct msgbuf {
u8 *begin;
u8 *end;
u8 *ptr;
};
/**
* msgb_init() - Initialize the given message buffer struct.
* @msgb: The buffer struct to initialize
* @ptr: Pointer to the underlying memory by which the buffer will be backed.
* @cap: Size of the underlying memory.
*
* Initialize the given message buffer struct using the provided memory as
* backing.
*/
static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap)
{
msgb->begin = ptr;
msgb->end = ptr + cap;
msgb->ptr = ptr;
}
/**
* msgb_bytes_used() - Return the current number of bytes used in the buffer.
* @msgb: The message buffer.
*/
static inline size_t msgb_bytes_used(const struct msgbuf *msgb)
{
return msgb->ptr - msgb->begin;
}
static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value)
{
*msgb->ptr = value;
msgb->ptr += sizeof(u8);
}
static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value)
{
put_unaligned_le16(value, msgb->ptr);
msgb->ptr += sizeof(u16);
}
/**
* msgb_push_u16() - Push a u16 value to the buffer.
* @msgb: The message buffer.
* @value: The value to push to the buffer.
*/
static inline void msgb_push_u16(struct msgbuf *msgb, u16 value)
{
if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end))
return;
__msgb_push_u16(msgb, value);
}
/**
* msgb_push_syn() - Push SSH SYN bytes to the buffer.
* @msgb: The message buffer.
*/
static inline void msgb_push_syn(struct msgbuf *msgb)
{
msgb_push_u16(msgb, SSH_MSG_SYN);
}
/**
* msgb_push_buf() - Push raw data to the buffer.
* @msgb: The message buffer.
* @buf: The data to push to the buffer.
* @len: The length of the data to push to the buffer.
*/
static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len)
{
msgb->ptr = memcpy(msgb->ptr, buf, len) + len;
}
/**
* msgb_push_crc() - Compute CRC and push it to the buffer.
* @msgb: The message buffer.
* @buf: The data for which the CRC should be computed.
* @len: The length of the data for which the CRC should be computed.
*/
static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len)
{
msgb_push_u16(msgb, ssh_crc(buf, len));
}
/**
* msgb_push_frame() - Push a SSH message frame header to the buffer.
* @msgb: The message buffer
* @ty: The type of the frame.
* @len: The length of the payload of the frame.
* @seq: The sequence ID of the frame/packet.
*/
static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq)
{
u8 *const begin = msgb->ptr;
if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end))
return;
__msgb_push_u8(msgb, ty); /* Frame type. */
__msgb_push_u16(msgb, len); /* Frame payload length. */
__msgb_push_u8(msgb, seq); /* Frame sequence ID. */
msgb_push_crc(msgb, begin, msgb->ptr - begin);
}
/**
* msgb_push_ack() - Push a SSH ACK frame to the buffer.
* @msgb: The message buffer
* @seq: The sequence ID of the frame/packet to be ACKed.
*/
static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq)
{
/* SYN. */
msgb_push_syn(msgb);
/* ACK-type frame + CRC. */
msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq);
/* Payload CRC (ACK-type frames do not have a payload). */
msgb_push_crc(msgb, msgb->ptr, 0);
}
/**
* msgb_push_nak() - Push a SSH NAK frame to the buffer.
* @msgb: The message buffer
*/
static inline void msgb_push_nak(struct msgbuf *msgb)
{
/* SYN. */
msgb_push_syn(msgb);
/* NAK-type frame + CRC. */
msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00);
/* Payload CRC (ACK-type frames do not have a payload). */
msgb_push_crc(msgb, msgb->ptr, 0);
}
/**
* msgb_push_cmd() - Push a SSH command frame with payload to the buffer.
* @msgb: The message buffer.
* @seq: The sequence ID (SEQ) of the frame/packet.
* @rqid: The request ID (RQID) of the request contained in the frame.
* @rqst: The request to wrap in the frame.
*/
static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid,
const struct ssam_request *rqst)
{
const u8 type = SSH_FRAME_TYPE_DATA_SEQ;
u8 *cmd;
/* SYN. */
msgb_push_syn(msgb);
/* Command frame + CRC. */
msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq);
/* Frame payload: Command struct + payload. */
if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end))
return;
cmd = msgb->ptr;
__msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */
__msgb_push_u8(msgb, rqst->target_category); /* Target category. */
__msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */
__msgb_push_u8(msgb, 0x00); /* Target ID (in). */
__msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */
__msgb_push_u16(msgb, rqid); /* Request ID. */
__msgb_push_u8(msgb, rqst->command_id); /* Command ID. */
/* Command payload. */
msgb_push_buf(msgb, rqst->payload, rqst->length);
/* CRC for command struct + payload. */
msgb_push_crc(msgb, cmd, msgb->ptr - cmd);
}
#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH packet transport layer.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
#include <linux/atomic.h>
#include <linux/kfifo.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/serdev.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/surface_aggregator/serial_hub.h>
#include "ssh_parser.h"
/**
* enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl.
*
* @SSH_PTL_SF_SHUTDOWN_BIT:
* Indicates that the packet transport layer has been shut down or is
* being shut down and should not accept any new packets/data.
*/
enum ssh_ptl_state_flags {
SSH_PTL_SF_SHUTDOWN_BIT,
};
/**
* struct ssh_ptl_ops - Callback operations for packet transport layer.
* @data_received: Function called when a data-packet has been received. Both,
* the packet layer on which the packet has been received and
* the packet's payload data are provided to this function.
*/
struct ssh_ptl_ops {
void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data);
};
/**
* struct ssh_ptl - SSH packet transport layer.
* @serdev: Serial device providing the underlying data transport.
* @state: State(-flags) of the transport layer.
* @queue: Packet submission queue.
* @queue.lock: Lock for modifying the packet submission queue.
* @queue.head: List-head of the packet submission queue.
* @pending: Set/list of pending packets.
* @pending.lock: Lock for modifying the pending set.
* @pending.head: List-head of the pending set/list.
* @pending.count: Number of currently pending packets.
* @tx: Transmitter subsystem.
* @tx.running: Flag indicating (desired) transmitter thread state.
* @tx.thread: Transmitter thread.
* @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer.
* @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets.
* @tx.packet_wq: Waitqueue-head for packet transmit completion.
* @rx: Receiver subsystem.
* @rx.thread: Receiver thread.
* @rx.wq: Waitqueue-head for receiver thread.
* @rx.fifo: Buffer for receiving data/pushing data to receiver thread.
* @rx.buf: Buffer for evaluating data on receiver thread.
* @rx.blocked: List of recent/blocked sequence IDs to detect retransmission.
* @rx.blocked.seqs: Array of blocked sequence IDs.
* @rx.blocked.offset: Offset indicating where a new ID should be inserted.
* @rtx_timeout: Retransmission timeout subsystem.
* @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper.
* @rtx_timeout.timeout: Timeout interval for retransmission.
* @rtx_timeout.expires: Time specifying when the reaper work is next scheduled.
* @rtx_timeout.reaper: Work performing timeout checks and subsequent actions.
* @ops: Packet layer operations.
*/
struct ssh_ptl {
struct serdev_device *serdev;
unsigned long state;
struct {
spinlock_t lock;
struct list_head head;
} queue;
struct {
spinlock_t lock;
struct list_head head;
atomic_t count;
} pending;
struct {
atomic_t running;
struct task_struct *thread;
struct completion thread_cplt_tx;
struct completion thread_cplt_pkt;
struct wait_queue_head packet_wq;
} tx;
struct {
struct task_struct *thread;
struct wait_queue_head wq;
struct kfifo fifo;
struct sshp_buf buf;
struct {
u16 seqs[8];
u16 offset;
} blocked;
} rx;
struct {
spinlock_t lock;
ktime_t timeout;
ktime_t expires;
struct delayed_work reaper;
} rtx_timeout;
struct ssh_ptl_ops ops;
};
#define __ssam_prcond(func, p, fmt, ...) \
do { \
typeof(p) __p = (p); \
\
if (__p) \
func(__p, fmt, ##__VA_ARGS__); \
} while (0)
#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__)
#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__)
#define to_ssh_ptl(ptr, member) \
container_of(ptr, struct ssh_ptl, member)
int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev,
struct ssh_ptl_ops *ops);
void ssh_ptl_destroy(struct ssh_ptl *ptl);
/**
* ssh_ptl_get_device() - Get device associated with packet transport layer.
* @ptl: The packet transport layer.
*
* Return: Returns the device on which the given packet transport layer builds
* upon.
*/
static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl)
{
return ptl->serdev ? &ptl->serdev->dev : NULL;
}
int ssh_ptl_tx_start(struct ssh_ptl *ptl);
int ssh_ptl_tx_stop(struct ssh_ptl *ptl);
int ssh_ptl_rx_start(struct ssh_ptl *ptl);
int ssh_ptl_rx_stop(struct ssh_ptl *ptl);
void ssh_ptl_shutdown(struct ssh_ptl *ptl);
int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p);
void ssh_ptl_cancel(struct ssh_packet *p);
int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n);
/**
* ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for
* transfer.
* @ptl: The packet transport layer.
*
* Wakes up the packet transmitter thread, notifying it that the underlying
* transport has more space for data to be transmitted. If the packet
* transport layer has been shut down, calls to this function will be ignored.
*/
static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl)
{
if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state))
return;
complete(&ptl->tx.thread_cplt_tx);
}
void ssh_packet_init(struct ssh_packet *packet, unsigned long type,
u8 priority, const struct ssh_packet_ops *ops);
#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */
// SPDX-License-Identifier: GPL-2.0+
/*
* SSH message parser.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
#include <linux/compiler.h>
#include <linux/device.h>
#include <linux/types.h>
#include <linux/surface_aggregator/serial_hub.h>
#include "ssh_parser.h"
/**
* sshp_validate_crc() - Validate a CRC in raw message data.
* @src: The span of data over which the CRC should be computed.
* @crc: The pointer to the expected u16 CRC value.
*
* Computes the CRC of the provided data span (@src), compares it to the CRC
* stored at the given address (@crc), and returns the result of this
* comparison, i.e. %true if equal. This function is intended to run on raw
* input/message data.
*
* Return: Returns %true if the computed CRC matches the stored CRC, %false
* otherwise.
*/
static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc)
{
u16 actual = ssh_crc(src->ptr, src->len);
u16 expected = get_unaligned_le16(crc);
return actual == expected;
}
/**
* sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes.
* @src: The data span to check the start of.
*/
static bool sshp_starts_with_syn(const struct ssam_span *src)
{
return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN;
}
/**
* sshp_find_syn() - Find SSH SYN bytes in the given data span.
* @src: The data span to search in.
* @rem: The span (output) indicating the remaining data, starting with SSH
* SYN bytes, if found.
*
* Search for SSH SYN bytes in the given source span. If found, set the @rem
* span to the remaining data, starting with the first SYN bytes and capped by
* the source span length, and return %true. This function does not copy any
* data, but rather only sets pointers to the respective start addresses and
* length values.
*
* If no SSH SYN bytes could be found, set the @rem span to the zero-length
* span at the end of the source span and return %false.
*
* If partial SSH SYN bytes could be found at the end of the source span, set
* the @rem span to cover these partial SYN bytes, capped by the end of the
* source span, and return %false. This function should then be re-run once
* more data is available.
*
* Return: Returns %true if a complete SSH SYN sequence could be found,
* %false otherwise.
*/
bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem)
{
size_t i;
for (i = 0; i < src->len - 1; i++) {
if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) {
rem->ptr = src->ptr + i;
rem->len = src->len - i;
return true;
}
}
if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) {
rem->ptr = src->ptr + src->len - 1;
rem->len = 1;
return false;
}
rem->ptr = src->ptr + src->len;
rem->len = 0;
return false;
}
/**
* sshp_parse_frame() - Parse SSH frame.
* @dev: The device used for logging.
* @source: The source to parse from.
* @frame: The parsed frame (output).
* @payload: The parsed payload (output).
* @maxlen: The maximum supported message length.
*
* Parses and validates a SSH frame, including its payload, from the given
* source. Sets the provided @frame pointer to the start of the frame and
* writes the limits of the frame payload to the provided @payload span
* pointer.
*
* This function does not copy any data, but rather only validates the message
* data and sets pointers (and length values) to indicate the respective parts.
*
* If no complete SSH frame could be found, the frame pointer will be set to
* the %NULL pointer and the payload span will be set to the null span (start
* pointer %NULL, size zero).
*
* Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if
* the start of the message is invalid, %-EBADMSG if any (frame-header or
* payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than
* the maximum message length specified in the @maxlen parameter.
*/
int sshp_parse_frame(const struct device *dev, const struct ssam_span *source,
struct ssh_frame **frame, struct ssam_span *payload,
size_t maxlen)
{
struct ssam_span sf;
struct ssam_span sp;
/* Initialize output. */
*frame = NULL;
payload->ptr = NULL;
payload->len = 0;
if (!sshp_starts_with_syn(source)) {
dev_warn(dev, "rx: parser: invalid start of frame\n");
return -ENOMSG;
}
/* Check for minimum packet length. */
if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) {
dev_dbg(dev, "rx: parser: not enough data for frame\n");
return 0;
}
/* Pin down frame. */
sf.ptr = source->ptr + sizeof(u16);
sf.len = sizeof(struct ssh_frame);
/* Validate frame CRC. */
if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) {
dev_warn(dev, "rx: parser: invalid frame CRC\n");
return -EBADMSG;
}
/* Ensure packet does not exceed maximum length. */
sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len);
if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) {
dev_warn(dev, "rx: parser: frame too large: %llu bytes\n",
SSH_MESSAGE_LENGTH(sp.len));
return -EMSGSIZE;
}
/* Pin down payload. */
sp.ptr = sf.ptr + sf.len + sizeof(u16);
/* Check for frame + payload length. */
if (source->len < SSH_MESSAGE_LENGTH(sp.len)) {
dev_dbg(dev, "rx: parser: not enough data for payload\n");
return 0;
}
/* Validate payload CRC. */
if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) {
dev_warn(dev, "rx: parser: invalid payload CRC\n");
return -EBADMSG;
}
*frame = (struct ssh_frame *)sf.ptr;
*payload = sp;
dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n",
(*frame)->type, (*frame)->len);
return 0;
}
/**
* sshp_parse_command() - Parse SSH command frame payload.
* @dev: The device used for logging.
* @source: The source to parse from.
* @command: The parsed command (output).
* @command_data: The parsed command data/payload (output).
*
* Parses and validates a SSH command frame payload. Sets the @command pointer
* to the command header and the @command_data span to the command data (i.e.
* payload of the command). This will result in a zero-length span if the
* command does not have any associated data/payload. This function does not
* check the frame-payload-type field, which should be checked by the caller
* before calling this function.
*
* The @source parameter should be the complete frame payload, e.g. returned
* by the sshp_parse_frame() command.
*
* This function does not copy any data, but rather only validates the frame
* payload data and sets pointers (and length values) to indicate the
* respective parts.
*
* Return: Returns zero on success or %-ENOMSG if @source does not represent a
* valid command-type frame payload, i.e. is too short.
*/
int sshp_parse_command(const struct device *dev, const struct ssam_span *source,
struct ssh_command **command,
struct ssam_span *command_data)
{
/* Check for minimum length. */
if (unlikely(source->len < sizeof(struct ssh_command))) {
*command = NULL;
command_data->ptr = NULL;
command_data->len = 0;
dev_err(dev, "rx: parser: command payload is too short\n");
return -ENOMSG;
}
*command = (struct ssh_command *)source->ptr;
command_data->ptr = source->ptr + sizeof(struct ssh_command);
command_data->len = source->len - sizeof(struct ssh_command);
dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n",
(*command)->tc, (*command)->cid);
return 0;
}
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH message parser.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
#define _SURFACE_AGGREGATOR_SSH_PARSER_H
#include <linux/device.h>
#include <linux/kfifo.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/surface_aggregator/serial_hub.h>
/**
* struct sshp_buf - Parser buffer for SSH messages.
* @ptr: Pointer to the beginning of the buffer.
* @len: Number of bytes used in the buffer.
* @cap: Maximum capacity of the buffer.
*/
struct sshp_buf {
u8 *ptr;
size_t len;
size_t cap;
};
/**
* sshp_buf_init() - Initialize a SSH parser buffer.
* @buf: The buffer to initialize.
* @ptr: The memory backing the buffer.
* @cap: The length of the memory backing the buffer, i.e. its capacity.
*
* Initializes the buffer with the given memory as backing and set its used
* length to zero.
*/
static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap)
{
buf->ptr = ptr;
buf->len = 0;
buf->cap = cap;
}
/**
* sshp_buf_alloc() - Allocate and initialize a SSH parser buffer.
* @buf: The buffer to initialize/allocate to.
* @cap: The desired capacity of the buffer.
* @flags: The flags used for allocating the memory.
*
* Allocates @cap bytes and initializes the provided buffer struct with the
* allocated memory.
*
* Return: Returns zero on success and %-ENOMEM if allocation failed.
*/
static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags)
{
u8 *ptr;
ptr = kzalloc(cap, flags);
if (!ptr)
return -ENOMEM;
sshp_buf_init(buf, ptr, cap);
return 0;
}
/**
* sshp_buf_free() - Free a SSH parser buffer.
* @buf: The buffer to free.
*
* Frees a SSH parser buffer by freeing the memory backing it and then
* resetting its pointer to %NULL and length and capacity to zero. Intended to
* free a buffer previously allocated with sshp_buf_alloc().
*/
static inline void sshp_buf_free(struct sshp_buf *buf)
{
kfree(buf->ptr);
buf->ptr = NULL;
buf->len = 0;
buf->cap = 0;
}
/**
* sshp_buf_drop() - Drop data from the beginning of the buffer.
* @buf: The buffer to drop data from.
* @n: The number of bytes to drop.
*
* Drops the first @n bytes from the buffer. Re-aligns any remaining data to
* the beginning of the buffer.
*/
static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n)
{
memmove(buf->ptr, buf->ptr + n, buf->len - n);
buf->len -= n;
}
/**
* sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer.
* @buf: The buffer to write the data into.
* @fifo: The fifo to read the data from.
*
* Transfers the data contained in the fifo to the buffer, removing it from
* the fifo. This function will try to transfer as much data as possible,
* limited either by the remaining space in the buffer or by the number of
* bytes available in the fifo.
*
* Return: Returns the number of bytes transferred.
*/
static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf,
struct kfifo *fifo)
{
size_t n;
n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len);
buf->len += n;
return n;
}
/**
* sshp_buf_span_from() - Initialize a span from the given buffer and offset.
* @buf: The buffer to create the span from.
* @offset: The offset in the buffer at which the span should start.
* @span: The span to initialize (output).
*
* Initializes the provided span to point to the memory at the given offset in
* the buffer, with the length of the span being capped by the number of bytes
* used in the buffer after the offset (i.e. bytes remaining after the
* offset).
*
* Warning: This function does not validate that @offset is less than or equal
* to the number of bytes used in the buffer or the buffer capacity. This must
* be guaranteed by the caller.
*/
static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset,
struct ssam_span *span)
{
span->ptr = buf->ptr + offset;
span->len = buf->len - offset;
}
bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem);
int sshp_parse_frame(const struct device *dev, const struct ssam_span *source,
struct ssh_frame **frame, struct ssam_span *payload,
size_t maxlen);
int sshp_parse_command(const struct device *dev, const struct ssam_span *source,
struct ssh_command **command,
struct ssam_span *command_data);
#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* SSH request transport layer.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
#include <linux/atomic.h>
#include <linux/ktime.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/surface_aggregator/serial_hub.h>
#include <linux/surface_aggregator/controller.h>
#include "ssh_packet_layer.h"
/**
* enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl.
*
* @SSH_RTL_SF_SHUTDOWN_BIT:
* Indicates that the request transport layer has been shut down or is
* being shut down and should not accept any new requests.
*/
enum ssh_rtl_state_flags {
SSH_RTL_SF_SHUTDOWN_BIT,
};
/**
* struct ssh_rtl_ops - Callback operations for request transport layer.
* @handle_event: Function called when a SSH event has been received. The
* specified function takes the request layer, received command
* struct, and corresponding payload as arguments. If the event
* has no payload, the payload span is empty (not %NULL).
*/
struct ssh_rtl_ops {
void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd,
const struct ssam_span *data);
};
/**
* struct ssh_rtl - SSH request transport layer.
* @ptl: Underlying packet transport layer.
* @state: State(-flags) of the transport layer.
* @queue: Request submission queue.
* @queue.lock: Lock for modifying the request submission queue.
* @queue.head: List-head of the request submission queue.
* @pending: Set/list of pending requests.
* @pending.lock: Lock for modifying the request set.
* @pending.head: List-head of the pending set/list.
* @pending.count: Number of currently pending requests.
* @tx: Transmitter subsystem.
* @tx.work: Transmitter work item.
* @rtx_timeout: Retransmission timeout subsystem.
* @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper.
* @rtx_timeout.timeout: Timeout interval for retransmission.
* @rtx_timeout.expires: Time specifying when the reaper work is next scheduled.
* @rtx_timeout.reaper: Work performing timeout checks and subsequent actions.
* @ops: Request layer operations.
*/
struct ssh_rtl {
struct ssh_ptl ptl;
unsigned long state;
struct {
spinlock_t lock;
struct list_head head;
} queue;
struct {
spinlock_t lock;
struct list_head head;
atomic_t count;
} pending;
struct {
struct work_struct work;
} tx;
struct {
spinlock_t lock;
ktime_t timeout;
ktime_t expires;
struct delayed_work reaper;
} rtx_timeout;
struct ssh_rtl_ops ops;
};
#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__)
#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__)
#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__)
#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__)
#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__)
#define to_ssh_rtl(ptr, member) \
container_of(ptr, struct ssh_rtl, member)
/**
* ssh_rtl_get_device() - Get device associated with request transport layer.
* @rtl: The request transport layer.
*
* Return: Returns the device on which the given request transport layer
* builds upon.
*/
static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl)
{
return ssh_ptl_get_device(&rtl->ptl);
}
/**
* ssh_request_rtl() - Get request transport layer associated with request.
* @rqst: The request to get the request transport layer reference for.
*
* Return: Returns the &struct ssh_rtl associated with the given SSH request.
*/
static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst)
{
struct ssh_ptl *ptl;
ptl = READ_ONCE(rqst->packet.ptl);
return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL;
}
int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst);
bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending);
int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev,
const struct ssh_rtl_ops *ops);
int ssh_rtl_start(struct ssh_rtl *rtl);
int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout);
void ssh_rtl_shutdown(struct ssh_rtl *rtl);
void ssh_rtl_destroy(struct ssh_rtl *rtl);
int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags,
const struct ssh_request_ops *ops);
#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment