first commit

This commit is contained in:
Adrian Siekierka 2024-11-02 14:24:09 +01:00
commit 53237a9022
14 changed files with 1080 additions and 0 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tinyusb"]
path = tinyusb
url = https://github.com/asiekierka/tinyusb

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2019 Ha Thach (tinyusb.org)
Copyright (c) 2024 Adrian "asie" Siekierka
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.

304
Makefile Normal file
View File

@ -0,0 +1,304 @@
# SPDX-License-Identifier: CC0-1.0
#
# SPDX-FileContributor: Antonio Niño Díaz, 2023-2024
export BLOCKSDS ?= /opt/blocksds/core
export BLOCKSDSEXT ?= /opt/blocksds/external
export WONDERFUL_TOOLCHAIN ?= /opt/wonderful
ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/
# User config
# ===========
NAME := nrio-usb-disk
GAME_TITLE := nrio-usb-disk
GAME_SUBTITLE :=
GAME_AUTHOR := v0.1
GAME_ICON := icon.png
# DLDI and internal SD slot of DSi
# --------------------------------
# Root folder of the SD image
SDROOT := sdroot
# Name of the generated image it "DSi-1.sd" for no$gba in DSi mode
SDIMAGE := image.bin
# Source code paths
# -----------------
SOURCEDIRS := source
INCLUDEDIRS := source
GFXDIRS :=
BINDIRS :=
AUDIODIRS :=
# A single directory that is the root of NitroFS:
NITROFSDIR :=
# Defines passed to all files
# ---------------------------
DEFINES := -DPICOLIBC_LONG_LONG_PRINTF_SCANF
# Libraries
# ---------
LIBS := -lmm9 -lnds9
LIBDIRS := $(BLOCKSDS)/libs/maxmod \
$(BLOCKSDS)/libs/libnds
# Build artifacts
# ---------------
BUILDDIR := build/$(NAME)
ELF := build/$(NAME).elf
DUMP := build/$(NAME).dump
MAP := build/$(NAME).map
ROM := $(NAME).nds
# If NITROFSDIR is set, the soundbank created by mmutil will be saved to NitroFS
SOUNDBANKINFODIR := $(BUILDDIR)/maxmod
ifeq ($(strip $(NITROFSDIR)),)
SOUNDBANKDIR := $(BUILDDIR)/maxmod
else
SOUNDBANKDIR := $(BUILDDIR)/maxmod_nitrofs
endif
# Tools
# -----
PREFIX := $(ARM_NONE_EABI_PATH)arm-none-eabi-
CC := $(PREFIX)gcc
CXX := $(PREFIX)g++
LD := $(PREFIX)gcc
OBJDUMP := $(PREFIX)objdump
MKDIR := mkdir
RM := rm -rf
# Verbose flag
# ------------
ifeq ($(VERBOSE),1)
V :=
else
V := @
endif
# Source files
# ------------
ifneq ($(BINDIRS),)
SOURCES_BIN := $(shell find -L $(BINDIRS) -name "*.bin")
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(BINDIRS))
endif
ifneq ($(GFXDIRS),)
SOURCES_PNG := $(shell find -L $(GFXDIRS) -name "*.png")
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(GFXDIRS))
endif
ifneq ($(AUDIODIRS),)
SOURCES_AUDIO := $(shell find -L $(AUDIODIRS) -regex '.*\.\(it\|mod\|s3m\|wav\|xm\)')
ifneq ($(SOURCES_AUDIO),)
INCLUDEDIRS += $(SOUNDBANKINFODIR)
endif
endif
SOURCES_S := $(shell find -L $(SOURCEDIRS) -name "*.s")
SOURCES_C := $(shell find -L $(SOURCEDIRS) -name "*.c")
SOURCES_CPP := $(shell find -L $(SOURCEDIRS) -name "*.cpp")
# TinyUSB support
# ---------------
TINYUSB_PATH := tinyusb
TINYUSB_DEBUG := 0
include $(TINYUSB_PATH)/hw/bsp/nds_nrio/blocksds.mk
# Compiler and linker flags
# -------------------------
ARCH := -mthumb -mcpu=arm946e-s+nofp
SPECS := $(BLOCKSDS)/sys/crts/ds_arm9.specs
WARNFLAGS := -Wall
ifeq ($(SOURCES_CPP),)
LIBS += -lc
else
LIBS += -lstdc++ -lc
endif
INCLUDEFLAGS := $(foreach path,$(INCLUDEDIRS),-I$(path)) \
$(foreach path,$(LIBDIRS),-I$(path)/include)
LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib)
ASFLAGS += -x assembler-with-cpp $(INCLUDEFLAGS) $(DEFINES) \
$(ARCH) -ffunction-sections -fdata-sections \
-specs=$(SPECS)
CFLAGS += -std=gnu17 $(WARNFLAGS) $(INCLUDEFLAGS) $(DEFINES) \
$(ARCH) -O2 -ffunction-sections -fdata-sections \
-specs=$(SPECS) -flto
CXXFLAGS += -std=gnu++17 $(WARNFLAGS) $(INCLUDEFLAGS) $(DEFINES) \
$(ARCH) -O2 -ffunction-sections -fdata-sections \
-fno-exceptions -fno-rtti \
-specs=$(SPECS)
LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) -Wl,-Map,$(MAP) $(DEFINES) \
-Wl,--start-group $(LIBS) -Wl,--end-group -specs=$(SPECS) -flto
# Intermediate build files
# ------------------------
OBJS_ASSETS := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_PNG)))
HEADERS_ASSETS := $(patsubst %.bin,%_bin.h,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN))) \
$(patsubst %.png,%.h,$(addprefix $(BUILDDIR)/,$(SOURCES_PNG)))
ifneq ($(SOURCES_AUDIO),)
ifeq ($(strip $(NITROFSDIR)),)
OBJS_ASSETS += $(SOUNDBANKDIR)/soundbank.c.o
endif
HEADERS_ASSETS += $(SOUNDBANKINFODIR)/soundbank.h
endif
OBJS_SOURCES := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_S))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_C))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_CPP)))
OBJS := $(OBJS_ASSETS) $(OBJS_SOURCES)
DEPS := $(OBJS:.o=.d)
# Targets
# -------
.PHONY: all clean dump dldipatch sdimage
all: $(ROM)
ifneq ($(strip $(NITROFSDIR)),)
# Additional arguments for ndstool
NDSTOOL_ARGS := -d $(NITROFSDIR)
ifneq ($(SOURCES_AUDIO),)
NDSTOOL_ARGS += -d $(SOUNDBANKDIR)
endif
# Make the NDS ROM depend on the filesystem only if it is needed
$(ROM): $(NITROFSDIR)
endif
# Combine the title strings
ifeq ($(strip $(GAME_SUBTITLE)),)
GAME_FULL_TITLE := $(GAME_TITLE);$(GAME_AUTHOR)
else
GAME_FULL_TITLE := $(GAME_TITLE);$(GAME_SUBTITLE);$(GAME_AUTHOR)
endif
$(ROM): $(ELF)
@echo " NDSTOOL $@"
$(V)$(BLOCKSDS)/tools/ndstool/ndstool -c $@ \
-7 $(BLOCKSDS)/sys/default_arm7/arm7.elf -9 $(ELF) \
-b $(GAME_ICON) "$(GAME_FULL_TITLE)" \
-h 0x200 $(NDSTOOL_ARGS)
$(ELF): $(OBJS)
@echo " LD $@"
$(V)$(LD) -o $@ $(OBJS) $(LDFLAGS)
$(DUMP): $(ELF)
@echo " OBJDUMP $@"
$(V)$(OBJDUMP) -h -C -S $< > $@
dump: $(DUMP)
clean:
@echo " CLEAN"
$(V)$(RM) $(ROM) $(DUMP) build $(SDIMAGE)
sdimage:
@echo " MKFATIMG $(SDIMAGE) $(SDROOT)"
$(V)$(BLOCKSDS)/tools/mkfatimg/mkfatimg -t $(SDROOT) $(SDIMAGE)
dldipatch: $(ROM)
@echo " DLDIPATCH $(ROM)"
$(V)$(BLOCKSDS)/tools/dldipatch/dldipatch patch \
$(BLOCKSDS)/sys/dldi_r4/r4tf.dldi $(ROM)
# Rules
# -----
$(BUILDDIR)/%.s.o : %.s
@echo " AS $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.c.o : %.c
@echo " CC $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.arm.c.o : %.arm.c
@echo " CC $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
$(BUILDDIR)/%.cpp.o : %.cpp
@echo " CXX $<"
@$(MKDIR) -p $(@D)
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.arm.cpp.o : %.arm.cpp
@echo " CXX $<"
@$(MKDIR) -p $(@D)
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
$(BUILDDIR)/%.bin.o $(BUILDDIR)/%_bin.h : %.bin
@echo " BIN2C $<"
@$(MKDIR) -p $(@D)
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.bin.o $(BUILDDIR)/$*_bin.c
$(BUILDDIR)/%.png.o $(BUILDDIR)/%.h : %.png %.grit
@echo " GRIT $<"
@$(MKDIR) -p $(@D)
$(V)$(BLOCKSDS)/tools/grit/grit $< -ftc -W1 -o$(BUILDDIR)/$*
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.c
$(V)touch $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.h
ifneq ($(SOURCES_AUDIO),)
$(SOUNDBANKINFODIR)/soundbank.h: $(SOURCES_AUDIO)
@echo " MMUTIL $^"
@$(MKDIR) -p $(SOUNDBANKDIR)
@$(MKDIR) -p $(SOUNDBANKINFODIR)
@$(BLOCKSDS)/tools/mmutil/mmutil $^ -d \
-o$(SOUNDBANKDIR)/soundbank.bin -h$(SOUNDBANKINFODIR)/soundbank.h
ifeq ($(strip $(NITROFSDIR)),)
$(SOUNDBANKDIR)/soundbank.c.o: $(SOUNDBANKINFODIR)/soundbank.h
@echo " BIN2C soundbank.bin"
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $(SOUNDBANKDIR)/soundbank.bin \
$(SOUNDBANKDIR)
@echo " CC.9 soundbank_bin.c"
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(SOUNDBANKDIR)/soundbank.c.o \
$(SOUNDBANKDIR)/soundbank_bin.c
endif
endif
# All assets must be built before the source code
# -----------------------------------------------
$(SOURCES_S) $(SOURCES_C) $(SOURCES_CPP): $(HEADERS_ASSETS)
# Include dependency files if they exist
# --------------------------------------
-include $(DEPS)

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# nrio-usb-disk
Open source USB mass storage tool for Slot-1 NDS cartridges in conjunction with the NRIO/DS Linker Writer Slot-2 cartridge.
## License
MIT

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

