From 3bb550c12e3392623b2a805624fe4a5430cce6ca Mon Sep 17 00:00:00 2001 From: Gericom Date: Sun, 23 Nov 2025 14:02:39 +0100 Subject: [PATCH] Initial commit --- .gitignore | 49 ++ .gitmodules | 6 + LICENSE.txt | 17 + README.md | 17 + examples/mass-storage/Makefile | 60 ++ examples/mass-storage/arm7/Makefile | 137 +++++ examples/mass-storage/arm7/dldi_ds_arm7.ld | 232 ++++++++ examples/mass-storage/arm7/dldi_ds_arm7.specs | 8 + examples/mass-storage/arm7/source/Arm7State.h | 8 + examples/mass-storage/arm7/source/ExitMode.h | 10 + examples/mass-storage/arm7/source/common.h | 5 + examples/mass-storage/arm7/source/dldi.s | 16 + .../source/ipcServices/DldiIpcService.cpp | 53 ++ .../arm7/source/ipcServices/DldiIpcService.h | 19 + examples/mass-storage/arm7/source/main.cpp | 388 ++++++++++++ .../mass-storage/arm7/source/tusb_config.h | 112 ++++ .../arm7/source/usb_descriptors.c | 198 +++++++ .../arm7/source/usb_descriptors.h | 7 + examples/mass-storage/arm9/Makefile | 152 +++++ examples/mass-storage/arm9/source/common.h | 2 + examples/mass-storage/arm9/source/dldiIpc.cpp | 164 ++++++ examples/mass-storage/arm9/source/dldiIpc.h | 14 + examples/mass-storage/arm9/source/dldi_stub.s | 100 ++++ examples/mass-storage/arm9/source/main.cpp | 55 ++ examples/mass-storage/common/dldiIpcCommand.h | 16 + .../mass-storage/common/ipc/IpcService.cpp | 11 + examples/mass-storage/common/ipc/IpcService.h | 19 + .../common/ipc/ThreadIpcService.cpp | 33 ++ .../common/ipc/ThreadIpcService.h | 26 + examples/mass-storage/common/ipcChannels.h | 7 + examples/usb-speaker/Makefile | 60 ++ examples/usb-speaker/arm7/Makefile | 137 +++++ examples/usb-speaker/arm7/dldi_ds_arm7.ld | 232 ++++++++ examples/usb-speaker/arm7/dldi_ds_arm7.specs | 8 + examples/usb-speaker/arm7/source/Arm7State.h | 8 + examples/usb-speaker/arm7/source/ExitMode.h | 10 + examples/usb-speaker/arm7/source/common.h | 5 + examples/usb-speaker/arm7/source/main.cpp | 550 ++++++++++++++++++ .../usb-speaker/arm7/source/tusb_config.h | 118 ++++ .../arm7/source/usb_descriptors.cpp | 171 ++++++ .../usb-speaker/arm7/source/usb_descriptors.h | 37 ++ examples/usb-speaker/arm9/Makefile | 152 +++++ examples/usb-speaker/arm9/source/main.cpp | 45 ++ libs/libtwl | 1 + libs/tinyusb | 1 + platform/DSPicoUsb.h | 29 + platform/DSPicoUsbInEndpoint.cpp | 100 ++++ platform/DSPicoUsbInEndpoint.h | 28 + platform/DSPicoUsbOutEndpoint.cpp | 141 +++++ platform/DSPicoUsbOutEndpoint.h | 26 + platform/UsbStringDescriptor.h | 25 + platform/dcd_dspico.cpp | 315 ++++++++++ platform/tusb_os_custom.h | 161 +++++ 53 files changed, 4301 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 examples/mass-storage/Makefile create mode 100644 examples/mass-storage/arm7/Makefile create mode 100644 examples/mass-storage/arm7/dldi_ds_arm7.ld create mode 100644 examples/mass-storage/arm7/dldi_ds_arm7.specs create mode 100644 examples/mass-storage/arm7/source/Arm7State.h create mode 100644 examples/mass-storage/arm7/source/ExitMode.h create mode 100644 examples/mass-storage/arm7/source/common.h create mode 100644 examples/mass-storage/arm7/source/dldi.s create mode 100644 examples/mass-storage/arm7/source/ipcServices/DldiIpcService.cpp create mode 100644 examples/mass-storage/arm7/source/ipcServices/DldiIpcService.h create mode 100644 examples/mass-storage/arm7/source/main.cpp create mode 100644 examples/mass-storage/arm7/source/tusb_config.h create mode 100644 examples/mass-storage/arm7/source/usb_descriptors.c create mode 100644 examples/mass-storage/arm7/source/usb_descriptors.h create mode 100644 examples/mass-storage/arm9/Makefile create mode 100644 examples/mass-storage/arm9/source/common.h create mode 100644 examples/mass-storage/arm9/source/dldiIpc.cpp create mode 100644 examples/mass-storage/arm9/source/dldiIpc.h create mode 100644 examples/mass-storage/arm9/source/dldi_stub.s create mode 100644 examples/mass-storage/arm9/source/main.cpp create mode 100644 examples/mass-storage/common/dldiIpcCommand.h create mode 100644 examples/mass-storage/common/ipc/IpcService.cpp create mode 100644 examples/mass-storage/common/ipc/IpcService.h create mode 100644 examples/mass-storage/common/ipc/ThreadIpcService.cpp create mode 100644 examples/mass-storage/common/ipc/ThreadIpcService.h create mode 100644 examples/mass-storage/common/ipcChannels.h create mode 100644 examples/usb-speaker/Makefile create mode 100644 examples/usb-speaker/arm7/Makefile create mode 100644 examples/usb-speaker/arm7/dldi_ds_arm7.ld create mode 100644 examples/usb-speaker/arm7/dldi_ds_arm7.specs create mode 100644 examples/usb-speaker/arm7/source/Arm7State.h create mode 100644 examples/usb-speaker/arm7/source/ExitMode.h create mode 100644 examples/usb-speaker/arm7/source/common.h create mode 100644 examples/usb-speaker/arm7/source/main.cpp create mode 100644 examples/usb-speaker/arm7/source/tusb_config.h create mode 100644 examples/usb-speaker/arm7/source/usb_descriptors.cpp create mode 100644 examples/usb-speaker/arm7/source/usb_descriptors.h create mode 100644 examples/usb-speaker/arm9/Makefile create mode 100644 examples/usb-speaker/arm9/source/main.cpp create mode 160000 libs/libtwl create mode 160000 libs/tinyusb create mode 100644 platform/DSPicoUsb.h create mode 100644 platform/DSPicoUsbInEndpoint.cpp create mode 100644 platform/DSPicoUsbInEndpoint.h create mode 100644 platform/DSPicoUsbOutEndpoint.cpp create mode 100644 platform/DSPicoUsbOutEndpoint.h create mode 100644 platform/UsbStringDescriptor.h create mode 100644 platform/dcd_dspico.cpp create mode 100644 platform/tusb_os_custom.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e12840e --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Ignore build directories # +############################ +Debug/ +Release/ +build/ +out/ + +# Compiled source # +################### +*.com +*.class +*.dll +*.d +*.map +*.o +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +*.nds +*.elf +*.a +.vscode/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1d7bd4a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libs/tinyusb"] + path = libs/tinyusb + url = https://github.com/hathach/tinyusb.git +[submodule "libs/libtwl"] + path = libs/libtwl + url = https://github.com/Gericom/libtwl diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..59dc7d2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,17 @@ +Copyright (c) 2025 LNH team + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d670a4 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# DSpico USB Examples +This repository contains examples of using the DSpico USB port from a DS application. + +The `examples` folder contains the following examples: +- usb-speaker - Makes the DS/DSi/3DS work as a USB speaker for your PC +- mass-storage - Allows access to the DSpico micro SD card over USB + +The `platform` folder contains the tiny usb platform code for the DSpico. + +> [!IMPORTANT] +> There is currently a bug in the DSpico Firmware that sometimes causes the DSpico the crash if you use USB, reset the console and use USB again. If this happens, unplug USB and reset the console such that the DSpico performs a power cycle. You can plug the USB back in afterwards. + +## Environment +The examples require a pre-calico devkitpro/libnds environment to build. With such an environment setup, just run `make` in the folder of the example to compile it. + +## License +The platform code and examples are licensed under the Zlib license. For details, see `LICENSE.txt`. diff --git a/examples/mass-storage/Makefile b/examples/mass-storage/Makefile new file mode 100644 index 0000000..efd8337 --- /dev/null +++ b/examples/mass-storage/Makefile @@ -0,0 +1,60 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +export TARGET := $(shell basename $(CURDIR)) +export TOPDIR := $(CURDIR) + +# specify a directory which contains the nitro filesystem +# this is relative to the Makefile +NITRO_FILES := + +# These set the information text in the nds file +#GAME_TITLE := My Wonderful Homebrew +#GAME_SUBTITLE1 := built with devkitARM +#GAME_SUBTITLE2 := http://devitpro.org + +include $(DEVKITARM)/ds_rules + +.PHONY: checklibtwl checkarm7 checkarm9 clean + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all: checklibtwl checkarm7 checkarm9 $(TARGET).nds + +#--------------------------------------------------------------------------------- +checklibtwl: + $(MAKE) -C ../../libs/libtwl/libtwl7 + $(MAKE) -C ../../libs/libtwl/libtwl9 + +#--------------------------------------------------------------------------------- +checkarm7: checklibtwl + $(MAKE) -C arm7 + +#--------------------------------------------------------------------------------- +checkarm9: checklibtwl + $(MAKE) -C arm9 + +#--------------------------------------------------------------------------------- +$(TARGET).nds : $(NITRO_FILES) arm7/$(TARGET).elf arm9/$(TARGET).elf + ndstool -c $(TARGET).nds -7 arm7/$(TARGET).elf -9 arm9/$(TARGET).elf \ + -b $(GAME_ICON) "$(GAME_TITLE);$(GAME_SUBTITLE1);$(GAME_SUBTITLE2)" \ + $(_ADDFILES) + +#--------------------------------------------------------------------------------- +arm7/$(TARGET).elf: + $(MAKE) -C arm7 + +#--------------------------------------------------------------------------------- +arm9/$(TARGET).elf: + $(MAKE) -C arm9 + +#--------------------------------------------------------------------------------- +clean: + $(MAKE) -C arm9 clean + $(MAKE) -C arm7 clean + rm -f $(TARGET).nds $(TARGET).arm7 $(TARGET).arm9 diff --git a/examples/mass-storage/arm7/Makefile b/examples/mass-storage/arm7/Makefile new file mode 100644 index 0000000..60da066 --- /dev/null +++ b/examples/mass-storage/arm7/Makefile @@ -0,0 +1,137 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +# DATA is a list of directories containing binary files +# all directories are relative to this makefile +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := source \ + ../common/ipc \ + source/ipcServices \ + ../../../platform \ + ../../../libs/tinyusb/src \ + ../../../libs/tinyusb/src/device \ + ../../../libs/tinyusb/src/class/audio \ + ../../../libs/tinyusb/src/class/cdc \ + ../../../libs/tinyusb/src/class/msc \ + ../../../libs/tinyusb/src/common +INCLUDES := source include build ../common ../../../libs/tinyusb/src ../../../platform +DATA := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -marm -mthumb-interwork -DLIBTWL_ARM7 -DARM7 + +CFLAGS := -g -Wall -O2\ + -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer\ + -ffunction-sections -fdata-sections\ + -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\ + -Wsuggest-override -Werror=suggest-override + + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=../dldi_ds_arm7.specs -g $(ARCH) -Wl,--nmagic -Wl,-Map,$(notdir $*).map,--gc-sections -ffunction-sections -fdata-sections + +LIBS := -ltwl7 -lnds7 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl7 $(CURDIR)/../../../libs/libtwl/common + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export ARM7ELF := $(CURDIR)/$(TARGET).elf +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) *.elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(ARM7ELF) : $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/examples/mass-storage/arm7/dldi_ds_arm7.ld b/examples/mass-storage/arm7/dldi_ds_arm7.ld new file mode 100644 index 0000000..34f7b22 --- /dev/null +++ b/examples/mass-storage/arm7/dldi_ds_arm7.ld @@ -0,0 +1,232 @@ +/*-------------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public License, + v. 2.0. If a copy of the MPL was not distributed with this file, You can + obtain one at https://mozilla.org/MPL/2.0/. +--------------------------------------------------------------------------------*/ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(_start) + + +PHDRS { + crt0 PT_LOAD FLAGS(7); + arm7 PT_LOAD FLAGS(7); + arm7i PT_LOAD FLAGS(0x100007); +} + + +MEMORY { + ewram : ORIGIN = 0x02380000, LENGTH = 12M - 512K + rom : ORIGIN = 0x08000000, LENGTH = 32M + iwram : ORIGIN = 0x037fc000, LENGTH = (96K - 16K) + + twl_ewram : ORIGIN = 0x02e80000, LENGTH = 512K - 64K + twl_iwram : ORIGIN = 0x03000000, LENGTH = 256K +} + +__iwram_start = ORIGIN(iwram); +__iwram_top = ORIGIN(iwram)+ LENGTH(iwram); + +__sp_irq = __iwram_top - 0x100; +__sp_svc = __sp_irq - 0x100; +__sp_usr = __sp_svc - 0x100; + +__irq_flags = 0x04000000 - 8; +__irq_flagsaux = 0x04000000 - 0x40; +__irq_vector = 0x04000000 - 4; + +SECTIONS +{ + + .twl : + { + __arm7i_lma__ = LOADADDR(.twl); + __arm7i_start__ = .; + *(.twl) + *.twl*(.text .stub .text.* .gnu.linkonce.t.*) + *.twl*(.rodata) + *.twl*(.roda) + *.twl*(.rodata.*) + *.twl*(.data) + *.twl*(.data.*) + *.twl*(.gnu.linkonce.d*) + . = ALIGN(4); + __arm7i_end__ = .; + } >twl_iwram AT>twl_ewram :arm7i + + .twl_bss ALIGN(4) (NOLOAD) : + { + __twl_bss_start__ = .; + *(.twl_bss) + *.twl.*(.dynbss) + *.twl.*(.gnu.linkonce.b*) + *.twl.*(.bss*) + *.twl.*(COMMON) + . = ALIGN(4); + __twl_bss_end__ = .; + } >twl_iwram :NONE + + .crt0 : + { + KEEP (*(.crt0)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram :crt0 + + .text : + { + __arm7_lma__ = LOADADDR(.text); + __arm7_start__ = .; + KEEP (*(SORT_NONE(.init))) + *(.plt) + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP (*(.text.*personality*)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.glue_7t) *(.glue_7) *(.vfp11_veneer) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram :arm7 + + .fini : + { + KEEP (*(.fini)) + } >iwram AT>ewram + + .rodata : + { + *(.rodata) + *all.rodata*(*) + *(.roda) + *(.rodata.*) + *(.gnu.linkonce.r*) + SORT(CONSTRUCTORS) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >iwram AT>ewram + + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } >iwram AT>ewram + +/* Ensure the __preinit_array_start label is properly aligned. We + could instead move the label definition inside the section, but + the linker would then create the section even if it turns out to + be empty, which isn't pretty. */ + .preinit_array : { + . = ALIGN(32 / 8); + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >iwram AT>ewram + + .init_array : { + PROVIDE (__init_array_start = .); + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >iwram AT>ewram + + .fini_array : { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + PROVIDE (__fini_array_end = .); + } >iwram AT>ewram + + .ctors : + { + /* gcc uses crtbegin.o to find the start of the constructors, so + we make sure it is first. Because this is a wildcard, it + doesn't matter if the user does not actually link against + crtbegin.o; the linker won't look for a file to match a + wildcard. The wildcard also means that it doesn't matter which + directory crtbegin.o is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .eh_frame : + { + KEEP (*(.eh_frame)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .gcc_except_table : + { + *(.gcc_except_table) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + .jcr : { KEEP (*(.jcr)) } >iwram AT>ewram + .got : { *(.got.plt) *(.got) } >iwram AT>ewram + + .data ALIGN(4) : { + __data_start = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + CONSTRUCTORS + . = ALIGN(4); + __data_end = ABSOLUTE(.) ; + } >iwram AT>ewram + + .bss ALIGN(4) (NOLOAD) : + { + __arm7_end__ = .; + __bss_start = ABSOLUTE(.); + __bss_start__ = ABSOLUTE(.); + *(.dynbss) + *(.gnu.linkonce.b*) + *(.bss*) + *(COMMON) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __bss_end__ = ABSOLUTE(.); + __end__ = ABSOLUTE(.); + } >iwram + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} \ No newline at end of file diff --git a/examples/mass-storage/arm7/dldi_ds_arm7.specs b/examples/mass-storage/arm7/dldi_ds_arm7.specs new file mode 100644 index 0000000..8f01248 --- /dev/null +++ b/examples/mass-storage/arm7/dldi_ds_arm7.specs @@ -0,0 +1,8 @@ +%rename link old_link + +*link: +%(old_link) -T ../dldi_ds_arm7.ld%s --gc-sections --no-warn-rwx-segments + +*startfile: +ds_arm7_crt0%O%s crti%O%s crtbegin%O%s + diff --git a/examples/mass-storage/arm7/source/Arm7State.h b/examples/mass-storage/arm7/source/Arm7State.h new file mode 100644 index 0000000..b306ac5 --- /dev/null +++ b/examples/mass-storage/arm7/source/Arm7State.h @@ -0,0 +1,8 @@ +#pragma once + +/// @brief Enum representing the arm7 state. +enum class Arm7State +{ + Idle, + ExitRequested +}; diff --git a/examples/mass-storage/arm7/source/ExitMode.h b/examples/mass-storage/arm7/source/ExitMode.h new file mode 100644 index 0000000..1750dd8 --- /dev/null +++ b/examples/mass-storage/arm7/source/ExitMode.h @@ -0,0 +1,10 @@ +#pragma once + +/// @brief Enum representing the exit mode of launcher. +enum class ExitMode +{ + /// @brief Reset the system (DSi mode only). + Reset, + /// @brief Power off the system. + PowerOff +}; diff --git a/examples/mass-storage/arm7/source/common.h b/examples/mass-storage/arm7/source/common.h new file mode 100644 index 0000000..3cac3e4 --- /dev/null +++ b/examples/mass-storage/arm7/source/common.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +extern rtos_mutex_t gCardMutex; diff --git a/examples/mass-storage/arm7/source/dldi.s b/examples/mass-storage/arm7/source/dldi.s new file mode 100644 index 0000000..28d95fa --- /dev/null +++ b/examples/mass-storage/arm7/source/dldi.s @@ -0,0 +1,16 @@ +.global _dldi_start +.equ _dldi_start, 0x037F8000 +.global _io_dldi +.equ _io_dldi, (_dldi_start + 0x60) +.global _DLDI_startup_ptr +.equ _DLDI_startup_ptr, (_io_dldi + 0x8) +.global _DLDI_isInserted_ptr +.equ _DLDI_isInserted_ptr, (_io_dldi + 0xC) +.global _DLDI_readSectors_ptr +.equ _DLDI_readSectors_ptr, (_io_dldi + 0x10) +.global _DLDI_writeSectors_ptr +.equ _DLDI_writeSectors_ptr, (_io_dldi + 0x14) +.global _DLDI_clearStatus_ptr +.equ _DLDI_clearStatus_ptr, (_io_dldi + 0x18) +.global _DLDI_shutdown_ptr +.equ _DLDI_shutdown_ptr, (_io_dldi + 0x1C) \ No newline at end of file diff --git a/examples/mass-storage/arm7/source/ipcServices/DldiIpcService.cpp b/examples/mass-storage/arm7/source/ipcServices/DldiIpcService.cpp new file mode 100644 index 0000000..1ff6dac --- /dev/null +++ b/examples/mass-storage/arm7/source/ipcServices/DldiIpcService.cpp @@ -0,0 +1,53 @@ +#include "common.h" +#include +#include +#include "DldiIpcService.h" + +extern FN_MEDIUM_STARTUP _DLDI_startup_ptr; +extern FN_MEDIUM_READSECTORS _DLDI_readSectors_ptr; +extern FN_MEDIUM_WRITESECTORS _DLDI_writeSectors_ptr; + +void DldiIpcService::HandleMessage(u32 data) +{ + auto cmd = reinterpret_cast(data << 2); + switch (cmd->cmd) + { + case DLDI_IPC_CMD_SETUP: + SetupDldi(cmd); + break; + + case DLDI_IPC_CMD_READ_SECTORS: + ReadSectors(cmd); + break; + + case DLDI_IPC_CMD_WRITE_SECTORS: + WriteSectors(cmd); + break; + } +} + +void DldiIpcService::SetupDldi(const dldi_ipc_cmd_t* cmd) const +{ + memcpy((void*)0x037F8000, cmd->buffer, 16 * 1024); + bool result; + rtos_lockMutex(&gCardMutex); + result = _DLDI_startup_ptr(); + rtos_unlockMutex(&gCardMutex); + SendResponseMessage(result); +} + +void DldiIpcService::ReadSectors(const dldi_ipc_cmd_t* cmd) const +{ + rtos_lockMutex(&gCardMutex); + _DLDI_readSectors_ptr(cmd->sector, cmd->count, cmd->buffer); + rtos_unlockMutex(&gCardMutex); + SendResponseMessage(0); +} + +void DldiIpcService::WriteSectors(const dldi_ipc_cmd_t* cmd) const +{ + rtos_lockMutex(&gCardMutex); + _DLDI_writeSectors_ptr(cmd->sector, cmd->count, cmd->buffer); + rtos_unlockMutex(&gCardMutex); + SendResponseMessage(0); +} diff --git a/examples/mass-storage/arm7/source/ipcServices/DldiIpcService.h b/examples/mass-storage/arm7/source/ipcServices/DldiIpcService.h new file mode 100644 index 0000000..d5732f0 --- /dev/null +++ b/examples/mass-storage/arm7/source/ipcServices/DldiIpcService.h @@ -0,0 +1,19 @@ +#pragma once +#include "ipc/ThreadIpcService.h" +#include "dldiIpcCommand.h" +#include "ipcChannels.h" + +class DldiIpcService : public ThreadIpcService +{ + u32 _threadStack[128]; + + void SetupDldi(const dldi_ipc_cmd_t* cmd) const; + void ReadSectors(const dldi_ipc_cmd_t* cmd) const; + void WriteSectors(const dldi_ipc_cmd_t* cmd) const; + +public: + DldiIpcService() + : ThreadIpcService(IPC_CHANNEL_DLDI, 6, _threadStack, sizeof(_threadStack)) { } + + void HandleMessage(u32 data) override; +}; diff --git a/examples/mass-storage/arm7/source/main.cpp b/examples/mass-storage/arm7/source/main.cpp new file mode 100644 index 0000000..7825f50 --- /dev/null +++ b/examples/mass-storage/arm7/source/main.cpp @@ -0,0 +1,388 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ipcServices/DldiIpcService.h" +#include "ExitMode.h" +#include "Arm7State.h" +#include "tusb.h" +#include "usb_descriptors.h" + +static DldiIpcService sDldiIpcService; + +rtos_mutex_t gCardMutex; + +static rtos_event_t sVBlankEvent; +static ExitMode sExitMode; +static Arm7State sState; +static volatile u8 sMcuIrqFlag = false; + +static u32 sSdBlockCount; +static u8 sSector0Buffer[512] alignas(4); + +static rtos_thread_t sUsbThread; +static u32 sUsbThreadStack[512]; + +extern FN_MEDIUM_READSECTORS _DLDI_readSectors_ptr; +extern FN_MEDIUM_WRITESECTORS _DLDI_writeSectors_ptr; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVBlankEvent); +} + +static void mcuIrq(u32 irq2Mask) +{ + sMcuIrqFlag = true; +} + +static void checkMcuIrq(void) +{ + // mcu only exists in DSi mode + if (isDSiMode()) + { + // check and ack the flag atomically + if (mem_swapByte(false, &sMcuIrqFlag)) + { + // check the irq mask + u32 irqMask = mcu_getIrqMask(); + if (irqMask & MCU_IRQ_RESET) + { + // power button was released + sExitMode = ExitMode::Reset; + sState = Arm7State::ExitRequested; + } + else if (irqMask & MCU_IRQ_POWER_OFF) + { + // power button was held long to trigger a power off + sExitMode = ExitMode::PowerOff; + sState = Arm7State::ExitRequested; + } + } + } +} + +static void initializeVBlankIrq() +{ + rtos_createEvent(&sVBlankEvent); + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); +} + +static void usbThreadMain(void* arg) +{ + while (true) + { + tud_task(); + } +} + +// Based on https://github.com/asiekierka/nrio-usb-disk/blob/main/source/msc.c msc_find_block_count by Asie +// Note: This might not work correctly in some cases. It would be better if the DSpico would expose the actual SD capacity. +static u32 findSdCardBlockCount() +{ + rtos_lockMutex(&gCardMutex); + _DLDI_readSectors_ptr(0, 1, sSector0Buffer); + rtos_unlockMutex(&gCardMutex); + + u16 footer = *(u16*)(sSector0Buffer + 510); + if (footer == 0xAA55) + { + u8 bootOpcode = sSector0Buffer[0]; + if (bootOpcode == 0xEB || bootOpcode == 0xE9 || bootOpcode == 0xE8) + { + if (!memcmp(sSector0Buffer + 54, "FAT", 3) || !memcmp(sSector0Buffer + 82, "FAT32 ", 8)) + { + u32 totalSectors = *(u32*)(sSector0Buffer + 32); + if (totalSectors < 0x10000) + { + totalSectors = sSector0Buffer[19] | (sSector0Buffer[20] << 8); + } + return totalSectors; + } + } + + u32 blockCount = 0; + for (u32 tableEntry = 0x1BE; tableEntry < 0x1FE; tableEntry += 16) + { + u32 pStart = *(u16*)(sSector0Buffer + tableEntry + 8) | (*(u16*)(sSector0Buffer + tableEntry + 10) << 16); + u32 pCount = *(u16*)(sSector0Buffer + tableEntry + 12) | (*(u16*)(sSector0Buffer + tableEntry + 14) << 16); + u32 pEnd = pStart + pCount; + if (pEnd > blockCount) + { + blockCount = pEnd; + } + } + + return blockCount; + } + + return 0; +} + +static void initializeArm7() +{ + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + rtos_createMutex(&gCardMutex); + + // clear sound registers + dmaFillWords(0, (void*)0x04000400, 0x100); + + pmic_setAmplifierEnable(true); + sys_setSoundPower(true); + + readUserSettings(); + pmic_setPowerLedBlink(PMIC_CONTROL_POWER_LED_BLINK_NONE); + + sio_setGpioSiIrq(false); + sio_setGpioMode(RCNT0_L_MODE_GPIO); + + rtc_init(); + + sDldiIpcService.Start(); + + snd_setMasterVolume(127); + snd_setMasterEnable(true); + + initializeVBlankIrq(); + + if (isDSiMode()) + { + rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); + rtos_enableIrq2Mask(RTOS_IRQ2_MCU); + } + + ipc_setArm7SyncBits(7); + + while (ipc_getArm9SyncBits() != 6) + { + rtos_waitEvent(&sVBlankEvent, true, true); + } + + sSdBlockCount = findSdCardBlockCount(); + + tusb_rhport_init_t dev_init = + { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(0, &dev_init); + + rtos_createThread(&sUsbThread, 3, usbThreadMain, NULL, sUsbThreadStack, sizeof(sUsbThreadStack)); + rtos_wakeupThread(&sUsbThread); +} + +static void updateArm7IdleState() +{ + checkMcuIrq(); + + if (sState == Arm7State::ExitRequested) + { + snd_setMasterVolume(0); // mute sound + } +} + +static bool performExit(ExitMode exitMode) +{ + switch (exitMode) + { + case ExitMode::Reset: + { + mcu_setWarmBootFlag(true); + mcu_hardReset(); + break; + } + case ExitMode::PowerOff: + { + pmic_shutdown(); + break; + } + } + + while (true); // wait infinitely for exit +} + +static void updateArm7ExitRequestedState() +{ + performExit(sExitMode); +} + +static void updateArm7() +{ + switch (sState) + { + case Arm7State::Idle: + { + updateArm7IdleState(); + break; + } + case Arm7State::ExitRequested: + { + updateArm7ExitRequestedState(); + break; + } + } +} + +int main() +{ + sState = Arm7State::Idle; + initializeArm7(); + + while (true) + { + rtos_waitEvent(&sVBlankEvent, true, true); + updateArm7(); + } + + return 0; +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) +{ + (void) lun; + + const char vid[] = "DSpico"; + const char pid[] = "Mass Storage"; + const char rev[] = "1.0"; + + memcpy(vendor_id , vid, strlen(vid)); + memcpy(product_id , pid, strlen(pid)); + memcpy(product_rev, rev, strlen(rev)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) +{ + (void) lun; + return true; +} + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) +{ + (void) lun; + + *block_count = sSdBlockCount; + *block_size = 512; +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) +{ + (void) lun; + (void) power_condition; + return true; +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) +{ + (void) lun; + + if (offset != 0 || (bufsize % 512) != 0) + { + return -1; + } + + rtos_lockMutex(&gCardMutex); + _DLDI_readSectors_ptr(lba, bufsize / 512, buffer); + rtos_unlockMutex(&gCardMutex); + + return (int32_t)bufsize; +} + +bool tud_msc_is_writable_cb(uint8_t lun) +{ + (void) lun; + return true; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) +{ + (void) lun; + + if (offset != 0 || (bufsize % 512) != 0) + { + return -1; + } + + rtos_lockMutex(&gCardMutex); + _DLDI_writeSectors_ptr(lba, bufsize / 512, buffer); + rtos_unlockMutex(&gCardMutex); + + return (int32_t)bufsize; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) +{ + // read10 & write10 has their own callback and MUST not be handled here + + void const* response = NULL; + int32_t resplen = 0; + + // most scsi handled is input + bool in_xfer = true; + + switch (scsi_cmd[0]) + { + default: + { + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + } + + // return resplen must not larger than bufsize + if (resplen > bufsize) + { + resplen = bufsize; + } + + if (response && (resplen > 0)) + { + if(in_xfer) + { + memcpy(buffer, response, (size_t)resplen); + } + else + { + // SCSI output + } + } + + return (int32_t)resplen; +} diff --git a/examples/mass-storage/arm7/source/tusb_config.h b/examples/mass-storage/arm7/source/tusb_config.h new file mode 100644 index 0000000..4c10ab2 --- /dev/null +++ b/examples/mass-storage/arm7/source/tusb_config.h @@ -0,0 +1,112 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#include "usb_descriptors.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#define CFG_TUSB_MCU OPT_MCU_DSPICO +#endif + +#define TUP_DCD_ENDPOINT_MAX 16 + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_CUSTOM +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 1 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 0 +#define CFG_TUD_VENDOR 0 + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_EP_BUFSIZE 8192 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ \ No newline at end of file diff --git a/examples/mass-storage/arm7/source/usb_descriptors.c b/examples/mass-storage/arm7/source/usb_descriptors.c new file mode 100644 index 0000000..27274d5 --- /dev/null +++ b/examples/mass-storage/arm7/source/usb_descriptors.c @@ -0,0 +1,198 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +// #include "bsp/board_api.h" +#include "tusb.h" +#include "usb_descriptors.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0110, + + // Use Interface Association Descriptor (IAD) for Audio + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *)&desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN) + +#define EPNUM_MSC_OUT 0x01 +#define EPNUM_MSC_IN 0x81 + +uint8_t const desc_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 4, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), + +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void)index; // for multiple configurations + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// String Descriptor Index +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, +}; + +// array of pointer to string descriptors +char const *string_desc_arr[] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "LNH", // 1: Manufacturer + "DSpico", // 2: Product + NULL, // 3: Serials will use unique ID if possible + "DSpico MSC" // 4: MSC Interface +}; + +static uint16_t _desc_str[32 + 1]; + +// Get USB Serial number string from unique ID if available. Return number of character. +// Input is string descriptor from index 1 (index 0 is type + len) +static inline size_t board_usb_get_serial(uint16_t desc_str1[], size_t max_chars) { + uint8_t uid[16] TU_ATTR_ALIGNED(4); + size_t uid_len; + + // TODO work with make, but not working with esp32s3 cmake + // if ( board_get_unique_id ) { + // uid_len = board_get_unique_id(uid, sizeof(uid)); + // }else { + // fixed serial string is 01234567889ABCDEF + uint32_t* uid32 = (uint32_t*) (uintptr_t) uid; + uid32[0] = 0x67452301; + uid32[1] = 0xEFCDAB89; + uid_len = 8; + // } + + if ( uid_len > max_chars / 2 ) uid_len = max_chars / 2; + + for ( size_t i = 0; i < uid_len; i++ ) { + for ( size_t j = 0; j < 2; j++ ) { + const char nibble_to_hex[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + uint8_t const nibble = (uid[i] >> (j * 4)) & 0xf; + desc_str1[i * 2 + (1 - j)] = nibble_to_hex[nibble]; // UTF-16-LE + } + } + + return 2 * uid_len; +} + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + (void) langid; + size_t chr_count; + + switch ( index ) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: + chr_count = board_usb_get_serial(_desc_str + 1, 32); + break; + + default: + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL; + + const char *str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + if ( chr_count > max_count ) chr_count = max_count; + + // Convert ASCII string into UTF-16 + for ( size_t i = 0; i < chr_count; i++ ) { + _desc_str[1 + i] = str[i]; + } + break; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); + + return _desc_str; +} diff --git a/examples/mass-storage/arm7/source/usb_descriptors.h b/examples/mass-storage/arm7/source/usb_descriptors.h new file mode 100644 index 0000000..1a13e84 --- /dev/null +++ b/examples/mass-storage/arm7/source/usb_descriptors.h @@ -0,0 +1,7 @@ +#pragma once + +enum +{ + ITF_NUM_MSC, + ITF_NUM_TOTAL +}; diff --git a/examples/mass-storage/arm9/Makefile b/examples/mass-storage/arm9/Makefile new file mode 100644 index 0000000..58a169c --- /dev/null +++ b/examples/mass-storage/arm9/Makefile @@ -0,0 +1,152 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +# DATA is a list of directories containing binary files +# all directories are relative to this makefile +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := source \ + source/core +INCLUDES := include source ../common +DATA := data +GRAPHICS := gfx + + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -marm -mthumb-interwork -DLIBTWL_ARM9 -DARM9 + +CFLAGS := -g -Wall -O2\ + -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\ + -ffunction-sections -fdata-sections\ + -ffast-math \ + -fno-devirtualize-speculatively \ + -Werror=return-type \ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\ + -Wsuggest-override -Werror=suggest-override + +CFLAGS += -Werror=implicit-function-declaration + +ASFLAGS := -g $(ARCH) $(INCLUDE) -march=armv5te -mtune=arm946e-s + +LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--gc-sections,--use-blx -ffunction-sections -fdata-sections + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -ltwl9 -lnds9 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl9 $(CURDIR)/../../../libs/libtwl/common + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export ARM9ELF := $(CURDIR)/$(TARGET).elf +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(PNGFILES:.png=.o)\ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) *.elf *.nds* *.bin + + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(ARM9ELF) : $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +%.nft2.o : %.nft2 + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# This rule creates assembly source files using grit +# grit takes an image file and a .grit describing how the file is to be processed +# add additional rules like this for each image extension +# you use in the graphics folders +#--------------------------------------------------------------------------------- +%.s %.h: %.png %.grit +#--------------------------------------------------------------------------------- + grit $< -fts -o$* + +-include $(DEPSDIR)/*.d + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/examples/mass-storage/arm9/source/common.h b/examples/mass-storage/arm9/source/common.h new file mode 100644 index 0000000..c717672 --- /dev/null +++ b/examples/mass-storage/arm9/source/common.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/examples/mass-storage/arm9/source/dldiIpc.cpp b/examples/mass-storage/arm9/source/dldiIpc.cpp new file mode 100644 index 0000000..37d1e76 --- /dev/null +++ b/examples/mass-storage/arm9/source/dldiIpc.cpp @@ -0,0 +1,164 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include "ipcChannels.h" +#include "dldiIpcCommand.h" +#include "dldiIpc.h" + +extern u8 gDldiStub[]; + +static rtos_mutex_t sMutex; +static rtos_event_t sEvent; + +alignas(32) static dldi_ipc_cmd_t sIpcCommand; + +alignas(32) static u8 sTempBuffers[2][512]; + +static bool sDldiInitSuccess; + +static void ipcMessageHandler(u32 channel, u32 data, void* arg) +{ + sDldiInitSuccess = data; + rtos_signalEvent(&sEvent); +} + +static bool setup() +{ + u32 dldiFeatures = *(vu32*)(gDldiStub + 0x64); + if (dldiFeatures & FEATURE_SLOT_NDS) + REG_EXMEMCNT |= (1 << 11); // slot 1 to arm7 + if (dldiFeatures & FEATURE_SLOT_GBA) + REG_EXMEMCNT |= (1 << 7); // slot 2 to arm7 + sDldiInitSuccess = false; + sIpcCommand.cmd = DLDI_IPC_CMD_SETUP; + sIpcCommand.buffer = gDldiStub; + sIpcCommand.sector = 0; + sIpcCommand.count = 0; + rtos_clearEvent(&sEvent); + DC_FlushRange(gDldiStub, 16 * 1024); + DC_FlushRange(&sIpcCommand, sizeof(sIpcCommand)); + ipc_sendFifoMessage(IPC_CHANNEL_DLDI, (u32)&sIpcCommand >> 2); + rtos_waitEvent(&sEvent, false, true); + return sDldiInitSuccess; +} + +bool dldi_init() +{ + rtos_createMutex(&sMutex); + rtos_createEvent(&sEvent); + ipc_setChannelHandler(IPC_CHANNEL_DLDI, ipcMessageHandler, nullptr); + return setup(); +} + +static void readSectorsCacheAligned(void* buffer, u32 sector, u32 count) +{ + sIpcCommand.cmd = DLDI_IPC_CMD_READ_SECTORS; + sIpcCommand.buffer = buffer; + sIpcCommand.sector = sector; + sIpcCommand.count = count; + DC_InvalidateRange(buffer, 512 * count); + rtos_clearEvent(&sEvent); + DC_FlushRange(&sIpcCommand, sizeof(sIpcCommand)); + ipc_sendFifoMessage(IPC_CHANNEL_DLDI, (u32)&sIpcCommand >> 2); + rtos_waitEvent(&sEvent, false, true); +} + +static void readSectorsNotCacheAligned(void* buffer, u32 sector, u32 count) +{ + rtos_clearEvent(&sEvent); + sIpcCommand.cmd = DLDI_IPC_CMD_READ_SECTORS; + sIpcCommand.count = 1; + // not cache aligned, use a temp buffer + DC_InvalidateRange(sTempBuffers[0], 512); + for (u32 i = 0; i < count; i++) + { + sIpcCommand.buffer = sTempBuffers[i & 1]; + sIpcCommand.sector = sector + i; + DC_FlushRange(&sIpcCommand, sizeof(sIpcCommand)); + ipc_sendFifoMessage(IPC_CHANNEL_DLDI, (u32)&sIpcCommand >> 2); + if (i != 0) + memcpy((u8*)buffer + 512 * (i - 1), sTempBuffers[(i - 1) & 1], 512); + if (i != count - 1) + DC_InvalidateRange(sTempBuffers[(i + 1) & 1], 512); + rtos_waitEvent(&sEvent, false, true); + } + memcpy((u8*)buffer + 512 * (count - 1), sTempBuffers[(count - 1) & 1], 512); +} + +extern "C" void dldi_readSectors(void* buffer, u32 sector, u32 count) +{ + if (count == 0) + return; + rtos_lockMutex(&sMutex); + { + if ((u32)buffer & 0x1F) + { + readSectorsNotCacheAligned(buffer, sector, count); + } + else + { + readSectorsCacheAligned(buffer, sector, count); + } + } + rtos_unlockMutex(&sMutex); +} + +static void writeSectorsCacheAligned(const void* buffer, u32 sector, u32 count) +{ + sIpcCommand.cmd = DLDI_IPC_CMD_WRITE_SECTORS; + sIpcCommand.buffer = (void*)buffer; + sIpcCommand.sector = sector; + sIpcCommand.count = count; + DC_FlushRange(buffer, 512 * count); + rtos_clearEvent(&sEvent); + DC_FlushRange(&sIpcCommand, sizeof(sIpcCommand)); + ipc_sendFifoMessage(IPC_CHANNEL_DLDI, (u32)&sIpcCommand >> 2); + rtos_waitEvent(&sEvent, false, true); +} + +static void writeSectorsNotCacheAligned(const void* buffer, u32 sector, u32 count) +{ + rtos_clearEvent(&sEvent); + sIpcCommand.cmd = DLDI_IPC_CMD_WRITE_SECTORS; + sIpcCommand.count = 1; + // not cache aligned, use a temp buffer + memcpy(sTempBuffers[0], (u8*)buffer, 512); + DC_FlushRange(sTempBuffers[0], 512); + for (u32 i = 0; i < count; i++) + { + sIpcCommand.buffer = sTempBuffers[i & 1]; + sIpcCommand.sector = sector + i; + DC_FlushRange(&sIpcCommand, sizeof(sIpcCommand)); + ipc_sendFifoMessage(IPC_CHANNEL_DLDI, (u32)&sIpcCommand >> 2); + if (i != count - 1) + { + memcpy(sTempBuffers[(i + 1) & 1], (u8*)buffer + 512 * (i + 1), 512); + DC_FlushRange(sTempBuffers[(i + 1) & 1], 512); + } + rtos_waitEvent(&sEvent, false, true); + } + memcpy((u8*)buffer + 512 * (count - 1), sTempBuffers[(count - 1) & 1], 512); +} + +extern "C" void dldi_writeSectors(const void* buffer, u32 sector, u32 count) +{ + if (count == 0) + return; + rtos_lockMutex(&sMutex); + { + if ((u32)buffer & 0x1F) + { + writeSectorsNotCacheAligned(buffer, sector, count); + } + else + { + writeSectorsCacheAligned(buffer, sector, count); + } + } + rtos_unlockMutex(&sMutex); +} \ No newline at end of file diff --git a/examples/mass-storage/arm9/source/dldiIpc.h b/examples/mass-storage/arm9/source/dldiIpc.h new file mode 100644 index 0000000..f8178e8 --- /dev/null +++ b/examples/mass-storage/arm9/source/dldiIpc.h @@ -0,0 +1,14 @@ +#pragma once + +bool dldi_init(); + +#ifdef __cplusplus +extern "C" { +#endif + +void dldi_readSectors(void* buffer, u32 sector, u32 count); +void dldi_writeSectors(const void* buffer, u32 sector, u32 count); + +#ifdef __cplusplus +} +#endif diff --git a/examples/mass-storage/arm9/source/dldi_stub.s b/examples/mass-storage/arm9/source/dldi_stub.s new file mode 100644 index 0000000..cd4c2a8 --- /dev/null +++ b/examples/mass-storage/arm9/source/dldi_stub.s @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------- + + Copyright (C) 2006 - 2016 + Michael Chisholm (Chishm) + Dave Murphy (WinterMute) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + 3. This notice may not be removed or altered from any source + distribution. + +---------------------------------------------------------------------------------*/ + +#include + + .align 4 + .arm + .global gDldiStub +@--------------------------------------------------------------------------------- + +.equ DLDI_ALLOCATED_SPACE, 16384 + +gDldiStub: + +dldi_start: + +@--------------------------------------------------------------------------------- +@ Driver patch file standard header -- 16 bytes + .word 0xBF8DA5ED @ Magic number to identify this region + .asciz " Chishm" @ Identifying Magic string (8 bytes with null terminator) + .byte 0x01 @ Version number + .byte DLDI_SIZE_16KB @16KiB @ Log [base-2] of the size of this driver in bytes. + .byte 0x00 @ Sections to fix + .byte DLDI_SIZE_16KB @16KiB @ Log [base-2] of the allocated space in bytes. + +@--------------------------------------------------------------------------------- +@ Text identifier - can be anything up to 47 chars + terminating null -- 16 bytes + .align 4 + .asciz "Default (No interface)" + +@--------------------------------------------------------------------------------- +@ Offsets to important sections within the data -- 32 bytes + .align 6 + .word 0x037F8000 //dldi_start @ data start + .word 0x037FC000 //dldi_end @ data end + .word 0x00000000 @ Interworking glue start -- Needs address fixing + .word 0x00000000 @ Interworking glue end + .word 0x00000000 @ GOT start -- Needs address fixing + .word 0x00000000 @ GOT end + .word 0x00000000 @ bss start -- Needs setting to zero + .word 0x00000000 @ bss end + +@--------------------------------------------------------------------------------- +@ DISC_INTERFACE data -- 32 bytes + .ascii "DLDI" @ ioType + .word 0x00000000 @ Features + .word _DLDI_startup @ + .word _DLDI_isInserted @ + .word _DLDI_readSectors @ Function pointers to standard device driver functions + .word _DLDI_writeSectors @ + .word _DLDI_clearStatus @ + .word _DLDI_shutdown @ + +@--------------------------------------------------------------------------------- + +_DLDI_startup: +_DLDI_isInserted: +_DLDI_readSectors: +_DLDI_writeSectors: +_DLDI_clearStatus: +_DLDI_shutdown: + mov r0, #0x00 @ Return false for every function + bx lr + + + +@--------------------------------------------------------------------------------- + .align + .pool + +dldi_data_end: + +@ Pad to end of allocated space +.space DLDI_ALLOCATED_SPACE - (dldi_data_end - dldi_start) + +dldi_end: + .end +@--------------------------------------------------------------------------------- diff --git a/examples/mass-storage/arm9/source/main.cpp b/examples/mass-storage/arm9/source/main.cpp new file mode 100644 index 0000000..1ee4042 --- /dev/null +++ b/examples/mass-storage/arm9/source/main.cpp @@ -0,0 +1,55 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include "dldiIpc.h" + +static rtos_event_t sVblankEvent; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVblankEvent); +} + +int main(int argc, char* argv[]) +{ + *(vu32*)0x04000000 = 0x10000; + *(vu16*)0x05000000 = 31 << 10; + *(vu16*)0x0400006C = 0; + + mem_setDsCartridgeCpu(EXMEMCNT_SLOT1_CPU_ARM7); + + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + rtos_createEvent(&sVblankEvent); + + while (ipc_getArm7SyncBits() != 7); + + if (dldi_init()) + { + *(vu16*)0x05000000 = (31 << 5); + } + else + { + *(vu16*)0x05000000 = 31; + } + + ipc_setArm9SyncBits(6); + + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); + + while (true) + { + rtos_waitEvent(&sVblankEvent, true, true); + } + + return 0; +} \ No newline at end of file diff --git a/examples/mass-storage/common/dldiIpcCommand.h b/examples/mass-storage/common/dldiIpcCommand.h new file mode 100644 index 0000000..b0319cc --- /dev/null +++ b/examples/mass-storage/common/dldiIpcCommand.h @@ -0,0 +1,16 @@ +#pragma once + +typedef enum +{ + DLDI_IPC_CMD_SETUP, + DLDI_IPC_CMD_READ_SECTORS, + DLDI_IPC_CMD_WRITE_SECTORS +} DldiIpcCommand; + +typedef struct alignas(32) +{ + u32 cmd; + void* buffer; + u32 sector; + u32 count; +} dldi_ipc_cmd_t; \ No newline at end of file diff --git a/examples/mass-storage/common/ipc/IpcService.cpp b/examples/mass-storage/common/ipc/IpcService.cpp new file mode 100644 index 0000000..815bed7 --- /dev/null +++ b/examples/mass-storage/common/ipc/IpcService.cpp @@ -0,0 +1,11 @@ +#include "common.h" +#include +#include "IpcService.h" + +void IpcService::Start() +{ + ipc_setChannelHandler(_ipcChannel, [] (u32 channel, u32 data, void* arg) + { + static_cast(arg)->OnMessageReceived(data); + }, this); +} diff --git a/examples/mass-storage/common/ipc/IpcService.h b/examples/mass-storage/common/ipc/IpcService.h new file mode 100644 index 0000000..16519ea --- /dev/null +++ b/examples/mass-storage/common/ipc/IpcService.h @@ -0,0 +1,19 @@ +#pragma once +#include + +class IpcService +{ + const u32 _ipcChannel; +protected: + explicit IpcService(u32 ipcChannel) + : _ipcChannel(ipcChannel) { } + + void SendResponseMessage(u32 data) const + { + ipc_sendFifoMessage(_ipcChannel, data); + } + +public: + virtual void Start(); + virtual void OnMessageReceived(u32 data) = 0; +}; diff --git a/examples/mass-storage/common/ipc/ThreadIpcService.cpp b/examples/mass-storage/common/ipc/ThreadIpcService.cpp new file mode 100644 index 0000000..89aea25 --- /dev/null +++ b/examples/mass-storage/common/ipc/ThreadIpcService.cpp @@ -0,0 +1,33 @@ +#include "common.h" +#include "ThreadIpcService.h" + +void ThreadIpcService::ThreadMain() +{ + while (true) + { + rtos_waitEvent(&_event, false, true); + if (_messageValid) + { + _messageValid = false; + HandleMessage(_message); + } + } +} + +void ThreadIpcService::Start() +{ + rtos_createEvent(&_event); + rtos_createThread(&_thread, _priority, [] (void* arg) + { + static_cast(arg)->ThreadMain(); + }, this, _stack, _stackSize); + rtos_wakeupThread(&_thread); + IpcService::Start(); +} + +void ThreadIpcService::OnMessageReceived(u32 data) +{ + _message = data; + _messageValid = true; + rtos_signalEvent(&_event); +} diff --git a/examples/mass-storage/common/ipc/ThreadIpcService.h b/examples/mass-storage/common/ipc/ThreadIpcService.h new file mode 100644 index 0000000..a441d25 --- /dev/null +++ b/examples/mass-storage/common/ipc/ThreadIpcService.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include "IpcService.h" + +class ThreadIpcService : public IpcService +{ + rtos_thread_t _thread; + rtos_event_t _event; + u32* _stack; + u32 _stackSize; + u8 _priority; + bool _messageValid = false; + u32 _message; + + void ThreadMain(); + +public: + ThreadIpcService(u32 ipcChannel, u8 priority, u32* stack, u32 stackSize) + : IpcService(ipcChannel), _stack(stack), _stackSize(stackSize), _priority(priority) { } + + void Start() override; + void OnMessageReceived(u32 data) override; + + virtual void HandleMessage(u32 data); +}; diff --git a/examples/mass-storage/common/ipcChannels.h b/examples/mass-storage/common/ipcChannels.h new file mode 100644 index 0000000..2a923ca --- /dev/null +++ b/examples/mass-storage/common/ipcChannels.h @@ -0,0 +1,7 @@ +#pragma once + +#define IPC_CHANNEL_DSI_SD 16 +#define IPC_CHANNEL_DLDI 17 +#define IPC_CHANNEL_LOADER 18 +#define IPC_CHANNEL_SOUND 19 +#define IPC_CHANNEL_RTC 20 diff --git a/examples/usb-speaker/Makefile b/examples/usb-speaker/Makefile new file mode 100644 index 0000000..efd8337 --- /dev/null +++ b/examples/usb-speaker/Makefile @@ -0,0 +1,60 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +export TARGET := $(shell basename $(CURDIR)) +export TOPDIR := $(CURDIR) + +# specify a directory which contains the nitro filesystem +# this is relative to the Makefile +NITRO_FILES := + +# These set the information text in the nds file +#GAME_TITLE := My Wonderful Homebrew +#GAME_SUBTITLE1 := built with devkitARM +#GAME_SUBTITLE2 := http://devitpro.org + +include $(DEVKITARM)/ds_rules + +.PHONY: checklibtwl checkarm7 checkarm9 clean + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all: checklibtwl checkarm7 checkarm9 $(TARGET).nds + +#--------------------------------------------------------------------------------- +checklibtwl: + $(MAKE) -C ../../libs/libtwl/libtwl7 + $(MAKE) -C ../../libs/libtwl/libtwl9 + +#--------------------------------------------------------------------------------- +checkarm7: checklibtwl + $(MAKE) -C arm7 + +#--------------------------------------------------------------------------------- +checkarm9: checklibtwl + $(MAKE) -C arm9 + +#--------------------------------------------------------------------------------- +$(TARGET).nds : $(NITRO_FILES) arm7/$(TARGET).elf arm9/$(TARGET).elf + ndstool -c $(TARGET).nds -7 arm7/$(TARGET).elf -9 arm9/$(TARGET).elf \ + -b $(GAME_ICON) "$(GAME_TITLE);$(GAME_SUBTITLE1);$(GAME_SUBTITLE2)" \ + $(_ADDFILES) + +#--------------------------------------------------------------------------------- +arm7/$(TARGET).elf: + $(MAKE) -C arm7 + +#--------------------------------------------------------------------------------- +arm9/$(TARGET).elf: + $(MAKE) -C arm9 + +#--------------------------------------------------------------------------------- +clean: + $(MAKE) -C arm9 clean + $(MAKE) -C arm7 clean + rm -f $(TARGET).nds $(TARGET).arm7 $(TARGET).arm9 diff --git a/examples/usb-speaker/arm7/Makefile b/examples/usb-speaker/arm7/Makefile new file mode 100644 index 0000000..60da066 --- /dev/null +++ b/examples/usb-speaker/arm7/Makefile @@ -0,0 +1,137 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +# DATA is a list of directories containing binary files +# all directories are relative to this makefile +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := source \ + ../common/ipc \ + source/ipcServices \ + ../../../platform \ + ../../../libs/tinyusb/src \ + ../../../libs/tinyusb/src/device \ + ../../../libs/tinyusb/src/class/audio \ + ../../../libs/tinyusb/src/class/cdc \ + ../../../libs/tinyusb/src/class/msc \ + ../../../libs/tinyusb/src/common +INCLUDES := source include build ../common ../../../libs/tinyusb/src ../../../platform +DATA := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -marm -mthumb-interwork -DLIBTWL_ARM7 -DARM7 + +CFLAGS := -g -Wall -O2\ + -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer\ + -ffunction-sections -fdata-sections\ + -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\ + -Wsuggest-override -Werror=suggest-override + + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=../dldi_ds_arm7.specs -g $(ARCH) -Wl,--nmagic -Wl,-Map,$(notdir $*).map,--gc-sections -ffunction-sections -fdata-sections + +LIBS := -ltwl7 -lnds7 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl7 $(CURDIR)/../../../libs/libtwl/common + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export ARM7ELF := $(CURDIR)/$(TARGET).elf +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) *.elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(ARM7ELF) : $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/examples/usb-speaker/arm7/dldi_ds_arm7.ld b/examples/usb-speaker/arm7/dldi_ds_arm7.ld new file mode 100644 index 0000000..34f7b22 --- /dev/null +++ b/examples/usb-speaker/arm7/dldi_ds_arm7.ld @@ -0,0 +1,232 @@ +/*-------------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public License, + v. 2.0. If a copy of the MPL was not distributed with this file, You can + obtain one at https://mozilla.org/MPL/2.0/. +--------------------------------------------------------------------------------*/ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(_start) + + +PHDRS { + crt0 PT_LOAD FLAGS(7); + arm7 PT_LOAD FLAGS(7); + arm7i PT_LOAD FLAGS(0x100007); +} + + +MEMORY { + ewram : ORIGIN = 0x02380000, LENGTH = 12M - 512K + rom : ORIGIN = 0x08000000, LENGTH = 32M + iwram : ORIGIN = 0x037fc000, LENGTH = (96K - 16K) + + twl_ewram : ORIGIN = 0x02e80000, LENGTH = 512K - 64K + twl_iwram : ORIGIN = 0x03000000, LENGTH = 256K +} + +__iwram_start = ORIGIN(iwram); +__iwram_top = ORIGIN(iwram)+ LENGTH(iwram); + +__sp_irq = __iwram_top - 0x100; +__sp_svc = __sp_irq - 0x100; +__sp_usr = __sp_svc - 0x100; + +__irq_flags = 0x04000000 - 8; +__irq_flagsaux = 0x04000000 - 0x40; +__irq_vector = 0x04000000 - 4; + +SECTIONS +{ + + .twl : + { + __arm7i_lma__ = LOADADDR(.twl); + __arm7i_start__ = .; + *(.twl) + *.twl*(.text .stub .text.* .gnu.linkonce.t.*) + *.twl*(.rodata) + *.twl*(.roda) + *.twl*(.rodata.*) + *.twl*(.data) + *.twl*(.data.*) + *.twl*(.gnu.linkonce.d*) + . = ALIGN(4); + __arm7i_end__ = .; + } >twl_iwram AT>twl_ewram :arm7i + + .twl_bss ALIGN(4) (NOLOAD) : + { + __twl_bss_start__ = .; + *(.twl_bss) + *.twl.*(.dynbss) + *.twl.*(.gnu.linkonce.b*) + *.twl.*(.bss*) + *.twl.*(COMMON) + . = ALIGN(4); + __twl_bss_end__ = .; + } >twl_iwram :NONE + + .crt0 : + { + KEEP (*(.crt0)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram :crt0 + + .text : + { + __arm7_lma__ = LOADADDR(.text); + __arm7_start__ = .; + KEEP (*(SORT_NONE(.init))) + *(.plt) + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP (*(.text.*personality*)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.glue_7t) *(.glue_7) *(.vfp11_veneer) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram :arm7 + + .fini : + { + KEEP (*(.fini)) + } >iwram AT>ewram + + .rodata : + { + *(.rodata) + *all.rodata*(*) + *(.roda) + *(.rodata.*) + *(.gnu.linkonce.r*) + SORT(CONSTRUCTORS) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >iwram AT>ewram + + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } >iwram AT>ewram + +/* Ensure the __preinit_array_start label is properly aligned. We + could instead move the label definition inside the section, but + the linker would then create the section even if it turns out to + be empty, which isn't pretty. */ + .preinit_array : { + . = ALIGN(32 / 8); + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >iwram AT>ewram + + .init_array : { + PROVIDE (__init_array_start = .); + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >iwram AT>ewram + + .fini_array : { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + PROVIDE (__fini_array_end = .); + } >iwram AT>ewram + + .ctors : + { + /* gcc uses crtbegin.o to find the start of the constructors, so + we make sure it is first. Because this is a wildcard, it + doesn't matter if the user does not actually link against + crtbegin.o; the linker won't look for a file to match a + wildcard. The wildcard also means that it doesn't matter which + directory crtbegin.o is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .eh_frame : + { + KEEP (*(.eh_frame)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .gcc_except_table : + { + *(.gcc_except_table) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + .jcr : { KEEP (*(.jcr)) } >iwram AT>ewram + .got : { *(.got.plt) *(.got) } >iwram AT>ewram + + .data ALIGN(4) : { + __data_start = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + CONSTRUCTORS + . = ALIGN(4); + __data_end = ABSOLUTE(.) ; + } >iwram AT>ewram + + .bss ALIGN(4) (NOLOAD) : + { + __arm7_end__ = .; + __bss_start = ABSOLUTE(.); + __bss_start__ = ABSOLUTE(.); + *(.dynbss) + *(.gnu.linkonce.b*) + *(.bss*) + *(COMMON) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __bss_end__ = ABSOLUTE(.); + __end__ = ABSOLUTE(.); + } >iwram + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} \ No newline at end of file diff --git a/examples/usb-speaker/arm7/dldi_ds_arm7.specs b/examples/usb-speaker/arm7/dldi_ds_arm7.specs new file mode 100644 index 0000000..8f01248 --- /dev/null +++ b/examples/usb-speaker/arm7/dldi_ds_arm7.specs @@ -0,0 +1,8 @@ +%rename link old_link + +*link: +%(old_link) -T ../dldi_ds_arm7.ld%s --gc-sections --no-warn-rwx-segments + +*startfile: +ds_arm7_crt0%O%s crti%O%s crtbegin%O%s + diff --git a/examples/usb-speaker/arm7/source/Arm7State.h b/examples/usb-speaker/arm7/source/Arm7State.h new file mode 100644 index 0000000..b306ac5 --- /dev/null +++ b/examples/usb-speaker/arm7/source/Arm7State.h @@ -0,0 +1,8 @@ +#pragma once + +/// @brief Enum representing the arm7 state. +enum class Arm7State +{ + Idle, + ExitRequested +}; diff --git a/examples/usb-speaker/arm7/source/ExitMode.h b/examples/usb-speaker/arm7/source/ExitMode.h new file mode 100644 index 0000000..1750dd8 --- /dev/null +++ b/examples/usb-speaker/arm7/source/ExitMode.h @@ -0,0 +1,10 @@ +#pragma once + +/// @brief Enum representing the exit mode of launcher. +enum class ExitMode +{ + /// @brief Reset the system (DSi mode only). + Reset, + /// @brief Power off the system. + PowerOff +}; diff --git a/examples/usb-speaker/arm7/source/common.h b/examples/usb-speaker/arm7/source/common.h new file mode 100644 index 0000000..3cac3e4 --- /dev/null +++ b/examples/usb-speaker/arm7/source/common.h @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +extern rtos_mutex_t gCardMutex; diff --git a/examples/usb-speaker/arm7/source/main.cpp b/examples/usb-speaker/arm7/source/main.cpp new file mode 100644 index 0000000..ba57fab --- /dev/null +++ b/examples/usb-speaker/arm7/source/main.cpp @@ -0,0 +1,550 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ExitMode.h" +#include "Arm7State.h" +#include "tusb.h" +#include "usb_descriptors.h" + +const uint32_t sample_rates[] = { 44100 }; + +uint32_t current_sample_rate = 44100; + +#define N_SAMPLE_RATES TU_ARRAY_SIZE(sample_rates) + +enum +{ + VOLUME_CTRL_0_DB = 0, + VOLUME_CTRL_10_DB = 2560, + VOLUME_CTRL_20_DB = 5120, + VOLUME_CTRL_30_DB = 7680, + VOLUME_CTRL_40_DB = 10240, + VOLUME_CTRL_50_DB = 12800, + VOLUME_CTRL_60_DB = 15360, + VOLUME_CTRL_70_DB = 17920, + VOLUME_CTRL_80_DB = 20480, + VOLUME_CTRL_90_DB = 23040, + VOLUME_CTRL_100_DB = 25600, + VOLUME_CTRL_SILENCE = 0x8000, +}; + +// Audio controls +// Current states +int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0 +int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0 + +// Buffer for speaker data +int32_t spk_buf[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ / 4]; +// Speaker data size received in the last frame +volatile int spk_data_size; +// Resolution per format +const uint8_t resolutions_per_format[CFG_TUD_AUDIO_FUNC_1_N_FORMATS] = { CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX }; +// Current resolution, update on format change +volatile uint8_t current_resolution; + +#define AUDIO_BUFFER_LENGTH 2048 + +static bool sAudioStarted; + +#define AUDIO_STREAM_PLAYER_TIMER 3 +#define AUDIO_STREAM_PLAYER_SAFETY_BLOCKS 4 +#define AUDIO_STREAM_PLAYER_THREAD_PRIORITY 15 +#define AUDIO_STREAM_PLAYER_RING_BLOCKS 32 +#define AUDIO_STREAM_PLAYER_BLOCK_SAMPLES 64 + +static rtos_event_t sAudioBlockEvent; +static rtos_thread_t sAudioThread; +static u32 sAudioThreadStack[512]; + +static s16 sAudioRingL[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(4); +static s16 sAudioRingR[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(4); + +static volatile u8 sReadBlock; +static volatile u8 sWriteBlock; + +rtos_mutex_t gCardMutex; + +static rtos_thread_t sUsbThread; +static u32 sUsbThreadStack[512]; + +static rtos_event_t sVBlankEvent; +static ExitMode sExitMode; +static Arm7State sState; +static volatile u8 sMcuIrqFlag = false; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVBlankEvent); +} + +static void mcuIrq(u32 irq2Mask) +{ + sMcuIrqFlag = true; +} + +static void checkMcuIrq(void) +{ + // mcu only exists in DSi mode + if (isDSiMode()) + { + // check and ack the flag atomically + if (mem_swapByte(false, &sMcuIrqFlag)) + { + // check the irq mask + u32 irqMask = mcu_getIrqMask(); + if (irqMask & MCU_IRQ_RESET) + { + // power button was released + sExitMode = ExitMode::Reset; + sState = Arm7State::ExitRequested; + } + else if (irqMask & MCU_IRQ_POWER_OFF) + { + // power button was held long to trigger a power off + sExitMode = ExitMode::PowerOff; + sState = Arm7State::ExitRequested; + } + } + } +} + +static void initializeVBlankIrq() +{ + rtos_createEvent(&sVBlankEvent); + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); +} + +static void usbThreadMain(void* arg) +{ + while (true) + { + tud_task(); + } +} + +static void initializeArm7() +{ + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + // clear sound registers + dmaFillWords(0, (void*)0x04000400, 0x100); + + pmic_setAmplifierEnable(true); + sys_setSoundPower(true); + + readUserSettings(); + pmic_setPowerLedBlink(PMIC_CONTROL_POWER_LED_BLINK_NONE); + + sio_setGpioSiIrq(false); + sio_setGpioMode(RCNT0_L_MODE_GPIO); + + rtc_init(); + + snd_setMasterVolume(127); + snd_setMasterEnable(true); + + initializeVBlankIrq(); + + if (isDSiMode()) + { + rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); + rtos_enableIrq2Mask(RTOS_IRQ2_MCU); + } + + tusb_rhport_init_t dev_init = + { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(0, &dev_init); + + sReadBlock = 0; + sWriteBlock = 0; + rtos_createThread(&sUsbThread, 3, usbThreadMain, NULL, sUsbThreadStack, sizeof(sUsbThreadStack)); + rtos_wakeupThread(&sUsbThread); + + ipc_setArm7SyncBits(7); +} + +static void updateArm7IdleState() +{ + checkMcuIrq(); + + if (sState == Arm7State::ExitRequested) + { + snd_setMasterVolume(0); // mute sound + } +} + +static bool performExit(ExitMode exitMode) +{ + switch (exitMode) + { + case ExitMode::Reset: + { + mcu_setWarmBootFlag(true); + mcu_hardReset(); + break; + } + case ExitMode::PowerOff: + { + pmic_shutdown(); + break; + } + } + + while (true); // wait infinitely for exit +} + +static void updateArm7ExitRequestedState() +{ + performExit(sExitMode); +} + +static void updateArm7() +{ + switch (sState) + { + case Arm7State::Idle: + { + updateArm7IdleState(); + break; + } + case Arm7State::ExitRequested: + { + updateArm7ExitRequestedState(); + break; + } + } +} + +// Helper for clock get requests +static bool tud_audio_clock_get_request(uint8_t rhport, audio_control_request_t const *request) +{ + if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) + { + if (request->bRequest == AUDIO_CS_REQ_CUR) + { + audio_control_cur_4_t curf = { (int32_t) tu_htole32(current_sample_rate) }; + return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &curf, sizeof(curf)); + } + else if (request->bRequest == AUDIO_CS_REQ_RANGE) + { + audio_control_range_4_n_t(N_SAMPLE_RATES) rangef = + { + .wNumSubRanges = tu_htole16(N_SAMPLE_RATES) + }; + for (uint8_t i = 0; i < N_SAMPLE_RATES; i++) + { + rangef.subrange[i].bMin = (int32_t) sample_rates[i]; + rangef.subrange[i].bMax = (int32_t) sample_rates[i]; + rangef.subrange[i].bRes = 0; + } + + return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &rangef, sizeof(rangef)); + } + } + else if (request->bControlSelector == AUDIO_CS_CTRL_CLK_VALID + && request->bRequest == AUDIO_CS_REQ_CUR) + { + audio_control_cur_1_t cur_valid = { .bCur = 1 }; + return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &cur_valid, sizeof(cur_valid)); + } + return false; +} + +// Helper for clock set requests +static bool tud_audio_clock_set_request(u8 rhport, const audio_control_request_t* request, const u8* buf) +{ + (void)rhport; + + if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) + { + current_sample_rate = (u32)((const audio_control_cur_4_t*)buf)->bCur; + return true; + } + else + { + return false; + } +} + +// Helper for feature unit get requests +static bool tud_audio_feature_unit_get_request(u8 rhport, const audio_control_request_t* request) +{ + if (request->bControlSelector == AUDIO_FU_CTRL_MUTE && request->bRequest == AUDIO_CS_REQ_CUR) + { + audio_control_cur_1_t mute1 = { .bCur = mute[request->bChannelNumber] }; + return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &mute1, sizeof(mute1)); + } + else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) + { + if (request->bRequest == AUDIO_CS_REQ_RANGE) + { + audio_control_range_2_n_t(1) range_vol = + { + tu_htole16(1), + { { tu_htole16(-VOLUME_CTRL_50_DB), tu_htole16(VOLUME_CTRL_0_DB), tu_htole16(256) } } + }; + return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &range_vol, sizeof(range_vol)); + } + else if (request->bRequest == AUDIO_CS_REQ_CUR) + { + audio_control_cur_2_t cur_vol = { .bCur = tu_htole16(volume[request->bChannelNumber]) }; + return tud_audio_buffer_and_schedule_control_xfer(rhport, (const tusb_control_request_t*)request, &cur_vol, sizeof(cur_vol)); + } + } + return false; +} + +// Helper for feature unit set requests +static bool tud_audio_feature_unit_set_request(u8 rhport, const audio_control_request_t* request, const u8* buf) +{ + (void)rhport; + + if (request->bControlSelector == AUDIO_FU_CTRL_MUTE) + { + mute[request->bChannelNumber] = ((const audio_control_cur_1_t*)buf)->bCur; + return true; + } + else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) + { + volume[request->bChannelNumber] = ((const audio_control_cur_2_t*)buf)->bCur; + return true; + } + else + { + return false; + } +} + +// Invoked when audio class specific get request received for an entity +bool tud_audio_get_req_entity_cb(u8 rhport, const tusb_control_request_t* p_request) +{ + auto request = (const audio_control_request_t*)p_request; + switch (request->bEntityID) + { + case UAC2_ENTITY_CLOCK: + { + return tud_audio_clock_get_request(rhport, request); + } + case UAC2_ENTITY_FEATURE_UNIT: + { + return tud_audio_feature_unit_get_request(rhport, request); + } + default: + { + return false; + } + } +} + +// Invoked when audio class specific set request received for an entity +bool tud_audio_set_req_entity_cb(u8 rhport, const tusb_control_request_t* p_request, u8* buf) +{ + auto request = (const audio_control_request_t*)p_request; + switch (request->bEntityID) + { + case UAC2_ENTITY_FEATURE_UNIT: + { + return tud_audio_feature_unit_set_request(rhport, request, buf); + } + case UAC2_ENTITY_CLOCK: + { + return tud_audio_clock_set_request(rhport, request, buf); + } + default: + { + return false; + } + } +} + +bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, const tusb_control_request_t* p_request) +{ + (void)rhport; + + const u8 itf = tu_u16_low(tu_le16toh(p_request->wIndex)); + const u8 alt = tu_u16_low(tu_le16toh(p_request->wValue)); + + (void)itf; + (void)alt; + + if (sAudioStarted) + { + // Stop audio playback + snd_stopChannel(0); + snd_stopChannel(1); + tmr_stop(AUDIO_STREAM_PLAYER_TIMER); + rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_ackIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_sleepThread(&sAudioThread); + sReadBlock = 0; + sWriteBlock = 0; + sAudioStarted = false; + } + + return true; +} + +bool tud_audio_set_itf_cb(uint8_t rhport, const tusb_control_request_t* p_request) +{ + (void)rhport; + uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); + uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); + + spk_data_size = 0; + if (alt != 0) + { + current_resolution = resolutions_per_format[alt - 1]; + } + + return true; +} + +static void fillRingBlock(u32 block) +{ + s16* blockPtrL = &sAudioRingL[block][0]; + s16* blockPtrR = &sAudioRingR[block][0]; + + tud_audio_read(spk_buf, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 4); + + s16* src = (s16*)spk_buf; + s16* limit = (s16*)spk_buf + AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 2; + while (src < limit) + { + *blockPtrL++ = *src++; + *blockPtrR++ = *src++; + } +} + +static void audioThreadMain(void* arg) +{ + do + { + bool doUpdate = true; + while (doUpdate) + { + u32 writeBlock = sWriteBlock; + int freeBlocks = sReadBlock - writeBlock - 1; + if (freeBlocks < 0) + { + freeBlocks += AUDIO_STREAM_PLAYER_RING_BLOCKS; + } + + if (freeBlocks > AUDIO_STREAM_PLAYER_SAFETY_BLOCKS && tud_audio_available() >= AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 4) + { + fillRingBlock(writeBlock); + if (++writeBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS) + { + writeBlock = 0; + } + sWriteBlock = writeBlock; + } + else + { + doUpdate = false; + } + } + + rtos_waitEvent(&sAudioBlockEvent, false, true); + } while(true); +} + +void tud_audio_feedback_params_cb(u8 func_id, u8 alt_itf, audio_feedback_params_t* feedback_param) +{ + (void)func_id; + (void)alt_itf; + feedback_param->method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT; + feedback_param->sample_freq = current_sample_rate; +} + +bool tud_audio_rx_done_pre_read_cb(u8 rhport, u16 n_bytes_received, u8 func_id, u8 ep_out, u8 cur_alt_setting) +{ + (void)rhport; + (void)func_id; + (void)ep_out; + (void)cur_alt_setting; + + while (!sAudioStarted && tud_audio_available() >= AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * 4) + { + fillRingBlock(sWriteBlock); + sWriteBlock++; + + if (sWriteBlock == 8) + { + u32 timer = -((33513982 + current_sample_rate) / (current_sample_rate * 2)); + REG_SOUNDxSAD(0) = (u32)&sAudioRingL[0][0]; + REG_SOUNDxSAD(1) = (u32)&sAudioRingR[0][0]; + REG_SOUNDxTMR(0) = timer; + REG_SOUNDxTMR(1) = timer; + REG_SOUNDxPNT(0) = 0; + REG_SOUNDxPNT(1) = 0; + REG_SOUNDxLEN(0) = sizeof(sAudioRingL) >> 2; + REG_SOUNDxLEN(1) = sizeof(sAudioRingR) >> 2; + REG_SOUNDxCNT(0) = SOUNDCNT_VOLUME(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16; + REG_SOUNDxCNT(1) = SOUNDCNT_VOLUME(127) | SOUNDCNT_PAN(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16; + + rtos_createEvent(&sAudioBlockEvent); + rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_ackIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_setIrqFunc(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER), [] (u32 irqMask) + { + u32 readBlock = sReadBlock; + if (++readBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS) + { + readBlock = 0; + } + sReadBlock = readBlock; + rtos_signalEvent(&sAudioBlockEvent); + }); + tmr_configure(AUDIO_STREAM_PLAYER_TIMER, TMCNT_H_CLK_SYS_DIV_64, timer << 1, true); + + rtos_createThread(&sAudioThread, AUDIO_STREAM_PLAYER_THREAD_PRIORITY, audioThreadMain, + nullptr, sAudioThreadStack, sizeof(sAudioThreadStack)); + + tmr_start(AUDIO_STREAM_PLAYER_TIMER); + rtos_enableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + + sAudioStarted = true; + rtos_wakeupThread(&sAudioThread); + + snd_startChannel(0); + snd_startChannel(1); + break; + } + } + + return true; +} + +int main() +{ + sState = Arm7State::Idle; + initializeArm7(); + + while (true) + { + rtos_waitEvent(&sVBlankEvent, true, true); + updateArm7(); + } + + return 0; +} diff --git a/examples/usb-speaker/arm7/source/tusb_config.h b/examples/usb-speaker/arm7/source/tusb_config.h new file mode 100644 index 0000000..c5ab6d4 --- /dev/null +++ b/examples/usb-speaker/arm7/source/tusb_config.h @@ -0,0 +1,118 @@ +#pragma once +#include "usb_descriptors.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#define CFG_TUSB_MCU OPT_MCU_DSPICO +#endif + +#define TUP_DCD_ENDPOINT_MAX 16 + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_CUSTOM +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 1 +#define CFG_TUD_VENDOR 0 + +//-------------------------------------------------------------------- +// AUDIO CLASS DRIVER CONFIGURATION +//-------------------------------------------------------------------- + +// Enable if Full-Speed on OSX, also set feedback EP size to 3 +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0 + +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 1 + +#define CFG_TUD_AUDIO_FUNC_1_DESC_LEN TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN + +// How many formats are used, need to adjust USB descriptor if changed +#define CFG_TUD_AUDIO_FUNC_1_N_FORMATS 1 + +// Audio format type I specifications +/* 24bit/48kHz is the best quality for headset or 24bit/96kHz for 2ch speaker, + high-speed is needed beyond this */ +#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 44100 +#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2 + +// 16bit in 16bit slots +#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX 2 +#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX 16 + +// EP and buffer size - for isochronous EP's, the buffer and EP size are equal (different sizes would not make sense) +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 + +#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_EP_SZ_OUT TUD_AUDIO_EP_SIZE(CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX) + +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ (CFG_TUD_AUDIO_FUNC_1_FORMAT_1_EP_SZ_OUT*8) +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX CFG_TUD_AUDIO_FUNC_1_FORMAT_1_EP_SZ_OUT // Maximum EP IN size for all AS alternate settings used + +// Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces - We restrict us here to have a constant number for all audio functions (which means this has to be the maximum number of AS interfaces an audio function has and a second audio function with less AS interfaces just wastes a few bytes) +#define CFG_TUD_AUDIO_FUNC_1_N_AS_INT 1 + +// Size of control request buffer +#define CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ 64 + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/examples/usb-speaker/arm7/source/usb_descriptors.cpp b/examples/usb-speaker/arm7/source/usb_descriptors.cpp new file mode 100644 index 0000000..00e61c7 --- /dev/null +++ b/examples/usb-speaker/arm7/source/usb_descriptors.cpp @@ -0,0 +1,171 @@ +#include "common.h" +#include +#include "UsbStringDescriptor.h" +#include "tusb.h" +#include "usb_descriptors.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) + +const u8* tud_descriptor_device_cb(void) +{ + static const tusb_desc_device_t deviceDescriptor = + { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0110, + + // Use Interface Association Descriptor (IAD) for Audio + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = 0x01 + }; + + return (const u8*)&deviceDescriptor; +} + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN) + +#define EPNUM_AUDIO_FB 0x01 +#define EPNUM_AUDIO_OUT 0x01 + +const u8* tud_descriptor_configuration_cb(u8 index) +{ + (void)index; + + static const u8 configurationDescriptor[] = + { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + /* Standard Interface Association Descriptor (IAD) */ + TUD_AUDIO_DESC_IAD(/*_firstitf*/ 0, /*_nitfs*/ 0x02, /*_stridx*/ 0x00), + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ 0, /*_nEPs*/ 0x00, /*_stridx*/ STRID_AUDIO_INTERFACE), + /* Class-Specific AC Interface Header Descriptor(4.7.2) */ + TUD_AUDIO_DESC_CS_AC( + /*_bcdADC*/ 0x0200, + /*_category*/ AUDIO_FUNC_DESKTOP_SPEAKER, + /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN, + /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS), + /* Clock Source Descriptor(4.7.2.1) */ + TUD_AUDIO_DESC_CLK_SRC( + /*_clkid*/ UAC2_ENTITY_CLOCK, + /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_PRO_CLK, + /*_ctrl*/ (AUDIO_CTRL_RW << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), + /*_assocTerm*/ UAC2_ENTITY_INPUT_TERMINAL, + /*_stridx*/ 0x00), + /* Input Terminal Descriptor(4.7.2.4) */ + TUD_AUDIO_DESC_INPUT_TERM( + /*_termid*/ UAC2_ENTITY_INPUT_TERMINAL, + /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, + /*_assocTerm*/ 0x00, + /*_clkid*/ UAC2_ENTITY_CLOCK, + /*_nchannelslogical*/ 0x02, + /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, + /*_idxchannelnames*/ 0x00, + /*_ctrl*/ 0 * (AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS), + /*_stridx*/ 0x00), + /* Output Terminal Descriptor(4.7.2.5) */ + TUD_AUDIO_DESC_OUTPUT_TERM( + /*_termid*/ UAC2_ENTITY_OUTPUT_TERMINAL, + /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, + /*_assocTerm*/ UAC2_ENTITY_INPUT_TERMINAL, + /*_srcid*/ UAC2_ENTITY_FEATURE_UNIT, + /*_clkid*/ UAC2_ENTITY_CLOCK, + /*_ctrl*/ 0x0000, + /*_stridx*/ 0x00), + /* Feature Unit Descriptor(4.7.2.8) */ + TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL( + /*_unitid*/ UAC2_ENTITY_FEATURE_UNIT, + /*_srcid*/ UAC2_ENTITY_INPUT_TERMINAL, + /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, + /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, + /*_ctrlch2*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, + /*_stridx*/ 0x00), + /* Standard AS Interface Descriptor(4.9.1) */ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */ \ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ 1, /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ STRID_AUDIO_INTERFACE), + /* Standard AS Interface Descriptor(4.9.1) */ + /* Interface 1, Alternate 1 - alternate interface for data streaming */ \ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ 1, /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ STRID_AUDIO_INTERFACE), + /* Class-Specific AS Interface Descriptor(4.9.2) */ + TUD_AUDIO_DESC_CS_AS_INT( + /*_termid*/ UAC2_ENTITY_INPUT_TERMINAL, + /*_ctrl*/ AUDIO_CTRL_NONE, + /*_formattype*/ AUDIO_FORMAT_TYPE_I, + /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, + /*_nchannelsphysical*/ 0x02, + /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, + /*_stridx*/ 0x00), + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */ + TUD_AUDIO_DESC_TYPE_I_FORMAT(CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX, CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX), + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */ + TUD_AUDIO_DESC_STD_AS_ISO_EP( + /*_ep*/ EPNUM_AUDIO_OUT, + /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), + /*_maxEPsize*/ CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX, + /*_interval*/ 0x01), + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */ + TUD_AUDIO_DESC_CS_AS_ISO_EP( + /*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, + /*_ctrl*/ AUDIO_CTRL_NONE, + /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC, + /*_lockdelay*/ 0x0001), + /* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ EPNUM_AUDIO_FB | 0x80, /*_epsize*/ 4, /*_interval*/ 1) + }; + + return configurationDescriptor; +} + +const u16* tud_descriptor_string_cb(u8 index, u16 langid) +{ + switch (index) + { + case STRID_LANGID: + { + static const UsbStringDescriptor<2> descriptor(0x0409); + return (const u16*)&descriptor; + } + case STRID_MANUFACTURER: + { + return USB_STRING_DESCRIPTOR(u"LNH"); + } + case STRID_PRODUCT: + { + return USB_STRING_DESCRIPTOR(u"DSpico Speaker Example"); + } + case STRID_SERIAL: + { + return USB_STRING_DESCRIPTOR(u"123456789"); + } + case STRID_AUDIO_INTERFACE: + { + return USB_STRING_DESCRIPTOR(u"DSpico Speakers"); + } + default: + { + return nullptr; + } + } +} diff --git a/examples/usb-speaker/arm7/source/usb_descriptors.h b/examples/usb-speaker/arm7/source/usb_descriptors.h new file mode 100644 index 0000000..655f031 --- /dev/null +++ b/examples/usb-speaker/arm7/source/usb_descriptors.h @@ -0,0 +1,37 @@ +#pragma once + +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_AUDIO_INTERFACE +}; + +enum +{ + ITF_NUM_AUDIO_CONTROL = 0, + ITF_NUM_AUDIO_STREAMING, + ITF_NUM_TOTAL +}; + +#define UAC2_ENTITY_INPUT_TERMINAL 0x01 +#define UAC2_ENTITY_FEATURE_UNIT 0x02 +#define UAC2_ENTITY_OUTPUT_TERMINAL 0x03 +#define UAC2_ENTITY_CLOCK 0x04 + +#define TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN) diff --git a/examples/usb-speaker/arm9/Makefile b/examples/usb-speaker/arm9/Makefile new file mode 100644 index 0000000..58a169c --- /dev/null +++ b/examples/usb-speaker/arm9/Makefile @@ -0,0 +1,152 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +# DATA is a list of directories containing binary files +# all directories are relative to this makefile +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := source \ + source/core +INCLUDES := include source ../common +DATA := data +GRAPHICS := gfx + + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -marm -mthumb-interwork -DLIBTWL_ARM9 -DARM9 + +CFLAGS := -g -Wall -O2\ + -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\ + -ffunction-sections -fdata-sections\ + -ffast-math \ + -fno-devirtualize-speculatively \ + -Werror=return-type \ + $(ARCH) + +CFLAGS += $(INCLUDE) +CXXFLAGS := $(CFLAGS) -std=gnu++23 -Wno-volatile -fno-rtti -fno-exceptions -fno-threadsafe-statics\ + -Wsuggest-override -Werror=suggest-override + +CFLAGS += -Werror=implicit-function-declaration + +ASFLAGS := -g $(ARCH) $(INCLUDE) -march=armv5te -mtune=arm946e-s + +LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map),--gc-sections,--use-blx -ffunction-sections -fdata-sections + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -ltwl9 -lnds9 + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) $(CURDIR)/../../../libs/libtwl/libtwl9 $(CURDIR)/../../../libs/libtwl/common + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export ARM9ELF := $(CURDIR)/$(TARGET).elf +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(PNGFILES:.png=.o)\ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) *.elf *.nds* *.bin + + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(ARM9ELF) : $(OFILES) + @echo linking $(notdir $@) + @$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +%.nft2.o : %.nft2 + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# This rule creates assembly source files using grit +# grit takes an image file and a .grit describing how the file is to be processed +# add additional rules like this for each image extension +# you use in the graphics folders +#--------------------------------------------------------------------------------- +%.s %.h: %.png %.grit +#--------------------------------------------------------------------------------- + grit $< -fts -o$* + +-include $(DEPSDIR)/*.d + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/examples/usb-speaker/arm9/source/main.cpp b/examples/usb-speaker/arm9/source/main.cpp new file mode 100644 index 0000000..22070c0 --- /dev/null +++ b/examples/usb-speaker/arm9/source/main.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static rtos_event_t sVblankEvent; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVblankEvent); +} + +int main(int argc, char* argv[]) +{ + *(vu32*)0x04000000 = 0x10000; + *(vu16*)0x05000000 = 31 << 5; + *(vu16*)0x0400006C = 0; + + mem_setDsCartridgeCpu(EXMEMCNT_SLOT1_CPU_ARM7); + + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + rtos_createEvent(&sVblankEvent); + + while (ipc_getArm7SyncBits() != 7); + + ipc_setArm9SyncBits(6); + + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); + + while (true) + { + rtos_waitEvent(&sVblankEvent, true, true); + } + + return 0; +} \ No newline at end of file diff --git a/libs/libtwl b/libs/libtwl new file mode 160000 index 0000000..9f75e53 --- /dev/null +++ b/libs/libtwl @@ -0,0 +1 @@ +Subproject commit 9f75e53c30889f0227199760a9e3a2aa9e8dca0c diff --git a/libs/tinyusb b/libs/tinyusb new file mode 160000 index 0000000..8eeddaa --- /dev/null +++ b/libs/tinyusb @@ -0,0 +1 @@ +Subproject commit 8eeddaab364e413153ebd0a8302f85dfb2e60e9f diff --git a/platform/DSPicoUsb.h b/platform/DSPicoUsb.h new file mode 100644 index 0000000..1f85a53 --- /dev/null +++ b/platform/DSPicoUsb.h @@ -0,0 +1,29 @@ +#pragma once +#include + +#define DSPICO_CMD_USB_COMMAND(command, arguments) (0xE800000000000000ull | ((u64)(command) << 48) | (arguments)) +#define DSPICO_CMD_USB_COMMAND_INIT DSPICO_CMD_USB_COMMAND(1, 0) +#define DSPICO_CMD_USB_COMMAND_BEGIN_SET_ADDRESS DSPICO_CMD_USB_COMMAND(2, 0) +#define DSPICO_CMD_USB_COMMAND_REMOTE_WAKEUP DSPICO_CMD_USB_COMMAND(3, 0) +#define DSPICO_CMD_USB_COMMAND_CONNECT DSPICO_CMD_USB_COMMAND(4, 0) +#define DSPICO_CMD_USB_COMMAND_DISCONNECT DSPICO_CMD_USB_COMMAND(5, 0) +#define DSPICO_CMD_USB_COMMAND_SOF_ENABLE DSPICO_CMD_USB_COMMAND(6, 0) +#define DSPICO_CMD_USB_COMMAND_SOF_DISABLE DSPICO_CMD_USB_COMMAND(7, 0) +#define DSPICO_CMD_USB_COMMAND_EP_CLOSE_ALL DSPICO_CMD_USB_COMMAND(8, 0) +#define DSPICO_CMD_USB_COMMAND_EP_STALL(ep) DSPICO_CMD_USB_COMMAND(9, (u64)(ep) << 40) +#define DSPICO_CMD_USB_COMMAND_EP_CLEAR_STALL(ep) DSPICO_CMD_USB_COMMAND(10, (u64)(ep) << 40) +#define DSPICO_CMD_USB_COMMAND_EP_CLOSE(ep) DSPICO_CMD_USB_COMMAND(11, (u64)(ep) << 40) +#define DSPICO_CMD_USB_COMMAND_FINISH_SET_ADDRESS(addr) DSPICO_CMD_USB_COMMAND(12, (u64)(addr) << 40) +#define DSPICO_CMD_USB_COMMAND_EP_OPEN(ep, maxPktSize, xfer) DSPICO_CMD_USB_COMMAND(13, ((u64)(ep) << 40) | ((u64)((xfer) & 3) << 32) | ((u64)((maxPktSize) & 0x7FF))) +#define DSPICO_CMD_USB_COMMAND_CLEAR_EVENT_QUEUE DSPICO_CMD_USB_COMMAND(14, 0) +#define DSPICO_CMD_USB_COMMAND_DEINIT DSPICO_CMD_USB_COMMAND(15, 0) +#define DSPICO_CMD_USB_COMMAND_BEGIN_TRANSFER(ep, offset, length) DSPICO_CMD_USB_COMMAND(16, ((u64)(ep) << 40) | ((u64)(offset) << 32) | (length)) +#define DSPICO_CMD_USB_COMMAND_INTERRUPT_ENABLE DSPICO_CMD_USB_COMMAND(17, 0) +#define DSPICO_CMD_USB_COMMAND_INTERRUPT_DISABLE DSPICO_CMD_USB_COMMAND(18, 0) + +#define DSPICO_CMD_USB_WRITE_DATA(offset, endpoint, isLast, totalLength) (0xE900000000000000ull | ((u64)(offset) << 48) | ((u64)(endpoint) << 40) | ((u64)(isLast) << 32) | (totalLength)) +#define DSPICO_CMD_USB_READ_DATA(offset, endpoint) (0xEA00000000000000ull | ((u64)(offset) << 32) | ((u64)(endpoint) << 40)) +#define DSPICO_CMD_USB_GET_EVENT 0xEB00000000000000ull + +#define DSPICO_USB_DEFAULT_COMMAND_SETTINGS (MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_0 | MCCNT1_CMD_SCRAMBLE |\ + MCCNT1_LATENCY2(0) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0)) diff --git a/platform/DSPicoUsbInEndpoint.cpp b/platform/DSPicoUsbInEndpoint.cpp new file mode 100644 index 0000000..f51caf0 --- /dev/null +++ b/platform/DSPicoUsbInEndpoint.cpp @@ -0,0 +1,100 @@ +#include "common.h" +#include "libtwl/dma/dmaNitro.h" +#include "tusb.h" +#include "device/dcd.h" +#include "DSPicoUsb.h" +#include "DSPicoUsbInEndpoint.h" + +void DSPicoUsbInEndpoint::BeginTransfer(const u8* buffer, u32 length) +{ + rtos_lockMutex(&_mutex); + { + _buffer = buffer; + _bufferLength = length; + _remaining = length; + _bufferOffset = 0; + _currentDSPicoBuffer = 0; + _transferActive = true; + SendUsbBlock(true); + SendUsbBlock(false); + } + rtos_unlockMutex(&_mutex); +} + +void DSPicoUsbInEndpoint::ProcessTransferCompleteEvent(u32 transferredBytes) +{ + rtos_lockMutex(&_mutex); + { + if (_transferActive) + { + _remaining -= transferredBytes; + if (_remaining == 0) + { + dcd_event_xfer_complete(0, _endpointAddress, _bufferLength, XFER_RESULT_SUCCESS, true); + _transferActive = false; + } + else + { + u32 length = _remaining; + if (length > 512) + { + length = 512; + } + rtos_lockMutex(&gCardMutex); + { + card_romSetCmd(DSPICO_CMD_USB_COMMAND_BEGIN_TRANSFER(_endpointAddress, 1 - _currentDSPicoBuffer, length)); + card_romStartXfer(DSPICO_USB_DEFAULT_COMMAND_SETTINGS, false); + card_romWaitBusy(); + } + rtos_unlockMutex(&gCardMutex); + + SendUsbBlock(false); + } + } + else + { + // This seems to happen only once on the control endpoint. Idk why. + dcd_event_xfer_complete(0, _endpointAddress, transferredBytes, XFER_RESULT_SUCCESS, true); + } + } + rtos_unlockMutex(&_mutex); +} + +void DSPicoUsbInEndpoint::SendUsbBlock(bool startTransfer) +{ + u32 length = _bufferLength - _bufferOffset; + if (length > 0) + { + if (length > 512) + { + length = 512; + } + + rtos_lockMutex(&gCardMutex); + { + REG_DMA3SAD = (u32)(_buffer + _bufferOffset); + REG_DMA3DAD = (u32)®_MCD1; + REG_DMA3CNT = 0xA6400001; + card_romSetCmd(DSPICO_CMD_USB_WRITE_DATA(_currentDSPicoBuffer, _endpointAddress, startTransfer, length)); + card_romStartXfer(MCCNT1_DIR_WRITE | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_512 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(8) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0), false); + card_romWaitBusy(); + REG_DMA3CNT = 0; + } + rtos_unlockMutex(&gCardMutex); + + _bufferOffset += length; + _currentDSPicoBuffer = 1 - _currentDSPicoBuffer; + } + else if (startTransfer) + { + // zero length transfer + rtos_lockMutex(&gCardMutex); + { + card_romSetCmd(DSPICO_CMD_USB_WRITE_DATA(_currentDSPicoBuffer, _endpointAddress, true, 0)); + card_romStartXfer(DSPICO_USB_DEFAULT_COMMAND_SETTINGS, false); + card_romWaitBusy(); + } + rtos_unlockMutex(&gCardMutex); + } +} diff --git a/platform/DSPicoUsbInEndpoint.h b/platform/DSPicoUsbInEndpoint.h new file mode 100644 index 0000000..c41e93e --- /dev/null +++ b/platform/DSPicoUsbInEndpoint.h @@ -0,0 +1,28 @@ +#pragma once +#include "libtwl/rtos/rtosMutex.h" + +class DSPicoUsbInEndpoint +{ +public: + explicit DSPicoUsbInEndpoint(u8 endpointAddress) + : _endpointAddress(endpointAddress), _currentDSPicoBuffer(0), _buffer(nullptr) + , _bufferLength(0), _remaining(0), _bufferOffset(0), _transferActive(false) + { + rtos_createMutex(&_mutex); + } + + void BeginTransfer(const u8* buffer, u32 length); + void ProcessTransferCompleteEvent(u32 transferredBytes); + +private: + u8 _endpointAddress; + u8 _currentDSPicoBuffer; + const u8* _buffer; + u32 _bufferLength; + u32 _remaining; + u32 _bufferOffset; + rtos_mutex_t _mutex; + bool _transferActive; + + void SendUsbBlock(bool startTransfer); +}; diff --git a/platform/DSPicoUsbOutEndpoint.cpp b/platform/DSPicoUsbOutEndpoint.cpp new file mode 100644 index 0000000..1a1b9fe --- /dev/null +++ b/platform/DSPicoUsbOutEndpoint.cpp @@ -0,0 +1,141 @@ +#include "common.h" +#include "libtwl/dma/dmaNitro.h" +#include "tusb.h" +#include "device/dcd.h" +#include "DSPicoUsb.h" +#include "DSPicoUsbOutEndpoint.h" + +void DSPicoUsbOutEndpoint::BeginTransfer(u8* buffer, u32 length) +{ + rtos_lockMutex(&_mutex); + { + _buffer = buffer; + _bufferLength = length; + _bufferOffset = 0; + _totalReceived = 0; + _currentDSPicoBuffer = 0; + _transferActive = true; + u32 firstReceiveLength = _bufferLength; + if (firstReceiveLength > 512) + { + firstReceiveLength = 512; + } + rtos_lockMutex(&gCardMutex); + { + card_romSetCmd(DSPICO_CMD_USB_COMMAND_BEGIN_TRANSFER(_endpointAddress, _currentDSPicoBuffer, firstReceiveLength)); + card_romStartXfer(DSPICO_USB_DEFAULT_COMMAND_SETTINGS, false); + card_romWaitBusy(); + } + rtos_unlockMutex(&gCardMutex); + } + rtos_unlockMutex(&_mutex); +} + +void DSPicoUsbOutEndpoint::ProcessTransferCompleteEvent(u32 transferredBytes) +{ + rtos_lockMutex(&_mutex); + { + if (_transferActive) + { + _totalReceived += transferredBytes; + int length = _bufferLength - _bufferOffset - 512; + if (length > 512) + { + length = 512; + } + if (transferredBytes == 512) + { + if (length > 0) + { + rtos_lockMutex(&gCardMutex); + { + card_romSetCmd(DSPICO_CMD_USB_COMMAND_BEGIN_TRANSFER(_endpointAddress, 1 - _currentDSPicoBuffer, length)); + card_romStartXfer(DSPICO_USB_DEFAULT_COMMAND_SETTINGS, false); + card_romWaitBusy(); + } + rtos_unlockMutex(&gCardMutex); + } + } + + ReceiveUsbBlock(); + + if (transferredBytes < 512 || length == 0) + { + dcd_event_xfer_complete(0, _endpointAddress, _bufferOffset, XFER_RESULT_SUCCESS, true); + _transferActive = false; + } + } + else + { + // idk + dcd_event_xfer_complete(0, _endpointAddress, transferredBytes, XFER_RESULT_SUCCESS, true); + } + } + rtos_unlockMutex(&_mutex); +} + +void DSPicoUsbOutEndpoint::ReceiveUsbBlock() +{ + u32 length = _totalReceived - _bufferOffset; + if (length > 0) + { + if (length > 512) + { + length = 512; + } + rtos_lockMutex(&gCardMutex); + { + if (length == 512) + { + REG_DMA3SAD = (u32)®_MCD1; + REG_DMA3DAD = (u32)(_buffer + _bufferOffset); + REG_DMA3CNT = 0xA7000001; + } + card_romSetCmd(DSPICO_CMD_USB_READ_DATA(_currentDSPicoBuffer, _endpointAddress)); + card_romStartXfer(MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_512 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(4) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_LATENCY1(0), false); + if (length == 512) + { + card_romWaitBusy(); + REG_DMA3CNT = 0; + } + else if (length & 3) + { + u8* target = _buffer + _bufferOffset + length; + u8* dst = _buffer + _bufferOffset; + do + { + // Read data if available + if (card_romIsDataReady()) + { + u32 data = card_romGetData(); + if (dst + 3 < target) + { + *(u32*)dst = data; + dst += 4; + } + else if (dst < target) + { + *dst++ = data & 0xFF; + if (dst < target) + { + *dst++ = (data >> 8) & 0xFF; + if (dst < target) + { + *dst++ = (data >> 16) & 0xFF; + } + } + } + } + } while (card_romIsBusy()); + } + else + { + card_romCpuRead((u32*)(_buffer + _bufferOffset), length >> 2); + } + } + rtos_unlockMutex(&gCardMutex); + _bufferOffset += length; + _currentDSPicoBuffer = 1 - _currentDSPicoBuffer; + } +} diff --git a/platform/DSPicoUsbOutEndpoint.h b/platform/DSPicoUsbOutEndpoint.h new file mode 100644 index 0000000..e0ff4d4 --- /dev/null +++ b/platform/DSPicoUsbOutEndpoint.h @@ -0,0 +1,26 @@ +#pragma once + +class DSPicoUsbOutEndpoint +{ +public: + explicit DSPicoUsbOutEndpoint(u8 endpointAddress) + : _endpointAddress(endpointAddress), _currentDSPicoBuffer(0), _buffer(nullptr) + , _bufferLength(0), _bufferOffset(0), _totalReceived(0), _transferActive(false) + { + rtos_createMutex(&_mutex); + } + + void BeginTransfer(u8* buffer, u32 length); + void ProcessTransferCompleteEvent(u32 transferredBytes); +private: + u8 _endpointAddress; + u8 _currentDSPicoBuffer; + u8* _buffer; + u32 _bufferLength; + u32 _bufferOffset; + u32 _totalReceived; + bool _transferActive; + rtos_mutex_t _mutex; + + void ReceiveUsbBlock(); +}; diff --git a/platform/UsbStringDescriptor.h b/platform/UsbStringDescriptor.h new file mode 100644 index 0000000..d822901 --- /dev/null +++ b/platform/UsbStringDescriptor.h @@ -0,0 +1,25 @@ +#pragma once +#include "tusb.h" + +template +struct UsbStringDescriptor +{ + u8 length = 2 * N; + u8 descriptorType = TUSB_DESC_STRING; + char16_t string[N - 1]; + + explicit constexpr UsbStringDescriptor(const char16_t(&str)[N]) + { + for (size_t i = 0; i < N - 1; i++) + { + string[i] = str[i]; + } + } + + explicit constexpr UsbStringDescriptor(u16 languageId) + { + string[0] = languageId; + } +}; + +#define USB_STRING_DESCRIPTOR(str) ({ static const UsbStringDescriptor descriptor(str); (const u16*)&descriptor; }) diff --git a/platform/dcd_dspico.cpp b/platform/dcd_dspico.cpp new file mode 100644 index 0000000..ee88dbe --- /dev/null +++ b/platform/dcd_dspico.cpp @@ -0,0 +1,315 @@ +#include "common.h" +#include +#include +#include +#include +#include "tusb_option.h" +#include "tusb.h" + +#if CFG_TUD_ENABLED && CFG_TUSB_MCU == OPT_MCU_DSPICO + +#include "device/dcd.h" +#include "DSPicoUsb.h" +#include "DSPicoUsbInEndpoint.h" +#include "DSPicoUsbOutEndpoint.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +static rtos_event_t sCardIrqEvent; +static rtos_thread_t sUsbThread; +static u32 sUsbThreadStack[512]; + +static DSPicoUsbInEndpoint sInEndpoints[16] = +{ + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 0), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 1), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 2), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 3), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 4), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 5), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 6), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 7), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 8), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 9), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 10), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 11), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 12), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 13), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 14), + DSPicoUsbInEndpoint(TUSB_DIR_IN_MASK | 15) +}; + +static DSPicoUsbOutEndpoint sOutEndpoints[16] = +{ + DSPicoUsbOutEndpoint(0), + DSPicoUsbOutEndpoint(1), + DSPicoUsbOutEndpoint(2), + DSPicoUsbOutEndpoint(3), + DSPicoUsbOutEndpoint(4), + DSPicoUsbOutEndpoint(5), + DSPicoUsbOutEndpoint(6), + DSPicoUsbOutEndpoint(7), + DSPicoUsbOutEndpoint(8), + DSPicoUsbOutEndpoint(9), + DSPicoUsbOutEndpoint(10), + DSPicoUsbOutEndpoint(11), + DSPicoUsbOutEndpoint(12), + DSPicoUsbOutEndpoint(13), + DSPicoUsbOutEndpoint(14), + DSPicoUsbOutEndpoint(15) +}; + +static void sendCommand(u64 command) +{ + rtos_lockMutex(&gCardMutex); + card_romSetCmd(command); + card_romStartXfer(DSPICO_USB_DEFAULT_COMMAND_SETTINGS, false); + card_romWaitBusy(); + rtos_unlockMutex(&gCardMutex); +} + +static void cardIrq(u32 irqMask) +{ + rtos_signalEvent(&sCardIrqEvent); +} + +static void usbThreadMain(void* arg) +{ + while (true) + { + rtos_waitEvent(&sCardIrqEvent, false, true); + + u32 event; + bool lastEvent; + do + { + rtos_lockMutex(&gCardMutex); + card_romSetCmd(DSPICO_CMD_USB_GET_EVENT); + card_romStartXfer(MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_4 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(4) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0), false); + card_romCpuRead(&event, 1); + rtos_unlockMutex(&gCardMutex); + lastEvent = (event >> 31) != 0; + event &= ~(1 << 31); + if (event == 0) + { + // USB_EVENT_NONE + break; + } + else if ((event >> 30) == 1) + { + // USB_EVENT_SETUP_RECEIVED + tusb_control_request_t setup; + setup.wLength = (event >> 16) & 0x1FFF; + u32 direction = (event >> 29) & 1; + setup.wIndex = event & 0xFFFF; + + rtos_lockMutex(&gCardMutex); + card_romSetCmd(DSPICO_CMD_USB_GET_EVENT); + card_romStartXfer(MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_4 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(4) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0), false); + card_romCpuRead(&event, 1); + rtos_unlockMutex(&gCardMutex); + lastEvent = (event >> 31) != 0; + event &= ~(1 << 31); + setup.wValue = event & 0xFFFF; + setup.bRequest = (event >> 16) & 0xFF; + setup.bmRequestType = (event >> 24) & 0x7F; + setup.bmRequestType_bit.direction = direction; + dcd_event_setup_received(0, (const u8*)&setup, true); + } + else if ((event >> 28) == 2) + { + // USB_EVENT_SOF + dcd_event_sof(0, event & 0x7FF, true); + } + else if ((event >> 28) == 3) + { + // USB_EVENT_XFER_COMPLETE + u32 endpoint = event & 0xFF; + u32 transferredBytes = (event >> 8) & 0x1FFF; + if (tu_edpt_dir(endpoint) == TUSB_DIR_IN) + { + sInEndpoints[endpoint & ~0x80].ProcessTransferCompleteEvent(transferredBytes); + } + else + { + sOutEndpoints[endpoint & ~0x80].ProcessTransferCompleteEvent(transferredBytes); + } + } + else if (event == 1) + { + // USB_EVENT_BUS_RESET + dcd_event_bus_reset(0, TUSB_SPEED_FULL, true); + } + else if (event == 2) + { + // USB_EVENT_UNPLUGGED + dcd_event_bus_signal(0, DCD_EVENT_UNPLUGGED, true); + } + else if (event == 3) + { + // USB_EVENT_SUSPEND + dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); + } + else if (event == 4) + { + // USB_EVENT_RESUME + dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); + } + else + { + // invalid + } + } while (!lastEvent); + } +} + +/*------------------------------------------------------------------*/ +/* Device API + *------------------------------------------------------------------*/ + +// Initialize controller to device mode +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) +{ + (void) rhport; + (void) rh_init; + rtos_createEvent(&sCardIrqEvent); + rtos_disableIrqMask(RTOS_IRQ_DS_SLOTA_IREQ); + rtos_setIrqFunc(RTOS_IRQ_DS_SLOTA_IREQ, cardIrq); + rtos_ackIrqMask(RTOS_IRQ_DS_SLOTA_IREQ); + rtos_createThread(&sUsbThread, 2, usbThreadMain, NULL, sUsbThreadStack, sizeof(sUsbThreadStack)); + rtos_wakeupThread(&sUsbThread); + sendCommand(DSPICO_CMD_USB_COMMAND_INIT); + return true; +} + +bool dcd_deinit(uint8_t rhport) +{ + (void) rhport; + rtos_disableIrqMask(RTOS_IRQ_DS_SLOTA_IREQ); + rtos_setIrqFunc(RTOS_IRQ_DS_SLOTA_IREQ, NULL); + rtos_ackIrqMask(RTOS_IRQ_DS_SLOTA_IREQ); + sendCommand(DSPICO_CMD_USB_COMMAND_DEINIT); + return true; +} + +// Enable device interrupt +void dcd_int_enable(uint8_t rhport) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_INTERRUPT_ENABLE); + rtos_enableIrqMask(RTOS_IRQ_DS_SLOTA_IREQ); +} + +// Disable device interrupt +void dcd_int_disable(uint8_t rhport) +{ + (void) rhport; + rtos_disableIrqMask(RTOS_IRQ_DS_SLOTA_IREQ); + sendCommand(DSPICO_CMD_USB_COMMAND_INTERRUPT_DISABLE); +} + +// Receive Set Address request, mcu port must also include status IN response +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) +{ + (void) rhport; + (void) dev_addr; + sendCommand(DSPICO_CMD_USB_COMMAND_BEGIN_SET_ADDRESS); +} + +// Wake up host +void dcd_remote_wakeup(uint8_t rhport) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_REMOTE_WAKEUP); +} + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_CONNECT); +} + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_DISCONNECT); +} + +void dcd_sof_enable(uint8_t rhport, bool en) +{ + (void) rhport; + sendCommand(en ? DSPICO_CMD_USB_COMMAND_SOF_ENABLE : DSPICO_CMD_USB_COMMAND_SOF_DISABLE); +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) +{ + (void) rhport; + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS) + { + sendCommand(DSPICO_CMD_USB_COMMAND_FINISH_SET_ADDRESS((u8)request->wValue)); + } +} + +// Configure endpoint's registers according to descriptor +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_EP_OPEN( + ep_desc->bEndpointAddress, ep_desc->wMaxPacketSize, ep_desc->bmAttributes.xfer)); + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_EP_CLOSE_ALL); +} + +// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) +{ + (void) rhport; + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) + { + sInEndpoints[ep_addr & ~0x80].BeginTransfer(buffer, total_bytes); + } + else + { + sOutEndpoints[ep_addr & ~0x80].BeginTransfer(buffer, total_bytes); + } + return true; +} + +// Stall endpoint +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_EP_STALL(ep_addr)); +} + +// clear stall, data toggle is also reset to DATA0 +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_EP_CLEAR_STALL(ep_addr)); +} + +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + sendCommand(DSPICO_CMD_USB_COMMAND_EP_CLOSE(ep_addr)); +} + +#endif diff --git a/platform/tusb_os_custom.h b/platform/tusb_os_custom.h new file mode 100644 index 0000000..d770417 --- /dev/null +++ b/platform/tusb_os_custom.h @@ -0,0 +1,161 @@ +#pragma once +#include "common.h" +#include "libtwl/rtos/rtosMutex.h" +#include "libtwl/rtos/rtosEvent.h" + +typedef rtos_event_t osal_semaphore_def_t; +typedef rtos_event_t* osal_semaphore_t; + +static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) +{ + rtos_createEvent(semdef); + return semdef; +} + +static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) +{ + (void) semd_hdl; + return true; +} + +static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) +{ + rtos_signalEvent(sem_hdl); + return true; +} + +static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) +{ + (void) msec; + rtos_waitEvent(sem_hdl, false, true); + return true; +} + +static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) +{ + rtos_clearEvent(sem_hdl); +} + +typedef rtos_mutex_t osal_mutex_def_t; +typedef rtos_mutex_t* osal_mutex_t; + +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) +{ + rtos_createMutex(mdef); + return mdef; +} + +static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) +{ + (void) mutex_hdl; + return true; +} + +static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) +{ + if (msec == OSAL_TIMEOUT_NOTIMEOUT) + { + return rtos_tryLockMutex(mutex_hdl); + } + else + { + rtos_lockMutex(mutex_hdl); + return true; + } +} + +static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) +{ + rtos_unlockMutex(mutex_hdl); + return true; +} + +typedef struct +{ + rtos_event_t event; + volatile uint32_t readPtr; + volatile uint32_t writePtr; + uint32_t elementSize; + uint32_t bufferSize; + uint8_t* buffer; +} osal_queue_def_t; + +typedef osal_queue_def_t* osal_queue_t; + +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ + uint8_t _name##_buf[(_depth + 1)*sizeof(_type)]; \ + osal_queue_def_t _name = { \ + .readPtr = 0, \ + .writePtr = 0, \ + .elementSize = sizeof(_type), \ + .bufferSize = (_depth + 1)*sizeof(_type), \ + .buffer = _name##_buf \ + } + +static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) +{ + rtos_createEvent(&qdef->event); + qdef->readPtr = 0; + qdef->writePtr = 0; + return qdef; +} + +static inline bool osal_queue_delete(osal_queue_t qhdl) +{ + (void) qhdl; + return true; +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) +{ + do + { + uint32_t readPtr = qhdl->readPtr; + uint32_t writePtr = qhdl->writePtr; + if (readPtr != writePtr) + { + memcpy(data, &qhdl->buffer[readPtr], qhdl->elementSize); + readPtr += qhdl->elementSize; + if (readPtr == qhdl->bufferSize) + { + readPtr = 0; + } + qhdl->readPtr = readPtr; + return true; + } + + if (msec == OSAL_TIMEOUT_NOTIMEOUT) + { + return false; + } + + rtos_waitEvent(&qhdl->event, false, true); + } while (true); +} + +static inline bool osal_queue_send(osal_queue_t qhdl, const void* data, bool in_isr) +{ + uint32_t readPtr = qhdl->readPtr; + uint32_t writePtr = qhdl->writePtr; + uint32_t newWritePtr = writePtr + qhdl->elementSize; + if (newWritePtr == qhdl->bufferSize) + { + newWritePtr = 0; + } + if (newWritePtr == readPtr) + { + return false; + } + + memcpy(&qhdl->buffer[writePtr], data, qhdl->elementSize); + qhdl->writePtr = newWritePtr; + rtos_signalEvent(&qhdl->event); + return true; +} + +static inline bool osal_queue_empty(osal_queue_t qhdl) +{ + qhdl->readPtr = 0; + qhdl->writePtr = 0; + return true; +}