Documentation/driver-api/surface_aggregator/client.rst GitHub 원문 ↗

Linux 6.18.37 · Driver API

Writing Surface Aggregator Client Drivers

Surface Aggregator의 Non-SSAM·SSAM client 구성, device lifetime과 UID matching, little-endian synchronous request macro, event notifier filtering·reference counting을 설명하는 전문 번역입니다.

Source pathDocumentation/driver-api/surface_aggregator/client.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

client.rst:1-397

Conventional device를 다루는 Non-SSAM driver는 controller와 device link를 명시적으로 만들어 lifetime 및 suspend ordering을 보장합니다. SSAM device는 UID로 driver와 match하고 추가한 주체가 shutdown 전에 제거해야 합니다. Host request는 little-endian 계약을 지키며 수동 API 또는 일반·multi-device·client macro로 실행합니다. Event notifier는 registry·ID·mask·flag로 filtering하며 첫 등록과 마지막 해제에 맞춰 EC event를 enable·disable합니다.

문서 구성
원문 줄내용
1-48API 치환·문서 연결·client 유형
49-116Non-SSAM bind와 SSAM device lifecycle
117-156SSAM driver·UID matching·controller 접근
157-235Synchronous request 계약과 수동 실행
236-310일반·multi-device·client request macro
311-397Event notifier 등록·filtering·reference counting

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0+
2
3 .. |ssam_controller| replace:: :c:type:`struct ssam_controller <ssam_controller>`
4 .. |ssam_device| replace:: :c:type:`struct ssam_device <ssam_device>`
5 .. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver <ssam_device_driver>`
6 .. |ssam_client_bind| replace:: :c:func:`ssam_client_bind`
7 .. |ssam_client_link| replace:: :c:func:`ssam_client_link`
8 .. |ssam_get_controller| replace:: :c:func:`ssam_get_controller`
9 .. |ssam_controller_get| replace:: :c:func:`ssam_controller_get`
10 .. |ssam_controller_put| replace:: :c:func:`ssam_controller_put`
11 .. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc`
12 .. |ssam_device_add| replace:: :c:func:`ssam_device_add`
13 .. |ssam_device_remove| replace:: :c:func:`ssam_device_remove`
14 .. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register`
15 .. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister`
16 .. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver`
17 .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE`
18 .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register`
19 .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister`
20 .. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register`
21 .. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister`
22 .. |ssam_request_do_sync| replace:: :c:func:`ssam_request_do_sync`
23 .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask <ssam_event_mask>`
24
25
26 ======================
27 Writing Client Drivers
28 ======================
29
30 For the API documentation, refer to:
31
32 .. toctree::
33 :maxdepth: 2
34
35 client-api
36
37
38 Overview
39 ========
40
41 Client drivers can be set up in two main ways, depending on how the
42 corresponding device is made available to the system. We specifically
43 differentiate between devices that are presented to the system via one of
44 the conventional ways, e.g. as platform devices via ACPI, and devices that
45 are non-discoverable and instead need to be explicitly provided by some
46 other mechanism, as discussed further below.
47
48
49 Non-SSAM Client Drivers
50 =======================
51
52 All communication with the SAM EC is handled via the |ssam_controller|
53 representing that EC to the kernel. Drivers targeting a non-SSAM device (and
54 thus not being a |ssam_device_driver|) need to explicitly establish a
55 connection/relation to that controller. This can be done via the
56 |ssam_client_bind| function. Said function returns a reference to the SSAM
57 controller, but, more importantly, also establishes a device link between
58 client device and controller (this can also be done separate via
59 |ssam_client_link|). It is important to do this, as it, first, guarantees
60 that the returned controller is valid for use in the client driver for as
61 long as this driver is bound to its device, i.e. that the driver gets
62 unbound before the controller ever becomes invalid, and, second, as it
63 ensures correct suspend/resume ordering. This setup should be done in the
64 driver's probe function, and may be used to defer probing in case the SSAM
65 subsystem is not ready yet, for example:
66
67 .. code-block:: c
68
69 static int client_driver_probe(struct platform_device *pdev)
70 {
71 struct ssam_controller *ctrl;
72
73 ctrl = ssam_client_bind(&pdev->dev);
74 if (IS_ERR(ctrl))
75 return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
76
77 // ...
78
79 return 0;
80 }
81
82 The controller may be separately obtained via |ssam_get_controller| and its
83 lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|.
84 Note that none of these functions, however, guarantee that the controller
85 will not be shut down or suspended. These functions essentially only operate
86 on the reference, i.e. only guarantee a bare minimum of accessibility
87 without any guarantees at all on practical operability.
88
89
90 Adding SSAM Devices
91 ===================
92
93 If a device does not already exist/is not already provided via conventional
94 means, it should be provided as |ssam_device| via the SSAM client device
95 hub. New devices can be added to this hub by entering their UID into the
96 corresponding registry. SSAM devices can also be manually allocated via
97 |ssam_device_alloc|, subsequently to which they have to be added via
98 |ssam_device_add| and eventually removed via |ssam_device_remove|. By
99 default, the parent of the device is set to the controller device provided
100 for allocation, however this may be changed before the device is added. Note
101 that, when changing the parent device, care must be taken to ensure that the
102 controller lifetime and suspend/resume ordering guarantees, in the default
103 setup provided through the parent-child relation, are preserved. If
104 necessary, by use of |ssam_client_link| as is done for non-SSAM client
105 drivers and described in more detail above.
106
107 A client device must always be removed by the party which added the
108 respective device before the controller shuts down. Such removal can be
109 guaranteed by linking the driver providing the SSAM device to the controller
110 via |ssam_client_link|, causing it to unbind before the controller driver
111 unbinds. Client devices registered with the controller as parent are
112 automatically removed when the controller shuts down, but this should not be
113 relied upon, especially as this does not extend to client devices with a
114 different parent.
115
116
117 SSAM Client Drivers
118 ===================
119
120 SSAM client device drivers are, in essence, no different than other device
121 driver types. They are represented via |ssam_device_driver| and bind to a
122 |ssam_device| via its UID (:c:type:`struct ssam_device.uid <ssam_device>`)
123 member and the match table
124 (:c:type:`struct ssam_device_driver.match_table <ssam_device_driver>`),
125 which should be set when declaring the driver struct instance. Refer to the
126 |SSAM_DEVICE| macro documentation for more details on how to define members
127 of the driver's match table.
128
129 The UID for SSAM client devices consists of a ``domain``, a ``category``,
130 a ``target``, an ``instance``, and a ``function``. The ``domain`` is used
131 differentiate between physical SAM devices
132 (:c:type:`SSAM_DOMAIN_SERIALHUB <ssam_device_domain>`), i.e. devices that can
133 be accessed via the Surface Serial Hub, and virtual ones
134 (:c:type:`SSAM_DOMAIN_VIRTUAL <ssam_device_domain>`), such as client-device
135 hubs, that have no real representation on the SAM EC and are solely used on
136 the kernel/driver-side. For physical devices, ``category`` represents the
137 target category, ``target`` the target ID, and ``instance`` the instance ID
138 used to access the physical SAM device. In addition, ``function`` references
139 a specific device functionality, but has no meaning to the SAM EC. The
140 (default) name of a client device is generated based on its UID.
141
142 A driver instance can be registered via |ssam_device_driver_register| and
143 unregistered via |ssam_device_driver_unregister|. For convenience, the
144 |module_ssam_device_driver| macro may be used to define module init- and
145 exit-functions registering the driver.
146
147 The controller associated with a SSAM client device can be found in its
148 :c:type:`struct ssam_device.ctrl <ssam_device>` member. This reference is
149 guaranteed to be valid for at least as long as the client driver is bound,
150 but should also be valid for as long as the client device exists. Note,
151 however, that access outside of the bound client driver must ensure that the
152 controller device is not suspended while making any requests or
153 (un-)registering event notifiers (and thus should generally be avoided). This
154 is guaranteed when the controller is accessed from inside the bound client
155 driver.
156
157
158 Making Synchronous Requests
159 ===========================
160
161 Synchronous requests are (currently) the main form of host-initiated
162 communication with the EC. There are a couple of ways to define and execute
163 such requests, however, most of them boil down to something similar as shown
164 in the example below. This example defines a write-read request, meaning
165 that the caller provides an argument to the SAM EC and receives a response.
166 The caller needs to know the (maximum) length of the response payload and
167 provide a buffer for it.
168
169 Care must be taken to ensure that any command payload data passed to the SAM
170 EC is provided in little-endian format and, similarly, any response payload
171 data received from it is converted from little-endian to host endianness.
172
173 .. code-block:: c
174
175 int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret)
176 {
177 struct ssam_request rqst;
178 struct ssam_response resp;
179 int status;
180
181 /* Convert request argument to little-endian. */
182 __le32 arg_le = cpu_to_le32(arg);
183 __le32 ret_le = cpu_to_le32(0);
184
185 /*
186 * Initialize request specification. Replace this with your values.
187 * The rqst.payload field may be NULL if rqst.length is zero,
188 * indicating that the request does not have any argument.
189 *
190 * Note: The request parameters used here are not valid, i.e.
191 * they do not correspond to an actual SAM/EC request.
192 */
193 rqst.target_category = SSAM_SSH_TC_SAM;
194 rqst.target_id = SSAM_SSH_TID_SAM;
195 rqst.command_id = 0x02;
196 rqst.instance_id = 0x03;
197 rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
198 rqst.length = sizeof(arg_le);
199 rqst.payload = (u8 *)&arg_le;
200
201 /* Initialize request response. */
202 resp.capacity = sizeof(ret_le);
203 resp.length = 0;
204 resp.pointer = (u8 *)&ret_le;
205
206 /*
207 * Perform actual request. The response pointer may be null in case
208 * the request does not have any response. This must be consistent
209 * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification
210 * above.
211 */
212 status = ssam_request_do_sync(ctrl, &rqst, &resp);
213
214 /*
215 * Alternatively use
216 *
217 * ssam_request_do_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le));
218 *
219 * to perform the request, allocating the message buffer directly
220 * on the stack as opposed to allocation via kzalloc().
221 */
222
223 /*
224 * Convert request response back to native format. Note that in the
225 * error case, this value is not touched by the SSAM core, i.e.
226 * 'ret_le' will be zero as specified in its initialization.
227 */
228 *ret = le32_to_cpu(ret_le);
229
230 return status;
231 }
232
233 Note that |ssam_request_do_sync| in its essence is a wrapper over lower-level
234 request primitives, which may also be used to perform requests. Refer to its
235 implementation and documentation for more details.
236
237 An arguably more user-friendly way of defining such functions is by using
238 one of the generator macros, for example via:
239
240 .. code-block:: c
241
242 SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, {
243 .target_category = SSAM_SSH_TC_TMP,
244 .target_id = SSAM_SSH_TID_SAM,
245 .command_id = 0x03,
246 .instance_id = 0x00,
247 });
248
249 This example defines a function
250
251 .. code-block:: c
252
253 static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
254
255 executing the specified request, with the controller passed in when calling
256 said function. In this example, the argument is provided via the ``arg``
257 pointer. Note that the generated function allocates the message buffer on
258 the stack. Thus, if the argument provided via the request is large, these
259 kinds of macros should be avoided. Also note that, in contrast to the
260 previous non-macro example, this function does not do any endianness
261 conversion, which has to be handled by the caller. Apart from those
262 differences the function generated by the macro is similar to the one
263 provided in the non-macro example above.
264
265 The full list of such function-generating macros is
266
267 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and
268 without argument.
269 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no
270 argument.
271 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but
272 with argument.
273
274 Refer to their respective documentation for more details. For each one of
275 these macros, a special variant is provided, which targets request types
276 applicable to multiple instances of the same device type:
277
278 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N`
279 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R`
280 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W`
281
282 The difference of those macros to the previously mentioned versions is, that
283 the device target and instance IDs are not fixed for the generated function,
284 but instead have to be provided by the caller of said function.
285
286 Additionally, variants for direct use with client devices, i.e.
287 |ssam_device|, are also provided. These can, for example, be used as
288 follows:
289
290 .. code-block:: c
291
292 SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
293 .target_category = SSAM_SSH_TC_BAT,
294 .command_id = 0x01,
295 });
296
297 This invocation of the macro defines a function
298
299 .. code-block:: c
300
301 static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
302
303 executing the specified request, using the device IDs and controller given
304 in the client device. The full list of such macros for client devices is:
305
306 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N`
307 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R`
308 - :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W`
309
310
311 Handling Events
312 ===============
313
314 To receive events from the SAM EC, an event notifier must be registered for
315 the desired event via |ssam_notifier_register|. The notifier must be
316 unregistered via |ssam_notifier_unregister| once it is not required any
317 more. For |ssam_device| type clients, the |ssam_device_notifier_register| and
318 |ssam_device_notifier_unregister| wrappers should be preferred as they properly
319 handle hot-removal of client devices.
320
321 Event notifiers are registered by providing (at minimum) a callback to call
322 in case an event has been received, the registry specifying how the event
323 should be enabled, an event ID specifying for which target category and,
324 optionally and depending on the registry used, for which instance ID events
325 should be enabled, and finally, flags describing how the EC will send these
326 events. If the specific registry does not enable events by instance ID, the
327 instance ID must be set to zero. Additionally, a priority for the respective
328 notifier may be specified, which determines its order in relation to any
329 other notifier registered for the same target category.
330
331 By default, event notifiers will receive all events for the specific target
332 category, regardless of the instance ID specified when registering the
333 notifier. The core may be instructed to only call a notifier if the target
334 ID or instance ID (or both) of the event match the ones implied by the
335 notifier IDs (in case of target ID, the target ID of the registry), by
336 providing an event mask (see |ssam_event_mask|).
337
338 In general, the target ID of the registry is also the target ID of the
339 enabled event (with the notable exception being keyboard input events on the
340 Surface Laptop 1 and 2, which are enabled via a registry with target ID 1,
341 but provide events with target ID 2).
342
343 A full example for registering an event notifier and handling received
344 events is provided below:
345
346 .. code-block:: c
347
348 u32 notifier_callback(struct ssam_event_notifier *nf,
349 const struct ssam_event *event)
350 {
351 int status = ...
352
353 /* Handle the event here ... */
354
355 /* Convert return value and indicate that we handled the event. */
356 return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
357 }
358
359 int setup_notifier(struct ssam_device *sdev,
360 struct ssam_event_notifier *nf)
361 {
362 /* Set priority wrt. other handlers of same target category. */
363 nf->base.priority = 1;
364
365 /* Set event/notifier callback. */
366 nf->base.fn = notifier_callback;
367
368 /* Specify event registry, i.e. how events get enabled/disabled. */
369 nf->event.reg = SSAM_EVENT_REGISTRY_KIP;
370
371 /* Specify which event to enable/disable */
372 nf->event.id.target_category = sdev->uid.category;
373 nf->event.id.instance = sdev->uid.instance;
374
375 /*
376 * Specify for which events the notifier callback gets executed.
377 * This essentially tells the core if it can skip notifiers that
378 * don't have target or instance IDs matching those of the event.
379 */
380 nf->event.mask = SSAM_EVENT_MASK_STRICT;
381
382 /* Specify event flags. */
383 nf->event.flags = SSAM_EVENT_SEQUENCED;
384
385 return ssam_notifier_register(sdev->ctrl, nf);
386 }
387
388 Multiple event notifiers can be registered for the same event. The event
389 handler core takes care of enabling and disabling events when notifiers are
390 registered and unregistered, by keeping track of how many notifiers for a
391 specific event (combination of registry, event target category, and event
392 instance ID) are currently registered. This means that a specific event will
393 be enabled when the first notifier for it is being registered and disabled
394 when the last notifier for it is being unregistered. Note that the event
395 flags are therefore only used on the first registered notifier, however, one
396 should take care that notifiers for a specific event are always registered
397 with the same flag and it is considered a bug to do otherwise.
398

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

Client driver 문서와 API 치환

1-37

이 문서는 `GPL-2.0+` SPDX license를 사용하며 Surface Aggregator client driver 작성 방법을 설명합니다.

머리말의 replace 지시문은 `ssam_controller`, `ssam_device`, `ssam_device_driver` 구조체와 client bind·link, controller reference, device lifecycle, driver 등록, notifier, synchronous request, event mask API를 본문에서 일관된 교차 참조로 표시합니다.

API 세부 문서는 `client-api` 문서를 포함하는 `toctree`에서 최대 깊이 2로 연결합니다.

주요 API 치환 그룹
그룹Symbol
Core types`ssam_controller`, `ssam_device`, `ssam_device_driver`
Client relation`ssam_client_bind`, `ssam_client_link`, `ssam_get_controller`
Reference lifecycle`ssam_controller_get`, `ssam_controller_put`
Device lifecycle`ssam_device_alloc`, `ssam_device_add`, `ssam_device_remove`
Driver lifecycle`ssam_device_driver_register`, `ssam_device_driver_unregister`
Event and request`ssam_notifier_register`, `ssam_request_do_sync`, `ssam_event_mask`

.. SPDX-License-Identifier: GPL-2.0+

.. |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_get_controller| replace:: :c:func:`ssam_get_controller`
.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get`
.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put`
.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc`
.. |ssam_device_add| replace:: :c:func:`ssam_device_add`
.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove`
.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register`
.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister`
.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver`
.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE`
.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register`
.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister`
.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register`
.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister`
.. |ssam_request_do_sync| replace:: :c:func:`ssam_request_do_sync`
.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask <ssam_event_mask>`


