Skip to content

Commit c507188

Browse files
committed
[ext] Refactor TinyUSB module structure and config
- Allow running device and host mode on separate ports at the same time. - Allow speed selection of HS port. - Correctly access all host and device class files.
1 parent 26d1edc commit c507188

File tree

8 files changed

+265
-114
lines changed

8 files changed

+265
-114
lines changed

examples/stm32f3_discovery/usb_dfu/project.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<extends>modm:disco-f303vc</extends>
33
<options>
44
<option name="modm:build:build.path">../../../build/stm32f3_discovery/usb_dfu</option>
5-
<option name="modm:tinyusb:config">device.dfu</option>
5+
<option name="modm:tinyusb:config">device.dfu_rt</option>
66
</options>
77
<modules>
88
<module>modm:build:scons</module>

ext/hathach/module.lb

Lines changed: 119 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
33
#
4-
# Copyright (c) 2020, Niklas Hauser
4+
# Copyright (c) 2020, 2024, Niklas Hauser
55
#
66
# This file is part of the modm project.
77
#
@@ -13,6 +13,7 @@
1313
from pathlib import Path
1414
from collections import defaultdict
1515
tusb_config = {}
16+
default_port = 0
1617

1718
# -----------------------------------------------------------------------------
1819
def init(module):
@@ -21,14 +22,16 @@ def init(module):
2122

2223
def prepare(module, options):
2324
device = options[":target"]
25+
has_otg_hs = device.has_driver("usb_otg_hs")
26+
has_otg_fs = device.has_driver("usb_otg_fs")
2427
if not (device.has_driver("usb") or
25-
device.has_driver("usb_otg_fs") or
26-
device.has_driver("usb_otg_hs") or
28+
has_otg_fs or
29+
has_otg_hs or
2730
device.has_driver("udp") or
2831
device.has_driver("uhp")):
2932
return False
3033

31-
configs = {"device.cdc", "device.msc", "device.vendor", "device.midi", "device.dfu"}
34+
configs = ["device.cdc", "device.dfu_rt", "device.midi", "device.msc", "device.vendor"]
3235
# TODO: Allow all device classes
3336
# configs = {"device.{}".format(p.parent.name)
3437
# for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")}
@@ -39,30 +42,42 @@ def prepare(module, options):
3942
module.add_list_option(
4043
EnumerationOption(name="config",
4144
description="Endpoint Configuration",
42-
enumeration=sorted(configs),
45+
enumeration=configs,
4346
dependencies=lambda vs:
44-
[":tinyusb:{}".format(v.replace(".", ":")) for v in vs]))
47+
[":tinyusb:" + v.replace(".", ":") for v in vs]))
48+
4549

4650
# Most devices only have FS=usb, some STM32 have FS/HS=usb_otg_fs/usb_otg_hs
4751
# and some STM32 only have HS=usb_otg_hs, so we only define this option if
4852
# there is a choice and default this to FS by default.
49-
if device.has_driver("usb_otg_hs") and device.has_driver("usb_otg_fs"):
53+
if has_otg_hs:
5054
module.add_option(
51-
EnumerationOption(name="speed",
52-
description="USB Port Speed",
53-
enumeration={"full": "fs", "high": "hs"},
54-
default="full",
55-
dependencies=lambda s: ":platform:usb:{}s".format(s[0])))
56-
57-
module.add_submodule(TinyUsbDeviceModule())
58-
module.add_submodule(TinyUsbHostModule())
55+
EnumerationOption(name="max-speed", description="Maximum HS port speed",
56+
enumeration=["full", "high"], default="high"))
57+
# DEPRECATED: 2025q1
58+
module.add_alias(
59+
Alias(name="speed", description="Renamed for more clarity",
60+
destination=":tinyusb:max-speed"))
61+
# Detect the number of ports and the default port
62+
ports = 2 if (has_otg_hs and has_otg_fs) else 1
63+
if has_otg_hs and not has_otg_fs:
64+
global default_port
65+
default_port = 1
66+
# Generate the host and device modules
67+
module.add_submodule(TinyUsbDeviceModule(ports))
68+
module.add_submodule(TinyUsbHostModule(ports))
5969
module.depends(":cmsis:device", ":architecture:atomic", ":architecture:interrupt", ":platform:usb")
6070
return True
6171

6272

6373
def validate(env):
64-
if env.has_module(":tinyusb:device") and env.has_module(":tinyusb:host"):
65-
raise ValidateException("TinyUSB won't let you use both Device *and* Host at the same time!")
74+
has_device = env.has_module(":tinyusb:device")
75+
has_host = env.has_module(":tinyusb:host")
76+
if has_device and has_host:
77+
device_port = env.get(":tinyusb:device:port")
78+
host_port = env.get(":tinyusb:host:port")
79+
if device_port == host_port and host_port is not None:
80+
raise ValidateException("You cannot place Device and Host on the same USB port!")
6681