2
source/dldi32k.s Normal file
View File

@ -0,0 +1,2 @@
.global __dldi_size
.equ __dldi_size, 32768

104
source/main.c Normal file
View File

@ -0,0 +1,104 @@
/*
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <nds.h>
#include <nds/arm9/dldi.h>
#include "tusb.h"
#include "msc.h"
#include "ui.h"
static void wait_for_key(uint16_t mask) {
while (1) {
swiWaitForVBlank();
scanKeys();
if (keysDown() & mask)
break;
}
}
static void exit_to_loader(void) {
printf("\n" UI_COLOR_INFO "Press any button to exit...\n");
wait_for_key(0xFFFF);
exit(0);
}
int main(void) {
defaultExceptionHandler();
tusb_rhport_init_t dev_init = {
.role = TUSB_ROLE_DEVICE,
.speed = TUSB_SPEED_AUTO
};
tusb_init(BOARD_TUD_RHPORT, &dev_init);
ui_init();
if (isDSiMode()) {
printf(UI_COLOR_ERROR "This program is not compatible with DSi/3DS consoles.\n");
exit_to_loader();
}
if (!msc_dldi_initialize()) {
printf(UI_COLOR_ERROR "Could not initialize DLDI!\n");
exit_to_loader();
}
printf(UI_COLOR_INFO "Ready.\n");
while (1) {
tud_task();
scanKeys();
if (keysDown() & KEY_START)
break;
}
}
// Invoked when device is mounted
void tud_mount_cb(void) {
printf(UI_COLOR_INFO "Device connected.\n");
}
// Invoked when device is unmounted
void tud_umount_cb(void) {
printf(UI_COLOR_INFO "Device disconnected.\n");
}
// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en) {
(void) remote_wakeup_en;
}
// Invoked when usb bus is resumed
void tud_resume_cb(void) {
}

220
source/msc.c Normal file
View File

@ -0,0 +1,220 @@
/**
* Copyright (c) 2019 Ha Thach (tinyusb.org)
* Copyright (c) 2024 Adrian "asie" Siekierka
*
* 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 <nds.h>
#include <nds/arm9/dldi.h>
#include "tusb.h"
#include "ui.h"
static bool ejected = false;
static bool errata_last_sector = false;
// Initialize DLDI driver.
bool msc_dldi_initialize(void) {
const DISC_INTERFACE *io = dldiGetInternal();
if (!io->startup())
return false;
if (!io->isInserted())
return false;
// Some cartridges crash upon trying to access the final sector.
if (
!memcmp(&io->ioType, "SG3D", 4) /* TODO: Verify */
|| !memcmp(&io->ioType, "R4TF", 4)
) {
printf(UI_COLOR_WARNING "Enabled workaround for last sector access bug.\n");
errata_last_sector = true;
}
return true;
}
// 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[] = "USB-NDS";
const char rev[] = "1.0";
int name_len = strlen(io_dldi_data->friendlyName);
if (name_len > 16) name_len = 16;
memcpy(vendor_id, vid, strlen(vid));
memcpy(product_id, io_dldi_data->friendlyName, name_len);
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) {
// RAM disk is ready until ejected
if (ejected) {
// Additional Sense 3A-00 is NOT_FOUND
tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00);
return false;
}
return true;
}
static uint32_t block_count_cached = 0;
uint32_t msc_find_block_count(void) {
uint8_t sec_buffer[512];
if (block_count_cached)
return block_count_cached;
// Read sector 0
const DISC_INTERFACE *io = dldiGetInternal();
io->readSectors(0, 1, sec_buffer);
uint16_t footer = *((uint16_t*) (sec_buffer + 510));
if (footer == 0xAA55) {
// Valid header, but MBR or FAT?
uint8_t boot_opcode = sec_buffer[0];
if (boot_opcode == 0xEB || boot_opcode == 0xE9 || boot_opcode == 0xE8) {
if (!memcmp(sec_buffer + 54, "FAT", 3) || !memcmp(sec_buffer + 82, "FAT32 ", 8)) {
// Looks like a FAT partition.
uint32_t total_sectors = *((uint32_t*) (sec_buffer + 32));
if (total_sectors < 0x10000) {
total_sectors = sec_buffer[19] | (sec_buffer[20] << 8);
}
block_count_cached = total_sectors;
return total_sectors;
}
}
// Looks like an MBR header.
block_count_cached = 0;
for (uint16_t table_entry = 0x1BE; table_entry < 0x1FE; table_entry += 16) {
uint32_t p_start = *((uint16_t*) (sec_buffer + table_entry + 8))
| (*((uint16_t*) (sec_buffer + table_entry + 10)) << 16);
uint32_t p_count = *((uint16_t*) (sec_buffer + table_entry + 12))
| (*((uint16_t*) (sec_buffer + table_entry + 14)) << 16);
uint32_t p_end = p_start + p_count;
if (p_end > block_count_cached)
block_count_cached = p_end;
}
}
return block_count_cached;
}
// 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 = msc_find_block_count() - (errata_last_sector ? 1 : 0);
*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;
if (load_eject) {
if (start) {
// load disk storage
} else {
// unload disk storage
if (!ejected) {
printf(UI_COLOR_INFO "Storage ejected.\n");
ejected = true;
}
}
}
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) {
const DISC_INTERFACE *io = dldiGetInternal();
if (!(bufsize & 0x1FF) && !offset && io->readSectors(lba, bufsize >> 9, buffer)) {
return bufsize;
} else {
printf(UI_COLOR_ERROR "Read error [%ld, %ld]\n", lba, bufsize);
return 0;
}
}
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) {
const DISC_INTERFACE *io = dldiGetInternal();
if (!(bufsize & 0x1FF) && !offset && io->writeSectors(lba, bufsize >> 9, buffer)) {
return bufsize;
} else {
printf(UI_COLOR_ERROR "Write error [%ld, %ld]\n", lba, bufsize);
return 0;
}
}
// 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 (MUST not be handled here)
int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
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
return -1;
}
// 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 resplen;
}