======================
Writing Client Drivers
======================

For the API documentation, refer to:

.. toctree::
   :maxdepth: 2

   client-api

Client driver 구성 방식 개요

38-48

Client driver는 대응 device가 system에 제공되는 방식에 따라 크게 두 가지로 구성합니다.

첫 번째는 ACPI를 통해 platform device로 제공되는 경우처럼 conventional discovery 경로로 system에 나타나는 device입니다. 두 번째는 discover할 수 없어서 다른 mechanism이 명시적으로 제공해야 하는 device입니다.

Surface Aggregator client 유형
Conventional discoveryACPI or platform deviceNon-SSAM Client Driver
Non-discoverable deviceExplicit provisioningSSAM Client Driver

Device가 system에 나타나는 방식에 따라 일반 device driver와 SSAM client device 경로가 갈립니다.

Overview
========

Client drivers can be set up in two main ways, depending on how the
corresponding device is made available to the system. We specifically
differentiate between devices that are presented to the system via one of
the conventional ways, e.g. as platform devices via ACPI, and devices that
are non-discoverable and instead need to be explicitly provided by some
other mechanism, as discussed further below.

Non-SSAM Client Driver 연결

49-88

SAM EC와의 모든 통신은 kernel에서 해당 EC를 나타내는 `struct ssam_controller`를 거칩니다. Non-SSAM device를 대상으로 하여 `ssam_device_driver`가 아닌 driver는 이 controller와의 연결 또는 관계를 명시적으로 설정해야 합니다.