6782

6883
def build(env):
@@ -90,7 +105,7 @@ def build(env):
90105

91106
target = env[":target"].identifier
92107
if target.platform == "stm32":
93-
tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_STM32{}".format(target.family.upper())
108+
tusb_config["CFG_TUSB_MCU"] = f"OPT_MCU_STM32{target.family.upper()}"
94109
# TODO: use modm-devices driver type for this
95110
fs_dev = (target.family in ["f0", "f3", "l0", "g4"] or
96111
(target.family == "f1" and target.name <= "03"))
@@ -106,7 +121,7 @@ def build(env):
106121
env.copy("tinyusb/src/portable/microchip/samg/", "portable/microchip/samg/")
107122
else:
108123
series = "e5x" if target.series.startswith("e5") else target.series
109-
tusb_config["CFG_TUSB_MCU"] = "OPT_MCU_SAM{}".format(series.upper())
124+
tusb_config["CFG_TUSB_MCU"] = f"OPT_MCU_SAM{series.upper()}"
110125
env.copy("tinyusb/src/portable/microchip/samd/", "portable/microchip/samd/")
111126

112127
elif target.platform == "rp":
@@ -130,21 +145,16 @@ def build(env):
130145
tusb_config["CFG_TUSB_DEBUG_PRINTF"] = "tinyusb_debug_printf"
131146
# env.collect(":build:cppdefines.debug", "CFG_TUSB_DEBUG=2")
132147

133-
has_device = env.has_module(":tinyusb:device")
134-
has_host = env.has_module(":tinyusb:host")
135-
# On STM32 with both speeds, the option decides, otherwise we must default
136-
# to HS for the few STM32 with *only* usb_otg_hs, all other devices are FS.
137-
speed = env.get("speed", "hs" if env[":target"].has_driver("usb_otg_hs") else "fs")
138-
port = 0 if speed == "fs" else 1
139-
140-
tusb_config["CFG_TUSB_RHPORT0_MODE"] = "OPT_MODE_NONE"
141-
tusb_config["CFG_TUSB_RHPORT1_MODE"] = "OPT_MODE_NONE"
142-
mode = None
143-
if has_device: mode = "OPT_MODE_DEVICE";
144-
if has_host: mode = "OPT_MODE_HOST";
145-
if mode is not None:
146-
if "hs" in speed: mode = "({} | OPT_MODE_HIGH_SPEED)".format(mode);
147-
tusb_config["CFG_TUSB_RHPORT{}_MODE".format(port)] = mode
148+
ports = {}
149+
global default_port
150+
for mode in ["device", "host"]:
151+
if env.has_module(f":tinyusb:{mode}"):
152+
port = env.get(f":tinyusb:{mode}:port", default_port)
153+
ports[port] = mode[0]
154+
mode = f"OPT_MODE_{mode.upper()}"
155+
if port == 1 and (speed := env.get("max-speed")) is not None:
156+
mode = f"({mode} | OPT_MODE_{speed.upper()}_SPEED)"
157+
tusb_config[f"CFG_TUSB_RHPORT{port}_MODE"] = mode
148158

149159
itf_config = env["config"]
150160
# Enumerate the configurations
@@ -160,7 +170,7 @@ def build(env):
160170
endpoints = {}
161171
endpoint_counter = 0
162172
for devclass, devitfs in config_enum.items():
163-
prefix = "{}".format(devclass.upper())
173+
prefix = devclass.upper()
164174
for itf in devitfs:
165175
endpoint_counter += 1
166176
itf_prefix = prefix + str(itf)
@@ -169,10 +179,10 @@ def build(env):
169179
itf_enum.extend([itf_prefix, itf_prefix + "_DATA"])
170180
endpoints[itf_prefix + "_NOTIF"] = hex(0x80 | endpoint_counter)
171181
endpoint_counter += 1
172-
elif devclass in ["msc", "midi", "vendor", "dfu"]:
182+
elif devclass in ["msc", "midi", "vendor", "dfu_rt"]:
173183
itf_enum.append(itf_prefix)
174184
else:
175-
raise ValidateException("Unknown ITF device class '{}'!".format(devclass))
185+
raise ValidateException(f"Unknown ITF device class '{devclass}'!")
176186

177187
endpoints[itf_prefix + "_OUT"] = hex(endpoint_counter)
178188
# SAMG doesn't support using the same EP number for IN and OUT
@@ -181,22 +191,26 @@ def build(env):
181191
endpoints[itf_prefix + "_IN"] = hex(0x80 | endpoint_counter)
182192

