develop/api/design_guidelines.html · develop/api/design_guidelines.rst 공식 원문 ↗

Zephyr 3.7.0 · Develop · API Design

API Design Guidelines

일관되고 유지보수하기 쉬운 Zephyr API를 설계하기 위한 callback, Kconfig, 오류 반환 규칙을 설명합니다.

Official pathdevelop/api/design_guidelines.html
Source filedevelop/api/design_guidelines.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Callback의 object·invocation data·user context 순서, optional feature의 memory·link-time 처리, -ENOSYS와 -ENOTSUP 구분을 다룹니다.

Callback

Object · Data · Context

Optional

Kconfig · CMake · Doxygen

오류

ENOSYS · ENOTSUP

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 ABA96F63C381BEB2B7F128531C2D5F489F63091F9A8B32055505E6F8BC7D51CF
.. _design_guidelines:

API Design Guidelines
#####################

Zephyr development and evolution is a group effort, and to simplify
maintenance and enhancements there are some general policies that should
be followed when developing a new capability or interface.

Using Callbacks
***************

Many APIs involve passing a callback as a parameter or as a member of a
configuration structure.  The following policies should be followed when
specifying the signature of a callback:

* The first parameter should be a pointer to the object most closely
  associated with the callback.  In the case of device drivers this
  would be ``const struct device *dev``.  For library functions it may be a
  pointer to another object that was referenced when the callback was
  provided.

* The next parameter(s) should be additional information specific to the
  callback invocation, such as a channel identifier, new status value,
  and/or a message pointer followed by the message length.

* The final parameter should be a ``void *user_data`` pointer carrying
  context that allows a shared callback function to locate additional
  material necessary to process the callback.

An exception to providing ``user_data`` as the last parameter may be
allowed when the callback itself was provided through a structure that
will be embedded in another structure.  An example of such a case is
:c:type:`gpio_callback`, normally defined within a data structure
specific to the code that also defines the callback function.  In those
cases further context can accessed by the callback indirectly by
:c:macro:`CONTAINER_OF`.

Examples
========

* The requirements of :c:type:`k_timer_expiry_t` invoked when a system
  timer alarm fires are satisfied by::

    void handle_timeout(struct k_timer *timer)
    { ... }

  The assumption here, as with :c:type:`gpio_callback`, is that the
  timer is embedded in a structure reachable from
  :c:macro:`CONTAINER_OF` that can provide additional context to the
  callback.

* The requirements of :c:type:`counter_alarm_callback_t` invoked when a
  counter device alarm fires are satisfied by::

    void handle_alarm(const struct device *dev,
                      uint8_t chan_id,
		      uint32_t ticks,
		      void *user_data)
    { ... }

  This provides more complete useful information, including which
  counter channel timed-out and the counter value at which the timeout
  occurred, as well as user context which may or may not be the
  :c:type:`counter_alarm_cfg` used to register the callback, depending
  on user needs.

Conditional Data and APIs
*************************

APIs and libraries may provide features that are expensive in RAM or
code size but are optional in the sense that some applications can be
implemented without them.  Examples of such feature include
:kconfig:option:`capturing a timestamp <CONFIG_CAN_RX_TIMESTAMP>` or
:kconfig:option:`providing an alternative interface <CONFIG_SPI_ASYNC>`.  The
developer in coordination with the community must determine whether
enabling the features is to be controllable through a Kconfig option.

In the case where a feature is determined to be optional the following
practices should be followed.

* Any data that is accessed only when the feature is enabled should be
  conditionally included via ``#ifdef CONFIG_MYFEATURE`` in the
  structure or union declaration.  This reduces memory use for
  applications that don't need the capability.
* Function declarations that are available only when the option is
  enabled should be provided unconditionally.  Add a note in the
  description that the function is available only when the specified
  feature is enabled, referencing the required Kconfig symbol by name.
  In the cases where the function is used but not enabled the definition
  of the function shall be excluded from compilation, so references to
  the unsupported API will result in a link-time error.
* Where code specific to the feature is isolated in a source file that
  has no other content that file should be conditionally included in
  ``CMakeLists.txt``::

    zephyr_sources_ifdef(CONFIG_MYFEATURE foo_funcs.c)
* Where code specific to the feature is part of a source file that has
  other content the feature-specific code should be conditionally
  processed using ``#ifdef CONFIG_MYFEATURE``.

The Kconfig flag used to enable the feature should be added to the
``PREDEFINED`` variable in :file:`doc/zephyr.doxyfile.in` to ensure the
conditional API and functions appear in generated documentation.

Return Codes
************

Implementations of an API, for example an API for accessing a peripheral might
implement only a subset of the functions that is required for minimal operation.
A distinction is needed between APIs that are not supported and those that are
not implemented or optional:

- APIs that are supported but not implemented shall return ``-ENOSYS``.

- Optional APIs that are not supported by the hardware should be implemented and
  the return code in this case shall be ``-ENOTSUP``.

- When an API is implemented, but the particular combination of options
  requested in the call cannot be satisfied by the implementation the call shall
  return ``-ENOTSUP``. (For example, a request for a level-triggered GPIO interrupt on
  hardware that supports only edge-triggered interrupts)