`ssam_client_bind()`는 SSAM controller reference를 반환하면서 client device와 controller 사이에 device link도 만듭니다. Link만 별도로 만들 때는 `ssam_client_link()`를 사용할 수 있습니다.

Device link는 client driver가 bind되어 있는 동안 반환된 controller가 유효하도록 하여 controller가 무효가 되기 전에 client driver가 먼저 unbind되게 합니다. 또한 suspend/resume ordering을 올바르게 보장합니다.

이 설정은 driver의 `probe` 함수에서 수행해야 하며 SSAM subsystem이 아직 준비되지 않았다면 `-EPROBE_DEFER`로 probing을 미룰 수 있습니다. 예제는 `ssam_client_bind(&pdev->dev)`가 `-ENODEV`를 반환한 경우 이를 probe defer로 바꿉니다.

Controller는 `ssam_get_controller()`로 별도 획득하고 `ssam_controller_get()`과 `ssam_controller_put()`으로 lifetime을 유지할 수도 있습니다. 그러나 이 함수들은 controller가 shutdown 또는 suspend되지 않는다고 보장하지 않으며, reference 수준의 최소 접근성만 보장할 뿐 실제 동작 가능성은 보장하지 않습니다.

Non-SSAM client bind 보장
Client driver probe`ssam_client_bind()`Controller referenceDevice link
Device linkClient unbind before controller invalidationValid controller while bound
Device linkCorrect suspend/resume ordering
SSAM not ready`-ENODEV``-EPROBE_DEFER`