183193
if target.platform == "stm32":
184-
irq_data = env.query(":platform:usb:irqs")
185-
irqs = irq_data["port_irqs"][speed]
194+
irqs = []
195+
for port, uirqs in env.query(":platform:usb:irqs")["port_irqs"].items():
196+
port = {"fs": 0, "hs": 1}[port]
197+
if port in ports:
198+
irqs.extend([(irq, port, ports[port]) for irq in uirqs])
186199
elif target.platform == "sam" and target.family in ["g5x"]:
187-
irqs = ["UDP"]
200+
irqs = [("UDP", 0, "d")]
188201
elif target.platform == "sam" and target.family in ["d5x/e5x"]:
189-
irqs = ["USB_OTHER", "USB_SOF_HSOF", "USB_TRCPT0", "USB_TRCPT1"]
202+
irqs = [("USB_OTHER", 0, "d"), ("USB_SOF_HSOF", 0, "d"),
203+
("USB_TRCPT0", 0, "d"), ("USB_TRCPT1", 0, "d")]
190204
elif target.platform == "rp" and target.family in ["20"]:
191-
irqs = ["USBCTRL_IRQ"]
205+
irqs = [("USBCTRL_IRQ", 0, "d")]
192206
else:
193-
irqs = ["USB"]
207+
irqs = [("USB", 0, "d")]
194208

