Skip to content
50 changes: 50 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub const MemoryRegion = internals.MemoryRegion;

const regz = @import("tools/regz");

pub const tusb = @import("modules/tinyusb");

// If more ports are available, the error "error: evaluation exceeded 1000 backwards branches" may occur.
// In such cases, consider increasing the argument value for @setEvalBranchQuota().
const port_list: []const struct {
Expand Down Expand Up @@ -860,6 +862,54 @@ pub fn MicroBuild(port_select: PortSelect) type {
.{target.cpu.model.name},
);
}
/// Adds build step for tinyUSB and adds a c translate module to app "tinyusb_api" which can be imported
/// input includes and c sources need to include tusb_descriptors.c (or equivalent)
/// and tusb_config.h at a minimum (see) tinyusb documentation for details
/// The api can be imported into a project: `const tinyusb = @import("tinyusb_api");`
/// it's contents is dependant on the configuration in tusb_config.h
pub fn addTinyUsbLib(
root_build: *Build,
fw: *Firmware,
c_sources: []const LazyPath,
includes: []const LazyPath,
debug_printing: bool,
) void {
const tusb_dep = fw.mb.dep.builder.dependency("modules/tinyusb", .{});
const tusb_mod = tusb_dep.module("tinyusb");
const target = root_build.resolveTargetQuery(fw.target.zig_target);

if (debug_printing)
tusb.enableDebug(tusb_mod);

tusb_mod.resolved_target = target;
for (c_sources) |source| {
tusb_mod.addCSourceFile(.{ .file = source });
}
for (includes) |path| {
tusb_mod.addIncludePath(path);
}
tusb.addChip(tusb_mod.owner, tusb_mod, fw.target.chip.name);

const tinyusb = root_build.addLibrary(.{
.linkage = .static,
.name = "tinyusb",
.root_module = tusb_mod,
.use_llvm = true,
});
const lib_step = root_build.addInstallArtifact(tinyusb, .{});
root_build.getInstallStep().dependOn(&lib_step.step);
fw.add_object_file(lib_step.emitted_bin.?);

const tusb_api_c = tusb.getTinyUsbCTranslate(
tusb_mod.owner,
target,
);

for (includes) |path| {
tusb_api_c.addIncludePath(path);
}
fw.app_mod.addImport("tinyusb", tusb_api_c.addModule("tinyusb_api"));
}
};
}

Expand Down
1 change: 1 addition & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
.@"modules/foundation-libc" = .{ .path = "modules/foundation-libc" },
.@"modules/riscv32-common" = .{ .path = "modules/riscv32-common" },
.@"modules/rtt" = .{ .path = "modules/rtt" },
.@"modules/tinyusb" = .{ .path = "modules/tinyusb" },

// simulators
.@"sim/aviron" = .{ .path = "sim/aviron", .lazy = true },
Expand Down
177 changes: 177 additions & 0 deletions modules/tinyusb/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
const std = @import("std");
const Build = std.Build;

// TODO: Switch to lazy dependencies?

pub fn build(b: *Build) !void {
addTinyUSBLib(b);
}
const tusb_h_contents =
\\#include_next "tusb.h"
\\#define TU_ATTR_FAST_FUNC __attribute__((section(".ram_text")))
;

pub fn addTinyUSBLib(b: *Build) void {
const module = b.addModule("tinyusb", .{
.link_libc = false,
// seems important to release fast to avoid some extra dependencies
.optimize = .ReleaseFast,
});
const wf_step = b.addWriteFile("tusb.h", tusb_h_contents);
module.addIncludePath(wf_step.getDirectory());

// TinyUSB src folder
const tusb_dep = b.dependency("tusb", .{});
const tusb_src = tusb_dep.path("src");
module.addIncludePath(tusb_src);
for (tinyusb_source_files) |file| {
module.addCSourceFile(.{ .file = tusb_src.path(b, file) });
}

// Add libc for embedded
const newlib_dep = b.dependency("newlib", .{});
module.addIncludePath(newlib_dep.path("newlib/libc/include"));
}

const SupportedChips = enum {
RP2040,
};

/// Add chip specific things to built library
// / TODO: Can this be a build option rather than a function call?
pub fn addChip(b: *Build, module: *Build.Module, chip_name: []const u8) void {
const option = std.meta.stringToEnum(
SupportedChips,
chip_name,
) orelse {
std.debug.print("BUILD ERROR: Chip not supported: {s}\n\r", .{chip_name});
@panic("Chip not supported for TinyUSB yet");
};
switch (option) {
.RP2040 => {
module.addCMacro("CFG_TUSB_MCU", "OPT_MCU_RP2040");
module.addCMacro("CFG_TUSB_OS", "OPT_OS_NONE");
module.addCMacro("PICO_RP2040", "1");
module.addCMacro("BOARD_TUD_RHPORT", "0");
module.addCMacro("PICO_RP2040_USB_FAST_IRQ", "1");
module.addCMacro("__not_in_flash(group)", "__attribute__((section(\".ram_text\")))");

// add empty files for pico headers to be happy
// TODO: empty seems to work but maybe we'd prefer to add something.
// TODO: is the step order implicit when doing addIncludePath due to lazy directories
const wf_step = b.addWriteFiles();
_ = wf_step.add("pico/config_autogen.h", "");
_ = wf_step.add("pico/version.h", "");
module.addIncludePath(wf_step.getDirectory());
// pico-sdk headers
addPicoSdkInclude(b, module);
},
}
}