`probe`에서 만든 device link가 lifetime과 power-management ordering을 묶습니다.

Non-SSAM Client Drivers
=======================

All communication with the SAM EC is handled via the |ssam_controller|
representing that EC to the kernel. Drivers targeting a non-SSAM device (and
thus not being a |ssam_device_driver|) need to explicitly establish a
connection/relation to that controller. This can be done via the
|ssam_client_bind| function. Said function returns a reference to the SSAM
controller, but, more importantly, also establishes a device link between
client device and controller (this can also be done separate via
|ssam_client_link|). It is important to do this, as it, first, guarantees
that the returned controller is valid for use in the client driver for as
long as this driver is bound to its device, i.e. that the driver gets
unbound before the controller ever becomes invalid, and, second, as it
ensures correct suspend/resume ordering. This setup should be done in the
driver's probe function, and may be used to defer probing in case the SSAM
subsystem is not ready yet, for example:

.. code-block:: c

   static int client_driver_probe(struct platform_device *pdev)
   {
           struct ssam_controller *ctrl;

           ctrl = ssam_client_bind(&pdev->dev);
           if (IS_ERR(ctrl))
                   return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);

           // ...

           return 0;
   }

The controller may be separately obtained via |ssam_get_controller| and its
lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|.
Note that none of these functions, however, guarantee that the controller
will not be shut down or suspended. These functions essentially only operate
on the reference, i.e. only guarantee a bare minimum of accessibility
without any guarantees at all on practical operability.

SSAM device 추가와 제거

89-116

Device가 conventional 방식으로 이미 존재하거나 제공되지 않는다면 SSAM client device hub를 통해 `ssam_device`로 제공해야 합니다. 대응 registry에 UID를 입력하면 새 device를 hub에 추가할 수 있습니다.

SSAM device를 수동으로 만들 때는 `ssam_device_alloc()`으로 할당하고 `ssam_device_add()`로 추가한 뒤 최종적으로 `ssam_device_remove()`로 제거합니다.