195209
env.substitutions = {
196210
"target": target,
197211
"config": tusb_config,
198212
"irqs": irqs,
199-
"port": port,
213+
"ports": ports,
200214
"with_debug": env.has_module(":debug"),
201215
"with_cdc": env.has_module(":tinyusb:device:cdc"),
202216
"itfs": itfs,
@@ -207,9 +221,24 @@ def build(env):
207221
if len(itf_config): env.template("tusb_descriptors.c.in");
208222
env.template("tusb_port.cpp.in")
209223

224+
# Add support files shared between device and host classes
225+
classes = env.generated_local_files(filterfunc=lambda path: "_device.c" in path or "_host.c" in path)
226+
classes = {(Path(c).parent.name, Path(c).name.rsplit("_", 1)[0]) for c in classes}
227+
# Add support files outside of naming scheme
228+
if any(p == "net" for p, _ in classes): classes.update({("net", "ncm"), ("net", "net_device")})
229+
if ("dfu", "dfu_rt") in classes: classes.add(("dfu", "dfu"))
230+
# Filter out classes without support files
231+
classes = ((p, n) for p, n in classes if n not in ["bth", "dfu_rt", "vendor", "ecm_rndis", "ecm"])
232+
# Now copy the shared support file
233+
for parent, name in classes:
234+
env.copy(f"tinyusb/src/class/{parent}/{name}.h", f"class/{parent}/{name}.h")
235+
210236

211237
# -----------------------------------------------------------------------------
212238
class TinyUsbDeviceModule(Module):
239+
def __init__(self, ports):
240+
self.ports = ports
241+
213242
def init(self, module):
214243
module.name = ":tinyusb:device"
215244
module.description = """
@@ -223,20 +252,31 @@ Configuration options:
223252
"""
224253

225254
def prepare(self, module, options):
226-
paths = {p.parent for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.h")}
227-
for path in paths:
228-
module.add_submodule(TinyUsbClassModule(path, "device"))
255+
classes = {(p.parent.name, p.name.replace("_device.c", ""))
256+
for p in Path(localpath("tinyusb/src/class/")).glob("*/*_device.c")}
257+
for parent, name in classes:
258+
module.add_submodule(TinyUsbClassModule(parent, name, "device"))
259+
if self.ports == 2:
260+
module.add_option(
261+
EnumerationOption(name="port", description="USB Port selection",
262+
enumeration={"fs": 0, "hs": 1}, default="fs",
263+
dependencies=lambda s: f":platform:usb:{s[0]}s"))
229264
return True
230265

266+
# def validate(self, env):
267+
# if env.has_module(":tinyusb:device:ncm") and env.has_module(":tinyusb:device:ecm_rndis"):
268+
# raise ValidateException("TinyUSB cannot enable both ECM_RNDIS and NCM network drivers!")
269+
231270
def build(self, env):
232271
env.outbasepath = "modm/ext/tinyusb"
233272
env.copy("tinyusb/src/device/", "device/")
234273

235274

236275
# -----------------------------------------------------------------------------
237276
class TinyUsbHostModule(Module):
238-
def __init__(self):
277+
def __init__(self, ports):
239278
self.config = {}
279+
self.ports = ports
240280

241281
def init(self, module):
242282
module.name = ":tinyusb:host"
@@ -257,9 +297,15 @@ Configuration options:
257297
options[":target"].has_driver("uhp:samg*")):
258298
return False
259299

260-
paths = {p.parent for p in Path(localpath("tinyusb/src/class/")).glob("*/*_host.h")}
261-
for path in paths:
262-
module.add_submodule(TinyUsbClassModule(path, "host"))
300+
classes = {(p.parent.name, p.name.replace("_host.c", ""))
301+
for p in Path(localpath("tinyusb/src/class/")).glob("*/*_host.c")}
302+
for parent, name in classes:
303+
module.add_submodule(TinyUsbClassModule(parent, name, "host"))
304+
if self.ports == 2:
305+
module.add_option(
306+
EnumerationOption(name="port", description="USB Port selection",
307+
enumeration={"fs": 0, "hs": 1}, default="hs",
308+
dependencies=lambda s: f":platform:usb:{s[0]}s"))
263309
return True
264310

265311
def build(self, env):
@@ -270,6 +316,9 @@ Configuration options:
270316
# -----------------------------------------------------------------------------
271317
# Only contains those configurations that this modules can generate a descriptor for
272318
tu_config_descr = {"device": {
319+
"bth": """
320+
- `CFG_TUD_BTH_ISO_ALT_COUNT` = [undef] modm default: 1
321+
""",
273322
"cdc": """
274323
- `CFG_TUD_CDC_EP_BUFSIZE` = 64/512 (fs/hs)
275324
- `CFG_TUD_CDC_RX_BUFSIZE` = [undef] modm default: 512
@@ -291,13 +340,14 @@ tu_config_descr = {"device": {
291340
}
292341
}
293342
class TinyUsbClassModule(Module):
294-
def __init__(self, path, mode):
343+
def __init__(self, parent, name, mode):
344+
self.parent = parent
345+
self.name = name
295346
self.mode = mode
296-
self.name = str(path.name)
297347

298348
def init(self, module):
299-
module.name = ":tinyusb:{}:{}".format(self.mode, self.name)
300-
descr = "# {} class {}".format(self.mode.capitalize(), self.name.upper())
349+
module.name = f":tinyusb:{self.mode}:{self.name}"
350+
descr = f"# {self.mode.capitalize()} class {self.name.upper()}"
301351
conf = tu_config_descr.get(self.mode, {}).get(self.name, "")
302352
if conf: descr += "\n\nConfiguration options:\n" + conf
303353
else: descr += "\n\nPlease consult the TinyUSB docs for configuration options.\n"
@@ -314,19 +364,25 @@ class TinyUsbClassModule(Module):
314364

315365
def build(self, env):
316366
env.outbasepath = "modm/ext/tinyusb"
317-
env.copy("tinyusb/src/class/{}".format(self.name), "class/{}".format(self.name),
318-
ignore=env.ignore_files("*_host.*" if "device" in self.mode else "*_device.*"))
367+
for suffix in ["h", "c"]:
368+
# both classes share the `net_device.h` file
369+
if self.name in ["ncm", "ecm_rndis"] and suffix == "h":
370+
continue
371+
file = f"class/{self.parent}/{self.name}_{self.mode}.{suffix}"
372+
env.copy(f"tinyusb/src/{file}", file)
319373

320374
global tusb_config
321-
cfg_name = "CFG_TU{}_{}".format(self.mode[0].upper(), self.name.upper())
322-
if "DFU" in cfg_name: cfg_name += "_RUNTIME";
323-
tusb_config[cfg_name] = env[":tinyusb:config"].count("{}.{}".format(self.mode, self.name))
324-
speed = env.get(":tinyusb:speed", 0)
375+
cfg_name = f"CFG_TU{self.mode[0].upper()}_{self.name.upper()}"
376+
if "DFU_RT" in cfg_name: cfg_name = cfg_name.replace("_RT", "_RUNTIME")
377+
tusb_config[cfg_name] = env[":tinyusb:config"].count(f"{self.mode}.{self.name}")
378+
is_hs = env.get(":tinyusb:max-speed", "full") == "high"
325379

326380
if self.mode == "device":
327381
# These are the defaults that don't crash TinyUSB.
328382
if self.name in ["cdc", "midi", "vendor"]:
329-
tusb_config[cfg_name+"_RX_BUFSIZE"] = 512 if speed else 64
330-
tusb_config[cfg_name+"_TX_BUFSIZE"] = 512 if speed else 64
383+
tusb_config[cfg_name+"_RX_BUFSIZE"] = 512 if is_hs else 64
384+
tusb_config[cfg_name+"_TX_BUFSIZE"] = 512 if is_hs else 64
331385
if self.name in ["msc"]:
332386
tusb_config[cfg_name+"_EP_BUFSIZE"] = 512
387+
if self.name in ["bth"]:
388+
tusb_config[cfg_name+"_ISO_ALT_COUNT"] = 1

0 commit comments

Comments
 (0)