346 lines · 14.3 KB · Text
RawDownload
.. SPDX-License-Identifier: GPL-2.0+ .. |u8| replace:: :c:type:`u8 <u8>`.. |u16| replace:: :c:type:`u16 <u16>`.. |TYPE| replace:: ``TYPE``.. |LEN| replace:: ``LEN``.. |SEQ| replace:: ``SEQ``.. |SYN| replace:: ``SYN``.. |NAK| replace:: ``NAK``.. |ACK| replace:: ``ACK``.. |DATA| replace:: ``DATA``.. |DATA_SEQ| replace:: ``DATA_SEQ``.. |DATA_NSQ| replace:: ``DATA_NSQ``.. |TC| replace:: ``TC``.. |TID| replace:: ``TID``.. |SID| replace:: ``SID``.. |IID| replace:: ``IID``.. |RQID| replace:: ``RQID``.. |CID| replace:: ``CID`` ===========================Surface Serial Hub Protocol=========================== The Surface Serial Hub (SSH) is the central communication interface for theembedded Surface Aggregator Module controller (SAM or EC), found on newerSurface generations. We will refer to this protocol and interface asSAM-over-SSH, as opposed to SAM-over-HID for the older generations. On Surface devices with SAM-over-SSH, SAM is connected to the host via UARTand defined in ACPI as device with ID ``MSHW0084``. On these devices,significant functionality is provided via SAM, including access to batteryand power information and events, thermal read-outs and events, and manymore. For Surface Laptops, keyboard input is handled via HID directedthrough SAM, on the Surface Laptop 3 and Surface Book 3 this also includestouchpad input. Note that the standard disclaimer for this subsystem also applies to thisdocument: All of this has been reverse-engineered and may thus be erroneousand/or incomplete. All CRCs used in the following are two-byte ``crc_itu_t(0xffff, ...)``.All multi-byte values are little-endian, there is no implicit padding betweenvalues.  SSH Packet Protocol: Definitions================================ The fundamental communication unit of the SSH protocol is a frame(:c:type:`struct ssh_frame <ssh_frame>`). A frame consists of the followingfields, packed together and in order: .. flat-table:: SSH Frame   :widths: 1 1 4   :header-rows: 1    * - Field     - Type     - Description    * - |TYPE|     - |u8|     - Type identifier of the frame.    * - |LEN|     - |u16|     - Length of the payload associated with the frame.    * - |SEQ|     - |u8|     - Sequence ID (see explanation below). Each frame structure is followed by a CRC over this structure. The CRC overthe frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directlyafter the frame structure and before the payload. The payload is followed byits own CRC (over all payload bytes). If the payload is not present (i.e.the frame has ``LEN=0``), the CRC of the payload is still present and willevaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, itequals the number of bytes between the CRC of the frame and the CRC of thepayload. Additionally, the following fixed two-byte sequences are used: .. flat-table:: SSH Byte Sequences   :widths: 1 1 4   :header-rows: 1    * - Name     - Value     - Description    * - |SYN|     - ``[0xAA, 0x55]``     - Synchronization bytes. A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| andCRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes,followed finally, regardless if the payload is present, the payload CRC. Themessages corresponding to an exchange are, in part, identified by having thesame sequence ID (|SEQ|), stored inside the frame (more on this in the nextsection). The sequence ID is a wrapping counter. A frame can have the following types(:c:type:`enum ssh_frame_type <ssh_frame_type>`): .. flat-table:: SSH Frame Types   :widths: 1 1 4   :header-rows: 1    * - Name     - Value     - Short Description    * - |NAK|     - ``0x04``     - Sent on error in previously received message.    * - |ACK|     - ``0x40``     - Sent to acknowledge receival of |DATA| frame.    * - |DATA_SEQ|     - ``0x80``     - Sent to transfer data. Sequenced.    * - |DATA_NSQ|     - ``0x00``     - Same as |DATA_SEQ|, but does not need to be ACKed. Both |NAK|- and |ACK|-type frames are used to control flow of messages andthus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on theother hand must carry a payload. The flow sequence and interaction ofdifferent frame types will be described in more depth in the next section.  SSH Packet Protocol: Flow Sequence================================== Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. Incase of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a|DATA_SEQ|-type frame, the receiving party has to acknowledge receival ofthe frame by responding with a message containing an |ACK|-type frame withthe same sequence ID of the |DATA| frame. In other words, the sequence ID ofthe |ACK| frame specifies the |DATA| frame to be acknowledged. In case of anerror, e.g. an invalid CRC, the receiving party responds with a messagecontaining an |NAK|-type frame. As the sequence ID of the previous dataframe, for which an error is indicated via the |NAK| frame, cannot be reliedupon, the sequence ID of the |NAK| frame should not be used and is set tozero. After receival of an |NAK| frame, the sending party should re-send alloutstanding (non-ACKed) messages. Sequence IDs are not synchronized between the two parties, meaning that theyare managed independently for each party. Identifying the messagescorresponding to a single exchange thus relies on the sequence ID as well asthe type of the message, and the context. Specifically, the sequence ID isused to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames. An example exchange might look like this: ::     tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) -----------------------------    rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) -- where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)``indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame,``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over theprevious payload. In case of an error, the exchange would look like this: ::     tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) -----------------------------    rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) -- upon which the sender should re-send the message. ``FRAME(N)`` indicates an|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixedto zero. For |DATA_NSQ|-type frames, both exchanges are the same: ::     tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ----------------------    rx: ------------------------------------------------------------------- Here, an error can be detected, but not corrected or indicated to thesending party. These exchanges are symmetric, i.e. switching ``rx`` and``tx`` results again in a valid exchange. Currently, no longer exchanges areknown.  Commands: Requests, Responses, and Events========================================= Commands are sent as payload inside a data frame. Currently, this is theonly known payload type of |DATA| frames, with a payload-type value of``0x80`` (:c:type:`SSH_PLD_TYPE_CMD <ssh_payload_type>`). The command-type payload (:c:type:`struct ssh_command <ssh_command>`)consists of an eight-byte command structure, followed by optional andvariable length command data. The length of this optional data is derivedfrom the frame payload length given in the corresponding frame, i.e. it is``frame.len - sizeof(struct ssh_command)``. The command struct contains thefollowing fields, packed together and in order: .. flat-table:: SSH Command   :widths: 1 1 4   :header-rows: 1    * - Field     - Type     - Description    * - |TYPE|     - |u8|     - Type of the payload. For commands always ``0x80``.    * - |TC|     - |u8|     - Target category.    * - |TID|     - |u8|     - Target ID for commands/messages.    * - |SID|     - |u8|     - Source ID for commands/messages.    * - |IID|     - |u8|     - Instance ID.    * - |RQID|     - |u16|     - Request ID.    * - |CID|     - |u8|     - Command ID. The command struct and data, in general, does not contain any failuredetection mechanism (e.g. CRCs), this is solely done on the frame level. Command-type payloads are used by the host to send commands and requests tothe EC as well as by the EC to send responses and events back to the host.We differentiate between requests (sent by the host), responses (sent by theEC in response to a request), and events (sent by the EC without a precedingrequest). Commands and events are uniquely identified by their target category(``TC``) and command ID (``CID``). The target category specifies a generalcategory for the command (e.g. system in general, vs. battery and AC, vs.temperature, and so on), while the command ID specifies the command insidethat category. Only the combination of |TC| + |CID| is unique. Additionally,commands have an instance ID (``IID``), which is used to differentiatebetween different sub-devices. For example ``TC=3`` ``CID=1`` is arequest to get the temperature on a thermal sensor, where |IID| specifiesthe respective sensor. If the instance ID is not used, it should be set tozero. If instance IDs are used, they, in general, start with a value of one,whereas zero may be used for instance independent queries, if applicable. Aresponse to a request should have the same target category, command ID, andinstance ID as the corresponding request. Responses are matched to their corresponding request via the request ID(``RQID``) field. This is a 16 bit wrapping counter similar to the sequenceID on the frames. Note that the sequence ID of the frames for arequest-response pair does not match. Only the request ID has to match.Frame-protocol wise these are two separate exchanges, and may even beseparated, e.g. by an event being sent after the request but before theresponse. Not all commands produce a response, and this is not detectable by|TC| + |CID|. It is the responsibility of the issuing party to wait for aresponse (or signal this to the communication framework, as is done inSAN/ACPI via the ``SNC`` flag). Events are identified by unique and reserved request IDs. These IDs shouldnot be used by the host when sending a new request. They are used on thehost to, first, detect events and, second, match them with a registeredevent handler. Request IDs for events are chosen by the host and directed tothe EC when setting up and enabling an event source (via theenable-event-source request). The EC then uses the specified request ID forevents sent from the respective source. Note that an event should still beidentified by its target category, command ID, and, if applicable, instanceID, as a single event source can send multiple different event types. Ingeneral, however, a single target category should map to a single reservedevent request ID. Furthermore, requests, responses, and events have an associated target ID(``TID``) and source ID (``SID``). These two fields indicate where a messageoriginates from (``SID``) and what the intended target of the message is(``TID``). Note that a response to a specific request therefore has the sourceand target IDs swapped when compared to the original request (i.e. the requesttarget is the response source and the request source is the response target).See (:c:type:`enum ssh_request_id <ssh_request_id>`) for possible values ofboth. Note that, even though requests and events should be uniquely identifiable bytarget category and command ID alone, the EC may require specific target ID andinstance ID values to accept a command. A command that is accepted for``TID=1``, for example, may not be accepted for ``TID=2`` and vice versa. Whilethis may not always hold in reality, you can think of different target/sourceIDs indicating different physical ECs with potentially different feature sets.  Limitations and Observations============================ The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel,with up to ``U16_MAX`` pending requests (neglecting request IDs reserved forevents). In practice, however, this is more limited. From our testing(although via a python and thus a user-space program), it seems that the ECcan handle up to four requests (mostly) reliably in parallel at a certaintime. With five or more requests in parallel, consistent discarding ofcommands (ACKed frame but no command response) has been observed. For fivesimultaneous commands, this reproducibly resulted in one command beingdropped and four commands being handled. However, it has also been noted that, even with three requests in parallel,occasional frame drops happen. Apart from this, with a limit of threepending requests, no dropped commands (i.e. command being dropped but framecarrying command being ACKed) have been observed. In any case, frames (andpossibly also commands) should be re-sent by the host if a certain timeoutis exceeded. This is done by the EC for frames with a timeout of one second,up to two re-tries (i.e. three transmissions in total). The limit ofre-tries also applies to received NAKs, and, in a worst case scenario, canlead to entire messages being dropped. While this also seems to work fine for pending data frames as long as notransmission failures occur, implementation and handling of these seems todepend on the assumption that there is only one non-acknowledged data frame.In particular, the detection of repeated frames relies on the last sequencenumber. This means that, if a frame that has been successfully received bythe EC is sent again, e.g. due to the host not receiving an |ACK|, the ECwill only detect this if it has the sequence ID of the last frame receivedby the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1``followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0``frame as such, and thus execute the command in this frame each time it hasbeen received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` andthen repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition ofthe first one and ignore it, thus executing the contained command only once. In conclusion, this suggests a limit of at most one pending un-ACKed frame(per party, effectively leading to synchronous communication regardingframes) and at most three pending commands. The limit to synchronous frametransfers seems to be consistent with behavior observed on Windows.