기본 parent는 allocation에 제공한 controller device입니다. Device를 추가하기 전에 parent를 바꿀 수 있지만, 기본 parent-child relation이 제공하던 controller lifetime과 suspend/resume ordering 보장을 유지해야 합니다. 필요하면 Non-SSAM client와 마찬가지로 `ssam_client_link()`를 사용합니다.

Client device를 추가한 주체는 controller가 shutdown되기 전에 반드시 그 device를 제거해야 합니다. SSAM device를 제공하는 driver를 `ssam_client_link()`로 controller에 연결하면 controller driver보다 먼저 unbind되므로 이를 보장할 수 있습니다.

Controller를 parent로 등록한 client device는 controller shutdown 때 자동 제거되지만 여기에 의존해서는 안 됩니다. 특히 parent가 다른 client device에는 이 자동 제거가 적용되지 않습니다.

SSAM client device lifecycle
Registry UID or `ssam_device_alloc()``ssam_device_add()`Active `ssam_device``ssam_device_remove()`
Controller parentDefault lifetime and suspend/resume ordering
Different parent`ssam_client_link()`Preserved ordering guarantees
Provider driver unbindRemove client deviceController shutdown

Device를 추가한 주체가 controller shutdown 전에 제거 책임을 완료해야 합니다.


Adding SSAM Devices
===================

If a device does not already exist/is not already provided via conventional
means, it should be provided as |ssam_device| via the SSAM client device
hub. New devices can be added to this hub by entering their UID into the
corresponding registry. SSAM devices can also be manually allocated via
|ssam_device_alloc|, subsequently to which they have to be added via
|ssam_device_add| and eventually removed via |ssam_device_remove|. By
default, the parent of the device is set to the controller device provided
for allocation, however this may be changed before the device is added. Note
that, when changing the parent device, care must be taken to ensure that the
controller lifetime and suspend/resume ordering guarantees, in the default
setup provided through the parent-child relation, are preserved. If
necessary, by use of |ssam_client_link| as is done for non-SSAM client
drivers and described in more detail above.

A client device must always be removed by the party which added the
respective device before the controller shuts down. Such removal can be
guaranteed by linking the driver providing the SSAM device to the controller
via |ssam_client_link|, causing it to unbind before the controller driver
unbinds. Client devices registered with the controller as parent are
automatically removed when the controller shuts down, but this should not be
relied upon, especially as this does not extend to client devices with a
different parent.

SSAM Client Driver와 UID matching

117-156

SSAM client device driver는 본질적으로 다른 device driver 유형과 같습니다. `struct ssam_device_driver`로 표현되며 `struct ssam_device.uid`와 `struct ssam_device_driver.match_table`을 통해 `ssam_device`에 bind합니다. Driver 구조체를 선언할 때 match table을 설정하고 entry 정의 방식은 `SSAM_DEVICE` macro 문서를 따릅니다.

SSAM client device UID는 `domain`, `category`, `target`, `instance`, `function`으로 구성됩니다.

`domain`은 Surface Serial Hub를 통해 접근할 수 있는 물리 SAM device인 `SSAM_DOMAIN_SERIALHUB`와, SAM EC에 실제 표현이 없고 kernel/driver 쪽에서만 사용하는 client-device hub 같은 virtual device인 `SSAM_DOMAIN_VIRTUAL`을 구분합니다.

물리 device에서 `category`는 target category, `target`은 target ID, `instance`는 physical SAM device 접근에 사용하는 instance ID입니다. `function`은 특정 device 기능을 가리키지만 SAM EC 자체에는 의미가 없습니다. Client device의 기본 이름은 UID를 바탕으로 생성합니다.

Driver instance는 `ssam_device_driver_register()`로 등록하고 `ssam_device_driver_unregister()`로 해제합니다. `module_ssam_device_driver` macro는 driver를 등록하는 module init·exit function을 간편하게 정의합니다.

SSAM client device와 연관된 controller는 `struct ssam_device.ctrl`에 있습니다. 이 reference는 적어도 client driver가 bind된 동안 유효하고 일반적으로 client device가 존재하는 동안 유효해야 합니다.

Bind된 client driver 바깥에서 controller에 접근할 때는 request 또는 event notifier 등록·해제 중 controller device가 suspend되지 않도록 직접 보장해야 하므로 일반적으로 피해야 합니다. Bind된 client driver 내부 접근에는 이 보장이 제공됩니다.

SSAM client UID
Field의미
domainPhysical Serial Hub 또는 virtual kernel-side device
categoryPhysical device target category
targetTarget ID
instanceInstance ID
functionDriver-side 기능 구분, SAM EC에는 의미 없음

SSAM Client Drivers
===================

SSAM client device drivers are, in essence, no different than other device
driver types. They are represented via |ssam_device_driver| and bind to a
|ssam_device| via its UID (:c:type:`struct ssam_device.uid <ssam_device>`)
member and the match table
(:c:type:`struct ssam_device_driver.match_table <ssam_device_driver>`),
which should be set when declaring the driver struct instance. Refer to the
|SSAM_DEVICE| macro documentation for more details on how to define members
of the driver's match table.

The UID for SSAM client devices consists of a ``domain``, a ``category``,
a ``target``, an ``instance``, and a ``function``. The ``domain`` is used
differentiate between physical SAM devices
(:c:type:`SSAM_DOMAIN_SERIALHUB <ssam_device_domain>`), i.e. devices that can
be accessed via the Surface Serial Hub, and virtual ones
(:c:type:`SSAM_DOMAIN_VIRTUAL <ssam_device_domain>`), such as client-device
hubs, that have no real representation on the SAM EC and are solely used on
the kernel/driver-side. For physical devices, ``category`` represents the
target category, ``target`` the target ID, and ``instance`` the instance ID
used to access the physical SAM device. In addition, ``function`` references
a specific device functionality, but has no meaning to the SAM EC. The
(default) name of a client device is generated based on its UID.