Part 3

한국어 전문 번역

API design 지침

Zephyr의 개발과 발전은 여러 사람이 함께하는 작업입니다. 유지보수와 개선을 단순하게 만들려면 새 capability나 interface를 개발할 때 몇 가지 일반 policy를 따라야 합니다.

Callback 사용

많은 API가 callback을 parameter로 전달하거나 configuration structure의 member로 받습니다. Callback signature를 정할 때 다음 policy를 따릅니다.

  • 첫 parameter는 callback과 가장 밀접하게 관련된 object의 pointer여야 합니다. Device driver에서는 const struct device *dev가 됩니다. Library function에서는 callback을 제공할 때 함께 참조한 다른 object의 pointer일 수 있습니다.
  • 다음 parameter는 channel identifier, 새 status value, message pointer와 뒤따르는 message length처럼 해당 callback invocation에 필요한 추가 정보여야 합니다.
  • 마지막 parameter는 void *user_data pointer여야 합니다. Shared callback function이 callback 처리에 필요한 추가 자료를 찾을 수 있는 context를 전달합니다.

user_data를 마지막 parameter로 제공하지 않아도 되는 예외는 callback 자체가 다른 structure에 embed될 structure를 통해 제공되는 경우입니다. 대표적으로 gpio_callback은 callback function을 정의하는 code별 data structure 안에 보통 정의됩니다. 이런 경우 callback은 CONTAINER_OF를 통해 추가 context에 간접적으로 접근할 수 있습니다.

예제

  • k_timer_expiry_t는 system timer alarm이 발생할 때 호출되며 다음 signature가 요구 사항을 충족합니다.
    void handle_timeout(struct k_timer *timer)
    { ... }
    여기서는 gpio_callback과 마찬가지로 timer가 CONTAINER_OF로 도달 가능한 structure 안에 embed되어 있고, 그 structure가 callback에 추가 context를 제공한다고 가정합니다.
  • counter_alarm_callback_t는 counter device alarm이 발생할 때 호출되며 다음 signature가 요구 사항을 충족합니다.
    void handle_alarm(const struct device *dev,
                      uint8_t chan_id,
                      uint32_t ticks,
                      void *user_data)
    { ... }
    어느 counter channel에서 timeout이 발생했는지, timeout 시점의 counter value가 무엇인지, user context가 무엇인지까지 더 완전하고 유용한 정보를 제공합니다. User 필요에 따라 context는 callback 등록에 사용한 counter_alarm_cfg일 수도 있고 아닐 수도 있습니다.

조건부 data와 API

API와 library는 RAM이나 code size 비용이 크지만 일부 application에는 필요하지 않은 optional feature를 제공할 수 있습니다. 예를 들어 timestamp capture를 위한 CONFIG_CAN_RX_TIMESTAMP나 alternative interface를 위한 CONFIG_SPI_ASYNC가 있습니다. Developer는 community와 협의해 Kconfig option으로 feature enable 여부를 제어할지 결정해야 합니다.

Feature가 optional이라고 결정되면 다음 practice를 따릅니다.

  • Feature가 enable일 때만 접근하는 data는 structure나 union declaration 안에서 #ifdef CONFIG_MYFEATURE로 조건부 포함합니다. Capability가 필요 없는 application의 memory 사용량을 줄입니다.
  • Option이 enable일 때만 제공되는 function declaration도 무조건 공개합니다. Function description에는 지정 feature가 enable일 때만 사용할 수 있다고 적고 필요한 Kconfig symbol 이름을 명시합니다. Function을 사용했지만 feature가 enable되지 않았다면 function definition을 compile에서 제외해 unsupported API reference가 link-time error를 일으키게 합니다.
  • Feature 전용 code가 다른 내용 없는 source file에 분리되어 있다면 CMakeLists.txt에서 file을 조건부 포함합니다.
    zephyr_sources_ifdef(CONFIG_MYFEATURE foo_funcs.c)
  • Feature 전용 code가 다른 내용도 있는 source file 일부라면 #ifdef CONFIG_MYFEATURE로 해당 code만 조건부 처리합니다.

Conditional API와 function이 생성 documentation에 나타나게 하려면 feature enable에 사용하는 Kconfig flag를 doc/zephyr.doxyfile.in의 PREDEFINED variable에 추가합니다.

Return code

Peripheral 접근 API 같은 API implementation은 최소 동작에 필요한 function 중 일부만 구현할 수 있습니다. 지원하지 않는 API와 구현하지 않았거나 optional인 API를 구분해야 합니다.

  • 지원되는 API지만 구현되지 않았다면 -ENOSYS를 반환해야 합니다.
  • Optional API가 hardware에서 지원되지 않는다면 function 자체는 구현하고 -ENOTSUP를 반환해야 합니다.
  • API는 구현됐지만 호출에서 요청한 option 조합을 implementation이 만족할 수 없다면 -ENOTSUP를 반환해야 합니다. 예를 들어 edge-triggered interrupt만 지원하는 hardware에 level-triggered GPIO interrupt를 요청한 경우입니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.