OpenCloudOS-Kernel/drivers/platform/surface/aggregator/ssh_parser.c

229 lines
7.3 KiB
C
Raw Normal View History

platform/surface: Add Surface Aggregator subsystem Add Surface System Aggregator Module core and Surface Serial Hub driver, required for the embedded controller found on Microsoft Surface devices. The Surface System Aggregator Module (SSAM, SAM or Surface Aggregator) is an embedded controller (EC) found on 4th and later generation Microsoft Surface devices, with the exception of the Surface Go series. This EC provides various functionality, depending on the device in question. This can include battery status and thermal reporting (5th and later generations), but also HID keyboard (6th+) and touchpad input (7th+) on Surface Laptop and Surface Book 3 series devices. This patch provides the basic necessities for communication with the SAM EC on 5th and later generation devices. On these devices, the EC provides an interface that acts as serial device, called the Surface Serial Hub (SSH). 4th generation devices, on which the EC interface is provided via an HID-over-I2C device, are not supported by this patch. Specifically, this patch adds a driver for the SSH device (device HID MSHW0084 in ACPI), as well as a controller structure and associated API. This represents the functional core of the Surface Aggregator kernel subsystem, introduced with this patch, and will be expanded upon in subsequent commits. The SSH driver acts as the main attachment point for this subsystem and sets-up and manages the controller structure. The controller in turn provides a basic communication interface, allowing to send requests from host to EC and receiving the corresponding responses, as well as managing and receiving events, sent from EC to host. It is structured into multiple layers, with the top layer presenting the API used by other kernel drivers and the lower layers modeled after the serial protocol used for communication. Said other drivers are then responsible for providing the (Surface model specific) functionality accessible through the EC (e.g. battery status reporting, thermal information, ...) via said controller structure and API, and will be added in future commits. Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com> Link: https://lore.kernel.org/r/20201221183959.1186143-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede <hdegoede@redhat.com>
2020-12-22 02:39:51 +08:00
// 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;
}