From 18507c97eb2ab494f5964c7360c8e65d40a2aa3a Mon Sep 17 00:00:00 2001 From: Jiu-xiao <2592509183@qq.com> Date: Wed, 7 Oct 2026 00:17:36 +0100 Subject: [PATCH] Optional interface name per CDC An item of USB..cdc may set interface; it becomes the control and data interface names of the CDCUart, so a host can tell CDCs with the same VID:PID apart. Items without it generate the same code as before. --- README.md | 16 +++++++++++++ src/libxr/generator_code_stm32.py | 32 +++++++++++++++++++------- tests/test_generator_code_stm32.py | 36 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 98f6050..602a51a 100644 --- a/README.md +++ b/README.md @@ -408,6 +408,22 @@ USB: - {tx_fifo_size: 128, rx_fifo_size: 128, queue_size: 3} ``` +每一项可以加上 `interface`,作为这路 CDC 控制接口和数据接口的名字。几路 CDC 的 VID:PID 相同,主机按接口名 +区分它们;没有 `interface` 的一路使用 LibXR 的默认名 `XRUSB CDC Control` 和 `XRUSB CDC Data`: + +Each item can add `interface`, the name of the control and data interfaces of that CDC. The CDCs +share VID:PID, and the host tells them apart by the interface name; a CDC without `interface` +keeps the LibXR default names `XRUSB CDC Control` and `XRUSB CDC Data`: + +```yaml +USB: + usb_otg_hs: + enable: true + cdc: + - {tx_fifo_size: 128, rx_fifo_size: 128, queue_size: 3, interface: Console} + - {tx_fifo_size: 128, rx_fifo_size: 128, queue_size: 3, interface: Telemetry} +``` + `database.enable` 为 true 时,`app_main()` 生成 `STM32Flash flash(FLASH_REGIONS, FLASH_REGION_NUMBER)` 和 `DatabaseRaw database(flash)`,数据库以 `database` 注册;`FLASH_REGIONS` 来自 `User/flash_map.hpp`,数据库使用 Flash 末尾的两个扇区。模板参数 `N` 是 Flash 的最小写入单元,单位为 diff --git a/src/libxr/generator_code_stm32.py b/src/libxr/generator_code_stm32.py index bfab385..c4a1746 100644 --- a/src/libxr/generator_code_stm32.py +++ b/src/libxr/generator_code_stm32.py @@ -1116,7 +1116,8 @@ def _usb_settings(instance: str) -> tuple[str, dict] | None: enable 的默认值由 _default_usb_enables() 先行写入,仍然缺少时为 false;未启用的实例不再补其他 设置。已启用的实例按固定顺序补上缺少的设置:包大小、缓冲区和 FIFO 大小、dma_section、cdc 列表 - (每路 CDC 一项,含 tx_fifo_size、rx_fifo_size 和 queue_size,默认一路),以及描述符(默认 + (每路 CDC 一项,含 tx_fifo_size、rx_fifo_size、queue_size 和可选的 interface,默认一路), + 以及描述符(默认 1d50:6199 / 0x0100 / "XRUSB-DEMO-",1d50:6199 的分配记录见 https://github.com/openmoko/openmoko-usb-oui/commit/27f3846d77e0d0d10271b809b831f70040c6197a)。 旧版的 cdc_tx_fifo_size、cdc_rx_fifo_size 和 cdc_queue_size 转换为 cdc 的一项,并在原来的位置 @@ -1125,7 +1126,8 @@ def _usb_settings(instance: str) -> tuple[str, dict] | None: _default_usb_enables() writes the default enable first; when it is still missing it is false, and a disabled instance gets no other setting. An enabled instance gets its missing settings in a fixed order: packet size, buffer and FIFO sizes, dma_section, the cdc list (one - item per CDC with tx_fifo_size, rx_fifo_size and queue_size, one CDC by default), and the + item per CDC with tx_fifo_size, rx_fifo_size, queue_size and an optional interface, one CDC + by default), and the descriptor (default 1d50:6199 / 0x0100 / "XRUSB-DEMO-"; see the link above for the allocation of 1d50:6199). The earlier cdc_tx_fifo_size, cdc_rx_fifo_size and cdc_queue_size become one item of cdc, written back where they were. An ep0_packet_size @@ -1272,6 +1274,9 @@ def _check_cdc(prefix: str, cfg: dict, is_otg: bool) -> None: Check the cdc list and give each item its defaults: a non-empty list whose items are mappings with positive integer values. + 可选的 interface 是字符串,不补默认值。 + The optional interface is a string and gets no default. + CDC 的路数不得超过端点号 EP1 到 EP15 能容纳的路数;OTG 设备的接收 FIFO 为 EP0 和每路 CDC 的 OUT 端点各留 64 字节。 The number of CDCs may not exceed what the endpoint numbers EP1 to EP15 hold; the receive @@ -1294,6 +1299,8 @@ def _check_cdc(prefix: str, cfg: dict, is_otg: bool) -> None: raise _invalid_setting(f"{key}[{index}]", item, "a mapping", "映射") for name, default in _CDC_DEFAULTS.items(): _integer(f"{key}[{index}].{name}", item.setdefault(name, default)) + if "interface" in item: + item["interface"] = _text(f"{key}[{index}].interface", item["interface"]) if len(cdc) > _MAX_CDC: raise LibXRConfigError( tr( @@ -1500,13 +1507,16 @@ def _usb_buffer_plan(is_otg: bool, cdc_count: int, ep0: int, tx: int, rx: int) - def _cdc_items(cfg: dict) -> list[dict]: - """USB 设备的 CDC 串口设置列表:每个 CDC 一个映射,含 tx_fifo_size、rx_fifo_size 和 - queue_size,均为整数。 - The list of CDC serial port settings of a USB device: one mapping per CDC with - tx_fifo_size, rx_fifo_size and queue_size, all integers. + """USB 设备的 CDC 串口设置列表:每个 CDC 一个映射,含整数 tx_fifo_size、rx_fifo_size 和 + queue_size,以及接口名 interface(未设置时为空字符串)。 + The list of CDC serial port settings of a USB device: one mapping per CDC with the integers + tx_fifo_size, rx_fifo_size and queue_size and the interface name interface (an empty string + when it is not set). """ return [ - {name: _integer(f"cdc.{name}", item[name]) for name in _CDC_DEFAULTS} for item in cfg["cdc"] + {name: _integer(f"cdc.{name}", item[name]) for name in _CDC_DEFAULTS} + | {"interface": _text("cdc.interface", item.get("interface"))} + for item in cfg["cdc"] ] @@ -2177,6 +2187,11 @@ def endpoint(number: int) -> str: cdc_names.append(cdc_name) data_in = 2 * index + 1 data_out = index + 1 if is_otg else data_in + # 设置了 interface 时它同时作为控制接口和数据接口的名字;主机按它区分 VID:PID 相同的 + # 各路 CDC。 + # A set interface names both the control and the data interface; the host tells the + # CDCs that share VID:PID apart by it. + interface = cdc["interface"].replace('"', '\\"') lines += layout( f"static USB::CDCUart {cdc_name}", [ @@ -2186,7 +2201,8 @@ def endpoint(number: int) -> str: str(cdc["rx_fifo_size"]), str(cdc["tx_fifo_size"]), str(cdc["queue_size"]), - ], + ] + + ([f'"{interface}"'] * 2 if interface else []), ) def buffer(suffix: str) -> "str | Braces": diff --git a/tests/test_generator_code_stm32.py b/tests/test_generator_code_stm32.py index 13e3c87..c2c04e0 100644 --- a/tests/test_generator_code_stm32.py +++ b/tests/test_generator_code_stm32.py @@ -476,6 +476,42 @@ def test_each_cdc_takes_its_own_queue_sizes(self): self.assertIn("USB::Endpoint::EPNumber::EP2, 64, 256, 2);", code) self.assertIn("USB::Endpoint::EPNumber::EP4, 128, 128, 7);", code) + def test_a_cdc_interface_names_its_control_and_data_interfaces(self): + # interface 接在队列容量之后,作为 CDCUart 的控制接口名和数据接口名;未设置的 CDC + # 使用 LibXR 的默认名,不写 interface 键。 + # interface follows the queue sizes as the control and data interface names of + # CDCUart; a CDC without it keeps the LibXR default names, and no interface key is + # written for it. + project = self.usb_otg_hs(cdc=[{"interface": 'Port "A"'}, {}]) + code = self.generate(project) + self.assertIn( + " static USB::CDCUart usb_otg_hs_cdc(\n" + " USB::Endpoint::EPNumber::EP1, USB::Endpoint::EPNumber::EP1,\n" + ' USB::Endpoint::EPNumber::EP2, 128, 128, 3, "Port \\"A\\"", "Port \\"A\\"");\n' + " static USB::CDCUart usb_otg_hs_cdc2(USB::Endpoint::EPNumber::EP3,\n" + " USB::Endpoint::EPNumber::EP2,\n" + " USB::Endpoint::EPNumber::EP4, 128, 128, 3);\n", + code, + ) + self.assertEqual( + generator.libxr_settings["USB"]["usb_otg_hs"]["cdc"], + [ + { + "interface": 'Port "A"', + "tx_fifo_size": 128, + "rx_fifo_size": 128, + "queue_size": 3, + }, + {"tx_fifo_size": 128, "rx_fifo_size": 128, "queue_size": 3}, + ], + ) + + def test_a_cdc_interface_must_be_a_string(self): + project = self.usb_otg_hs(cdc=[{"interface": 7}]) + with self.assertRaises(LibXRConfigError) as raised: + self.generate(project) + self.assertIn("USB.usb_otg_hs.cdc[0].interface", str(raised.exception)) + def test_on_fsdev_each_cdc_has_a_data_endpoint_and_a_notification_endpoint( self, ):