31
source/msc.h Normal file
View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2024 Adrian "asie" Siekierka
*
* 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 _MSC_H_
#define _MSC_H_
#include <stdbool.h>
#include <stdint.h>
bool msc_dldi_initialize(void);
#endif /* _MSC_H_ */

107
source/tusb_config.h Normal file
View File

@ -0,0 +1,107 @@
/*
* 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_
#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
#error CFG_TUSB_MCU must be defined
#endif
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_NONE
#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_VENDOR 0
// MSC Buffer size of Device Mass storage
#define CFG_TUD_MSC_EP_BUFSIZE 4096
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

70
source/ui.c Normal file
View File

@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
//
// SPDX-FileContributor: Adrian "asie" Siekierka, 2024
#include <stdio.h>
#include <nds.h>
#include <nds/arm9/dldi.h>
#include <fat.h>
#include "ui.h"
#include "nds/system.h"
static PrintConsole bottomConsole, topConsole;
#define NRIO_REG(index) GBA_BUS[(index) * 0x10000]
#define NRIO_CHIP_ID NRIO_REG(0x70)
#define NRIO_CHIP_REV NRIO_REG(0x72)
void ui_init(void) {
powerOn(POWER_ALL_2D);
videoSetMode(MODE_0_2D);
videoSetModeSub(MODE_0_2D);
vramSetPrimaryBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_SUB_BG, VRAM_D_MAIN_BG_0x06000000);
setBrightness(3, 0);
consoleInit(&bottomConsole,
0, BgType_Text4bpp, BgSize_T_256x256, 22, 3, false, true);
consoleInit(&topConsole,
0, BgType_Text4bpp, BgSize_T_256x256, 22, 3, true, true);
consoleSelect(&topConsole);
printf("\x1b[2J");
printf("\x1b[4;0H");
printf("\x1b[37;1m");
printf(" _\n");
printf(" _ __ _ __(_) __\n");
printf(" | '_ \\| '__| |'_ \\\n");
printf(" | | | | | | |(_) )\n");
printf(" |_| |_|_| |_|.__/\n");
printf("\x1b[37;0m");
printf(" _ _ __| |__\n");
printf(" | | | | / _| '_ \\\n");
printf(" | |_| |_\\_ \\ |_) )\n");
printf(" \\____|____/____/\n");
printf("\x1b[30;1m");
printf(" __| (_)__| | __\n");
printf(" / _' | / _| |/ /\n");
printf(" ( (_| | \\_ \\ <\n");
printf(" \\__,_|_|__/_|\\_\\ ");
printf("\x1b[37;0m");
printf("0.1");
printf("\x1b[21;0H");
printf("\x1b[37;1mUSB controller %04X, rev %02X\n", NRIO_CHIP_ID, NRIO_CHIP_REV & 0xFF);
printf("\x1b[37;0m%s", io_dldi_data->friendlyName);
consoleSelect(&bottomConsole);
printf("\x1b[2J");
printf("\x1b[23;0H");
}
void ui_select_top(void) {
consoleSelect(&topConsole);
}
void ui_select_bottom(void) {
consoleSelect(&bottomConsole);
}

17
source/ui.h Normal file
View File

@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
//
// SPDX-FileContributor: Adrian "asie" Siekierka, 2024
#ifndef _UI_H_
#define _UI_H_
#define UI_COLOR_ERROR "\x1b[31;1m"
#define UI_COLOR_INFO "\x1b[37;1m"
#define UI_COLOR_SUCCESS "\x1b[32;1m"
#define UI_COLOR_WARNING "\x1b[33;1m"
void ui_init(void);
void ui_select_top(void);
void ui_select_bottom(void);
#endif /* _UI_H_ */