A driver instance can be registered via |ssam_device_driver_register| and
unregistered via |ssam_device_driver_unregister|. For convenience, the
|module_ssam_device_driver| macro may be used to define module init- and
exit-functions registering the driver.

The controller associated with a SSAM client device can be found in its
:c:type:`struct ssam_device.ctrl <ssam_device>` member. This reference is
guaranteed to be valid for at least as long as the client driver is bound,
but should also be valid for as long as the client device exists. Note,
however, that access outside of the bound client driver must ensure that the
controller device is not suspended while making any requests or
(un-)registering event notifiers (and thus should generally be avoided). This
is guaranteed when the controller is accessed from inside the bound client
driver.

Synchronous request 계약과 endianness

157-172

Synchronous request는 현재 host가 시작하는 EC communication의 주된 형태입니다. 여러 정의·실행 방식이 있지만 대부분 뒤의 예제와 같은 구조로 귀결됩니다.

예제는 caller가 SAM EC에 argument를 제공하고 response를 받는 write-read request입니다. Caller는 response payload의 최대 길이를 알아야 하며 이를 담을 buffer를 제공해야 합니다.

SAM EC로 전달하는 command payload data는 little-endian format이어야 합니다. 반대로 수신한 response payload data는 little-endian에서 host endianness로 변환해야 합니다.

Synchronous request 데이터 계약
항목Caller 책임
Request typeWrite-read
Command payloadHost에서 little-endian으로 변환
Response capacity최대 payload 길이와 buffer 제공
Response payloadLittle-endian에서 host endianness로 변환


Making Synchronous Requests
===========================

Synchronous requests are (currently) the main form of host-initiated
communication with the EC. There are a couple of ways to define and execute
such requests, however, most of them boil down to something similar as shown
in the example below. This example defines a write-read request, meaning
that the caller provides an argument to the SAM EC and receives a response.
The caller needs to know the (maximum) length of the response payload and
provide a buffer for it.

Care must be taken to ensure that any command payload data passed to the SAM
EC is provided in little-endian format and, similarly, any response payload
data received from it is converted from little-endian to host endianness.

수동 synchronous request 실행

173-235

수동 예제는 `struct ssam_request`와 `struct ssam_response`를 초기화합니다. Argument는 `cpu_to_le32()`로 little-endian으로 바꾸고 response 저장소도 `__le32`로 준비합니다.

Request specification에는 `target_category`, `target_id`, `command_id`, `instance_id`, `flags`, payload `length`, `payload` pointer를 지정합니다. Payload가 없는 request는 `length`를 0으로 두고 `payload`를 `NULL`로 둘 수 있습니다. 예제의 parameter는 설명용이며 실제 SAM/EC request와 대응하지 않습니다.

Response에는 buffer `capacity`, 현재 `length`, `pointer`를 지정합니다. Response가 없는 request라면 response pointer를 null로 둘 수 있지만 specification의 `SSAM_REQUEST_HAS_RESPONSE` flag와 반드시 일치해야 합니다.

`ssam_request_do_sync()`가 실제 request를 수행합니다. 대안인 `ssam_request_do_sync_onstack()`은 `kzalloc()` allocation 대신 message buffer를 stack에 둡니다.

완료 뒤 `le32_to_cpu()`로 response를 native format으로 변환합니다. Error 경로에서는 SSAM core가 response 값을 건드리지 않으므로 예제의 `ret_le`는 초기값 0을 유지합니다.

`ssam_request_do_sync()`는 본질적으로 더 낮은 수준의 request primitive를 감싼 wrapper이며, 필요하면 그 primitive를 직접 사용할 수도 있습니다.

수동 synchronous request
Host argument`cpu_to_le32()``ssam_request` payload`ssam_request_do_sync()`
`ssam_response` capacity and pointerEC response`le32_to_cpu()`Host result
On-stack alternative`ssam_request_do_sync_onstack()`No `kzalloc()` message buffer

Specification과 response buffer를 준비한 뒤 동기 실행하고 native endianness로 복원합니다.

.. code-block:: c

   int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret)
   {
           struct ssam_request rqst;
           struct ssam_response resp;
           int status;

           /* Convert request argument to little-endian. */
           __le32 arg_le = cpu_to_le32(arg);
           __le32 ret_le = cpu_to_le32(0);

           /*
            * Initialize request specification. Replace this with your values.
            * The rqst.payload field may be NULL if rqst.length is zero,
            * indicating that the request does not have any argument.
            *
            * Note: The request parameters used here are not valid, i.e.
            *       they do not correspond to an actual SAM/EC request.
            */
           rqst.target_category = SSAM_SSH_TC_SAM;
           rqst.target_id = SSAM_SSH_TID_SAM;
           rqst.command_id = 0x02;
           rqst.instance_id = 0x03;
           rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
           rqst.length = sizeof(arg_le);
           rqst.payload = (u8 *)&arg_le;

           /* Initialize request response. */
           resp.capacity = sizeof(ret_le);
           resp.length = 0;
           resp.pointer = (u8 *)&ret_le;

           /*
            * Perform actual request. The response pointer may be null in case
            * the request does not have any response. This must be consistent
            * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification
            * above.
            */
           status = ssam_request_do_sync(ctrl, &rqst, &resp);

           /*
            * Alternatively use
            *
            *   ssam_request_do_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le));
            *
            * to perform the request, allocating the message buffer directly
            * on the stack as opposed to allocation via kzalloc().
            */

           /*
            * Convert request response back to native format. Note that in the
            * error case, this value is not touched by the SSAM core, i.e.
            * 'ret_le' will be zero as specified in its initialization.
            */
           *ret = le32_to_cpu(ret_le);

           return status;
   }