/// Enables debug logging in the generated tusb library
/// TODO: Can this be controlled through build options somehow rather than a function like this?
pub fn enableDebug(module: *Build.Module) void {
// Add printf implementation
const prntf_dep = module.owner.lazyDependency("printf", .{});
if (prntf_dep) |dep| {
module.addIncludePath(dep.path(""));
module.addCSourceFile(.{ .file = dep.path("printf.c") });
module.addCMacro("CFG_TUSB_DEBUG_PRINTF", "printf_");
module.addCMacro("snprintf", "snprintf_");
module.addCMacro("CFG_TUSB_DEBUG", "3");
}
}

pub fn getTinyUsbCTranslate(
b: *Build,
target: Build.ResolvedTarget,
) *Build.Step.TranslateC {
// const target = b.standardTargetOptions(.{});
const tusb_dep = b.dependency("tusb", .{});
const tusb_src = tusb_dep.path("src");
const tinyusb_c = b.addTranslateC(.{
.root_source_file = tusb_src.path(b, "tusb.h"),
.target = target,
.optimize = .ReleaseFast,
.link_libc = false,
});

tinyusb_c.defineCMacro("CFG_TUSB_MCU", "OPT_MCU_NONE");
tinyusb_c.defineCMacro("CFG_TUSB_OS", "OPT_OS_NONE");

// TinyUSB src folder
tinyusb_c.addIncludePath(tusb_src);

// Add libc for embedded
const newlib_dep = b.dependency("newlib", .{});
tinyusb_c.addIncludePath(newlib_dep.path("newlib/libc/include"));

return tinyusb_c;
}

/// List of all .c files in the tinyUSB src directory less the portable directory files
/// tinyusb does a good job only compiling based on options selected.
const tinyusb_source_files = [_][]const u8{
"tusb.c",
"device/usbd.c",
"device/usbd_control.c",
"class/msc/msc_host.c",
"class/msc/msc_device.c",
"class/cdc/cdc_host.c",
"class/cdc/cdc_rndis_host.c",
"class/cdc/cdc_device.c",
"class/dfu/dfu_rt_device.c",
"class/dfu/dfu_device.c",
"class/video/video_device.c",
"class/usbtmc/usbtmc_device.c",
"class/vendor/vendor_host.c",
"class/vendor/vendor_device.c",
"class/net/ecm_rndis_device.c",
"class/net/ncm_device.c",
// "class/mtp/mtp_device.c", not available in older tinyusb compatible with sdk?
"class/audio/audio_device.c",
"class/bth/bth_device.c",
"class/midi/midi_device.c",
// "class/midi/midi_host.c", not available in older tinyusb compatible with sdk?
"class/hid/hid_device.c",
"class/hid/hid_host.c",
"host/usbh.c",
"host/hub.c",
"common/tusb_fifo.c",
"typec/usbc.c",
"portable/raspberrypi/rp2040/rp2040_usb.c",
"portable/raspberrypi/rp2040/dcd_rp2040.c",
};

fn addPicoSdkInclude(b: *std.Build, m: *std.Build.Module) void {
const pico_sdk_dep = b.lazyDependency("pico_sdk", .{});
if (pico_sdk_dep) |pico_dep| {
for (rp_2040_includes) |dir| {
m.addIncludePath(pico_dep.path(dir));
}
}
}