194
source/usb_descriptors.c Normal file
View File

@ -0,0 +1,194 @@
/*
* 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"
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device =
{
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0xC258,
.idProduct = 0x1303,
.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
//--------------------------------------------------------------------+
enum
{
ITF_NUM_MSC,
ITF_NUM_TOTAL
};
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX
// LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number
// 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ...
#define EPNUM_MSC_OUT 0x02
#define EPNUM_MSC_IN 0x82
#elif CFG_TUSB_MCU == OPT_MCU_CXD56
// CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number
// 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN)
#define EPNUM_MSC_OUT 0x02
#define EPNUM_MSC_IN 0x81
#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY)
// MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h
// e.g EP1 OUT & EP1 IN cannot exist together
#define EPNUM_MSC_OUT 0x01
#define EPNUM_MSC_IN 0x82
#else
#define EPNUM_MSC_OUT 0x01
#define EPNUM_MSC_IN 0x81
#endif
uint8_t const desc_fs_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, 0, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64),
};
#if TUD_OPT_HIGH_SPEED
uint8_t const desc_hs_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, 0, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512),
};
#endif
// 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
#if TUD_OPT_HIGH_SPEED
// Although we are highspeed, host may be fullspeed.
return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration;
#else
return desc_fs_configuration;
#endif
}
//--------------------------------------------------------------------+
// 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)
"NDS FLASH", // 1: Manufacturer
"NRIO USB Disk", // 2: Product
NULL, // 3: Serials will use unique ID if possible
};
static uint16_t _desc_str[32 + 1];
// 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;
}

1
tinyusb Submodule

@ -0,0 +1 @@
Subproject commit 5bfb6a6108d39f92232584ee0e04c31651195330