Note that |ssam_request_do_sync| in its essence is a wrapper over lower-level
request primitives, which may also be used to perform requests. Refer to its
implementation and documentation for more details.

Synchronous request generator macro

236-284

Generator macro를 사용하면 request function을 더 편리하게 정의할 수 있습니다. 예제의 `SSAM_DEFINE_SYNC_REQUEST_W`는 `__ssam_tmp_perf_mode_set()` 함수를 만들고 target category·ID, command ID, instance ID를 고정합니다.

생성된 함수는 호출 때 `struct ssam_controller *ctrl`과 `const __le32 *arg`를 받습니다. Message buffer는 stack에 할당하므로 argument가 크면 이런 macro를 피해야 합니다.

Macro가 생성한 함수는 이전 수동 예제와 달리 endianness conversion을 하지 않으므로 caller가 처리해야 합니다. 이 차이와 stack allocation 외의 동작은 수동 예제와 유사합니다.

일반 macro 계열은 return value와 argument가 모두 없는 `SSAM_DEFINE_SYNC_REQUEST_N`, return value만 있는 `SSAM_DEFINE_SYNC_REQUEST_R`, argument만 있는 `SSAM_DEFINE_SYNC_REQUEST_W`입니다.

동일 device type의 여러 instance에 적용하는 `SSAM_DEFINE_SYNC_REQUEST_MD_N`, `_MD_R`, `_MD_W` 변형도 있습니다. 이 macro는 target ID와 instance ID를 생성 함수에 고정하지 않고 caller가 제공하게 합니다.

Synchronous request generator macro
Macro familyReturnArgumentID 방식
`SSAM_DEFINE_SYNC_REQUEST_N`없음없음고정
`SSAM_DEFINE_SYNC_REQUEST_R`있음없음고정
`SSAM_DEFINE_SYNC_REQUEST_W`없음있음고정
`SSAM_DEFINE_SYNC_REQUEST_MD_N`없음없음Caller 제공
`SSAM_DEFINE_SYNC_REQUEST_MD_R`있음없음Caller 제공
`SSAM_DEFINE_SYNC_REQUEST_MD_W`없음있음Caller 제공

생성 함수는 stack message buffer를 사용하고 endianness 변환은 caller 책임입니다.


An arguably more user-friendly way of defining such functions is by using
one of the generator macros, for example via:

.. code-block:: c

   SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, {
           .target_category = SSAM_SSH_TC_TMP,
           .target_id       = SSAM_SSH_TID_SAM,
           .command_id      = 0x03,
           .instance_id     = 0x00,
   });

This example defines a function

.. code-block:: c

   static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);

executing the specified request, with the controller passed in when calling
said function. In this example, the argument is provided via the ``arg``
pointer. Note that the generated function allocates the message buffer on
the stack. Thus, if the argument provided via the request is large, these
kinds of macros should be avoided. Also note that, in contrast to the
previous non-macro example, this function does not do any endianness
conversion, which has to be handled by the caller. Apart from those
differences the function generated by the macro is similar to the one
provided in the non-macro example above.

The full list of such function-generating macros is

- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and
  without argument.
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no
  argument.
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but
  with argument.

Refer to their respective documentation for more details. For each one of
these macros, a special variant is provided, which targets request types
applicable to multiple instances of the same device type:

- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N`
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R`
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W`

The difference of those macros to the previously mentioned versions is, that
the device target and instance IDs are not fixed for the generated function,
but instead have to be provided by the caller of said function.

Client device용 request macro

285-310

`ssam_device`와 직접 사용하는 client device 전용 macro 변형도 제공합니다.

예제의 `SSAM_DEFINE_SYNC_REQUEST_CL_R`은 `ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret)` 함수를 정의합니다. 생성 함수는 client device에 들어 있는 device ID와 controller를 사용해 지정된 request를 실행합니다.

Client device용 전체 macro는 argument와 return이 없는 `SSAM_DEFINE_SYNC_REQUEST_CL_N`, return만 있는 `_CL_R`, argument만 있는 `_CL_W`입니다.

Client device request macro
MacroReturnArgumentContext
`SSAM_DEFINE_SYNC_REQUEST_CL_N`없음없음`ssam_device`
`SSAM_DEFINE_SYNC_REQUEST_CL_R`있음없음`ssam_device`
`SSAM_DEFINE_SYNC_REQUEST_CL_W`없음있음`ssam_device`


Additionally, variants for direct use with client devices, i.e.
|ssam_device|, are also provided. These can, for example, be used as
follows:

.. code-block:: c

   SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
           .target_category = SSAM_SSH_TC_BAT,
           .command_id      = 0x01,
   });

This invocation of the macro defines a function

.. code-block:: c

   static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);

executing the specified request, using the device IDs and controller given
in the client device. The full list of such macros for client devices is:

- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N`
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R`
- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W`

Event notifier 등록과 filtering

311-342

SAM EC event를 받으려면 원하는 event에 대해 `ssam_notifier_register()`로 event notifier를 등록하고 더 필요하지 않을 때 `ssam_notifier_unregister()`로 해제해야 합니다.

`ssam_device` 유형 client는 client device의 hot-removal을 올바르게 처리하는 `ssam_device_notifier_register()`와 `ssam_device_notifier_unregister()` wrapper를 우선 사용해야 합니다.

Notifier 등록에는 최소한 event 수신 시 호출할 callback, event enable 방법을 지정하는 registry, target category와 registry에 따라 선택적인 instance ID를 지정하는 event ID, EC가 event를 보내는 방식을 나타내는 flag가 필요합니다.

