요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. 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 the
embedded Surface Aggregator Module controller (SAM or EC), found on newer
Surface generations. We will refer to this protocol and interface as
SAM-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 UART
and defined in ACPI as device with ID ``MSHW0084``. On these devices,
significant functionality is provided via SAM, including access to battery
and power information and events, thermal read-outs and events, and many
more. For Surface Laptops, keyboard input is handled via HID directed
through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes
touchpad input.
Note that the standard disclaimer for this subsystem also applies to this
document: All of this has been reverse-engineered and may thus be erroneous
and/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 between
values.
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 following
fields, 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 over
the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly
after the frame structure and before the payload. The payload is followed by
its 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 will
evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it
equals the number of bytes between the CRC of the frame and the CRC of the
payload.
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| and
CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes,
followed finally, regardless if the payload is present, the payload CRC. The
messages corresponding to an exchange are, in part, identified by having the
same sequence ID (|SEQ|), stored inside the frame (more on this in the next
section). 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 and
thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the
other hand must carry a payload. The flow sequence and interaction of
different 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. In
case 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 of
the frame by responding with a message containing an |ACK|-type frame with
the same sequence ID of the |DATA| frame. In other words, the sequence ID of
the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an
error, e.g. an invalid CRC, the receiving party responds with a message
containing an |NAK|-type frame. As the sequence ID of the previous data
frame, for which an error is indicated via the |NAK| frame, cannot be relied
upon, the sequence ID of the |NAK| frame should not be used and is set to
zero. After receival of an |NAK| frame, the sending party should re-send all
outstanding (non-ACKed) messages.
Sequence IDs are not synchronized between the two parties, meaning that they
are managed independently for each party. Identifying the messages
corresponding to a single exchange thus relies on the sequence ID as well as
the type of the message, and the context. Specifically, the sequence ID is
used 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 the
previous 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 fixed
to 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 the
sending party. These exchanges are symmetric, i.e. switching ``rx`` and
``tx`` results again in a valid exchange. Currently, no longer exchanges are
known.
Commands: Requests, Responses, and Events
=========================================
Commands are sent as payload inside a data frame. Currently, this is the
only 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 and
variable length command data. The length of this optional data is derived
from the frame payload length given in the corresponding frame, i.e. it is
``frame.len - sizeof(struct ssh_command)``. The command struct contains the
following 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 failure
detection 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 to
the 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 the
EC in response to a request), and events (sent by the EC without a preceding
request).
Commands and events are uniquely identified by their target category
(``TC``) and command ID (``CID``). The target category specifies a general
category for the command (e.g. system in general, vs. battery and AC, vs.
temperature, and so on), while the command ID specifies the command inside
that category. Only the combination of |TC| + |CID| is unique. Additionally,
commands have an instance ID (``IID``), which is used to differentiate
between different sub-devices. For example ``TC=3`` ``CID=1`` is a
request to get the temperature on a thermal sensor, where |IID| specifies
the respective sensor. If the instance ID is not used, it should be set to
zero. 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. A
response to a request should have the same target category, command ID, and
instance 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 sequence
ID on the frames. Note that the sequence ID of the frames for a
request-response pair does not match. Only the request ID has to match.
Frame-protocol wise these are two separate exchanges, and may even be
separated, e.g. by an event being sent after the request but before the
response. 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 a
response (or signal this to the communication framework, as is done in
SAN/ACPI via the ``SNC`` flag).
Events are identified by unique and reserved request IDs. These IDs should
not be used by the host when sending a new request. They are used on the
host to, first, detect events and, second, match them with a registered
event handler. Request IDs for events are chosen by the host and directed to
the EC when setting up and enabling an event source (via the
enable-event-source request). The EC then uses the specified request ID for
events sent from the respective source. Note that an event should still be
identified by its target category, command ID, and, if applicable, instance
ID, as a single event source can send multiple different event types. In
general, however, a single target category should map to a single reserved
event request ID.
Furthermore, requests, responses, and events have an associated target ID
(``TID``) and source ID (``SID``). These two fields indicate where a message
originates from (``SID``) and what the intended target of the message is
(``TID``). Note that a response to a specific request therefore has the source
and target IDs swapped when compared to the original request (i.e. the request
target 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 of
both.
Note that, even though requests and events should be uniquely identifiable by
target category and command ID alone, the EC may require specific target ID and
instance 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. While
this may not always hold in reality, you can think of different target/source
IDs 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 for
events). In practice, however, this is more limited. From our testing
(although via a python and thus a user-space program), it seems that the EC
can handle up to four requests (mostly) reliably in parallel at a certain
time. With five or more requests in parallel, consistent discarding of
commands (ACKed frame but no command response) has been observed. For five
simultaneous commands, this reproducibly resulted in one command being
dropped 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 three
pending requests, no dropped commands (i.e. command being dropped but frame
carrying command being ACKed) have been observed. In any case, frames (and
possibly also commands) should be re-sent by the host if a certain timeout
is 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 of
re-tries also applies to received NAKs, and, in a worst case scenario, can
lead to entire messages being dropped.
While this also seems to work fine for pending data frames as long as no
transmission failures occur, implementation and handling of these seems to
depend on the assumption that there is only one non-acknowledged data frame.
In particular, the detection of repeated frames relies on the last sequence
number. This means that, if a frame that has been successfully received by
the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC
will only detect this if it has the sequence ID of the last frame received
by 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 has
been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and
then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of
the 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 regarding
frames) and at most three pending commands. The limit to synchronous frame
transfers seems to be consistent with behavior observed on Windows.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Protocol symbol 치환
1-21이 문서는 `GPL-2.0+` SPDX license를 사용합니다.
머리말의 replace 지시문은 `u8`, `u16` type과 SSH frame·command field 및 frame type을 본문에서 일관되게 표시합니다.
.. 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
22-46Surface Serial Hub(SSH)는 최신 Surface 세대의 embedded Surface Aggregator Module controller(SAM 또는 EC)를 위한 중앙 communication interface입니다. 이전 세대의 `SAM-over-HID`와 구분하여 이 protocol과 interface를 `SAM-over-SSH`라고 부릅니다.
SAM-over-SSH device에서 SAM은 UART로 host와 연결되고 ACPI ID `MSHW0084`인 device로 정의됩니다.
SAM은 battery·power 정보와 event, thermal read-out과 event 등 중요한 기능을 제공합니다. Surface Laptop은 keyboard HID input도 SAM을 통하며, Surface Laptop 3와 Surface Book 3에서는 touchpad input까지 포함합니다.
이 문서의 내용은 reverse engineering 결과이므로 잘못됐거나 불완전할 수 있다는 subsystem의 일반적인 주의 사항이 적용됩니다.
뒤에서 사용하는 모든 CRC는 2-byte `crc_itu_t(0xffff, ...)`입니다. 모든 multi-byte 값은 little-endian이며 값 사이에 implicit padding은 없습니다.
Surface Serial Hub Protocol
===========================
The Surface Serial Hub (SSH) is the central communication interface for the
embedded Surface Aggregator Module controller (SAM or EC), found on newer
Surface generations. We will refer to this protocol and interface as
SAM-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 UART
and defined in ACPI as device with ID ``MSHW0084``. On these devices,
significant functionality is provided via SAM, including access to battery
and power information and events, thermal read-outs and events, and many
more. For Surface Laptops, keyboard input is handled via HID directed
through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes
touchpad input.
Note that the standard disclaimer for this subsystem also applies to this
document: All of this has been reverse-engineered and may thus be erroneous
and/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 between
values.
SSH frame 구조와 CRC
47-82SSH protocol의 기본 communication unit은 `struct ssh_frame`입니다. Frame은 `TYPE`, `LEN`, `SEQ` field를 이 순서로 padding 없이 packed합니다.
`TYPE`은 1-byte frame type identifier이고, `LEN`은 연관 payload 길이를 나타내는 little-endian 2-byte 값이며, `SEQ`는 1-byte sequence ID입니다.
Frame 구조 바로 뒤에는 `TYPE`, `LEN`, `SEQ` 전체를 계산한 frame CRC가 놓이고 그다음 payload가 옵니다. Payload 뒤에는 모든 payload byte를 계산한 별도 payload CRC가 항상 놓입니다.
`LEN=0`으로 payload가 없어도 payload CRC는 존재하며 값은 `0xffff`입니다. `LEN`은 어떤 CRC도 포함하지 않고 frame CRC와 payload CRC 사이의 byte 수만 나타냅니다.
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 following
fields, 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 over
the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly
after the frame structure and before the payload. The payload is followed by
its 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 will
evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it
equals the number of bytes between the CRC of the frame and the CRC of the
payload.
Synchronization byte와 message 조립
83-103SSH는 고정된 2-byte synchronization sequence `SYN=[0xAA, 0x55]`를 사용합니다.
Message는 `SYN`, frame의 `TYPE`·`LEN`·`SEQ`, frame CRC, `LEN > 0`이면 payload, 그리고 payload 존재 여부와 무관하게 payload CRC의 순서로 구성됩니다.
하나의 exchange에 대응하는 message는 일부 경우 frame 안의 같은 `SEQ`로 식별합니다. Sequence ID는 끝에서 처음으로 되돌아가는 wrapping counter입니다.
Payload가 없어도 마지막 payload CRC는 반드시 전송됩니다.
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| and
CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes,
followed finally, regardless if the payload is present, the payload CRC. The
messages corresponding to an exchange are, in part, identified by having the
same sequence ID (|SEQ|), stored inside the frame (more on this in the next
section). The sequence ID is a wrapping counter.
SSH frame type
104-136`enum ssh_frame_type`은 `NAK`, `ACK`, `DATA_SEQ`, `DATA_NSQ`의 네 frame type을 정의합니다.
`NAK` 값 `0x04`는 앞서 받은 message에 오류가 있을 때 보냅니다. `ACK` 값 `0x40`은 `DATA` frame 수신을 확인합니다.
`DATA_SEQ` 값 `0x80`은 ACK가 필요한 sequenced data 전송에 사용합니다. `DATA_NSQ` 값 `0x00`은 같은 data 전송이지만 ACK가 필요 없습니다.
Flow control용 `NAK`와 `ACK` frame은 payload를 싣지 않습니다. 반면 `DATA_SEQ`와 `DATA_NSQ`는 반드시 payload를 가져야 합니다.
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 and
thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the
other hand must carry a payload. The flow sequence and interaction of
different frame types will be described in more depth in the next section.
ACK·NAK flow sequence
137-160각 exchange는 `SYN`으로 시작하고 `DATA_SEQ` 또는 `DATA_NSQ` frame, frame CRC, payload, payload CRC가 이어집니다.
`DATA_NSQ` exchange는 전송 직후 끝납니다. `DATA_SEQ`를 받은 쪽은 같은 sequence ID의 `ACK` frame message로 수신을 확인해야 하며, ACK의 `SEQ`가 어떤 DATA frame을 확인하는지 지정합니다.
Invalid CRC 같은 오류가 있으면 수신 측은 `NAK` frame message로 응답합니다. 오류가 난 이전 data frame의 sequence ID는 신뢰할 수 없으므로 NAK의 sequence ID는 사용하지 않고 0으로 설정합니다.
NAK를 받은 송신 측은 아직 ACK되지 않은 outstanding message를 모두 다시 보내야 합니다.
Sequence ID는 두 party 사이에서 synchronize되지 않고 각자 독립적으로 관리합니다. 따라서 exchange 식별은 sequence ID뿐 아니라 message type과 context에도 의존합니다. `SEQ`는 ACK와 대응 `DATA_SEQ`를 연결하지만 DATA frame끼리를 연결하지는 않습니다.
ACK는 같은 SEQ를 사용하고 NAK는 SEQ를 0으로 고정합니다.
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. In
case 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 of
the frame by responding with a message containing an |ACK|-type frame with
the same sequence ID of the |DATA| frame. In other words, the sequence ID of
the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an
error, e.g. an invalid CRC, the receiving party responds with a message
containing an |NAK|-type frame. As the sequence ID of the previous data
frame, for which an error is indicated via the |NAK| frame, cannot be relied
upon, the sequence ID of the |NAK| frame should not be used and is set to
zero. After receival of an |NAK| frame, the sending party should re-send all
outstanding (non-ACKed) messages.
Sequence IDs are not synchronized between the two parties, meaning that they
are managed independently for each party. Identifying the messages
corresponding to a single exchange thus relies on the sequence ID as well as
the type of the message, and the context. Specifically, the sequence ID is
used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not
``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames.
Exchange 예제와 error 특성
161-192정상 `DATA_SEQ` exchange에서 송신 측은 `SYN FRAME(D) CRC(F) PAYLOAD CRC(P)`를 보내고, 수신 측은 같은 `SEQ`의 `SYN FRAME(A) CRC(F) CRC(P)`로 응답합니다. `FRAME(D)`는 `DATA_SEQ`, `FRAME(A)`는 ACK, `CRC(F)`는 앞선 frame CRC, `CRC(P)`는 앞선 payload CRC를 뜻합니다.
오류 exchange에서는 수신 측이 `FRAME(N)`인 NAK를 보내고 송신 측은 message를 다시 보내야 합니다. NAK frame의 sequence ID는 0으로 고정됩니다.
`DATA_NSQ` exchange는 송신 message 뒤에 수신 응답이 없습니다. 오류를 탐지할 수는 있지만 송신 측에 알리거나 수정할 수 없습니다.
이 exchange는 symmetric하므로 `rx`와 `tx`를 바꿔도 유효합니다. 현재 이보다 더 긴 exchange는 알려져 있지 않습니다.
원문의 ASCII timing diagram을 동일한 message 관계로 구조화했습니다.
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 the
previous 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 fixed
to 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 the
sending party. These exchanges are symmetric, i.e. switching ``rx`` and
``tx`` results again in a valid exchange. Currently, no longer exchanges are
known.
Command payload 형식
193-206Command는 data frame의 payload로 전송합니다. 현재 알려진 `DATA` frame payload type은 command뿐이며 payload-type 값은 `SSH_PLD_TYPE_CMD`인 `0x80`입니다.
`struct ssh_command` command-type payload는 8-byte command 구조체 뒤에 optional variable-length command data가 이어집니다.
Optional data 길이는 대응 frame의 payload length에서 command 구조체 크기를 뺀 `frame.len - sizeof(struct ssh_command)`입니다. Command field는 padding 없이 정해진 순서로 packed됩니다.
Frame LEN에서 고정 command header를 제외한 나머지가 command data입니다.
Commands: Requests, Responses, and Events
=========================================
Commands are sent as payload inside a data frame. Currently, this is the
only 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 and
variable length command data. The length of this optional data is derived
from the frame payload length given in the corresponding frame, i.e. it is
``frame.len - sizeof(struct ssh_command)``. The command struct contains the
following fields, packed together and in order:
SSH command field
207-2458-byte `ssh_command`는 `TYPE`, `TC`, `TID`, `SID`, `IID`, `RQID`, `CID`를 순서대로 packed합니다.
`TYPE`은 command payload에서 항상 `0x80`입니다. `TC`는 target category, `TID`는 command/message target ID, `SID`는 source ID, `IID`는 instance ID입니다.
`RQID`는 2-byte request ID이고 `CID`는 command ID입니다.
Command 구조체와 data 자체에는 CRC 같은 failure detection mechanism이 없습니다. 무결성 검사는 전적으로 frame level에서 수행합니다.
.. 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 failure
detection mechanism (e.g. CRCs), this is solely done on the frame level.
Request·response·event와 TC·CID·IID
246-265Host는 command-type payload로 EC에 command와 request를 보내고, EC는 host에 response와 event를 보냅니다. Host가 보낸 것은 request, 선행 request에 대한 EC message는 response, 선행 request 없이 EC가 보낸 것은 event로 구분합니다.
Command와 event는 target category `TC`와 command ID `CID`의 조합으로 고유하게 식별합니다. `TC`는 system, battery·AC, temperature 같은 일반 범주를, `CID`는 그 범주 안의 command를 지정합니다. `TC` 또는 `CID` 단독이 아니라 조합만 고유합니다.
`IID`는 서로 다른 sub-device를 구분합니다. 예를 들어 `TC=3`, `CID=1`은 thermal sensor temperature request이고 `IID`가 sensor를 지정합니다.
Instance ID를 사용하지 않으면 0으로 설정해야 합니다. 사용하는 경우 일반적으로 1부터 시작하며, 적용 가능하면 0을 instance-independent query에 사용할 수 있습니다.
Request에 대한 response는 대응 request와 같은 target category, command ID, instance ID를 가져야 합니다.
Command-type payloads are used by the host to send commands and requests to
the 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 the
EC in response to a request), and events (sent by the EC without a preceding
request).
Commands and events are uniquely identified by their target category
(``TC``) and command ID (``CID``). The target category specifies a general
category for the command (e.g. system in general, vs. battery and AC, vs.
temperature, and so on), while the command ID specifies the command inside
that category. Only the combination of |TC| + |CID| is unique. Additionally,
commands have an instance ID (``IID``), which is used to differentiate
between different sub-devices. For example ``TC=3`` ``CID=1`` is a
request to get the temperature on a thermal sensor, where |IID| specifies
the respective sensor. If the instance ID is not used, it should be set to
zero. 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. A
response to a request should have the same target category, command ID, and
instance ID as the corresponding request.
RQID matching과 event 예약
266-288Response는 `RQID` field로 대응 request와 match합니다. RQID는 frame의 sequence ID와 비슷한 16-bit wrapping counter입니다.
Request-response pair의 frame sequence ID는 서로 같지 않으며 RQID만 같아야 합니다. Frame protocol 관점에서는 서로 별개의 두 exchange이고 request와 response 사이에 event가 끼어들 수도 있습니다.
모든 command가 response를 만들지는 않으며 `TC + CID`만으로 response 유무를 판별할 수 없습니다. Request 발행자가 response를 기다릴 책임이 있고, SAN/ACPI는 `SNC` flag로 communication framework에 이를 알립니다.
Event는 host request에 사용하면 안 되는 고유한 reserved RQID로 식별합니다. Host는 이 ID로 event를 감지하고 등록된 event handler와 match합니다.
Host는 enable-event-source request로 event source를 설정·활성화할 때 event RQID를 EC에 전달하고, EC는 해당 source가 보내는 event에 그 RQID를 사용합니다.
한 event source가 여러 event type을 보낼 수 있으므로 event는 여전히 `TC`, `CID`, 필요하면 `IID`로 식별해야 합니다. 일반적으로 하나의 target category는 하나의 reserved event RQID에 mapping되어야 합니다.
Request response matching과 event routing은 frame SEQ와 독립적으로 동작합니다.
Responses are matched to their corresponding request via the request ID
(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence
ID on the frames. Note that the sequence ID of the frames for a
request-response pair does not match. Only the request ID has to match.
Frame-protocol wise these are two separate exchanges, and may even be
separated, e.g. by an event being sent after the request but before the
response. 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 a
response (or signal this to the communication framework, as is done in
SAN/ACPI via the ``SNC`` flag).
Events are identified by unique and reserved request IDs. These IDs should
not be used by the host when sending a new request. They are used on the
host to, first, detect events and, second, match them with a registered
event handler. Request IDs for events are chosen by the host and directed to
the EC when setting up and enabling an event source (via the
enable-event-source request). The EC then uses the specified request ID for
events sent from the respective source. Note that an event should still be
identified by its target category, command ID, and, if applicable, instance
ID, as a single event source can send multiple different event types. In
general, however, a single target category should map to a single reserved
event request ID.
Target ID와 Source ID
289-305Request, response, event에는 target ID `TID`와 source ID `SID`가 연결됩니다. `SID`는 message 발신지를, `TID`는 의도한 수신 대상을 나타냅니다.
특정 request의 response는 원래 request와 비교해 source와 target ID가 서로 바뀝니다. 즉 request target이 response source가 되고 request source가 response target이 됩니다. 가능한 값은 `enum ssh_request_id`를 참조합니다.
Request와 event는 이론상 `TC`와 `CID`만으로 고유하게 식별되지만 EC가 command를 받아들이려면 특정 `TID`와 `IID`가 필요할 수 있습니다. 예를 들어 `TID=1`에서 허용되는 command가 `TID=2`에서는 거부될 수 있고 그 반대도 가능합니다.
현실에서 항상 정확한 비유는 아니지만, 서로 다른 target/source ID를 feature set이 다른 물리 EC로 생각할 수 있습니다.
Response는 request의 source와 target을 교환합니다.
Furthermore, requests, responses, and events have an associated target ID
(``TID``) and source ID (``SID``). These two fields indicate where a message
originates from (``SID``) and what the intended target of the message is
(``TID``). Note that a response to a specific request therefore has the source
and target IDs swapped when compared to the original request (i.e. the request
target 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 of
both.
Note that, even though requests and events should be uniquely identifiable by
target category and command ID alone, the EC may require specific target ID and
instance 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. While
this may not always hold in reality, you can think of different target/source
IDs indicating different physical ECs with potentially different feature sets.
병렬 request와 retry 한계
306-328이론적으로 protocol은 최대 `U8_MAX` frame을 병렬 처리하고 event용 reserved request ID를 제외하면 최대 `U16_MAX` pending request를 다룰 수 있습니다. 실제 한계는 훨씬 낮습니다.
Python user-space program을 통한 test에서는 EC가 동시에 최대 4개 request를 대체로 안정적으로 처리했습니다. 5개 이상이면 frame은 ACK됐지만 command response가 없는 command discard가 반복해서 관찰됐습니다. 동시에 5개를 보내면 재현 가능하게 1개가 drop되고 4개가 처리됐습니다.
병렬 request가 3개일 때도 가끔 frame drop이 관찰됐지만, pending request를 3개로 제한하면 ACK된 frame의 command만 drop되는 현상은 관찰되지 않았습니다.
어떤 경우든 일정 timeout을 넘기면 host가 frame과 필요하면 command도 다시 보내야 합니다. EC는 frame에 대해 1초 timeout과 최대 2회 retry, 즉 총 3회 transmission을 사용합니다.
Retry 한도는 받은 NAK에도 적용되며 최악의 경우 message 전체가 drop될 수 있습니다.
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 for
events). In practice, however, this is more limited. From our testing
(although via a python and thus a user-space program), it seems that the EC
can handle up to four requests (mostly) reliably in parallel at a certain
time. With five or more requests in parallel, consistent discarding of
commands (ACKed frame but no command response) has been observed. For five
simultaneous commands, this reproducibly resulted in one command being
dropped 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 three
pending requests, no dropped commands (i.e. command being dropped but frame
carrying command being ACKed) have been observed. In any case, frames (and
possibly also commands) should be re-sent by the host if a certain timeout
is 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 of
re-tries also applies to received NAKs, and, in a worst case scenario, can
lead to entire messages being dropped.
Repeated frame 감지와 최종 제한
329-346Transmission failure가 없다면 여러 pending data frame도 동작하는 듯하지만, 구현과 처리는 ACK되지 않은 data frame이 하나뿐이라는 가정에 의존하는 것으로 보입니다.
Repeated frame 감지는 마지막 sequence number에 의존합니다. EC가 성공적으로 받은 frame을 host가 ACK를 받지 못해 다시 보내면, EC는 그 frame의 sequence ID가 EC가 마지막으로 받은 frame의 ID와 같을 때만 repetition으로 감지합니다.
예를 들어 `SEQ=0`, `SEQ=1`, 다시 `SEQ=0` 순서로 보내면 두 번째 `SEQ=0`을 repetition으로 감지하지 못해 안의 command를 두 번 실행합니다.
반대로 `SEQ=0`, `SEQ=1`, 다시 `SEQ=1`을 보내면 두 번째 `SEQ=1`을 반복으로 감지해 무시하므로 command를 한 번만 실행합니다.
따라서 party마다 pending un-ACKed frame은 최대 1개로 제한하여 frame 수준에서 사실상 synchronous communication을 사용하고, pending command는 최대 3개로 제한하는 것이 타당합니다. Synchronous frame transfer 제한은 Windows에서 관찰된 동작과도 일치합니다.
마지막으로 받은 SEQ만 반복 판정에 사용되므로 un-ACKed frame을 하나로 제한합니다.
While this also seems to work fine for pending data frames as long as no
transmission failures occur, implementation and handling of these seems to
depend on the assumption that there is only one non-acknowledged data frame.
In particular, the detection of repeated frames relies on the last sequence
number. This means that, if a frame that has been successfully received by
the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC
will only detect this if it has the sequence ID of the last frame received
by 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 has
been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and
then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of
the 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 regarding
frames) and at most three pending commands. The limit to synchronous frame
transfers seems to be consistent with behavior observed on Windows.
요약과 해설
ssh.rst:1-346SAM-over-SSH는 SYN-prefixed frame, frame·payload CRC, sequenced ACK/NAK flow 위에 8-byte command header를 싣습니다. Frame SEQ는 ACK를 연결하고 RQID는 request response와 event를 route하며, 실제 구현은 party당 ACK 대기 frame 1개와 pending command 3개 제한을 권장합니다.