const rp_2040_includes = [_][]const u8{
"src",
"src/common/pico_base_headers/include",
"src/common/pico_time/include",
"src/rp2_common/pico_platform_compiler/include",
"src/rp2_common/pico_platform_sections/include",
"src/rp2_common/pico_platform_panic/include",
"src/rp2_common/pico_platform_common/include",
"src/rp2_common/hardware_timer/include",
"src/rp2_common/hardware_base/include",
"src/common/pico_sync/include",
"src/rp2_common/hardware_sync/include",
"src/rp2_common/hardware_sync_spin_lock/include",
"src/rp2_common/hardware_irq/include",
"src/rp2_common/hardware_resets/include",
// I think these are the only things that would need to change for RP2350
"src/rp2040/pico_platform/include",
"src/rp2040/hardware_structs/include",
"src/rp2040/hardware_regs/include",
"src/rp2040/hardware_regs/include",
"src/rp2040/hardware_structs/include",
};
40 changes: 40 additions & 0 deletions modules/tinyusb/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.{
.name = .tinyusb,
.version = "0.1.0",
.fingerprint = 0x6f11d314f5263da7, // Changing this has security and trust implications?
.minimum_zig_version = "0.15.1",
.dependencies = .{
.tusb = .{
// matches pico-sdk 2.2.0 submodule hash
.url = "git+https://github.com/hathach/tinyusb.git#86ad6e56c1700e85f1c5678607a762cfe3aa2f47",
.hash = "N-V-__8AAGUZ_gDYqxEdexw7Rfp9YENlWbwlDPQu0bCfKxws",
// Latest release
// .url = "https://github.com/hathach/tinyusb/archive/refs/tags/0.19.0.tar.gz",
// .hash = "N-V-__8AAJebBAH6O7lcIkrzHzcWi6qE9wZoM3BhsLLCfuZX",
},
.newlib = .{
.url = "https://sourceware.org/pub/newlib/newlib-4.5.0.20241231.tar.gz",
.sha256 = "sha256:8f6903f8ceb0f991490a1e34074443c2a72b14dbd",
.hash = "N-V-__8AAJH7PAM8M1Wj7e7momRRAp7hssKtI2VvX0vEz-_B",
},
.printf = .{
.url = "https://github.com/mpaland/printf/archive/refs/tags/v4.0.0.zip",
.hash = "N-V-__8AALJuCQCrdHP5WGfuWR-16g1fK2VF3WA2C5p5e-jj",
.lazy = true,
},
.pico_sdk = .{
.url = "https://github.com/raspberrypi/pico-sdk/releases/download/2.2.0/pico-sdk-2.2.0.tar.gz",
.sha256 = "sha256:2678fe2b176cf64a7f71cd91749fdf9134c8cf7ff84b7199dfe5ea0d6dba6fa4",
.hash = "N-V-__8AAJZDcwFLNdIEF1wutjiH14f8fSET5jNORZIZKCaM",
.lazy = true,
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
"README.md",
},
}
11 changes: 11 additions & 0 deletions modules/tinyusb/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Issues
- Only supports RP2040
- Sometimes reset stalls when built. If effected comment out:
`unreset_block_wait(RESETS_RESET_USBCTRL_BITS);`
in /.../rp2040_usb.c:rp2040_usb_init as a workaround
# TODO
- How can we move the IRQ functions into ram
- USB Reset hang issue
- More chips supported
- Probably refactor to better align with project.
- Remove system includes dependency for arm-none-eabi
2 changes: 2 additions & 0 deletions port/raspberrypi/rp2xxx/src/hal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub const uart = @import("hal/uart.zig");
pub const usb = @import("hal/usb.zig");
pub const watchdog = @import("hal/watchdog.zig");

pub const tinyusb_exports = @import("hal/tusb/tusb_exports.zig");

comptime {
// HACK: tests can't access microzig. maybe there's a better way to do this.
if (!builtin.is_test and compatibility.chip == .RP2350) {
Expand Down
82 changes: 82 additions & 0 deletions port/raspberrypi/rp2xxx/src/hal/tusb/tusb_exports.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2xxx = microzig.hal;
const time = rp2xxx.time;
const peripherals = microzig.chip.peripherals;

var charbuf: [128]u8 = undefined;
var char_index: usize = 0;
const tusb_logger = std.log.scoped(.tusb);

export fn _putchar(char: u8) void {
if (char_index < charbuf.len and char != '\n') {
if (char == '\r') {
return;
}
charbuf[char_index] = char;
char_index += 1;
} else {
tusb_logger.debug("{s}", .{charbuf[0..char_index]});
char_index = 0;
}
}

// TODO: not sure why this is needed or if it's really right but seems to work.
export const _ctype_: u8 = undefined;

export fn panic(format: [*c]const u8, ...) void {
const z_str: []const u8 = std.mem.span(format);
std.log.info("{s}", .{std.mem.trim(u8, z_str, "\n\n")});
}

export fn strlen(str: [*c]const u8) c_uint {
return std.mem.len(str);
}
export fn tusb_time_millis_api() c_uint {
return @truncate(time.get_time_since_boot().to_us() * 1000);
}

export fn rp2040_chip_version() c_uint {
const chip_id = peripherals.SYSINFO.CHIP_ID.read();
// TODO: should be able to switch to clear API?
return @intCast(chip_id.REVISION);
}

pub fn irq_handler() linksection(".ram_text") callconv(.c) void {
if (handler_p) |h|
h();
}

//void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_priority);
const hdlr = *const fn () callconv(.c) void;

var handler_p: ?hdlr = null;

export fn irq_add_shared_handler(num: c_uint, handler: hdlr, order_priority: u8) callconv(.c) void {
_ = num;
handler_p = handler;
_ = order_priority;
}

// void irq_remove_handler(uint num, irq_handler_t handler);
export fn irq_remove_handler(num: c_uint, handler: hdlr) void {
_ = num;
_ = handler;
handler_p = null;
}

//void irq_set_enabled(uint num, bool enabled);
export fn irq_set_enabled(num: c_uint, enabled: bool) void {
_ = num; // TODO: Num should represent .usbctrl_irq
if (enabled) {
microzig.interrupt.enable(.USBCTRL_IRQ);
} else {
microzig.interrupt.disable(.USBCTRL_IRQ);
}
}

// void __weak hard_assertion_failure(void) {
export fn hard_assertion_failure() void {
std.log.err("Probably hw_endpoint_alloc failed", .{});
@panic("TUSB: Hard assertion Failed!");
}