Registry가 instance ID별 enable을 지원하지 않으면 instance ID를 0으로 설정해야 합니다. 같은 target category에 등록된 notifier 사이의 호출 순서는 선택적인 priority로 정합니다.

기본적으로 notifier는 등록 때 지정한 instance ID와 관계없이 특정 target category의 모든 event를 받습니다. `ssam_event_mask`를 제공하면 event의 target ID나 instance ID 또는 둘 다 notifier가 암시하는 ID와 일치할 때만 callback을 호출하도록 core에 지시할 수 있습니다.

일반적으로 registry의 target ID는 enable된 event의 target ID이기도 합니다. 예외로 Surface Laptop 1·2 keyboard input event는 target ID 1 registry로 enable하지만 target ID 2 event를 제공합니다.

Event notifier 등록 필드
필드역할
callbackEvent 수신 처리
registryEvent enable·disable 방법
event IDTarget category와 선택적 instance
flagsEC event 전송 방식
priority같은 target category notifier의 순서
event maskTarget·instance ID callback filtering

Handling Events
===============

To receive events from the SAM EC, an event notifier must be registered for
the desired event via |ssam_notifier_register|. The notifier must be
unregistered via |ssam_notifier_unregister| once it is not required any
more. For |ssam_device| type clients, the |ssam_device_notifier_register| and
|ssam_device_notifier_unregister| wrappers should be preferred as they properly
handle hot-removal of client devices.

Event notifiers are registered by providing (at minimum) a callback to call
in case an event has been received, the registry specifying how the event
should be enabled, an event ID specifying for which target category and,
optionally and depending on the registry used, for which instance ID events
should be enabled, and finally, flags describing how the EC will send these
events. If the specific registry does not enable events by instance ID, the
instance ID must be set to zero. Additionally, a priority for the respective
notifier may be specified, which determines its order in relation to any
other notifier registered for the same target category.

By default, event notifiers will receive all events for the specific target
category, regardless of the instance ID specified when registering the
notifier. The core may be instructed to only call a notifier if the target
ID or instance ID (or both) of the event match the ones implied by the
notifier IDs (in case of target ID, the target ID of the registry), by
providing an event mask (see |ssam_event_mask|).

In general, the target ID of the registry is also the target ID of the
enabled event (with the notable exception being keyboard input events on the
Surface Laptop 1 and 2, which are enabled via a registry with target ID 1,
but provide events with target ID 2).

Event callback과 enable reference counting

343-397

전체 예제에서 `notifier_callback()`은 event를 처리한 뒤 `ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED`를 반환하여 status를 notifier 값으로 변환하고 event를 처리했음을 표시합니다.

`setup_notifier()`는 priority, callback, `SSAM_EVENT_REGISTRY_KIP` registry, client UID의 target category와 instance, `SSAM_EVENT_MASK_STRICT` mask, `SSAM_EVENT_SEQUENCED` flag를 설정한 뒤 `ssam_notifier_register(sdev->ctrl, nf)`를 호출합니다.

같은 event에 여러 notifier를 등록할 수 있습니다. Event handler core는 registry, event target category, event instance ID 조합별 현재 notifier 수를 추적하여 enable·disable을 관리합니다.

특정 event의 첫 notifier를 등록할 때 event를 enable하고 마지막 notifier를 해제할 때 disable합니다. 따라서 event flag는 처음 등록한 notifier의 값만 사용되지만, 같은 event의 모든 notifier는 동일한 flag로 등록해야 하며 이를 어기면 bug로 간주합니다.

Event notifier reference counting
First notifierEnable EC eventCount = 1
Additional notifierIncrement countEvent remains enabled
Notifier unregisterDecrement countLast notifierDisable EC event
All notifiers for one eventSame event flags required

같은 event key를 공유하는 notifier 수가 EC event의 enable 상태를 결정합니다.

A full example for registering an event notifier and handling received
events is provided below:

.. code-block:: c

   u32 notifier_callback(struct ssam_event_notifier *nf,
                         const struct ssam_event *event)
   {
           int status = ...

           /* Handle the event here ... */

           /* Convert return value and indicate that we handled the event. */
           return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
   }

   int setup_notifier(struct ssam_device *sdev,
                      struct ssam_event_notifier *nf)
   {
           /* Set priority wrt. other handlers of same target category. */
           nf->base.priority = 1;

           /* Set event/notifier callback. */
           nf->base.fn = notifier_callback;

           /* Specify event registry, i.e. how events get enabled/disabled. */
           nf->event.reg = SSAM_EVENT_REGISTRY_KIP;

           /* Specify which event to enable/disable */
           nf->event.id.target_category = sdev->uid.category;
           nf->event.id.instance = sdev->uid.instance;

           /*
            * Specify for which events the notifier callback gets executed.
            * This essentially tells the core if it can skip notifiers that
            * don't have target or instance IDs matching those of the event.
            */
           nf->event.mask = SSAM_EVENT_MASK_STRICT;

           /* Specify event flags. */
           nf->event.flags = SSAM_EVENT_SEQUENCED;

           return ssam_notifier_register(sdev->ctrl, nf);
   }

Multiple event notifiers can be registered for the same event. The event
handler core takes care of enabling and disabling events when notifiers are
registered and unregistered, by keeping track of how many notifiers for a
specific event (combination of registry, event target category, and event
instance ID) are currently registered. This means that a specific event will
be enabled when the first notifier for it is being registered and disabled
when the last notifier for it is being unregistered. Note that the event
flags are therefore only used on the first registered notifier, however, one
should take care that notifiers for a specific event are always registered
with the same flag and it is considered a bug to do otherwise.