Part 1
요약·해설
Zephyr 3.7.0 기존 USB device stack의 역할과 폐기 일정을 짚고 Audio·Bluetooth HCI·CDC ACM·DFU·HID·MSC·networking·BOS, custom class, endpoint 재할당, native_sim USBIP 시험과 VID/PID 정책을 한 흐름으로 설명합니다.
CDC · HID · DFU · MSC
Interface · Endpoint · BOS
USBIP · lsusb
Part 2
접을 수 있는 영어 원문 전체
영어 원문 전체 펼치기
A9F6243E82C34E7088990FB6351BAB53E8B122D3BCE720C8DAB21FA8C99E558C
.. _usb_device_stack:
USB device support
##################
.. contents::
:local:
:depth: 3
Overview
********
The USB device stack is a hardware independent interface between USB
device controller driver and USB device class drivers or customer applications.
It is a port of the LPCUSB device stack and has been modified and expanded
over time. It provides the following functionalities:
* Uses the :ref:`usb_dc_api` provided by the device controller drivers to interact with
the USB device controller.
* Responds to standard device requests and returns standard descriptors,
essentially handling 'Chapter 9' processing, specifically the standard
device requests in table 9-3 from the universal serial bus specification
revision 2.0.
* Provides a programming interface to be used by USB device classes or
customer applications. The APIs is described in
:zephyr_file:`include/zephyr/usb/usb_device.h`
.. note::
It is planned to deprecate all APIs listed in :ref:`usb_api` and the
functions that depend on them between Zephyr v3.7.0 and v4.0.0, and remove
them in v4.2.0. The new USB device support, represented by the APIs in
:ref:`usb_device_next_api`, will become the default in Zephyr v4.0.0.
Supported USB classes
*********************
Audio
=====
There is an experimental implementation of the Audio class. It follows specification
version 1.00 (``bcdADC 0x0100``) and supports synchronous synchronisation type only.
See :zephyr:code-sample:`usb-audio-headphones-microphone` and
:zephyr:code-sample:`usb-audio-headset` samples for reference.
Bluetooth HCI USB transport layer
=================================
Bluetooth HCI USB transport layer implementation uses :ref:`bt_hci_raw`
to expose HCI interface to the host. It is not fully in line with the description
in the Bluetooth specification and consists only of an interface with the endpoint
configuration:
* HCI commands through control endpoint (host-to-device only)
* HCI events through interrupt IN endpoint
* ACL data through one bulk IN and one bulk OUT endpoints
A second interface for the voice channels has not been implemented as there is
no support for this type in :ref:`bluetooth`. It is not a big problem under Linux
if HCI USB transport layer is the only interface that appears in the configuration,
the btusb driver would not try to claim a second (isochronous) interface.
The consequence is that if HCI USB is used in a composite configuration and is
the first interface, then the Linux btusb driver will claim both the first and
the next interface, preventing other composite functions from working.
Because of this problem, HCI USB should not be used in a composite configuration.
This problem is fixed in the implementation for new USB support.
See :ref:`bluetooth-hci-usb-sample` sample for reference.
.. _usb_device_cdc_acm:
CDC ACM
=======
The CDC ACM class is used as backend for different subsystems in Zephyr.
However, its configuration may not be easy for the inexperienced user.
Below is a description of the different use cases and some pitfalls.
The interface for CDC ACM user is :ref:`uart_api` driver API.
But there are two important differences in behavior to a real UART controller:
* Data transfer is only possible after the USB device stack has been
initialized and started, until then any data is discarded
* If device is connected to the host, it still needs an application
on the host side which requests the data
* The CDC ACM poll out implementation follows the API and blocks when the TX
ring buffer is full only if the hw-flow-control property is enabled and
called from a non-ISR context.
The devicetree compatible property for CDC ACM UART is
:dtcompatible:`zephyr,cdc-acm-uart`.
CDC ACM support is automatically selected when USB device support is enabled
and a compatible node in the devicetree sources is present. If necessary,
CDC ACM support can be explicitly disabled by :kconfig:option:`CONFIG_USB_CDC_ACM`.
About four CDC ACM UART instances can be defined and used,
limited by the maximum number of supported endpoints on the controller.
CDC ACM UART node is supposed to be child of a USB device controller node.
Since the designation of the controller nodes varies from vendor to vendor,
and our samples and application should be as generic as possible,
the default USB device controller is usually assigned an ``zephyr_udc0``
node label. Often, CDC ACM UART is described in a devicetree overlay file
and looks like this:
.. code-block:: devicetree
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
};
Sample :zephyr:code-sample:`usb-cdc-acm` has similar overlay files.
And since no special properties are present, it may seem overkill to use
devicetree to describe CDC ACM UART. The motivation behind using devicetree
is the easy interchangeability of a real UART controller and CDC ACM UART
in applications.
Console over CDC ACM UART
-------------------------
With the CDC ACM UART node from above and ``zephyr,console`` property of the
chosen node, we can describe that CDC ACM UART is to be used with the console.
A similar overlay file is used by the :zephyr:code-sample:`usb-cdc-acm-console` sample.
.. code-block:: devicetree
/ {
chosen {
zephyr,console = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
};
Before the application uses the console, it is recommended to wait for
the DTR signal:
.. code-block:: c
const struct device *const dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
uint32_t dtr = 0;
if (usb_enable(NULL)) {
return;
}
while (!dtr) {
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
printk("nuqneH\n");
CDC ACM UART as backend
-----------------------
As for the console sample, it is possible to configure CDC ACM UART as
backend for other subsystems by setting :ref:`devicetree-chosen-nodes`
properties.
List of few Zephyr specific chosen properties which can be used to select
CDC ACM UART as backend for a subsystem or application:
* ``zephyr,bt-c2h-uart`` used in Bluetooth,
for example see :ref:`bluetooth-hci-uart-sample`
* ``zephyr,ot-uart`` used in OpenThread,
for example see :zephyr:code-sample:`coprocessor`
* ``zephyr,shell-uart`` used by shell for serial backend,
for example see :zephyr_file:`samples/subsys/shell/shell_module`
* ``zephyr,uart-mcumgr`` used by :zephyr:code-sample:`smp-svr` sample
POSIX default tty ECHO mitigation
---------------------------------
POSIX systems, like Linux, default to enabling ECHO on tty devices. Host side
application can disable ECHO by calling ``open()`` on the tty device and issuing
``ioctl()`` (preferably via ``tcsetattr()``) to disable echo if it is not desired.
Unfortunately, there is an inherent race between the ``open()`` and ``ioctl()``
where the ECHO is enabled and any characters received (even if host application
does not call ``read()``) will be echoed back. This issue is especially visible
when the CDC ACM port is used without any real UART on the other side because
there is no arbitrary delay due to baud rate.
To mitigate the issue, Zephyr CDC ACM implementation arms IN endpoint with ZLP
after device is configured. When the host reads the ZLP, which is pretty much
the best indication that host application has opened the tty device, Zephyr will
force :kconfig:option:`CONFIG_CDC_ACM_TX_DELAY_MS` millisecond delay before real
payload is sent. This should allow sufficient time for first, and only first,
application that opens the tty device to disable ECHO if ECHO is not desired.
If ECHO is not desired at all from CDC ACM device it is best to set up udev rule
to disable ECHO as soon as device is connected.
ECHO is particurarly unwanted when CDC ACM instance is used for Zephyr shell,
because the control characters to set color sent back to shell are interpreted
as (invalid) command and user will see garbage as a result. While minicom does
disable ECHO by default, on exit with reset it will restore the termios settings
to whatever was set on entry. Therefore, if minicom is the first application to
open the tty device, the exit with reset will enable ECHO back and thus set up
a problem for the next application (which cannot be mitigated at Zephyr side).
To prevent the issue it is recommended either to leave minicom without reset or
to disable ECHO before minicom is started.
DFU
===
USB DFU class implementation is tightly coupled to :ref:`dfu` and :ref:`mcuboot_api`.
This means that the target platform must support the :ref:`flash_img_api` API.
See :zephyr:code-sample:`usb-dfu` sample for reference.
USB Human Interface Devices (HID) support
=========================================
HID support abuses :ref:`device_model_api` simply to allow applications to use
the :c:func:`device_get_binding`. Note that there is no HID device API as such,
instead the interface is provided by :c:struct:`hid_ops`.
The default instance name is ``HID_n``, where n can be {0, 1, 2, ...} depending on
the :kconfig:option:`CONFIG_USB_HID_DEVICE_COUNT`.
Each HID instance requires a HID report descriptor. The interface to the core
and the report descriptor must be registered using :c:func:`usb_hid_register_device`.
As the USB HID specification is not only used by the USB subsystem, the USB HID API
reference is split into two parts, :ref:`usb_hid_common` and :ref:`usb_hid_device`.
HID helper macros from :ref:`usb_hid_common` should be used to compose a
HID report descriptor. Macro names correspond to those used in the USB HID specification.
For the HID class interface, an IN interrupt endpoint is required for each instance,
an OUT interrupt endpoint is optional. Thus, the minimum implementation requirement
for :c:struct:`hid_ops` is to provide ``int_in_ready`` callback.
.. code-block:: c
#define REPORT_ID 1
static bool configured;
static const struct device *hdev;
static void int_in_ready_cb(const struct device *dev)
{
static uint8_t report[2] = {REPORT_ID, 0};
if (hid_int_ep_write(hdev, report, sizeof(report), NULL)) {
LOG_ERR("Failed to submit report");
} else {
report[1]++;
}
}
static void status_cb(enum usb_dc_status_code status, const uint8_t *param)
{
if (status == USB_DC_RESET) {
configured = false;
}
if (status == USB_DC_CONFIGURED && !configured) {
int_in_ready_cb(hdev);
configured = true;
}
}
static const uint8_t hid_report_desc[] = {
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP),
HID_USAGE(HID_USAGE_GEN_DESKTOP_UNDEFINED),
HID_COLLECTION(HID_COLLECTION_APPLICATION),
HID_LOGICAL_MIN8(0x00),
HID_LOGICAL_MAX16(0xFF, 0x00),
HID_REPORT_ID(REPORT_ID),
HID_REPORT_SIZE(8),
HID_REPORT_COUNT(1),
HID_USAGE(HID_USAGE_GEN_DESKTOP_UNDEFINED),
HID_INPUT(0x02),
HID_END_COLLECTION,
};
static const struct hid_ops my_ops = {
.int_in_ready = int_in_ready_cb,
};
int main(void)
{
int ret;
hdev = device_get_binding("HID_0");
if (hdev == NULL) {
return -ENODEV;
}
usb_hid_register_device(hdev, hid_report_desc, sizeof(hid_report_desc),
&my_ops);
ret = usb_hid_init(hdev);
if (ret) {
return ret;
}
return usb_enable(status_cb);
}
If the application wishes to receive output reports via the OUT interrupt endpoint,
it must enable :kconfig:option:`CONFIG_ENABLE_HID_INT_OUT_EP` and provide
``int_out_ready`` callback.
The disadvantage of this is that Kconfig options such as
:kconfig:option:`CONFIG_ENABLE_HID_INT_OUT_EP` or
:kconfig:option:`CONFIG_HID_INTERRUPT_EP_MPS` apply to all instances. This design
issue will be fixed in the HID class implementation for the new USB support.
See :zephyr:code-sample:`usb-hid` or :zephyr:code-sample:`usb-hid-mouse` sample for reference.
Mass Storage Class
==================
MSC follows Bulk-Only Transport specification and uses :ref:`disk_access_api` to
access and expose a RAM disk, emulated block device on a flash partition,
or SD Card to the host. Only one disk instance can be exported at a time.
The disc to be used by the implementation is set by the
:kconfig:option:`CONFIG_MASS_STORAGE_DISK_NAME` and should be the same as the name
used by the disc access driver that the application wants to expose to the host.
SD card disk drivers use options :kconfig:option:`CONFIG_MMC_VOLUME_NAME` or
:kconfig:option:`CONFIG_SDMMC_VOLUME_NAME`, and flash and RAM disk drivers use
node property ``disk-name`` to set the disk name.
For the emulated block device on a flash partition, the flash partition and
flash disk to be used must be described in the devicetree. If a storage partition
is already described at the board level, application devicetree overlay must also
delete ``storage_partition`` node first. :kconfig:option:`CONFIG_MASS_STORAGE_DISK_NAME`
should be the same as ``disk-name`` property.
.. code-block:: devicetree
/delete-node/ &storage_partition;
&mx25r64 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
storage_partition: partition@0 {
label = "storage";
reg = <0x00000000 0x00020000>;
};
};
};
/ {
msc_disk0 {
compatible = "zephyr,flash-disk";
partition = <&storage_partition>;
disk-name = "NAND";
cache-size = <4096>;
};
};
The ``disk-property`` "NAND" may be confusing, but it is simply how some file
systems identifies the disc. Therefore, if the application also accesses the
file system on the exposed disc, default names should be used, see
:zephyr:code-sample:`usb-mass` sample for reference.
Networking
==========
There are three implementations that work in a similar way, providing a virtual
Ethernet connection between the remote (USB host) and Zephyr network support.
* CDC ECM class, enabled with :kconfig:option:`CONFIG_USB_DEVICE_NETWORK_ECM`
* CDC EEM class, enabled with :kconfig:option:`CONFIG_USB_DEVICE_NETWORK_EEM`
* RNDIS support, enabled with :kconfig:option:`CONFIG_USB_DEVICE_NETWORK_RNDIS`
See :zephyr:code-sample:`zperf` or :zephyr:code-sample:`socket-dumb-http-server` for reference.
Typically, users will need to add a configuration file overlay to the build,
such as :zephyr_file:`samples/net/zperf/overlay-netusb.conf`.
Applications using RNDIS support should enable :kconfig:option:`CONFIG_USB_DEVICE_OS_DESC`
for a better user experience on a host running Microsoft Windows OS.
Binary Device Object Store (BOS) support
****************************************
BOS handling can be enabled with Kconfig option :kconfig:option:`CONFIG_USB_DEVICE_BOS`.
This option also has the effect of changing device descriptor ``bcdUSB`` to ``0210``.
The application should register descriptors such as Capability Descriptor
using :c:func:`usb_bos_register_cap`. Registered descriptors are added to the root
BOS descriptor and handled by the stack.
See :zephyr:code-sample:`webusb` sample for reference.
Implementing a non-standard USB class
*************************************
The configuration of USB device is done in the stack layer.
The following structures and callbacks need to be defined:
* Part of USB Descriptor table
* USB Endpoint configuration table
* USB Device configuration structure
* Endpoint callbacks
* Optionally class, vendor and custom handlers
For example, for the USB loopback application:
.. literalinclude:: ../../../../../../subsys/usb/device/class/loopback.c
:language: c
:start-after: usb.rst config structure start
:end-before: usb.rst config structure end
:linenos:
Endpoint configuration:
.. literalinclude:: ../../../../../../subsys/usb/device/class/loopback.c
:language: c
:start-after: usb.rst endpoint configuration start
:end-before: usb.rst endpoint configuration end
:linenos:
USB Device configuration structure:
.. literalinclude:: ../../../../../../subsys/usb/device/class/loopback.c
:language: c
:start-after: usb.rst device config data start
:end-before: usb.rst device config data end
:linenos:
The vendor device requests are forwarded by the USB stack core driver to the
class driver through the registered vendor handler.
For the loopback class driver, :c:func:`loopback_vendor_handler` processes
the vendor requests:
.. literalinclude:: ../../../../../../subsys/usb/device/class/loopback.c
:language: c
:start-after: usb.rst vendor handler start
:end-before: usb.rst vendor handler end
:linenos:
The class driver waits for the :makevar:`USB_DC_CONFIGURED` device status code
before transmitting any data.
.. _testing_USB_native_sim:
Interface number and endpoint address assignment
************************************************
In USB terminology, a ``function`` is a device that provides a capability to the
host, such as a HID class device that implements a keyboard. A function
contains a collection of ``interfaces``; at least one interface is required. An
interface may contain device ``endpoints``; for example, at least one input
endpoint is required to implement a HID class device, and no endpoints are
required to implement a USB DFU class. A USB device that combines functions is
a multifunction USB device, for example, a combination of a HID class device
and a CDC ACM device.
With Zephyr RTOS USB support, various combinations are possible with built-in USB
classes/functions or custom user implementations. The limitation is the number
of available device endpoints. Each device endpoint is uniquely addressable.
The endpoint address is a combination of endpoint direction and endpoint
number, a four-bit value. Endpoint number zero is used for the default control
method to initialize and configure a USB device. By specification, a maximum of
``15 IN`` and ``15 OUT`` device endpoints are also available for use in functions.
The actual number depends on the device controller used. Not all controllers
support the maximum number of endpoints and all endpoint types. For example, a
device controller might support one IN and one OUT isochronous endpoint, but
only for endpoint number 8, resulting in endpoint addresses 0x88 and 0x08.
Also, one controller may be able to have IN/OUT endpoints on the same endpoint
number, interrupt IN endpoint 0x81 and bulk OUT endpoint 0x01, while the other
may only be able to handle one endpoint per endpoint number. Information about
the number of interfaces, interface associations, endpoint types, and addresses
is provided to the host by the interface, interface specific, and endpoint
descriptors.
Host driver for specific function, uses interface and endpoint descriptor to
obtain endpoint addresses, types, and other properties. This allows function
host drivers to be generic, for example, a multi-function device consisting of
one or more CDC ACM and one or more CDC ECM class implementations is possible
and no specific drivers are required.
Interface and endpoint descriptors of built-in USB class/function
implementations in Zephyr RTOS typically have default interface numbers and
endpoint addresses assigned in ascending order. During initialization,
default interface numbers may be reassigned based on the number of interfaces in
a given configuration. Endpoint addresses are reassigned based on controller
capabilities, since certain endpoint combinations are not possible with every
controller, and the number of interfaces in a given configuration. This also
means that the device side class/function in the Zephyr RTOS must check the
actual interface and endpoint descriptor values at runtime.
This mechanism also allows as to provide generic samples and generic
multifunction samples that are limited only by the resources provided by the
controller, such as the number of endpoints and the size of the endpoint FIFOs.
There may be host drivers for a specific function, for example in the Linux
Kernel, where the function driver does not read interface and endpoint
descriptors to check interface numbers or endpoint addresses, but instead uses
hardcoded values. Therefore, the host driver cannot be used in a generic way,
meaning it cannot be used with different device controllers and different
device configurations in combination with other functions. This may also be
because the driver is designed for a specific hardware and is not intended to
be used with a clone of this specific hardware. On the contrary, if the driver
is generic in nature and should work with different hardware variants, then it
must not use hardcoded interface numbers and endpoint addresses.
It is not possible to disable endpoint reassignment in Zephyr RTOS, which may
prevent you from implementing a hardware-clone firmware. Instead, if possible,
the host driver implementation should be fixed to use values from the interface
and endpoint descriptor.
Testing over USPIP in native_sim
********************************
A virtual USB controller implemented through USBIP might be used to test the USB
device stack. Follow the general build procedure to build the USB sample for
the :ref:`native_sim <native_sim>` configuration.
Run built sample with:
.. code-block:: console
west build -t run
In a terminal window, run the following command to list USB devices:
.. code-block:: console
$ usbip list -r localhost
Exportable USB devices
======================
- 127.0.0.1
1-1: unknown vendor : unknown product (2fe3:0100)
: /sys/devices/pci0000:00/0000:00:01.2/usb1/1-1
: (Defined at Interface level) (00/00/00)
: 0 - Vendor Specific Class / unknown subclass / unknown protocol (ff/00/00)
In a terminal window, run the following command to attach the USB device:
.. code-block:: console
$ sudo usbip attach -r localhost -b 1-1
The USB device should be connected to your Linux host, and verified with the
following commands:
.. code-block:: console
$ sudo usbip port
Imported USB devices
====================
Port 00: <Port in Use> at Full Speed(12Mbps)
unknown vendor : unknown product (2fe3:0100)
7-1 -> usbip://localhost:3240/1-1
-> remote bus/dev 001/002
$ lsusb -d 2fe3:0100
Bus 007 Device 004: ID 2fe3:0100
USB Vendor and Product identifiers
**********************************
The USB Vendor ID for the Zephyr project is ``0x2FE3``.
This USB Vendor ID must not be used when a vendor
integrates Zephyr USB device support into its own product.
Each USB :ref:`sample<usb-samples>` has its own unique Product ID.
The USB maintainer, if one is assigned, or otherwise the Zephyr Technical
Steering Committee, may allocate other USB Product IDs based on well-motivated
and documented requests.
The following Product IDs are currently used:
+----------------------------------------------------+--------+
| Sample | PID |
+====================================================+========+
| :zephyr:code-sample:`usb-cdc-acm` | 0x0001 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-cdc-acm-composite` | 0x0002 |
+----------------------------------------------------+--------+
| Reserved (previously: usb-hid-cdc) | 0x0003 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-cdc-acm-console` | 0x0004 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-dfu` (Run-Time) | 0x0005 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-hid` | 0x0006 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-hid-mouse` | 0x0007 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-mass` | 0x0008 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`testusb-app` | 0x0009 |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`webusb` | 0x000A |
+----------------------------------------------------+--------+
| :ref:`bluetooth-hci-usb-sample` | 0x000B |
+----------------------------------------------------+--------+
| :ref:`bluetooth-hci-usb-h4-sample` | 0x000C |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`wpan-usb` | 0x000D |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`uac2-explicit-feedback` | 0x000E |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`usb-dfu` (DFU Mode) | 0xFFFF |
+----------------------------------------------------+--------+
The USB device descriptor field ``bcdDevice`` (Device Release Number) represents
the Zephyr kernel major and minor versions as a binary coded decimal value.
Part 3
한국어 전문 번역
USB device 지원
개요
USB device stack은 USB device controller driver와 USB device class driver 또는 사용자 application 사이에 놓이는 hardware 독립 interface입니다. LPCUSB device stack을 이식한 뒤 오랜 기간 수정하고 확장해 왔습니다. 다음 기능을 제공합니다.
- Device controller driver가 제공하는 USB device controller API를 사용해 controller와 상호작용합니다.
- Standard device request에 응답하고 standard descriptor를 반환합니다. 실질적으로 USB specification revision 2.0의 Chapter 9 처리, 특히 표 9-3의 standard device request를 담당합니다.
- USB device class나 사용자 application이 사용할 programming interface를 제공합니다. API는
include/zephyr/usb/usb_device.h에 기술되어 있습니다.
지원하는 USB class
Audio
Audio class의 experimental 구현이 있습니다. Specification version 1.00(bcdADC 0x0100)을 따르며 synchronous synchronisation type만 지원합니다. usb-audio-headphones-microphone과 usb-audio-headset sample을 참고합니다.
Bluetooth HCI USB transport layer
Bluetooth HCI USB transport layer 구현은 Bluetooth HCI raw channel을 사용해 HCI interface를 host에 노출합니다. Bluetooth specification의 설명과 완전히 일치하지는 않으며 다음 endpoint 구성의 interface 하나만 제공합니다.
- HCI command: control endpoint를 사용하며 host-to-device 방향만 지원
- HCI event: interrupt IN endpoint 사용
- ACL data: bulk IN endpoint 하나와 bulk OUT endpoint 하나 사용
Bluetooth subsystem이 voice channel type을 지원하지 않으므로 voice channel용 두 번째 interface는 구현되지 않았습니다. Configuration에 HCI USB transport layer만 나타나는 Linux 환경에서는 큰 문제가 아닙니다. btusb driver가 두 번째 isochronous interface를 점유하려 하지 않기 때문입니다.
하지만 composite configuration에서 HCI USB가 첫 interface이면 Linux btusb driver가 첫 interface와 바로 다음 interface를 모두 점유해 다른 composite function이 동작하지 못합니다. 따라서 기존 구현의 HCI USB는 composite configuration에서 사용하면 안 됩니다. 새 USB 지원 구현에서는 이 문제가 수정됐습니다.
bluetooth-hci-usb-sample을 참고합니다.
CDC ACM
CDC ACM class는 Zephyr의 여러 subsystem에서 backend로 사용됩니다. 그러나 경험이 적은 사용자가 설정하기는 쉽지 않으므로, 주요 사용 사례와 주의할 함정을 설명합니다.
CDC ACM 사용자가 접하는 interface는 UART driver API입니다. 실제 UART controller와 비교하면 다음과 같은 중요한 동작 차이가 있습니다.
- USB device stack을 초기화하고 시작한 뒤에만 data를 전송할 수 있습니다. 그 전의 data는 모두 버려집니다.
- Device가 host에 연결됐더라도 host 쪽 application이 data를 요청해야 실제로 전달됩니다.
hw-flow-controlproperty가 활성화되어 있고 ISR이 아닌 context에서 호출했을 때만, CDC ACMpoll_out구현은 TX ring buffer가 가득 차면 UART API 계약에 따라 block합니다.
CDC ACM UART의 devicetree compatible property는 zephyr,cdc-acm-uart입니다. USB device 지원이 활성화되고 devicetree source에 compatible node가 있으면 CDC ACM 지원이 자동으로 선택됩니다. 필요하면 CONFIG_USB_CDC_ACM으로 명시적으로 끌 수 있습니다. Controller가 지원하는 최대 endpoint 수에 제한되지만 대략 CDC ACM UART instance 네 개를 정의해 사용할 수 있습니다.
CDC ACM UART node는 USB device controller node의 child여야 합니다. Vendor마다 controller node 이름이 다르고 sample과 application은 범용이어야 하므로, 기본 USB device controller에는 보통 zephyr_udc0 node label을 지정합니다. CDC ACM UART는 흔히 다음과 같은 devicetree overlay에 기술합니다.
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
};usb-cdc-acm sample에도 비슷한 overlay가 있습니다. 특별한 property가 없어서 devicetree가 과해 보일 수 있지만, application에서 실제 UART controller와 CDC ACM UART를 쉽게 교체할 수 있다는 것이 devicetree를 사용하는 이유입니다.
CDC ACM UART를 통한 console
앞의 CDC ACM UART node와 chosen node의 zephyr,console property를 사용하면 CDC ACM UART를 console로 쓰도록 선언할 수 있습니다. usb-cdc-acm-console sample도 비슷한 overlay를 사용합니다.
/ {
chosen {
zephyr,console = &cdc_acm_uart0;
};
};
&zephyr_udc0 {
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
};Application이 console을 사용하기 전에는 DTR signal을 기다리는 것이 좋습니다.
const struct device *const dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
uint32_t dtr = 0;
if (usb_enable(NULL)) {
return;
}
while (!dtr) {
uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
k_sleep(K_MSEC(100));
}
printk("nuqneH\n");CDC ACM UART를 backend로 사용
Console sample과 마찬가지로 devicetree chosen node property를 설정하면 다른 subsystem의 backend로 CDC ACM UART를 구성할 수 있습니다.
Subsystem 또는 application의 CDC ACM UART backend를 선택할 수 있는 Zephyr 전용 chosen property 예는 다음과 같습니다.
zephyr,bt-c2h-uart: Bluetooth에서 사용합니다. 예:bluetooth-hci-uart-samplezephyr,ot-uart: OpenThread에서 사용합니다. 예:coprocessorzephyr,shell-uart: shell의 serial backend에 사용합니다. 예:samples/subsys/shell/shell_modulezephyr,uart-mcumgr:smp-svrsample에서 사용합니다.
POSIX 기본 tty ECHO 완화
Linux 같은 POSIX system은 tty device의 ECHO를 기본으로 활성화합니다. Host application은 tty device에 open()을 호출하고 ioctl()을 실행해, 가급적 tcsetattr()을 통해 원치 않는 echo를 끌 수 있습니다. 그러나 open()과 ioctl() 사이에는 ECHO가 켜져 있는 고유한 race가 있습니다. 이때 수신된 문자는 host application이 read()을 호출하지 않아도 되돌아갑니다. CDC ACM port 반대편에 실제 UART가 없으면 baud rate로 인한 임의 지연도 없어 문제가 특히 잘 드러납니다.
이를 완화하기 위해 Zephyr CDC ACM 구현은 device가 configured 상태가 되면 IN endpoint에 ZLP를 준비합니다. Host가 이 ZLP를 읽었다는 것은 host application이 tty device를 열었다는 가장 좋은 신호에 가깝습니다. 그러면 Zephyr는 실제 payload를 보내기 전에 CONFIG_CDC_ACM_TX_DELAY_MS millisecond만큼 강제로 기다립니다. 최초로 tty를 여는 application이 원치 않는 ECHO를 끌 시간을 확보하기 위한 것입니다. CDC ACM device에서 ECHO가 전혀 필요 없다면 device 연결 즉시 ECHO를 끄는 udev rule을 설정하는 편이 가장 좋습니다.
CDC ACM instance를 Zephyr shell에 사용할 때는 ECHO가 특히 해롭습니다. Color 설정용 control character가 shell로 되돌아와 잘못된 command로 해석되므로 사용자는 깨진 문자를 보게 됩니다. minicom은 기본적으로 ECHO를 끄지만 reset을 수행하며 종료하면 진입 당시의 termios 설정을 복원합니다. minicom이 tty를 처음 연 application이면 reset 종료가 ECHO를 다시 켜 다음 application에 문제를 남기며, Zephyr 쪽에서는 이를 완화할 수 없습니다. Reset 없이 minicom을 종료하거나 minicom 실행 전에 ECHO를 꺼야 합니다.
DFU
USB DFU class 구현은 Device Firmware Upgrade와 MCUboot API에 밀접하게 결합되어 있습니다. 따라서 target platform이 Flash Image API를 지원해야 합니다. usb-dfu sample을 참고합니다.
USB Human Interface Devices(HID) 지원
HID 지원은 application이 device_get_binding을 사용할 수 있게 하려고 Device Driver Model을 다소 비정상적인 방식으로 활용합니다. 별도의 HID device API는 없으며 hid_ops가 interface를 제공합니다. 기본 instance 이름은 HID_n이고, CONFIG_USB_HID_DEVICE_COUNT 값에 따라 n은 0, 1, 2 등입니다.
각 HID instance에는 HID report descriptor가 필요합니다. Core interface와 report descriptor는 usb_hid_register_device로 등록해야 합니다.
USB HID specification은 USB subsystem 밖에서도 쓰이므로 API reference를 공통 HID 부분과 USB HID class 부분으로 나눕니다.
공통 HID API의 helper macro로 HID report descriptor를 구성해야 합니다. Macro 이름은 USB HID specification에서 사용하는 이름과 대응합니다.
HID class interface의 각 instance에는 interrupt IN endpoint가 필수이고 interrupt OUT endpoint는 선택입니다. 따라서 hid_ops의 최소 구현 요건은 int_in_ready callback을 제공하는 것입니다.
#define REPORT_ID 1
static bool configured;
static const struct device *hdev;
static void int_in_ready_cb(const struct device *dev)
{
static uint8_t report[2] = {REPORT_ID, 0};
if (hid_int_ep_write(hdev, report, sizeof(report), NULL)) {
LOG_ERR("Failed to submit report");
} else {
report[1]++;
}
}
static void status_cb(enum usb_dc_status_code status, const uint8_t *param)
{
if (status == USB_DC_RESET) {
configured = false;
}
if (status == USB_DC_CONFIGURED && !configured) {
int_in_ready_cb(hdev);
configured = true;
}
}
static const uint8_t hid_report_desc[] = {
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP),
HID_USAGE(HID_USAGE_GEN_DESKTOP_UNDEFINED),
HID_COLLECTION(HID_COLLECTION_APPLICATION),
HID_LOGICAL_MIN8(0x00),
HID_LOGICAL_MAX16(0xFF, 0x00),
HID_REPORT_ID(REPORT_ID),
HID_REPORT_SIZE(8),
HID_REPORT_COUNT(1),
HID_USAGE(HID_USAGE_GEN_DESKTOP_UNDEFINED),
HID_INPUT(0x02),
HID_END_COLLECTION,
};
static const struct hid_ops my_ops = {
.int_in_ready = int_in_ready_cb,
};
int main(void)
{
int ret;
hdev = device_get_binding("HID_0");
if (hdev == NULL) {
return -ENODEV;
}
usb_hid_register_device(hdev, hid_report_desc, sizeof(hid_report_desc),
&my_ops);
ret = usb_hid_init(hdev);
if (ret) {
return ret;
}
return usb_enable(status_cb);
}Application이 interrupt OUT endpoint로 output report를 받으려면 CONFIG_ENABLE_HID_INT_OUT_EP을 활성화하고 int_out_ready callback을 제공해야 합니다.
CONFIG_ENABLE_HID_INT_OUT_EP이나 CONFIG_HID_INTERRUPT_EP_MPS 같은 Kconfig option이 모든 instance에 공통 적용된다는 단점이 있습니다. 이 설계 문제는 새 USB 지원의 HID class 구현에서 수정됩니다. usb-hid 또는 usb-hid-mouse sample을 참고합니다.
Mass Storage Class
MSC는 Bulk-Only Transport specification을 따르고 Disk Access API를 사용해 RAM disk, flash partition의 emulated block device 또는 SD card를 host에 노출합니다. 한 번에 disk instance 하나만 export할 수 있습니다.
구현이 사용할 disk는 CONFIG_MASS_STORAGE_DISK_NAME으로 정하며 application이 host에 노출하려는 disk access driver의 이름과 같아야 합니다. SD card disk driver는 CONFIG_MMC_VOLUME_NAME 또는 CONFIG_SDMMC_VOLUME_NAME을 사용하고, flash와 RAM disk driver는 disk-name node property로 disk 이름을 정합니다.
Flash partition의 emulated block device를 사용하려면 flash partition과 flash disk를 devicetree에 기술해야 합니다. Board level에 storage partition이 이미 정의돼 있다면 application devicetree overlay에서 먼저 storage_partition node를 삭제해야 합니다. CONFIG_MASS_STORAGE_DISK_NAME은 disk-name property와 같아야 합니다.
/delete-node/ &storage_partition;
&mx25r64 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
storage_partition: partition@0 {
label = "storage";
reg = <0x00000000 0x00020000>;
};
};
};
/ {
msc_disk0 {
compatible = "zephyr,flash-disk";
partition = <&storage_partition>;
disk-name = "NAND";
cache-size = <4096>;
};
};원문에서 disk-property로 표현한 NAND 이름은 혼동될 수 있지만, 일부 file system이 disk를 식별하는 방식일 뿐입니다. Application이 노출된 disk의 file system에도 접근한다면 기본 이름을 사용하는 것이 좋습니다. usb-mass sample을 참고합니다.
Networking
비슷한 방식으로 동작하며 원격 USB host와 Zephyr network support 사이에 virtual Ethernet connection을 제공하는 구현은 세 가지입니다.
- CDC ECM class:
CONFIG_USB_DEVICE_NETWORK_ECM으로 활성화 - CDC EEM class:
CONFIG_USB_DEVICE_NETWORK_EEM으로 활성화 - RNDIS 지원:
CONFIG_USB_DEVICE_NETWORK_RNDIS로 활성화
zperf 또는 socket-dumb-http-server sample을 참고합니다. 보통 samples/net/zperf/overlay-netusb.conf 같은 configuration overlay file을 build에 추가해야 합니다.
RNDIS를 사용하는 application은 Microsoft Windows host에서 더 나은 사용 경험을 제공하도록 CONFIG_USB_DEVICE_OS_DESC를 활성화해야 합니다.
Binary Device Object Store(BOS) 지원
BOS 처리는 CONFIG_USB_DEVICE_BOS Kconfig option으로 활성화합니다. 이 option은 device descriptor의 bcdUSB도 0210으로 바꿉니다.
Application은 Capability Descriptor 같은 descriptor를 usb_bos_register_cap으로 등록해야 합니다. 등록된 descriptor는 root BOS descriptor에 추가되고 stack이 처리합니다. webusb sample을 참고합니다.
비표준 USB class 구현
USB device configuration은 stack layer에서 수행합니다.
다음 structure와 callback을 정의해야 합니다.
- USB Descriptor table의 일부
- USB Endpoint configuration table
- USB Device configuration structure
- Endpoint callback
- 선택 사항인 class, vendor 및 custom handler
USB loopback application의 USB descriptor table 부분은 다음과 같습니다. 원문의 literalinclude가 지정한 C code와 줄 순서를 그대로 보존합니다.
struct usb_loopback_config {
struct usb_if_descriptor if0;
struct usb_ep_descriptor if0_out_ep;
struct usb_ep_descriptor if0_in_ep;
} __packed;
USBD_CLASS_DESCR_DEFINE(primary, 0) struct usb_loopback_config loopback_cfg = {
/* Interface descriptor 0 */
.if0 = {
.bLength = sizeof(struct usb_if_descriptor),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_BCC_VENDOR,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
},
/* Data Endpoint OUT */
.if0_out_ep = {
.bLength = sizeof(struct usb_ep_descriptor),
.bDescriptorType = USB_DESC_ENDPOINT,
.bEndpointAddress = LOOPBACK_OUT_EP_ADDR,
.bmAttributes = USB_DC_EP_BULK,
.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
.bInterval = 0x00,
},
/* Data Endpoint IN */
.if0_in_ep = {
.bLength = sizeof(struct usb_ep_descriptor),
.bDescriptorType = USB_DESC_ENDPOINT,
.bEndpointAddress = LOOPBACK_IN_EP_ADDR,
.bmAttributes = USB_DC_EP_BULK,
.wMaxPacketSize = sys_cpu_to_le16(CONFIG_LOOPBACK_BULK_EP_MPS),
.bInterval = 0x00,
},
};Endpoint configuration은 다음과 같습니다.
static struct usb_ep_cfg_data ep_cfg[] = {
{
.ep_cb = loopback_out_cb,
.ep_addr = LOOPBACK_OUT_EP_ADDR,
},
{
.ep_cb = loopback_in_cb,
.ep_addr = LOOPBACK_IN_EP_ADDR,
},
};USB Device configuration structure는 다음과 같습니다.
USBD_DEFINE_CFG_DATA(loopback_config) = {
.usb_device_description = NULL,
.interface_config = loopback_interface_config,
.interface_descriptor = &loopback_cfg.if0,
.cb_usb_status = loopback_status_cb,
.interface = {
.class_handler = NULL,
.custom_handler = NULL,
.vendor_handler = loopback_vendor_handler,
},
.num_endpoints = ARRAY_SIZE(ep_cfg),
.endpoint = ep_cfg,
};Vendor device request는 USB stack core driver가 등록된 vendor handler를 통해 class driver로 전달합니다.
loopback_vendor_handler는 loopback class driver의 vendor request를 다음과 같이 처리합니다.
static int loopback_vendor_handler(struct usb_setup_packet *setup,
int32_t *len, uint8_t **data)
{
LOG_DBG("Class request: bRequest 0x%x bmRequestType 0x%x len %d",
setup->bRequest, setup->bmRequestType, *len);
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
return -ENOTSUP;
}
if (usb_reqtype_is_to_device(setup) &&
setup->bRequest == 0x5b) {
LOG_DBG("Host-to-Device, data %p", *data);
/*
* Copy request data in loopback_buf buffer and reuse
* it later in control device-to-host transfer.
*/
memcpy(loopback_buf, *data,
MIN(sizeof(loopback_buf), setup->wLength));
return 0;
}
if ((usb_reqtype_is_to_host(setup)) &&
(setup->bRequest == 0x5c)) {
LOG_DBG("Device-to-Host, wLength %d, data %p",
setup->wLength, *data);
*data = loopback_buf;
*len = MIN(sizeof(loopback_buf), setup->wLength);
return 0;
}
return -ENOTSUP;
}Class driver는 data를 전송하기 전에 USB_DC_CONFIGURED device status code를 기다립니다.
Interface number와 endpoint address 할당
USB 용어에서 function은 keyboard를 구현한 HID class device처럼 host에 기능을 제공하는 device입니다. Function은 하나 이상의 interface collection을 포함합니다. Interface는 device endpoint를 포함할 수 있습니다. HID class device에는 input endpoint가 적어도 하나 필요하지만 USB DFU class에는 endpoint가 필요 없습니다. HID class device와 CDC ACM device를 합친 것처럼 여러 function을 결합한 USB device가 multifunction USB device입니다.
Zephyr RTOS USB 지원은 built-in USB class/function과 사용자 구현을 여러 조합으로 구성할 수 있습니다. 한계는 사용 가능한 device endpoint 수입니다. Endpoint address는 endpoint 방향과 4-bit endpoint number의 조합이며 각 endpoint를 고유하게 식별합니다. Endpoint number 0은 USB device를 초기화하고 구성하는 기본 control method에 사용합니다. Specification상 function에서 추가로 사용할 수 있는 endpoint는 최대 15 IN과 15 OUT입니다.
실제 수는 device controller에 따라 다릅니다. 모든 controller가 최대 endpoint 수와 모든 endpoint type을 지원하지는 않습니다. 예를 들어 어떤 controller는 endpoint number 8에서만 IN/OUT isochronous endpoint 하나씩을 지원해 address가 0x88과 0x08이 됩니다. 어떤 controller는 같은 number에 interrupt IN 0x81과 bulk OUT 0x01을 함께 둘 수 있지만, 다른 controller는 endpoint number마다 endpoint 하나만 처리할 수 있습니다. Host는 interface, interface-specific 및 endpoint descriptor에서 interface 수, association, endpoint type과 address 정보를 받습니다.
특정 function의 host driver는 interface와 endpoint descriptor를 읽어 endpoint address, type과 다른 property를 얻습니다. 따라서 function host driver를 범용으로 만들 수 있습니다. 예를 들어 CDC ACM과 CDC ECM class 구현을 각각 하나 이상 포함한 multifunction device도 별도 전용 driver 없이 구성할 수 있습니다.
Zephyr RTOS built-in USB class/function의 interface와 endpoint descriptor에는 보통 오름차순의 기본 interface number와 endpoint address가 할당됩니다. 초기화 중 configuration의 interface 수에 따라 기본 interface number가 다시 배정될 수 있습니다. 모든 controller에서 가능한 endpoint 조합이 같지 않으므로 controller capability와 configuration의 interface 수에 따라 endpoint address도 다시 배정됩니다.
따라서 Zephyr device-side class/function은 runtime에 실제 interface와 endpoint descriptor 값을 확인해야 합니다. 이 mechanism 덕분에 endpoint 수와 FIFO 크기처럼 controller가 제공하는 resource만을 한계로 하는 범용 sample과 multifunction sample을 제공할 수 있습니다.
Linux kernel 등의 특정 function용 host driver가 descriptor에서 interface number나 endpoint address를 읽지 않고 hardcoded 값을 쓰는 경우가 있습니다. 이런 driver는 다른 device controller 또는 다른 function과 결합한 configuration에서 범용으로 사용할 수 없습니다. 특정 hardware 전용으로 설계되어 clone을 지원할 의도가 없기 때문일 수도 있습니다. 반대로 서로 다른 hardware variant에서 동작해야 하는 범용 driver라면 interface number와 endpoint address를 hardcode하면 안 됩니다.
Zephyr RTOS에서는 endpoint 재할당을 끌 수 없으므로 특정 hardware clone firmware를 구현하지 못할 수 있습니다. 가능하다면 host driver가 interface와 endpoint descriptor의 값을 사용하도록 수정해야 합니다.
native_sim에서 USBIP로 시험
USBIP로 구현된 virtual USB controller를 사용해 USB device stack을 시험할 수 있습니다. 일반 build 절차에 따라 native_sim configuration용 USB sample을 build합니다.
Build한 sample을 실행합니다.
west build -t run별도의 terminal에서 다음 command로 USB device 목록을 확인합니다.
$ usbip list -r localhost
Exportable USB devices
======================
- 127.0.0.1
1-1: unknown vendor : unknown product (2fe3:0100)
: /sys/devices/pci0000:00/0000:00:01.2/usb1/1-1
: (Defined at Interface level) (00/00/00)
: 0 - Vendor Specific Class / unknown subclass / unknown protocol (ff/00/00)다른 terminal에서 USB device를 attach합니다.
$ sudo usbip attach -r localhost -b 1-1USB device가 Linux host에 연결되어야 합니다. 다음 command로 연결 상태와 VID:PID를 확인합니다.
$ sudo usbip port
Imported USB devices
====================
Port 00: <Port in Use> at Full Speed(12Mbps)
unknown vendor : unknown product (2fe3:0100)
7-1 -> usbip://localhost:3240/1-1
-> remote bus/dev 001/002
$ lsusb -d 2fe3:0100
Bus 007 Device 004: ID 2fe3:0100USB Vendor와 Product identifier
Zephyr project의 USB Vendor ID는 0x2FE3입니다. Vendor가 Zephyr USB device 지원을 자체 product에 통합할 때 이 Vendor ID를 사용해서는 안 됩니다.
각 USB sample에는 고유한 Product ID가 있습니다. USB maintainer가 지정되어 있다면 maintainer가, 그렇지 않으면 Zephyr Technical Steering Committee가 충분한 근거와 문서가 있는 요청에 따라 다른 USB Product ID를 할당할 수 있습니다.
현재 사용하는 Product ID는 다음과 같습니다.
| Sample | PID |
|---|---|
| usb-cdc-acm | 0x0001 |
| usb-cdc-acm-composite | 0x0002 |
| 예약됨 (이전: usb-hid-cdc) | 0x0003 |
| usb-cdc-acm-console | 0x0004 |
| usb-dfu (Run-Time) | 0x0005 |
| usb-hid | 0x0006 |
| usb-hid-mouse | 0x0007 |
| usb-mass | 0x0008 |
| testusb-app | 0x0009 |
| webusb | 0x000A |
| bluetooth-hci-usb-sample | 0x000B |
| bluetooth-hci-usb-h4-sample | 0x000C |
| wpan-usb | 0x000D |
| uac2-explicit-feedback | 0x000E |
| usb-dfu (DFU Mode) | 0xFFFF |
USB device descriptor의 bcdDevice(Device Release Number) field는 Zephyr kernel major와 minor version을 binary-coded decimal 값으로 나타냅니다.
Source
출처
영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.