요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0+
.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl <ssh_ptl>`
.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit`
.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel`
.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown`
.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf`
.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl <ssh_rtl>`
.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit`
.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel`
.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown`
.. |ssh_packet| replace:: :c:type:`struct ssh_packet <ssh_packet>`
.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get`
.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put`
.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops <ssh_packet_ops>`
.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority <ssh_packet_base_priority>`
.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags <ssh_packet_flags>`
.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY`
.. |ssh_frame| replace:: :c:type:`struct ssh_frame <ssh_frame>`
.. |ssh_command| replace:: :c:type:`struct ssh_command <ssh_command>`
.. |ssh_request| replace:: :c:type:`struct ssh_request <ssh_request>`
.. |ssh_request_get| replace:: :c:func:`ssh_request_get`
.. |ssh_request_put| replace:: :c:func:`ssh_request_put`
.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops <ssh_request_ops>`
.. |ssh_request_init| replace:: :c:func:`ssh_request_init`
.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags <ssh_request_flags>`
.. |ssam_controller| replace:: :c:type:`struct ssam_controller <ssam_controller>`
.. |ssam_device| replace:: :c:type:`struct ssam_device <ssam_device>`
.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver <ssam_device_driver>`
.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind`
.. |ssam_client_link| replace:: :c:func:`ssam_client_link`
.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync <ssam_request_sync>`
.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry <ssam_event_registry>`
.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id <ssam_event_id>`
.. |ssam_nf| replace:: :c:type:`struct ssam_nf <ssam_nf>`
.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc`
.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec`
.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register`
.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister`
.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt <ssam_cplt>`
.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue <ssam_event_queue>`
.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit`
=====================
Core Driver Internals
=====================
Architectural overview of the Surface System Aggregator Module (SSAM) core
and Surface Serial Hub (SSH) driver. For the API documentation, refer to:
.. toctree::
:maxdepth: 2
internal-api
Overview
========
The SSAM core implementation is structured in layers, somewhat following the
SSH protocol structure:
Lower-level packet transport is implemented in the *packet transport layer
(PTL)*, directly building on top of the serial device (serdev)
infrastructure of the kernel. As the name indicates, this layer deals with
the packet transport logic and handles things like packet validation, packet
acknowledgment (ACKing), packet (retransmission) timeouts, and relaying
packet payloads to higher-level layers.
Above this sits the *request transport layer (RTL)*. This layer is centered
around command-type packet payloads, i.e. requests (sent from host to EC),
responses of the EC to those requests, and events (sent from EC to host).
It, specifically, distinguishes events from request responses, matches
responses to their corresponding requests, and implements request timeouts.
The *controller* layer is building on top of this and essentially decides
how request responses and, especially, events are dealt with. It provides an
event notifier system, handles event activation/deactivation, provides a
workqueue for event and asynchronous request completion, and also manages
the message counters required for building command messages (``SEQ``,
``RQID``). This layer basically provides a fundamental interface to the SAM
EC for use in other kernel drivers.
While the controller layer already provides an interface for other kernel
drivers, the client *bus* extends this interface to provide support for
native SSAM devices, i.e. devices that are not defined in ACPI and not
implemented as platform devices, via |ssam_device| and |ssam_device_driver|
simplify management of client devices and client drivers.
Refer to Documentation/driver-api/surface_aggregator/client.rst for
documentation regarding the client device/driver API and interface options
for other kernel drivers. It is recommended to familiarize oneself with
that chapter and the Documentation/driver-api/surface_aggregator/ssh.rst
before continuing with the architectural overview below.
Packet Transport Layer
======================
The packet transport layer is represented via |ssh_ptl| and is structured
around the following key concepts:
Packets
-------
Packets are the fundamental transmission unit of the SSH protocol. They are
managed by the packet transport layer, which is essentially the lowest layer
of the driver and is built upon by other components of the SSAM core.
Packets to be transmitted by the SSAM core are represented via |ssh_packet|
(in contrast, packets received by the core do not have any specific
structure and are managed entirely via the raw |ssh_frame|).
This structure contains the required fields to manage the packet inside the
transport layer, as well as a reference to the buffer containing the data to
be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it
contains an internal reference count, which is used for managing its
lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this
counter reaches zero, the ``release()`` callback provided to the packet via
its |ssh_packet_ops| reference is executed, which may then deallocate the
packet or its enclosing structure (e.g. |ssh_request|).
In addition to the ``release`` callback, the |ssh_packet_ops| reference also
provides a ``complete()`` callback, which is run once the packet has been
completed and provides the status of this completion, i.e. zero on success
or a negative errno value in case of an error. Once the packet has been
submitted to the packet transport layer, the ``complete()`` callback is
always guaranteed to be executed before the ``release()`` callback, i.e. the
packet will always be completed, either successfully, with an error, or due
to cancellation, before it will be released.
The state of a packet is managed via its ``state`` flags
(|ssh_packet_flags|), which also contains the packet type. In particular,
the following bits are noteworthy:
* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either
through error or success, is imminent. It indicates that no further
references of the packet should be taken and any existing references
should be dropped as soon as possible. The process setting this bit is
responsible for removing any references to this packet from the packet
queue and pending set.
* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the
``complete()`` callback and is used to ensure that this callback only runs
once.
* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on
the packet queue and cleared when it is dequeued.
* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to
the pending set and cleared when it is removed from it.
Packet Queue
------------
The packet queue is the first of the two fundamental collections in the
packet transport layer. It is a priority queue, with priority of the
respective packets based on the packet type (major) and number of tries
(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value.
All packets to be transmitted by the transport layer must be submitted to
this queue via |ssh_ptl_submit|. Note that this includes control packets
sent by the transport layer itself. Internally, data packets can be
re-submitted to this queue due to timeouts or NAK packets sent by the EC.
Pending Set
-----------
The pending set is the second of the two fundamental collections in the
packet transport layer. It stores references to packets that have already
been transmitted, but wait for acknowledgment (e.g. the corresponding ACK
packet) by the EC.
Note that a packet may both be pending and queued if it has been
re-submitted due to a packet acknowledgment timeout or NAK. On such a
re-submission, packets are not removed from the pending set.
Transmitter Thread
------------------
The transmitter thread is responsible for most of the actual work regarding
packet transmission. In each iteration, it (waits for and) checks if the
next packet on the queue (if any) can be transmitted and, if so, removes it
from the queue and increments its counter for the number of transmission
attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by
the EC, the packet is added to the pending set. Next, the packet's data is
submitted to the serdev subsystem. In case of an error or timeout during
this submission, the packet is completed by the transmitter thread with the
status value of the callback set accordingly. In case the packet is
unsequenced, i.e. does not require an ACK by the EC, the packet is completed
with success on the transmitter thread.
Transmission of sequenced packets is limited by the number of concurrently
pending packets, i.e. a limit on how many packets may be waiting for an ACK
from the EC in parallel. This limit is currently set to one (see
Documentation/driver-api/surface_aggregator/ssh.rst for the reasoning behind
this). Control packets (i.e. ACK and NAK) can always be transmitted.
Receiver Thread
---------------
Any data received from the EC is put into a FIFO buffer for further
processing. This processing happens on the receiver thread. The receiver
thread parses and validates the received message into its |ssh_frame| and
corresponding payload. It prepares and submits the necessary ACK (and on
validation error or invalid data NAK) packets for the received messages.
This thread also handles further processing, such as matching ACK messages
to the corresponding pending packet (via sequence ID) and completing it, as
well as initiating re-submission of all currently pending packets on
receival of a NAK message (re-submission in case of a NAK is similar to
re-submission due to timeout, see below for more details on that). Note that
the successful completion of a sequenced packet will always run on the
receiver thread (whereas any failure-indicating completion will run on the
process where the failure occurred).
Any payload data is forwarded via a callback to the next upper layer, i.e.
the request transport layer.
Timeout Reaper
--------------
The packet acknowledgment timeout is a per-packet timeout for sequenced
packets, started when the respective packet begins (re-)transmission (i.e.
this timeout is armed once per transmission attempt on the transmitter
thread). It is used to trigger re-submission or, when the number of tries
has been exceeded, cancellation of the packet in question.
This timeout is handled via a dedicated reaper task, which is essentially a
work item (re-)scheduled to run when the next packet is set to time out. The
work item then checks the set of pending packets for any packets that have
exceeded the timeout and, if there are any remaining packets, re-schedules
itself to the next appropriate point in time.
If a timeout has been detected by the reaper, the packet will either be
re-submitted if it still has some remaining tries left, or completed with
``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and
triggered by receival of a NAK, means that the packet is added to the queue
with a now incremented number of tries, yielding a higher priority. The
timeout for the packet will be disabled until the next transmission attempt
and the packet remains on the pending set.
Note that due to transmission and packet acknowledgment timeouts, the packet
transport layer is always guaranteed to make progress, if only through
timing out packets, and will never fully block.
Concurrency and Locking
-----------------------
There are two main locks in the packet transport layer: One guarding access
to the packet queue and one guarding access to the pending set. These
collections may only be accessed and modified under the respective lock. If
access to both collections is needed, the pending lock must be acquired
before the queue lock to avoid deadlocks.
In addition to guarding the collections, after initial packet submission
certain packet fields may only be accessed under one of the locks.
Specifically, the packet priority must only be accessed while holding the
queue lock and the packet timestamp must only be accessed while holding the
pending lock.
Other parts of the packet transport layer are guarded independently. State
flags are managed by atomic bit operations and, if necessary, memory
barriers. Modifications to the timeout reaper work item and expiration date
are guarded by their own lock.
The reference of the packet to the packet transport layer (``ptl``) is
somewhat special. It is either set when the upper layer request is submitted
or, if there is none, when the packet is first submitted. After it is set,
it will not change its value. Functions that may run concurrently with
submission, i.e. cancellation, can not rely on the ``ptl`` reference to be
set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas
setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry.
Some packet fields may be read outside of the respective locks guarding
them, specifically priority and state for tracing. In those cases, proper
access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such
read-only access is only allowed when stale values are not critical.
With respect to the interface for higher layers, packet submission
(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival
(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be
executed concurrently with respect to each other. Note that packet
submission may not run concurrently with itself for the same packet.
Equally, shutdown and data receival may also not run concurrently with
themselves (but may run concurrently with each other).
Request Transport Layer
=======================
The request transport layer is represented via |ssh_rtl| and builds on top
of the packet transport layer. It deals with requests, i.e. SSH packets sent
by the host containing a |ssh_command| as frame payload. This layer
separates responses to requests from events, which are also sent by the EC
via a |ssh_command| payload. While responses are handled in this layer,
events are relayed to the next upper layer, i.e. the controller layer, via
the corresponding callback. The request transport layer is structured around
the following key concepts:
Request
-------
Requests are packets with a command-type payload, sent from host to EC to
query data from or trigger an action on it (or both simultaneously). They
are represented by |ssh_request|, wrapping the underlying |ssh_packet|
storing its message data (i.e. SSH frame with command payload). Note that
all top-level representations, e.g. |ssam_request_sync| are built upon this
struct.
As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the
reference counter inside the packet struct (which can be accessed via
|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the
``release()`` callback of the |ssh_request_ops| reference of the request is
called.
Requests can have an optional response that is equally sent via a SSH
message with command-type payload (from EC to host). The party constructing
the request must know if a response is expected and mark this in the request
flags provided to |ssh_request_init|, so that the request transport layer
can wait for this response.
Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback
provided via its request ops reference and is guaranteed to be completed
before it is released once it has been submitted to the request transport
layer via |ssh_rtl_submit|. For a request without a response, successful
completion will occur once the underlying packet has been successfully
transmitted by the packet transport layer (i.e. from within the packet
completion callback). For a request with response, successful completion
will occur once the response has been received and matched to the request
via its request ID (which happens on the packet layer's data-received
callback running on the receiver thread). If the request is completed with
an error, the status value will be set to the corresponding (negative) errno
value.
The state of a request is again managed via its ``state`` flags
(|ssh_request_flags|), which also encode the request type. In particular,
the following bits are noteworthy:
* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either
through error or success, is imminent. It indicates that no further
references of the request should be taken and any existing references
should be dropped as soon as possible. The process setting this bit is
responsible for removing any references to this request from the request
queue and pending set.
* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the
``complete()`` callback and is used to ensure that this callback only runs
once.
* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on
the request queue and cleared when it is dequeued.
* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to
the pending set and cleared when it is removed from it.
Request Queue
-------------
The request queue is the first of the two fundamental collections in the
request transport layer. In contrast to the packet queue of the packet
transport layer, it is not a priority queue and the simple first come first
serve principle applies.
All requests to be transmitted by the request transport layer must be
submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may
not be re-submitted, and will not be re-submitted automatically on timeout.
Instead, the request is completed with a timeout error. If desired, the
caller can create and submit a new request for another try, but it must not
submit the same request again.
Pending Set
-----------
The pending set is the second of the two fundamental collections in the
request transport layer. This collection stores references to all pending
requests, i.e. requests awaiting a response from the EC (similar to what the
pending set of the packet transport layer does for packets).
Transmitter Task
----------------
The transmitter task is scheduled when a new request is available for
transmission. It checks if the next request on the request queue can be
transmitted and, if so, submits its underlying packet to the packet
transport layer. This check ensures that only a limited number of
requests can be pending, i.e. waiting for a response, at the same time. If
the request requires a response, the request is added to the pending set
before its packet is submitted.
Packet Completion Callback
--------------------------
The packet completion callback is executed once the underlying packet of a
request has been completed. In case of an error completion, the
corresponding request is completed with the error value provided in this
callback.
On successful packet completion, further processing depends on the request.
If the request expects a response, it is marked as transmitted and the
request timeout is started. If the request does not expect a response, it is
completed with success.
Data-Received Callback
----------------------
The data received callback notifies the request transport layer of data
being received by the underlying packet transport layer via a data-type
frame. In general, this is expected to be a command-type payload.
If the request ID of the command is one of the request IDs reserved for
events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the
event callback registered in the request transport layer. If the request ID
indicates a response to a request, the respective request is looked up in
the pending set and, if found and marked as transmitted, completed with
success.
Timeout Reaper
--------------
The request-response-timeout is a per-request timeout for requests expecting
a response. It is used to ensure that a request does not wait indefinitely
on a response from the EC and is started after the underlying packet has
been successfully completed.
This timeout is, similar to the packet acknowledgment timeout on the packet
transport layer, handled via a dedicated reaper task. This task is
essentially a work-item (re-)scheduled to run when the next request is set
to time out. The work item then scans the set of pending requests for any
requests that have timed out and completes them with ``-ETIMEDOUT`` as
status. Requests will not be re-submitted automatically. Instead, the issuer
of the request must construct and submit a new request, if so desired.
Note that this timeout, in combination with packet transmission and
acknowledgment timeouts, guarantees that the request layer will always make
progress, even if only through timing out packets, and never fully block.
Concurrency and Locking
-----------------------
Similar to the packet transport layer, there are two main locks in the
request transport layer: One guarding access to the request queue and one
guarding access to the pending set. These collections may only be accessed
and modified under the respective lock.
Other parts of the request transport layer are guarded independently. State
flags are (again) managed by atomic bit operations and, if necessary, memory
barriers. Modifications to the timeout reaper work item and expiration date
are guarded by their own lock.
Some request fields may be read outside of the respective locks guarding
them, specifically the state for tracing. In those cases, proper access is
ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only
access is only allowed when stale values are not critical.
With respect to the interface for higher layers, request submission
(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer
shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with
respect to each other. Note that request submission may not run concurrently
with itself for the same request (and also may only be called once per
request). Equally, shutdown may also not run concurrently with itself.
Controller Layer
================
The controller layer extends on the request transport layer to provide an
easy-to-use interface for client drivers. It is represented by
|ssam_controller| and the SSH driver. While the lower level transport layers
take care of transmitting and handling packets and requests, the controller
layer takes on more of a management role. Specifically, it handles device
initialization, power management, and event handling, including event
delivery and registration via the (event) completion system (|ssam_cplt|).
Event Registration
------------------
In general, an event (or rather a class of events) has to be explicitly
requested by the host before the EC will send it (HID input events seem to
be the exception). This is done via an event-enable request (similarly,
events should be disabled via an event-disable request once no longer
desired).
The specific request used to enable (or disable) an event is given via an
event registry, i.e. the governing authority of this event (so to speak),
represented by |ssam_event_registry|. As parameters to this request, the
target category and, depending on the event registry, instance ID of the
event to be enabled must be provided. This (optional) instance ID must be
zero if the registry does not use it. Together, target category and instance
ID form the event ID, represented by |ssam_event_id|. In short, both, event
registry and event ID, are required to uniquely identify a respective class
of events.
Note that a further *request ID* parameter must be provided for the
enable-event request. This parameter does not influence the class of events
being enabled, but instead is set as the request ID (RQID) on each event of
this class sent by the EC. It is used to identify events (as a limited
number of request IDs is reserved for use in events only, specifically one
to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific
class. Currently, the controller always sets this parameter to the target
category specified in |ssam_event_id|.
As multiple client drivers may rely on the same (or overlapping) classes of
events and enable/disable calls are strictly binary (i.e. on/off), the
controller has to manage access to these events. It does so via reference
counting, storing the counter inside an RB-tree based mapping with event
registry and ID as key (there is no known list of valid event registry and
event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and
|ssam_nf_refcount_dec| for details.
This management is done together with notifier registration (described in
the next section) via the top-level |ssam_notifier_register| and
|ssam_notifier_unregister| functions.
Event Delivery
--------------
To receive events, a client driver has to register an event notifier via
|ssam_notifier_register|. This increments the reference counter for that
specific class of events (as detailed in the previous section), enables the
class on the EC (if it has not been enabled already), and installs the
provided notifier callback.
Notifier callbacks are stored in lists, with one (RCU) list per target
category (provided via the event ID; NB: there is a fixed known number of
target categories). There is no known association from the combination of
event registry and event ID to the command data (target ID, target category,
command ID, and instance ID) that can be provided by an event class, apart
from target category and instance ID given via the event ID.
Note that due to the way notifiers are (or rather have to be) stored, client
drivers may receive events that they have not requested and need to account
for them. Specifically, they will, by default, receive all events from the
same target category. To simplify dealing with this, filtering of events by
target ID (provided via the event registry) and instance ID (provided via
the event ID) can be requested when registering a notifier. This filtering
is applied when iterating over the notifiers at the time they are executed.
All notifier callbacks are executed on a dedicated workqueue, the so-called
completion workqueue. After an event has been received via the callback
installed in the request layer (running on the receiver thread of the packet
transport layer), it will be put on its respective event queue
(|ssam_event_queue|). From this event queue the completion work item of that
queue (running on the completion workqueue) will pick up the event and
execute the notifier callback. This is done to avoid blocking on the
receiver thread.
There is one event queue per combination of target ID and target category.
This is done to ensure that notifier callbacks are executed in sequence for
events of the same target ID and target category. Callbacks can be executed
in parallel for events with a different combination of target ID and target
category.
Concurrency and Locking
-----------------------
Most of the concurrency related safety guarantees of the controller are
provided by the lower-level request transport layer. In addition to this,
event (un-)registration is guarded by its own lock.
Access to the controller state is guarded by the state lock. This lock is a
read/write semaphore. The reader part can be used to ensure that the state
does not change while functions depending on the state to stay the same
(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|,
|ssam_request_sync_submit|, and derivatives) are executed and this guarantee
is not already provided otherwise (e.g. through |ssam_client_bind| or
|ssam_client_link|). The writer part guards any transitions that will change
the state, i.e. initialization, destruction, suspension, and resumption.
The controller state may be accessed (read-only) outside the state lock for
smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|).
Note that such checks are not supposed to (and will not) protect against all
invalid usages, but rather aim to help catch them. In those cases, proper
variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``.
Assuming any preconditions on the state not changing have been satisfied,
all non-initialization and non-shutdown functions may run concurrently with
each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|,
|ssam_request_sync_submit|, as well as all functions building on top of those.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
문서 치환 symbol
1-44이 문서는 `GPL-2.0+` SPDX license를 사용합니다. 머리말의 replace 지시문은 packet transport layer(PTL), request transport layer(RTL), packet·request·controller·event 관련 C type과 function을 본문에서 일관된 kernel-doc 교차 참조로 표시합니다.
PTL 관련 치환에는 `ssh_ptl`, `ssh_ptl_submit`, `ssh_ptl_cancel`, `ssh_ptl_shutdown`, `ssh_ptl_rx_rcvbuf`가 있고, RTL 관련 치환에는 `ssh_rtl`, `ssh_rtl_submit`, `ssh_rtl_cancel`, `ssh_rtl_shutdown`이 있습니다.
Packet과 request lifecycle은 `ssh_packet`, `ssh_packet_get`, `ssh_packet_put`, `ssh_packet_ops`, `ssh_packet_flags`, `ssh_request`, `ssh_request_get`, `ssh_request_put`, `ssh_request_ops`, `ssh_request_init`, `ssh_request_flags` 등의 symbol로 설명합니다.
상위 계층은 `ssam_controller`, `ssam_device`, `ssam_device_driver`, `ssam_event_registry`, `ssam_event_id`, `ssam_nf`, notifier 등록·해제, completion system, event queue, synchronous request API를 참조합니다.
.. SPDX-License-Identifier: GPL-2.0+
.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl <ssh_ptl>`
.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit`
.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel`
.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown`
.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf`
.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl <ssh_rtl>`
.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit`
.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel`
.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown`
.. |ssh_packet| replace:: :c:type:`struct ssh_packet <ssh_packet>`
.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get`
.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put`
.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops <ssh_packet_ops>`
.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority <ssh_packet_base_priority>`
.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags <ssh_packet_flags>`
.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY`
.. |ssh_frame| replace:: :c:type:`struct ssh_frame <ssh_frame>`
.. |ssh_command| replace:: :c:type:`struct ssh_command <ssh_command>`
.. |ssh_request| replace:: :c:type:`struct ssh_request <ssh_request>`
.. |ssh_request_get| replace:: :c:func:`ssh_request_get`
.. |ssh_request_put| replace:: :c:func:`ssh_request_put`
.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops <ssh_request_ops>`
.. |ssh_request_init| replace:: :c:func:`ssh_request_init`
.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags <ssh_request_flags>`
.. |ssam_controller| replace:: :c:type:`struct ssam_controller <ssam_controller>`
.. |ssam_device| replace:: :c:type:`struct ssam_device <ssam_device>`
.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver <ssam_device_driver>`
.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind`
.. |ssam_client_link| replace:: :c:func:`ssam_client_link`
.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync <ssam_request_sync>`
.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry <ssam_event_registry>`
.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id <ssam_event_id>`
.. |ssam_nf| replace:: :c:type:`struct ssam_nf <ssam_nf>`
.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc`
.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec`
.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register`
.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister`
.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt <ssam_cplt>`
.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue <ssam_event_queue>`
.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit`
=====================
Core Driver Internals
45-56이 장은 Surface System Aggregator Module(SSAM) core와 Surface Serial Hub(SSH) driver의 architecture를 설명합니다.
구체적인 internal API 문서는 최대 깊이 2의 `toctree`로 연결된 `internal-api` 문서를 참조합니다.
Core Driver Internals
=====================
Architectural overview of the Surface System Aggregator Module (SSAM) core
and Surface Serial Hub (SSH) driver. For the API documentation, refer to:
.. toctree::
:maxdepth: 2
internal-api
계층 구조 개요
57-96SSAM core 구현은 SSH protocol 구조를 대체로 따라 여러 계층으로 구성됩니다.
가장 아래의 packet transport layer(PTL)는 kernel의 serial device(serdev) infrastructure 위에 직접 구축됩니다. Packet validation, ACK 처리, packet 재전송 timeout, payload를 상위 계층으로 전달하는 일을 담당합니다.
그 위의 request transport layer(RTL)는 command형 packet payload를 다룹니다. Host에서 EC로 보내는 request, 그 request에 대한 EC response, EC에서 host로 보내는 event를 구분하고, response를 대응 request와 match하며, request timeout을 구현합니다.
Controller layer는 response와 특히 event를 처리하는 정책을 제공합니다. Event notifier, event 활성화·비활성화, event 및 asynchronous request completion용 workqueue, command message의 `SEQ`·`RQID` counter를 관리하여 다른 kernel driver가 SAM EC를 사용할 수 있는 기본 interface를 제공합니다.
Client bus는 ACPI에 정의되지 않고 platform device로 구현되지 않은 native SSAM device를 `ssam_device`와 `ssam_device_driver`로 지원하여 client device와 driver 관리를 단순화합니다.
계속 읽기 전에 `Documentation/driver-api/surface_aggregator/client.rst`의 client API·interface option과 `Documentation/driver-api/surface_aggregator/ssh.rst`의 protocol 설명을 먼저 익히는 것이 권장됩니다.
낮은 전송 단위에서 client device model까지 책임을 단계적으로 확장합니다.
Overview
========
The SSAM core implementation is structured in layers, somewhat following the
SSH protocol structure:
Lower-level packet transport is implemented in the *packet transport layer
(PTL)*, directly building on top of the serial device (serdev)
infrastructure of the kernel. As the name indicates, this layer deals with
the packet transport logic and handles things like packet validation, packet
acknowledgment (ACKing), packet (retransmission) timeouts, and relaying
packet payloads to higher-level layers.
Above this sits the *request transport layer (RTL)*. This layer is centered
around command-type packet payloads, i.e. requests (sent from host to EC),
responses of the EC to those requests, and events (sent from EC to host).
It, specifically, distinguishes events from request responses, matches
responses to their corresponding requests, and implements request timeouts.
The *controller* layer is building on top of this and essentially decides
how request responses and, especially, events are dealt with. It provides an
event notifier system, handles event activation/deactivation, provides a
workqueue for event and asynchronous request completion, and also manages
the message counters required for building command messages (``SEQ``,
``RQID``). This layer basically provides a fundamental interface to the SAM
EC for use in other kernel drivers.
While the controller layer already provides an interface for other kernel
drivers, the client *bus* extends this interface to provide support for
native SSAM devices, i.e. devices that are not defined in ACPI and not
implemented as platform devices, via |ssam_device| and |ssam_device_driver|
simplify management of client devices and client drivers.
Refer to Documentation/driver-api/surface_aggregator/client.rst for
documentation regarding the client device/driver API and interface options
for other kernel drivers. It is recommended to familiarize oneself with
that chapter and the Documentation/driver-api/surface_aggregator/ssh.rst
before continuing with the architectural overview below.
Packet Transport Layer 개요
97-102Packet transport layer는 `struct ssh_ptl`로 표현되며, 이어지는 packet, packet queue, pending set, transmitter·receiver thread, timeout reaper, locking 개념을 중심으로 구성됩니다.
Packet의 제출부터 ACK 또는 timeout 완료까지의 구성 요소입니다.
Packet Transport Layer
======================
The packet transport layer is represented via |ssh_ptl| and is structured
around the following key concepts:
Packet과 lifecycle
103-151Packet은 SSH protocol의 기본 transmission unit입니다. SSAM core가 전송할 packet은 `struct ssh_packet`으로 표현되지만, core가 받은 packet은 별도 구조체 없이 raw `struct ssh_frame`으로만 관리됩니다.
`ssh_packet`에는 transport layer 내부 관리 field와 전송 data, 즉 `ssh_frame`으로 감싼 message가 든 buffer reference가 있습니다. Lifetime은 내부 reference count로 관리하며 `ssh_packet_get()`과 `ssh_packet_put()`으로 접근합니다.
Reference count가 0이 되면 `ssh_packet_ops`가 제공한 `release()` callback이 실행되어 packet 또는 이를 포함하는 `ssh_request` 같은 구조체를 해제할 수 있습니다.
`ssh_packet_ops`는 `complete()` callback도 제공합니다. 이 callback은 packet 완료 시 한 번 실행되며 성공이면 0, 오류이면 negative errno를 전달합니다. PTL에 제출된 packet은 성공·오류·취소 중 어느 결과든 `release()`보다 먼저 반드시 완료됩니다.
Packet state와 type은 `ssh_packet_flags`의 `state` bit로 관리합니다. `SSH_PACKET_SF_LOCKED_BIT`는 완료가 임박하여 새 reference를 만들면 안 되고 기존 reference도 가능한 한 빨리 놓아야 함을 뜻합니다. 이 bit를 설정한 process는 packet queue와 pending set의 reference를 제거할 책임이 있습니다.
`SSH_PACKET_SF_COMPLETED_BIT`는 `complete()`를 실행하는 process가 설정하여 callback이 한 번만 실행되도록 합니다. `SSH_PACKET_SF_QUEUED_BIT`와 `SSH_PACKET_SF_PENDING_BIT`는 각각 queue와 pending set에 추가될 때 설정되고 제거될 때 지워집니다.
Packets
-------
Packets are the fundamental transmission unit of the SSH protocol. They are
managed by the packet transport layer, which is essentially the lowest layer
of the driver and is built upon by other components of the SSAM core.
Packets to be transmitted by the SSAM core are represented via |ssh_packet|
(in contrast, packets received by the core do not have any specific
structure and are managed entirely via the raw |ssh_frame|).
This structure contains the required fields to manage the packet inside the
transport layer, as well as a reference to the buffer containing the data to
be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it
contains an internal reference count, which is used for managing its
lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this
counter reaches zero, the ``release()`` callback provided to the packet via
its |ssh_packet_ops| reference is executed, which may then deallocate the
packet or its enclosing structure (e.g. |ssh_request|).
In addition to the ``release`` callback, the |ssh_packet_ops| reference also
provides a ``complete()`` callback, which is run once the packet has been
completed and provides the status of this completion, i.e. zero on success
or a negative errno value in case of an error. Once the packet has been
submitted to the packet transport layer, the ``complete()`` callback is
always guaranteed to be executed before the ``release()`` callback, i.e. the
packet will always be completed, either successfully, with an error, or due
to cancellation, before it will be released.
The state of a packet is managed via its ``state`` flags
(|ssh_packet_flags|), which also contains the packet type. In particular,
the following bits are noteworthy:
* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either
through error or success, is imminent. It indicates that no further
references of the packet should be taken and any existing references
should be dropped as soon as possible. The process setting this bit is
responsible for removing any references to this packet from the packet
queue and pending set.
* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the
``complete()`` callback and is used to ensure that this callback only runs
once.
* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on
the packet queue and cleared when it is dequeued.
* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to
the pending set and cleared when it is removed from it.
Packet Queue
152-164Packet queue는 PTL의 두 기본 collection 중 첫 번째이며 priority queue입니다. Packet type이 major priority를, transmission try 수가 minor priority를 결정합니다. 구체적인 값은 `SSH_PACKET_PRIORITY`를 참조합니다.
PTL이 전송할 모든 packet은 `ssh_ptl_submit()`을 통해 이 queue에 제출해야 하며, PTL 자체가 보내는 control packet도 포함됩니다. Data packet은 timeout이나 EC가 보낸 NAK 때문에 내부적으로 다시 제출될 수 있습니다.
Packet type과 retry 횟수를 합쳐 다음 전송 packet을 선택합니다.
Packet Queue
------------
The packet queue is the first of the two fundamental collections in the
packet transport layer. It is a priority queue, with priority of the
respective packets based on the packet type (major) and number of tries
(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value.
All packets to be transmitted by the transport layer must be submitted to
this queue via |ssh_ptl_submit|. Note that this includes control packets
sent by the transport layer itself. Internally, data packets can be
re-submitted to this queue due to timeouts or NAK packets sent by the EC.
Packet Pending Set
165-176Pending set은 PTL의 두 번째 기본 collection입니다. 이미 전송되었지만 EC의 acknowledgment, 예를 들어 대응 ACK packet을 기다리는 packet reference를 저장합니다.
Packet acknowledgment timeout이나 NAK로 다시 제출된 packet은 pending set에서 제거되지 않으므로 queued 상태와 pending 상태를 동시에 가질 수 있습니다.
Pending Set
-----------
The pending set is the second of the two fundamental collections in the
packet transport layer. It stores references to packets that have already
been transmitted, but wait for acknowledgment (e.g. the corresponding ACK
packet) by the EC.
Note that a packet may both be pending and queued if it has been
re-submitted due to a packet acknowledgment timeout or NAK. On such a
re-submission, packets are not removed from the pending set.
Packet Transmitter Thread
177-197Transmitter thread는 packet 전송의 실제 작업 대부분을 담당합니다. 매 iteration에서 queue의 다음 packet이 전송 가능한지 기다리고 확인한 뒤, 가능하면 dequeue하고 transmission attempt 수인 tries를 증가시킵니다.
EC의 ACK가 필요한 sequenced packet은 pending set에 추가한 다음 serdev subsystem에 data를 제출합니다. 제출 중 오류나 timeout이 발생하면 transmitter thread가 해당 status로 packet을 완료합니다.
ACK가 필요 없는 unsequenced packet은 transmitter thread에서 성공으로 완료됩니다.
동시에 ACK를 기다릴 수 있는 sequenced packet 수는 제한됩니다. 현재 제한은 1이며 이유는 `Documentation/driver-api/surface_aggregator/ssh.rst`에 설명되어 있습니다. ACK·NAK control packet은 이 제한과 무관하게 언제나 전송할 수 있습니다.
Packet type에 따라 pending 등록과 완료 시점이 갈립니다.
Transmitter Thread
------------------
The transmitter thread is responsible for most of the actual work regarding
packet transmission. In each iteration, it (waits for and) checks if the
next packet on the queue (if any) can be transmitted and, if so, removes it
from the queue and increments its counter for the number of transmission
attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by
the EC, the packet is added to the pending set. Next, the packet's data is
submitted to the serdev subsystem. In case of an error or timeout during
this submission, the packet is completed by the transmitter thread with the
status value of the callback set accordingly. In case the packet is
unsequenced, i.e. does not require an ACK by the EC, the packet is completed
with success on the transmitter thread.
Transmission of sequenced packets is limited by the number of concurrently
pending packets, i.e. a limit on how many packets may be waiting for an ACK
from the EC in parallel. This limit is currently set to one (see
Documentation/driver-api/surface_aggregator/ssh.rst for the reasoning behind
this). Control packets (i.e. ACK and NAK) can always be transmitted.
Packet Receiver Thread
198-218EC에서 받은 모든 data는 추가 처리를 위해 FIFO buffer에 넣고 receiver thread가 처리합니다. 이 thread는 수신 message를 `ssh_frame`과 payload로 parse하고 validate하며, 유효한 message에는 필요한 ACK를, validation 오류나 잘못된 data에는 NAK를 준비해 제출합니다.
Receiver thread는 ACK의 sequence ID를 pending packet과 match하여 완료합니다. NAK를 받으면 현재 pending packet을 모두 다시 제출하기 시작하며, 이 재제출은 timeout에 의한 재제출과 비슷합니다.
Sequenced packet의 성공 completion은 항상 receiver thread에서 실행됩니다. 반면 실패를 나타내는 completion은 그 실패가 발생한 process에서 실행됩니다.
Payload data는 callback을 통해 바로 위의 request transport layer로 전달합니다.
수신 frame의 종류와 검증 결과에 따라 ACK·NAK·상위 전달이 결정됩니다.
Receiver Thread
---------------
Any data received from the EC is put into a FIFO buffer for further
processing. This processing happens on the receiver thread. The receiver
thread parses and validates the received message into its |ssh_frame| and
corresponding payload. It prepares and submits the necessary ACK (and on
validation error or invalid data NAK) packets for the received messages.
This thread also handles further processing, such as matching ACK messages
to the corresponding pending packet (via sequence ID) and completing it, as
well as initiating re-submission of all currently pending packets on
receival of a NAK message (re-submission in case of a NAK is similar to
re-submission due to timeout, see below for more details on that). Note that
the successful completion of a sequenced packet will always run on the
receiver thread (whereas any failure-indicating completion will run on the
process where the failure occurred).
Any payload data is forwarded via a callback to the next upper layer, i.e.
the request transport layer.
Packet Timeout Reaper
219-245Packet acknowledgment timeout은 sequenced packet마다 존재합니다. 각 transmission attempt가 transmitter thread에서 시작될 때 한 번 arm되며, timeout 시 packet을 다시 제출하거나 tries가 한도를 넘었다면 취소합니다.
전용 reaper task는 다음 packet의 timeout 시점에 실행되도록 반복 schedule되는 work item입니다. 실행되면 pending set에서 timeout을 넘긴 packet을 찾고, 남은 packet이 있으면 다음 적절한 시점으로 자신을 다시 schedule합니다.
Timeout packet에 try가 남아 있으면 재제출하고, 없으면 status `-ETIMEDOUT`으로 완료합니다. Timeout이나 NAK에 의한 재제출은 tries가 증가한 packet을 queue에 추가하므로 priority가 높아집니다.
재제출된 packet은 다음 transmission attempt 전까지 timeout이 disable되며 pending set에는 계속 남습니다.
Transmission timeout과 packet acknowledgment timeout 덕분에 PTL은 packet을 timeout시키는 방식으로라도 항상 진행하며 완전히 block되지 않습니다.
Pending packet의 기한과 남은 try에 따라 retry 또는 최종 완료를 선택합니다.
Timeout Reaper
--------------
The packet acknowledgment timeout is a per-packet timeout for sequenced
packets, started when the respective packet begins (re-)transmission (i.e.
this timeout is armed once per transmission attempt on the transmitter
thread). It is used to trigger re-submission or, when the number of tries
has been exceeded, cancellation of the packet in question.
This timeout is handled via a dedicated reaper task, which is essentially a
work item (re-)scheduled to run when the next packet is set to time out. The
work item then checks the set of pending packets for any packets that have
exceeded the timeout and, if there are any remaining packets, re-schedules
itself to the next appropriate point in time.
If a timeout has been detected by the reaper, the packet will either be
re-submitted if it still has some remaining tries left, or completed with
``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and
triggered by receival of a NAK, means that the packet is added to the queue
with a now incremented number of tries, yielding a higher priority. The
timeout for the packet will be disabled until the next transmission attempt
and the packet remains on the pending set.
Note that due to transmission and packet acknowledgment timeouts, the packet
transport layer is always guaranteed to make progress, if only through
timing out packets, and will never fully block.
PTL concurrency와 locking
246-287PTL에는 packet queue와 pending set을 각각 보호하는 두 main lock이 있습니다. 각 collection은 대응 lock을 잡은 동안에만 접근·수정할 수 있습니다. 두 collection을 모두 접근하려면 deadlock을 피하도록 pending lock을 먼저, queue lock을 나중에 획득해야 합니다.
최초 제출 뒤 packet priority는 queue lock을 보유할 때만, packet timestamp는 pending lock을 보유할 때만 접근할 수 있습니다.
State flag는 atomic bit operation과 필요 시 memory barrier로 관리합니다. Timeout reaper work item과 expiration date 변경은 별도 lock이 보호합니다.
Packet의 PTL reference인 `ptl`은 상위 request 제출 때, 상위 request가 없으면 packet 최초 제출 때 설정되고 이후 바뀌지 않습니다. Cancellation처럼 제출과 동시에 실행될 수 있는 function은 `ptl`이 이미 설정되었다고 가정할 수 없으므로 `READ_ONCE()`로 읽고 대칭적으로 `WRITE_ONCE()`로 설정합니다.
Tracing을 위한 priority와 state는 stale value가 치명적이지 않은 read-only 상황에서만 대응 lock 밖에서 읽을 수 있으며, 이때도 `WRITE_ONCE()`와 `READ_ONCE()`로 접근을 보장합니다.
상위 interface의 `ssh_ptl_submit()`, `ssh_ptl_cancel()`, `ssh_ptl_rx_rcvbuf()`, `ssh_ptl_shutdown()`은 서로 동시에 실행할 수 있습니다. 다만 같은 packet에 대한 submit은 자기 자신과 동시에 실행할 수 없고, shutdown과 data receive도 각각 자기 자신과 동시에 실행할 수는 없지만 서로 간에는 병행할 수 있습니다.
Concurrency and Locking
-----------------------
There are two main locks in the packet transport layer: One guarding access
to the packet queue and one guarding access to the pending set. These
collections may only be accessed and modified under the respective lock. If
access to both collections is needed, the pending lock must be acquired
before the queue lock to avoid deadlocks.
In addition to guarding the collections, after initial packet submission
certain packet fields may only be accessed under one of the locks.
Specifically, the packet priority must only be accessed while holding the
queue lock and the packet timestamp must only be accessed while holding the
pending lock.
Other parts of the packet transport layer are guarded independently. State
flags are managed by atomic bit operations and, if necessary, memory
barriers. Modifications to the timeout reaper work item and expiration date
are guarded by their own lock.
The reference of the packet to the packet transport layer (``ptl``) is
somewhat special. It is either set when the upper layer request is submitted
or, if there is none, when the packet is first submitted. After it is set,
it will not change its value. Functions that may run concurrently with
submission, i.e. cancellation, can not rely on the ``ptl`` reference to be
set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas
setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry.
Some packet fields may be read outside of the respective locks guarding
them, specifically priority and state for tracing. In those cases, proper
access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such
read-only access is only allowed when stale values are not critical.
With respect to the interface for higher layers, packet submission
(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival
(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be
executed concurrently with respect to each other. Note that packet
submission may not run concurrently with itself for the same packet.
Equally, shutdown and data receival may also not run concurrently with
themselves (but may run concurrently with each other).
Request Transport Layer 개요
288-299Request transport layer는 `struct ssh_rtl`로 표현되며 PTL 위에 구축됩니다. Host가 보내는 `ssh_command` payload의 SSH packet을 request로 다룹니다.
EC도 `ssh_command` payload로 보내는 request response와 event를 이 계층에서 구분합니다. Response는 RTL이 처리하고 event는 callback을 통해 바로 위 controller layer로 전달합니다.
같은 command-type payload를 request response와 event로 구분합니다.
Request Transport Layer
=======================
The request transport layer is represented via |ssh_rtl| and builds on top
of the packet transport layer. It deals with requests, i.e. SSH packets sent
by the host containing a |ssh_command| as frame payload. This layer
separates responses to requests from events, which are also sent by the EC
via a |ssh_command| payload. While responses are handled in this layer,
events are relayed to the next upper layer, i.e. the controller layer, via
the corresponding callback. The request transport layer is structured around
the following key concepts:
Request와 lifecycle
300-355Request는 data를 query하거나 action을 trigger하거나 둘 다 수행하도록 host에서 EC로 보내는 command-type payload packet입니다. `struct ssh_request`가 message data를 담은 내부 `ssh_packet`을 감싸며 `ssam_request_sync` 같은 모든 top-level 표현도 이 구조체 위에 구축됩니다.
`ssh_request`가 `ssh_packet`을 확장하므로 lifetime도 packet 내부 reference counter로 관리하고 `ssh_request_get()`과 `ssh_request_put()`으로 접근합니다. Counter가 0이 되면 request의 `ssh_request_ops`가 제공한 `release()` callback을 호출합니다.
Request에는 EC가 command-type SSH message로 보내는 optional response가 있을 수 있습니다. Request를 만드는 쪽은 response 예상 여부를 알아야 하며 `ssh_request_init()`에 전달하는 request flag에 표시하여 RTL이 response를 기다리게 해야 합니다.
Request ops의 `complete()` callback도 RTL에 `ssh_rtl_submit()`으로 제출한 뒤 release보다 먼저 반드시 실행됩니다. Response가 없는 request는 내부 packet이 PTL에서 성공적으로 전송되면 packet completion callback 안에서 성공 완료됩니다.
Response가 있는 request는 receiver thread에서 실행되는 packet layer data-received callback이 request ID로 response를 match한 뒤 성공 완료됩니다. Error completion이면 status는 대응 negative errno입니다.
Request type과 state는 `ssh_request_flags`로 관리합니다. `SSH_REQUEST_SF_LOCKED_BIT`는 완료 임박과 새 reference 금지를 나타내며 설정자가 request queue와 pending set의 reference를 제거해야 합니다.
`SSH_REQUEST_SF_COMPLETED_BIT`는 `complete()`가 한 번만 실행되도록 하고, `SSH_REQUEST_SF_QUEUED_BIT`와 `SSH_REQUEST_SF_PENDING_BIT`는 각각 queue와 pending set membership을 나타냅니다.
Request
-------
Requests are packets with a command-type payload, sent from host to EC to
query data from or trigger an action on it (or both simultaneously). They
are represented by |ssh_request|, wrapping the underlying |ssh_packet|
storing its message data (i.e. SSH frame with command payload). Note that
all top-level representations, e.g. |ssam_request_sync| are built upon this
struct.
As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the
reference counter inside the packet struct (which can be accessed via
|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the
``release()`` callback of the |ssh_request_ops| reference of the request is
called.
Requests can have an optional response that is equally sent via a SSH
message with command-type payload (from EC to host). The party constructing
the request must know if a response is expected and mark this in the request
flags provided to |ssh_request_init|, so that the request transport layer
can wait for this response.
Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback
provided via its request ops reference and is guaranteed to be completed
before it is released once it has been submitted to the request transport
layer via |ssh_rtl_submit|. For a request without a response, successful
completion will occur once the underlying packet has been successfully
transmitted by the packet transport layer (i.e. from within the packet
completion callback). For a request with response, successful completion
will occur once the response has been received and matched to the request
via its request ID (which happens on the packet layer's data-received
callback running on the receiver thread). If the request is completed with
an error, the status value will be set to the corresponding (negative) errno
value.
The state of a request is again managed via its ``state`` flags
(|ssh_request_flags|), which also encode the request type. In particular,
the following bits are noteworthy:
* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either
through error or success, is imminent. It indicates that no further
references of the request should be taken and any existing references
should be dropped as soon as possible. The process setting this bit is
responsible for removing any references to this request from the request
queue and pending set.
* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the
``complete()`` callback and is used to ensure that this callback only runs
once.
* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on
the request queue and cleared when it is dequeued.
* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to
the pending set and cleared when it is removed from it.
Request Queue
356-370Request queue는 RTL의 두 기본 collection 중 첫 번째입니다. PTL packet queue와 달리 priority queue가 아니며 단순한 first come first serve 원칙을 따릅니다.
RTL이 전송할 모든 request는 `ssh_rtl_submit()`을 통해 이 queue에 제출합니다. 한 번 제출한 request는 다시 제출할 수 없고 timeout 때 자동 재제출되지 않으며 timeout error로 완료됩니다.
다시 시도하려면 caller가 새 request를 만들어 제출해야 하며 같은 request instance를 다시 제출해서는 안 됩니다.
Request instance는 단 한 번만 제출할 수 있습니다.
Request Queue
-------------
The request queue is the first of the two fundamental collections in the
request transport layer. In contrast to the packet queue of the packet
transport layer, it is not a priority queue and the simple first come first
serve principle applies.
All requests to be transmitted by the request transport layer must be
submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may
not be re-submitted, and will not be re-submitted automatically on timeout.
Instead, the request is completed with a timeout error. If desired, the
caller can create and submit a new request for another try, but it must not
submit the same request again.
Request Pending Set
371-378Pending set은 RTL의 두 번째 기본 collection입니다. EC response를 기다리는 모든 pending request의 reference를 저장하며, PTL pending set이 ACK를 기다리는 packet을 저장하는 것과 유사합니다.
Pending Set
-----------
The pending set is the second of the two fundamental collections in the
request transport layer. This collection stores references to all pending
requests, i.e. requests awaiting a response from the EC (similar to what the
pending set of the packet transport layer does for packets).
Request Transmitter Task
379-389새 request가 전송 가능해지면 transmitter task가 schedule됩니다. Queue의 다음 request를 전송할 수 있는지 검사하고 가능하면 내부 packet을 PTL에 제출합니다.
이 검사는 동시에 response를 기다리는 pending request 수를 제한합니다. Response가 필요한 request는 packet을 제출하기 전에 pending set에 먼저 추가합니다.
Pending limit을 확인한 뒤 response 필요 여부에 따라 set 등록 순서를 지킵니다.
Transmitter Task
----------------
The transmitter task is scheduled when a new request is available for
transmission. It checks if the next request on the request queue can be
transmitted and, if so, submits its underlying packet to the packet
transport layer. This check ensures that only a limited number of
requests can be pending, i.e. waiting for a response, at the same time. If
the request requires a response, the request is added to the pending set
before its packet is submitted.
Packet Completion Callback
390-402Request의 내부 packet이 완료되면 packet completion callback이 실행됩니다. Packet이 error로 완료되면 request도 callback에 제공된 error 값으로 완료됩니다.
Packet이 성공한 뒤의 처리는 request 유형에 달립니다. Response를 기다리는 request는 transmitted 상태로 표시하고 request timeout을 시작합니다. Response가 필요 없는 request는 즉시 성공 완료합니다.
내부 packet 결과와 response 기대 여부가 request의 다음 상태를 결정합니다.
Packet Completion Callback
--------------------------
The packet completion callback is executed once the underlying packet of a
request has been completed. In case of an error completion, the
corresponding request is completed with the error value provided in this
callback.
On successful packet completion, further processing depends on the request.
If the request expects a response, it is marked as transmitted and the
request timeout is started. If the request does not expect a response, it is
completed with success.
Data-Received Callback
403-416Data-received callback은 내부 PTL이 data-type frame을 받았다고 RTL에 알립니다. 일반적으로 payload는 command type이어야 합니다.
Command의 request ID가 event에 예약된 1부터 `SSH_NUM_EVENTS`까지의 범위라면 RTL에 등록된 event callback으로 전달합니다.
Request ID가 request response를 나타내면 pending set에서 대응 request를 찾습니다. Request가 존재하고 transmitted로 표시되어 있으면 성공으로 완료합니다.
RQID가 event 예약 범위인지 request response인지에 따라 전달 목적지가 달라집니다.
Data-Received Callback
----------------------
The data received callback notifies the request transport layer of data
being received by the underlying packet transport layer via a data-type
frame. In general, this is expected to be a command-type payload.
If the request ID of the command is one of the request IDs reserved for
events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the
event callback registered in the request transport layer. If the request ID
indicates a response to a request, the respective request is looked up in
the pending set and, if found and marked as transmitted, completed with
success.
Request Timeout Reaper
417-436Request-response timeout은 response를 기대하는 request마다 존재하여 EC response를 무한히 기다리지 않게 합니다. 내부 packet이 성공적으로 완료된 뒤 시작됩니다.
PTL의 packet acknowledgment timeout과 마찬가지로, 다음 request의 timeout 시점에 실행되도록 반복 schedule되는 전용 reaper work item이 처리합니다.
Reaper는 pending request set을 scan하여 timeout된 request를 status `-ETIMEDOUT`으로 완료합니다. Request는 자동 재제출되지 않으며 필요하면 발행자가 새 request를 만들어 제출해야 합니다.
이 timeout은 packet transmission·acknowledgment timeout과 함께 RTL이 request나 packet을 timeout시키는 방식으로라도 계속 진행하고 완전히 block되지 않도록 보장합니다.
성공적으로 전송됐지만 response가 오지 않은 request를 최종 완료합니다.
Timeout Reaper
--------------
The request-response-timeout is a per-request timeout for requests expecting
a response. It is used to ensure that a request does not wait indefinitely
on a response from the EC and is started after the underlying packet has
been successfully completed.
This timeout is, similar to the packet acknowledgment timeout on the packet
transport layer, handled via a dedicated reaper task. This task is
essentially a work-item (re-)scheduled to run when the next request is set
to time out. The work item then scans the set of pending requests for any
requests that have timed out and completes them with ``-ETIMEDOUT`` as
status. Requests will not be re-submitted automatically. Instead, the issuer
of the request must construct and submit a new request, if so desired.
Note that this timeout, in combination with packet transmission and
acknowledgment timeouts, guarantees that the request layer will always make
progress, even if only through timing out packets, and never fully block.
RTL concurrency와 locking
437-462RTL도 request queue와 pending set을 각각 보호하는 두 main lock을 둡니다. 각 collection은 대응 lock을 보유한 동안에만 접근하거나 수정할 수 있습니다.
State flag는 atomic bit operation과 필요 시 memory barrier로 관리하고, timeout reaper work item과 expiration date 변경은 별도 lock으로 보호합니다.
Tracing을 위해 state를 lock 밖에서 읽을 때는 stale value가 치명적이지 않은 read-only 상황에서만 허용하며 `WRITE_ONCE()`와 `READ_ONCE()`를 사용합니다.
상위 interface의 `ssh_rtl_submit()`, `ssh_rtl_cancel()`, `ssh_rtl_shutdown()`은 서로 동시에 실행할 수 있습니다. 다만 같은 request의 submit은 자기 자신과 동시에 실행할 수 없고 request마다 한 번만 호출할 수 있습니다. Shutdown도 자기 자신과 동시에 실행할 수 없습니다.
Concurrency and Locking
-----------------------
Similar to the packet transport layer, there are two main locks in the
request transport layer: One guarding access to the request queue and one
guarding access to the pending set. These collections may only be accessed
and modified under the respective lock.
Other parts of the request transport layer are guarded independently. State
flags are (again) managed by atomic bit operations and, if necessary, memory
barriers. Modifications to the timeout reaper work item and expiration date
are guarded by their own lock.
Some request fields may be read outside of the respective locks guarding
them, specifically the state for tracing. In those cases, proper access is
ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only
access is only allowed when stale values are not critical.
With respect to the interface for higher layers, request submission
(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer
shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with
respect to each other. Note that request submission may not run concurrently
with itself for the same request (and also may only be called once per
request). Equally, shutdown may also not run concurrently with itself.
Controller Layer
463-473Controller layer는 RTL을 확장하여 client driver가 사용하기 쉬운 interface를 제공합니다. `struct ssam_controller`와 SSH driver로 표현됩니다.
하위 transport layer가 packet과 request의 전송·처리를 담당하는 반면 controller는 관리 역할을 맡습니다. Device initialization, power management, event delivery와 registration을 포함한 event handling을 event completion system인 `ssam_cplt`를 통해 처리합니다.
전송 primitive 위에 device와 event lifecycle을 제공합니다.
Controller Layer
================
The controller layer extends on the request transport layer to provide an
easy-to-use interface for client drivers. It is represented by
|ssam_controller| and the SSH driver. While the lower level transport layers
take care of transmitting and handling packets and requests, the controller
layer takes on more of a management role. Specifically, it handles device
initialization, power management, and event handling, including event
delivery and registration via the (event) completion system (|ssam_cplt|).
Event Registration
474-513일반적으로 host가 event 또는 event class를 명시적으로 요청해야 EC가 보냅니다. HID input event는 예외로 보입니다. Event-enable request로 활성화하고 더 이상 필요 없으면 event-disable request로 비활성화해야 합니다.
Event를 enable·disable하는 구체적인 request는 event의 관리 주체에 해당하는 `ssam_event_registry`가 정합니다. Request parameter로 target category와 registry에 따라 instance ID를 제공합니다. Registry가 instance ID를 쓰지 않으면 0이어야 합니다.
Target category와 optional instance ID가 `ssam_event_id`를 구성합니다. Event registry와 event ID를 함께 사용해야 event class를 고유하게 식별할 수 있습니다.
Enable-event request에는 별도의 request ID parameter도 필요합니다. 이 값은 활성화할 event class를 바꾸지 않고 EC가 보내는 해당 class의 모든 event에 RQID로 기록됩니다.
Event 식별용 request ID는 1부터 `SSH_NUM_EVENTS`까지로 제한되어 event 여부와 구체적인 class mapping에 사용됩니다. 현재 controller는 `ssam_event_id`의 target category를 이 parameter로 항상 설정합니다.
여러 client driver가 같거나 겹치는 event class를 사용할 수 있지만 enable·disable call은 on/off의 binary operation입니다. Controller는 event registry와 ID를 key로 하는 RB-tree mapping에 reference count를 저장하여 access를 조정합니다. 유효한 registry·ID 조합의 알려진 전체 목록은 없습니다.
자세한 reference count 동작은 `ssam_nf`, `ssam_nf_refcount_inc()`, `ssam_nf_refcount_dec()`를 참조합니다. 이 관리는 다음 절의 notifier registration과 함께 top-level `ssam_notifier_register()`·`ssam_notifier_unregister()`에서 수행됩니다.
Event Registration
------------------
In general, an event (or rather a class of events) has to be explicitly
requested by the host before the EC will send it (HID input events seem to
be the exception). This is done via an event-enable request (similarly,
events should be disabled via an event-disable request once no longer
desired).
The specific request used to enable (or disable) an event is given via an
event registry, i.e. the governing authority of this event (so to speak),
represented by |ssam_event_registry|. As parameters to this request, the
target category and, depending on the event registry, instance ID of the
event to be enabled must be provided. This (optional) instance ID must be
zero if the registry does not use it. Together, target category and instance
ID form the event ID, represented by |ssam_event_id|. In short, both, event
registry and event ID, are required to uniquely identify a respective class
of events.
Note that a further *request ID* parameter must be provided for the
enable-event request. This parameter does not influence the class of events
being enabled, but instead is set as the request ID (RQID) on each event of
this class sent by the EC. It is used to identify events (as a limited
number of request IDs is reserved for use in events only, specifically one
to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific
class. Currently, the controller always sets this parameter to the target
category specified in |ssam_event_id|.
As multiple client drivers may rely on the same (or overlapping) classes of
events and enable/disable calls are strictly binary (i.e. on/off), the
controller has to manage access to these events. It does so via reference
counting, storing the counter inside an RB-tree based mapping with event
registry and ID as key (there is no known list of valid event registry and
event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and
|ssam_nf_refcount_dec| for details.
This management is done together with notifier registration (described in
the next section) via the top-level |ssam_notifier_register| and
|ssam_notifier_unregister| functions.
Event Delivery
514-552Client driver가 event를 받으려면 `ssam_notifier_register()`로 event notifier를 등록해야 합니다. 이 함수는 해당 event class의 reference counter를 증가시키고, 아직 활성화되지 않았다면 EC에서 class를 enable한 뒤 notifier callback을 설치합니다.
Notifier callback은 target category마다 하나씩 존재하는 RCU list에 저장됩니다. Target category 수는 고정되어 알려져 있습니다.
Event registry와 event ID 조합에서 event class가 제공할 command data인 target ID, target category, command ID, instance ID를 모두 알아내는 알려진 association은 없습니다. Event ID가 제공하는 target category와 instance ID만 알 수 있습니다.
Notifier 저장 방식 때문에 client driver는 요청하지 않은 event도 받을 수 있으며 이를 처리해야 합니다. 기본적으로 같은 target category의 모든 event를 받습니다.
이를 단순화하도록 notifier 등록 시 event registry의 target ID와 event ID의 instance ID를 기준으로 filtering을 요청할 수 있습니다. Filter는 callback 실행 시 notifier list를 순회하면서 적용합니다.
모든 notifier callback은 completion workqueue라는 전용 workqueue에서 실행됩니다. PTL receiver thread에서 실행되는 RTL callback이 event를 받으면 대응 `ssam_event_queue`에 넣습니다. 그 queue의 completion work item이 completion workqueue에서 event를 꺼내 notifier callback을 실행하므로 receiver thread를 block하지 않습니다.
Target ID와 target category 조합마다 event queue가 하나씩 있습니다. 같은 조합의 event callback은 순서대로 실행하고, 조합이 다른 event callback은 병렬로 실행할 수 있습니다.
Receiver thread는 enqueue까지만 수행하고 callback은 completion workqueue가 실행합니다.
Event Delivery
--------------
To receive events, a client driver has to register an event notifier via
|ssam_notifier_register|. This increments the reference counter for that
specific class of events (as detailed in the previous section), enables the
class on the EC (if it has not been enabled already), and installs the
provided notifier callback.
Notifier callbacks are stored in lists, with one (RCU) list per target
category (provided via the event ID; NB: there is a fixed known number of
target categories). There is no known association from the combination of
event registry and event ID to the command data (target ID, target category,
command ID, and instance ID) that can be provided by an event class, apart
from target category and instance ID given via the event ID.
Note that due to the way notifiers are (or rather have to be) stored, client
drivers may receive events that they have not requested and need to account
for them. Specifically, they will, by default, receive all events from the
same target category. To simplify dealing with this, filtering of events by
target ID (provided via the event registry) and instance ID (provided via
the event ID) can be requested when registering a notifier. This filtering
is applied when iterating over the notifiers at the time they are executed.
All notifier callbacks are executed on a dedicated workqueue, the so-called
completion workqueue. After an event has been received via the callback
installed in the request layer (running on the receiver thread of the packet
transport layer), it will be put on its respective event queue
(|ssam_event_queue|). From this event queue the completion work item of that
queue (running on the completion workqueue) will pick up the event and
execute the notifier callback. This is done to avoid blocking on the
receiver thread.
There is one event queue per combination of target ID and target category.
This is done to ensure that notifier callbacks are executed in sequence for
events of the same target ID and target category. Callbacks can be executed
in parallel for events with a different combination of target ID and target
category.
Controller concurrency와 locking
553-578Controller의 concurrency safety 대부분은 하위 RTL이 제공합니다. 여기에 event 등록·해제를 보호하는 별도 lock이 추가됩니다.
Controller state 접근은 read/write semaphore인 state lock이 보호합니다. Reader 쪽은 state가 유지되어야 하는 function을 실행하는 동안 state가 바뀌지 않음을 보장합니다.
이 reader 보장은 `ssam_notifier_register()`, `ssam_notifier_unregister()`, `ssam_request_sync_submit()` 및 파생 function에 필요하며, `ssam_client_bind()`나 `ssam_client_link()`가 이미 같은 보장을 제공하는 경우에는 중복해서 필요하지 않습니다.
Writer 쪽은 initialization, destruction, suspension, resumption처럼 state를 바꾸는 모든 transition을 보호합니다.
잘못된 API 사용을 smoke-test하기 위해 `ssam_request_sync_submit()` 등에서 state lock 밖의 controller state를 read-only로 확인할 수 있습니다. 이 검사는 모든 잘못된 사용을 막는 동기화 장치가 아니라 발견을 돕는 진단이며 `WRITE_ONCE()`와 `READ_ONCE()`로 variable 접근을 보장합니다.
State가 변하지 않아야 한다는 precondition을 만족했다면 initialization과 shutdown을 제외한 모든 function은 서로 동시에 실행할 수 있습니다. 여기에는 `ssam_notifier_register()`, `ssam_notifier_unregister()`, `ssam_request_sync_submit()` 및 그 위에 구축된 모든 function이 포함됩니다.
Concurrency and Locking
-----------------------
Most of the concurrency related safety guarantees of the controller are
provided by the lower-level request transport layer. In addition to this,
event (un-)registration is guarded by its own lock.
Access to the controller state is guarded by the state lock. This lock is a
read/write semaphore. The reader part can be used to ensure that the state
does not change while functions depending on the state to stay the same
(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|,
|ssam_request_sync_submit|, and derivatives) are executed and this guarantee
is not already provided otherwise (e.g. through |ssam_client_bind| or
|ssam_client_link|). The writer part guards any transitions that will change
the state, i.e. initialization, destruction, suspension, and resumption.
The controller state may be accessed (read-only) outside the state lock for
smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|).
Note that such checks are not supposed to (and will not) protect against all
invalid usages, but rather aim to help catch them. In those cases, proper
variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``.
Assuming any preconditions on the state not changing have been satisfied,
all non-initialization and non-shutdown functions may run concurrently with
each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|,
|ssam_request_sync_submit|, as well as all functions building on top of those.
요약과 해설
internal.rst:1-578이 문서는 SSAM core를 serdev 기반 Packet Transport Layer, command 기반 Request Transport Layer, client-facing Controller Layer로 나누고 각 계층에서 packet·request lifetime, queue와 pending set, retry·timeout, event notifier, concurrency와 locking이 어떻게 맞물리는지 설명합니다.