Initial commit

This commit is contained in:
Gericom 2019-07-25 15:51:54 +02:00
commit de51d76e23
17 changed files with 1389 additions and 0 deletions

17
.gitattributes vendored Normal file
View File

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

83
.gitignore vendored Normal file
View File

@ -0,0 +1,83 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
build/
*.elf
*.nds

59
Makefile Normal file
View File

@ -0,0 +1,59 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
export TARGET := $(shell basename $(CURDIR))
export TOPDIR := $(CURDIR)
# specify a directory which contains the nitro filesystem
# this is relative to the Makefile
NITRODATA := 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
ifneq ($(strip $(NITRODATA)),)
export NITRO_FILES := $(CURDIR)/$(NITRODATA)
endif
include $(DEVKITARM)/ds_rules
.PHONY: checkarm7 checkarm9 clean
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all: checkarm7 checkarm9 $(TARGET).nds
#---------------------------------------------------------------------------------
checkarm7:
$(MAKE) -C arm7
#---------------------------------------------------------------------------------
checkarm9:
$(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

126
arm7/Makefile Normal file
View File

@ -0,0 +1,126 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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
INCLUDES := include build
DATA :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -mthumb-interwork
CFLAGS := -g -Wall -O2\
-mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer\
-ffast-math \
$(ARCH)
CFLAGS += $(INCLUDE) -DARM7
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -fno-rtti
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=ds_arm7.specs -g $(ARCH) -Wl,--nmagic -Wl,-Map,$(notdir $*).map
LIBS := -ldswifi7 -lmm7 -lnds7
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS)
#---------------------------------------------------------------------------------
# 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
#---------------------------------------------------------------------------------------

98
arm7/source/main.cpp Normal file
View File

@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------
default ARM7 core
Copyright (C) 2005 - 2010
Michael Noland (joat)
Jason Rogers (dovoto)
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 <nds.h>
#include <dswifi7.h>
#include <maxmod7.h>
//---------------------------------------------------------------------------------
void VblankHandler(void) {
//---------------------------------------------------------------------------------
Wifi_Update();
}
//---------------------------------------------------------------------------------
void VcountHandler() {
//---------------------------------------------------------------------------------
inputGetAndSend();
}
volatile bool exitflag = false;
//---------------------------------------------------------------------------------
void powerButtonCB() {
//---------------------------------------------------------------------------------
exitflag = true;
}
//---------------------------------------------------------------------------------
int main() {
//---------------------------------------------------------------------------------
// clear sound registers
dmaFillWords(0, (void*)0x04000400, 0x100);
REG_SOUNDCNT |= SOUND_ENABLE;
writePowerManagement(PM_CONTROL_REG, ( readPowerManagement(PM_CONTROL_REG) & ~PM_SOUND_MUTE ) | PM_SOUND_AMP );
powerOn(POWER_SOUND);
readUserSettings();
ledBlink(0);
irqInit();
// Start the RTC tracking IRQ
initClockIRQ();
fifoInit();
touchInit();
mmInstall(FIFO_MAXMOD);
SetYtrigger(80);
installWifiFIFO();
installSoundFIFO();
installSystemFIFO();
irqSet(IRQ_VCOUNT, VcountHandler);
irqSet(IRQ_VBLANK, VblankHandler);
irqEnable( IRQ_VBLANK | IRQ_VCOUNT | IRQ_NETWORK);
setPowerButtonCB(powerButtonCB);
// Keep the ARM7 mostly idle
while (!exitflag) {
if ( 0 == (REG_KEYINPUT & (KEY_SELECT | KEY_START | KEY_L | KEY_R))) {
exitflag = true;
}
swiWaitForVBlank();
}
return 0;
}

127
arm9/Makefile Normal file
View File

@ -0,0 +1,127 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>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/libdsp
INCLUDES := include
DATA :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -marm -mthumb-interwork
CFLAGS := -g -Wall -O2\
-march=armv5te -mtune=arm946e-s -fomit-frame-pointer\
-ffast-math \
$(ARCH)
CFLAGS += $(INCLUDE) -DARM9
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH) -march=armv5te -mtune=arm946e-s
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -lfilesystem -lfat -lnds9
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS)
#---------------------------------------------------------------------------------
# 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))
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)/*.*)))
#---------------------------------------------------------------------------------
# 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)) \
$(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)
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View File

@ -0,0 +1,271 @@
#include <nds.h>
#include <string.h>
#include "dsp.h"
#include "dsp_pipe.h"
#include "dsp_coff.h"
#include "DspProcess.h"
static DspProcess* sDspProcess = NULL;
void DspProcess::DspIrqHandler()
{
if(sDspProcess)
sDspProcess->HandleDspIrq();
}
void DspProcess::HandleDspIrq()
{
while(true)
{
u32 sources = (REG_DSP_SEM | (((REG_DSP_PSTS >> DSP_PCFG_IE_REP_SHIFT) & 7) << 16)) & _callbackSources;
if(!sources)
break;
while(sources)
{
int idx = __builtin_ctz(sources);
sources &= ~_callbackGroups[idx];
_callbackFuncs[idx](_callbackArgs[idx]);
}
}
}
DspProcess::DspProcess(bool forceDspSyncLoad)
: _slotB(0), _slotC(0), _codeSegs(0), _dataSegs(0), _flags(forceDspSyncLoad ? DSP_PROCESS_FLAG_SYNC_LOAD : 0),
_callbackSources(0)
{
for(int i = 0; i < TWR_WRAM_BC_SLOT_COUNT; i++)
{
_codeSlots[i] = 0xFF;
_dataSlots[i] = 0xFF;
}
for(int i = 0; i < DSP_PROCESS_CALLBACK_COUNT; i++)
{
_callbackFuncs[i] = NULL;
_callbackArgs[i] = NULL;
_callbackGroups[i] = 0;
}
}
bool DspProcess::SetMemoryMapping(bool isCode, u32 addr, u32 len, bool toDsp)
{
addr = DSP_MEM_ADDR_TO_CPU(addr);
len = DSP_MEM_ADDR_TO_CPU(len < 1 ? 1 : len);
int segBits = isCode ? _codeSegs : _dataSegs;
int start = addr >> TWR_WRAM_BC_SLOT_SHIFT;
int end = (addr + len - 1) >> TWR_WRAM_BC_SLOT_SHIFT;
for(int i = start; i <= end; i++)
{
if(!(segBits & (1 << i)))
continue;
int slot = isCode ? _codeSlots[i] : _dataSlots[i];
if(isCode)
twr_mapWramBSlot(slot, toDsp ? TWR_WRAM_B_SLOT_MASTER_DSP_CODE : TWR_WRAM_B_SLOT_MASTER_ARM9, toDsp ? i : slot, true);
else
twr_mapWramCSlot(slot, toDsp ? TWR_WRAM_C_SLOT_MASTER_DSP_DATA : TWR_WRAM_C_SLOT_MASTER_ARM9, toDsp ? i : slot, true);
}
return true;
}
bool DspProcess::EnumerateSections(dsp_process_sec_callback_t callback)
{
if(!callback)
return false;
dsp_coff_header_t coffHeader;
fseek(_coffFile, 0, SEEK_SET);
if(fread(&coffHeader, 1, sizeof(dsp_coff_header_t), _coffFile) != sizeof(dsp_coff_header_t))
return false;
int sectabOffset = sizeof(dsp_coff_header_t) + coffHeader.optHdrLength;
dsp_coff_section_t section;
for(int i = 0; i < coffHeader.nrSections; i++)
{
fseek(_coffFile, sectabOffset + i * sizeof(dsp_coff_section_t), SEEK_SET);
if(fread(&section, 1, sizeof(dsp_coff_section_t), _coffFile) != sizeof(dsp_coff_section_t))
return false;
if((section.flags & DSP_COFF_SECT_FLAG_BLK_HDR) || section.size == 0)
continue;
if(!callback(this, &coffHeader, &section))
return false;
}
return true;
}
bool DspProcess::LoadSection(const dsp_coff_header_t* header, const dsp_coff_section_t* section)
{
const char* name = (const char*)section->name.name;
char fullName[128];
if(section->name.zeroIfLong == 0)
{
fseek(_coffFile, header->symTabPtr + 0x12 * header->nrSyms + section->name.longNameOffset, SEEK_SET);
fread(fullName, 1, sizeof(fullName), _coffFile);
name = fullName;
}
struct
{
bool isCode;
bool noLoad;
int address;
} placements[4];
int nrPlacements = 0;
if(!strcmp(name, "SDK_USING_OS@d0"))
_flags |= DSP_PROCESS_FLAG_SYNC_LOAD;
if(section->flags & DSP_COFF_SECT_FLAG_MAP_IN_CODE_MEM)
{
bool noLoad = section->flags & DSP_COFF_SECT_FLAG_NOLOAD_CODE_MEM;
if(strstr(name, "@c0"))
{
placements[nrPlacements].isCode = true;
placements[nrPlacements].noLoad = noLoad;
placements[nrPlacements].address = DSP_MEM_ADDR_TO_CPU(section->codeAddr);
nrPlacements++;
}
if(strstr(name, "@c1"))
{
placements[nrPlacements].isCode = true;
placements[nrPlacements].noLoad = noLoad;
placements[nrPlacements].address = DSP_MEM_ADDR_TO_CPU(section->codeAddr) + TWR_WRAM_BC_SLOT_SIZE * 4;
nrPlacements++;
}
}
if(section->flags & DSP_COFF_SECT_FLAG_MAP_IN_DATA_MEM)
{
bool noLoad = section->flags & DSP_COFF_SECT_FLAG_NOLOAD_DATA_MEM;
if(strstr(name, "@d0"))
{
placements[nrPlacements].isCode = false;
placements[nrPlacements].noLoad = noLoad;
placements[nrPlacements].address = DSP_MEM_ADDR_TO_CPU(section->dataAddr);
nrPlacements++;
}
if(strstr(name, "@d1"))
{
placements[nrPlacements].isCode = false;
placements[nrPlacements].noLoad = noLoad;
placements[nrPlacements].address = DSP_MEM_ADDR_TO_CPU(section->dataAddr) + TWR_WRAM_BC_SLOT_SIZE * 4;
nrPlacements++;
}
}
for(int i = 0; i < nrPlacements; i++)
{
bool isCode = placements[i].isCode;
bool noLoad = placements[i].noLoad;
if(!noLoad)
fseek(_coffFile, section->sectionPtr, SEEK_SET);
int dst = placements[i].address;
int left = section->size;
while(left > 0)
{
int blockEnd = (dst + TWR_WRAM_BC_SLOT_SIZE) & ~(TWR_WRAM_BC_SLOT_SIZE - 1);
int blockLeft = blockEnd - dst;
int len = left < blockLeft ? left : blockLeft;
int seg = dst >> TWR_WRAM_BC_SLOT_SHIFT;
int slot = isCode ? _codeSlots[seg] : _dataSlots[seg];
if(slot == 0xFF)
{
u16* slots = isCode ? &_slotB : &_slotC;
int* segs = isCode ? &_codeSegs : &_dataSegs;
u8* slotMap = isCode ? _codeSlots : _dataSlots;
slot = __builtin_ctz(*slots);
if(slot >= TWR_WRAM_BC_SLOT_COUNT)
return false;
slotMap[seg] = slot;
*slots &= ~(1 << slot);
*segs |= 1 << seg;
if(isCode)
twr_mapWramBSlot(slot, TWR_WRAM_B_SLOT_MASTER_ARM9, slot, true);
else
twr_mapWramCSlot(slot, TWR_WRAM_C_SLOT_MASTER_ARM9, slot, true);
memset(DspToArm9Address(isCode, DSP_MEM_ADDR_TO_DSP(seg << TWR_WRAM_BC_SLOT_SHIFT)), 0, TWR_WRAM_BC_SLOT_SIZE);
}
if(!noLoad && fread(DspToArm9Address(isCode, DSP_MEM_ADDR_TO_DSP(dst)), 1, len, _coffFile) != len)
return false;
left -= len;
dst += len;
}
}
return true;
}
bool DspProcess::LoadProcess(const char* path, u16 slotB, u16 slotC)
{
_slotB = slotB;
_slotC = slotC;
_coffFile = fopen(path, "rb");
if(!_coffFile)
return false;
bool ok = EnumerateSections(DspProcess::LoadSection);
fclose(_coffFile);
_coffFile = NULL;
if(!ok)
return false;
SetMemoryMapping(true, 0, DSP_MEM_ADDR_TO_DSP(TWR_WRAM_BC_SLOT_SIZE * TWR_WRAM_BC_SLOT_COUNT), true);
SetMemoryMapping(false, 0, DSP_MEM_ADDR_TO_DSP(TWR_WRAM_BC_SLOT_SIZE * TWR_WRAM_BC_SLOT_COUNT), true);
return true;
}
bool DspProcess::Execute(const char* path, u16 slotB, u16 slotC)
{
if(sDspProcess)
return false;
if(!LoadProcess(path, slotB, slotC))
return false;
int irq = enterCriticalSection();
{
sDspProcess = this;
dsp_initPipe();
irqSet(IRQ_WIFI, DspProcess::DspIrqHandler);
SetCallback(DSP_PROCESS_CALLBACK_SEMAPHORE(15) | DSP_PROCESS_CALLBACK_REPLY(DSP_PIPE_CMD_REG), dsp_pipeIrqCallback, NULL);
irqEnable(IRQ_WIFI);
dsp_powerOn();
dsp_setCoreResetOff(_callbackSources >> 16);
dsp_setSemaphoreMask(~(_callbackSources & 0xFFFF));
SetupCallbacks();
//needed for some modules
if(_flags & DSP_PROCESS_FLAG_SYNC_LOAD)
for(int i = 0; i < 3; i++)
while(dsp_receiveData(i) != 1);
DspProcess::DspIrqHandler();
}
leaveCriticalSection(irq);
return true;
}
void DspProcess::SetCallback(u32 sources, dsp_process_irq_callback_t func, void* arg)
{
int irq = enterCriticalSection();
{
for(int i = 0; i < DSP_PROCESS_CALLBACK_COUNT; i++)
{
if(!(sources & (1 << i)))
continue;
_callbackFuncs[i] = func;
_callbackArgs[i] = arg;
_callbackGroups[i] = sources;
}
if(func)
{
REG_DSP_PCFG |= ((sources >> 16) & 7) << DSP_PCFG_IE_REP_SHIFT;
REG_DSP_PMASK &= ~(sources & 0xFFFF);
_callbackSources |= sources;
}
else
{
REG_DSP_PCFG &= ~(((sources >> 16) & 7) << DSP_PCFG_IE_REP_SHIFT);
REG_DSP_PMASK |= sources & 0xFFFF;
_callbackSources &= ~sources;
}
}
leaveCriticalSection(irq);
}

View File

@ -0,0 +1,67 @@
#pragma once
#include <stdio.h>
#include "twlwram.h"
#include "dsp_mem.h"
#include "dsp_coff.h"
class DspProcess;
typedef bool (*dsp_process_sec_callback_t)(DspProcess* process, const dsp_coff_header_t* header, const dsp_coff_section_t* section);
#define DSP_PROCESS_CALLBACK_COUNT (16 + 3)
#define DSP_PROCESS_CALLBACK_SEMAPHORE(i) (1 << (i))
#define DSP_PROCESS_CALLBACK_REPLY(i) (1 << ((i) + 16))
typedef void (*dsp_process_irq_callback_t)(void* arg);
#define DSP_PROCESS_FLAG_SYNC_LOAD 1
class DspProcess
{
FILE* _coffFile;
u16 _slotB;
u16 _slotC;
int _codeSegs;
int _dataSegs;
u8 _codeSlots[TWR_WRAM_BC_SLOT_COUNT];
u8 _dataSlots[TWR_WRAM_BC_SLOT_COUNT];
u32 _flags;
u32 _callbackSources;
dsp_process_irq_callback_t _callbackFuncs[DSP_PROCESS_CALLBACK_COUNT];
void* _callbackArgs[DSP_PROCESS_CALLBACK_COUNT];
u32 _callbackGroups[DSP_PROCESS_CALLBACK_COUNT];
static void DspIrqHandler();
void HandleDspIrq();
void* DspToArm9Address(bool isCodePtr, u32 addr)
{
addr = DSP_MEM_ADDR_TO_CPU(addr);
int seg = addr >> TWR_WRAM_BC_SLOT_SHIFT;
int offs = addr - (seg << TWR_WRAM_BC_SLOT_SHIFT);
int slot = isCodePtr ? _codeSlots[seg] : _dataSlots[seg];
return (u8*)twr_getBlockAddress(isCodePtr ? TWR_WRAM_BLOCK_B : TWR_WRAM_BLOCK_C) + slot * TWR_WRAM_BC_SLOT_SIZE + offs;
}
bool SetMemoryMapping(bool isCode, u32 addr, u32 len, bool toDsp);
bool EnumerateSections(dsp_process_sec_callback_t callback);
bool LoadSection(const dsp_coff_header_t* header, const dsp_coff_section_t* section);
static bool LoadSection(DspProcess* process, const dsp_coff_header_t* header, const dsp_coff_section_t* section)
{
return process->LoadSection(header, section);
}
bool LoadProcess(const char* path, u16 slotB, u16 slotC);
protected:
void SetCallback(u32 sources, dsp_process_irq_callback_t func, void* arg);
virtual void SetupCallbacks() { }
public:
DspProcess(bool forceDspSyncLoad = false);
bool Execute(const char* path, u16 slotB, u16 slotC);
};

View File

@ -0,0 +1,85 @@
#include <nds.h>
#include "dsp.h"
void dsp_setBlockReset(bool reset)
{
REG_SCFG_RST = reset ? 0 : 1;
}
void dsp_setClockEnabled(bool enabled)
{
if(enabled)
REG_SCFG_CLK |= 1 << 1;
else
REG_SCFG_CLK &= ~(1 << 1);
}
void dsp_resetInterface()
{
swiDelay(8);
if(!(REG_DSP_PCFG & DSP_PCFG_RESET))
return;
REG_DSP_PCFG &= ~(DSP_PCFG_IE_REP0 | DSP_PCFG_IE_REP1 | DSP_PCFG_IE_REP2);
REG_DSP_PSEM = 0;
REG_DSP_PCLEAR = 0xFFFF;
//clear them all
vu16 tmp = REG_DSP_REP0;
tmp = REG_DSP_REP1;
tmp = REG_DSP_REP2;
}
void dsp_setCoreResetOn()
{
swiDelay(8);
if(REG_DSP_PCFG & DSP_PCFG_RESET)
return;
REG_DSP_PCFG |= DSP_PCFG_RESET;
swiDelay(8);
while(REG_DSP_PSTS & DSP_PSTS_PERI_RESET);
}
void dsp_setCoreResetOff(u16 repIrqMask)
{
swiDelay(8);
while(REG_DSP_PSTS & DSP_PSTS_PERI_RESET);
dsp_resetInterface();
swiDelay(8);
REG_DSP_PCFG |= (repIrqMask & 7) << DSP_PCFG_IE_REP_SHIFT;
swiDelay(8);
REG_DSP_PCFG &= ~DSP_PCFG_RESET;
}
void dsp_powerOn()
{
dsp_setBlockReset(true);
dsp_setClockEnabled(true);
swiDelay(8);
dsp_setBlockReset(false);
dsp_setCoreResetOn();
}
void dsp_powerOff()
{
dsp_setBlockReset(true);
dsp_setClockEnabled(false);
}
void dsp_sendData(int id, u16 data)
{
swiDelay(8);
while(REG_DSP_PSTS & (1 << (DSP_PSTS_CMD_UNREAD_SHIFT + id)));
(&REG_DSP_CMD0)[4 * id] = data;
}
u16 dsp_receiveData(int id)
{
swiDelay(8);
while(!(REG_DSP_PSTS & (1 << (DSP_PSTS_REP_NEW_SHIFT + id))));
return (&REG_DSP_REP0)[4 * id];
}
bool dsp_receiveDataReady(int id)
{
swiDelay(8);
return (REG_DSP_PSTS & (1 << (DSP_PSTS_REP_NEW_SHIFT + id))) ? true : false;
}

77
arm9/source/libdsp/dsp.h Normal file
View File

@ -0,0 +1,77 @@
#pragma once
#define REG_DSP_PDATA (*(vu16*)0x4004300)
#define REG_DSP_PADR (*(vu16*)0x4004304)
#define DSP_PCFG_RESET 1
#define DSP_PCFG_IE_REP_SHIFT 9
#define DSP_PCFG_IE_REP0 (1 << DSP_PCFG_IE_REP_SHIFT)
#define DSP_PCFG_IE_REP1 (1 << (DSP_PCFG_IE_REP_SHIFT + 1))
#define DSP_PCFG_IE_REP2 (1 << (DSP_PCFG_IE_REP_SHIFT + 2))
#define REG_DSP_PCFG (*(vu16*)0x4004308)
#define DSP_PSTS_PERI_RESET (1 << 2)
#define DSP_PSTS_REP_NEW_SHIFT 10
#define DSP_PSTS_REP0_NEW (1 << DSP_PSTS_REP_NEW_SHIFT)
#define DSP_PSTS_REP1_NEW (1 << (DSP_PSTS_REP_NEW_SHIFT + 1))
#define DSP_PSTS_REP2_NEW (1 << (DSP_PSTS_REP_NEW_SHIFT + 2))
#define DSP_PSTS_CMD_UNREAD_SHIFT 13
#define DSP_PSTS_CMD0_UNREAD (1 << DSP_PSTS_CMD_UNREAD_SHIFT)
#define DSP_PSTS_CMD1_UNREAD (1 << (DSP_PSTS_CMD_UNREAD_SHIFT + 1))
#define DSP_PSTS_CMD2_UNREAD (1 << (DSP_PSTS_CMD_UNREAD_SHIFT + 2))
#define REG_DSP_PSTS (*(vu16*)0x400430C)
#define REG_DSP_PSEM (*(vu16*)0x4004310)
#define REG_DSP_PMASK (*(vu16*)0x4004314)
#define REG_DSP_PCLEAR (*(vu16*)0x4004318)
#define REG_DSP_SEM (*(vu16*)0x400431C)
#define REG_DSP_CMD0 (*(vu16*)0x4004320)
#define REG_DSP_REP0 (*(vu16*)0x4004324)
#define REG_DSP_CMD1 (*(vu16*)0x4004328)
#define REG_DSP_REP1 (*(vu16*)0x400432C)
#define REG_DSP_CMD2 (*(vu16*)0x4004330)
#define REG_DSP_REP2 (*(vu16*)0x4004334)
void dsp_setBlockReset(bool reset);
void dsp_setClockEnabled(bool enabled);
void dsp_resetInterface();
void dsp_setCoreResetOn();
void dsp_setCoreResetOff(u16 repIrqMask);
void dsp_powerOn();
void dsp_powerOff();
void dsp_sendData(int id, u16 data);
u16 dsp_receiveData(int id);
bool dsp_receiveDataReady(int id);
static inline void dsp_setSemaphore(u16 mask)
{
REG_DSP_PSEM = mask;
}
static inline void dsp_setSemaphoreMask(u16 mask)
{
REG_DSP_PMASK = mask;
}
static inline void dsp_clearSemaphore(u16 mask)
{
REG_DSP_PCLEAR = mask;
}
static inline u16 dsp_getSemaphore()
{
swiDelay(8);
return REG_DSP_SEM;
}

View File

@ -0,0 +1,102 @@
#pragma once
enum DspCoffSectFlag : u32
{
//DSP_COFF_SECT_FLAG_DATASECT = 0,
DSP_COFF_SECT_FLAG_CODESECT = (1 << 0),
DSP_COFF_SECT_FLAG_MAP_IN_CODE_MEM = (1 << 3),
DSP_COFF_SECT_FLAG_MAP_IN_DATA_MEM = (1 << 4),
DSP_COFF_SECT_FLAG_NOLOAD_CODE_MEM = (1 << 5),
DSP_COFF_SECT_FLAG_NOLOAD_DATA_MEM = (1 << 6),
DSP_COFF_SECT_FLAG_BLK_HDR = (1 << 7),
DSP_COFF_SECT_FLAG_LINE_DEBUG = (1 << 9)
};
// enum DspCoffSymNum
// {
// DSP_COFF_SYM_NUM_UNDEFINED = 0,
// DSP_COFF_SYM_NUM_DEBUG = 0xFFFE,
// DSP_COFF_SYM_NUM_ABSOLUTE = 0xFFFF
// };
// enum DspCoffSymType : u16
// {
// DSP_COFF_SYM_TYPE_NULL = 0,
// DSP_COFF_SYM_TYPE_VOID,
// DSP_COFF_SYM_TYPE_CHAR,
// DSP_COFF_SYM_TYPE_SHORT,
// DSP_COFF_SYM_TYPE_INT,
// DSP_COFF_SYM_TYPE_LONG,
// DSP_COFF_SYM_TYPE_FLOAT,
// DSP_COFF_SYM_TYPE_DOUBLE,
// DSP_COFF_SYM_TYPE_STRUCT,
// DSP_COFF_SYM_TYPE_UNION,
// DSP_COFF_SYM_TYPE_ENUM,
// DSP_COFF_SYM_TYPE_MOE,
// DSP_COFF_SYM_TYPE_BYTE,
// DSP_COFF_SYM_TYPE_WORD,
// DSP_COFF_SYM_TYPE_UINT,
// DSP_COFF_SYM_TYPE_DWORD
// };
// enum DspCoffSymDType
// {
// DSP_COFF_SYM_DTYPE_NULL = 0,
// DSP_COFF_SYM_DTYPE_POINTER,
// DSP_COFF_SYM_DTYPE_FUNCTION,
// DSP_COFF_SYM_DTYPE_ARRAY
// };
struct dsp_coff_header_t
{
u16 magic;
u16 nrSections;
u32 timeStamp;
u32 symTabPtr;
u32 nrSyms;
u16 optHdrLength;
u16 flags;
};
static_assert(sizeof(dsp_coff_header_t) == 20);
union dsp_coff_name_t
{
u8 name[8];
struct
{
u32 zeroIfLong;
u32 longNameOffset;
};
};
static_assert(sizeof(dsp_coff_name_t) == 8);
struct dsp_coff_section_t
{
dsp_coff_name_t name;
u32 codeAddr;
u32 dataAddr;
u32 size;
u32 sectionPtr;
u32 relocationPtr;
u32 lineDebugPtr;
u16 nrRelocs;
u16 nrLines;
DspCoffSectFlag flags;
};
static_assert(sizeof(dsp_coff_section_t) == 40);
// struct dsp_coff_symbol_t
// {
// dsp_coff_symbol_name_t name;
// u32 value;
// u16 sectionId;
// DspCoffSymType type;
// u8 storage;
// u8 followCount;
// u16 padding;
// };
// static_assert(sizeof(dsp_coff_symbol_t) == 18);

View File

@ -0,0 +1,4 @@
#pragma once
#define DSP_MEM_ADDR_TO_DSP(addr) ((u16)(((u32)(addr)) >> 1))
#define DSP_MEM_ADDR_TO_CPU(addr) ((u32)((addr) << 1))

View File

@ -0,0 +1,55 @@
#include <nds.h>
#include <stdio.h>
#include "dsp.h"
#include "dsp_mem.h"
#include "dsp_pipe.h"
static u32 sPipeMntrAddr = 0;
static dsp_pipe_port_callback_t sPipePortCallbackFuncs[DSP_PIPE_PORT_COUNT];
static void* sPipePortCallbackArgs[DSP_PIPE_PORT_COUNT];
void dsp_initPipe()
{
sPipeMntrAddr = 0;
for(int i = 0; i < DSP_PIPE_PORT_COUNT; i++)
{
sPipePortCallbackFuncs[i] = NULL;
sPipePortCallbackArgs[i] = NULL;
}
}
void dsp_pipeIrqCallback(void* arg)
{
iprintf("dsp_pipeCallback!\n");
while((dsp_getSemaphore() & 0x8000) || dsp_receiveDataReady(DSP_PIPE_CMD_REG))
{
dsp_clearSemaphore(0x8000);
while(dsp_receiveDataReady(DSP_PIPE_CMD_REG))
{
if(!sPipeMntrAddr)
{
sPipeMntrAddr = DSP_MEM_ADDR_TO_CPU(dsp_receiveData(DSP_PIPE_CMD_REG));
iprintf("sPipeMntrAddr = %x\n", sPipeMntrAddr);
}
else
{
u16 data = dsp_receiveData(DSP_PIPE_CMD_REG);
iprintf("Received %x\n", data);
int port = data >> 1;
int dir = data & 1;
if(sPipePortCallbackFuncs[port])
sPipePortCallbackFuncs[port](sPipePortCallbackArgs[port], port, dir);
else
{
}
}
}
}
}
void dsp_setPipePortCallback(int port, dsp_pipe_port_callback_t func, void* arg)
{
sPipePortCallbackFuncs[port] = func;
sPipePortCallbackArgs[port] = arg;
}

View File

@ -0,0 +1,37 @@
#pragma once
#define DSP_PIPE_CMD_REG 2
#define DSP_PIPE_DIR_COUNT 2
#define DSP_PIPE_DIR_IN 0
#define DSP_PIPE_DIR_OUT 1
#define DSP_PIPE_PORT_COUNT 8
#define DSP_PIPE_PORT_CONSOLE 0
#define DSP_PIPE_PORT_DMA 1
#define DSP_PIPE_PORT_AUDIO 2
#define DSP_PIPE_PORT_BINARY 3
#define DSP_PIPE_PORT_TEMP 4
typedef void (*dsp_pipe_port_callback_t)(void* arg, int port, int dir);
struct dsp_pipe_t
{
u16 addr;
u16 len;
u16 readPtr;
u16 writePtr;
u16 flags;
};
struct dsp_pipe_mon_t
{
dsp_pipe_t pipes[DSP_PIPE_PORT_COUNT][DSP_PIPE_DIR_COUNT];
};
void dsp_initPipe();
void dsp_pipeIrqCallback(void* arg);
void dsp_setPipePortCallback(int port, dsp_pipe_port_callback_t func, void* arg);

View File

@ -0,0 +1,64 @@
#include <nds.h>
#include "twlwram.h"
u32 twr_getBlockAddress(TWRWramBlock block)
{
switch(block)
{
case TWR_WRAM_BLOCK_A:
return TWR_WRAM_BASE + (((REG_MBK6 & TWR_MBK6_START_ADDR_MASK) >> TWR_MBK6_START_ADDR_SHIFT) << TWR_WRAM_A_SLOT_SHIFT);
case TWR_WRAM_BLOCK_B:
return TWR_WRAM_BASE + (((REG_MBK7 & TWR_MBK7_START_ADDR_MASK) >> TWR_MBK7_START_ADDR_SHIFT) << TWR_WRAM_BC_SLOT_SHIFT);
case TWR_WRAM_BLOCK_C:
return TWR_WRAM_BASE + (((REG_MBK8 & TWR_MBK8_START_ADDR_MASK) >> TWR_MBK8_START_ADDR_SHIFT) << TWR_WRAM_BC_SLOT_SHIFT);
}
return 0;
}
void twr_setBlockMapping(TWRWramBlock block, u32 start, u32 length, TWRWramBlockImageSize imageSize)
{
start -= TWR_WRAM_BASE;
u32 end;
switch(block)
{
case TWR_WRAM_BLOCK_A:
start >>= TWR_WRAM_A_SLOT_SHIFT;
length >>= TWR_WRAM_A_SLOT_SHIFT;
end = start + length;
REG_MBK6 = (start << TWR_MBK6_START_ADDR_SHIFT) | (imageSize << TWR_MBK6_IMAGE_SIZE_SHIFT) | (end << TWR_MBK6_END_ADDR_SHIFT);
break;
case TWR_WRAM_BLOCK_B:
start >>= TWR_WRAM_BC_SLOT_SHIFT;
length >>= TWR_WRAM_BC_SLOT_SHIFT;
end = start + length;
REG_MBK7 = (start << TWR_MBK7_START_ADDR_SHIFT) | (imageSize << TWR_MBK7_IMAGE_SIZE_SHIFT) | (end << TWR_MBK7_END_ADDR_SHIFT);
break;
case TWR_WRAM_BLOCK_C:
start >>= TWR_WRAM_BC_SLOT_SHIFT;
length >>= TWR_WRAM_BC_SLOT_SHIFT;
end = start + length;
REG_MBK8 = (start << TWR_MBK8_START_ADDR_SHIFT) | (imageSize << TWR_MBK8_IMAGE_SIZE_SHIFT) | (end << TWR_MBK8_END_ADDR_SHIFT);
break;
}
}
void twr_mapWramASlot(int slot, TWRWramASlotMaster master, int offset, bool enable)
{
if(slot < 0 || slot > 3 || offset < 0 || offset > 3)
return;
REG_MBK1[slot] = enable ? (TWR_WRAM_A_SLOT_ENABLE | master | TWR_WRAM_A_SLOT_OFFSET(offset)) : 0;
}
void twr_mapWramBSlot(int slot, TWRWramBSlotMaster master, int offset, bool enable)
{
if(slot < 0 || slot > 7 || offset < 0 || offset > 7)
return;
REG_MBK2[slot] = enable ? (TWR_WRAM_BC_SLOT_ENABLE | master | TWR_WRAM_BC_SLOT_OFFSET(offset)) : 0;
}
void twr_mapWramCSlot(int slot, TWRWramCSlotMaster master, int offset, bool enable)
{
if(slot < 0 || slot > 7 || offset < 0 || offset > 7)
return;
REG_MBK4[slot] = enable ? (TWR_WRAM_BC_SLOT_ENABLE | master | TWR_WRAM_BC_SLOT_OFFSET(offset)) : 0;
}

View File

@ -0,0 +1,85 @@
#pragma once
enum TWRWramBlock
{
TWR_WRAM_BLOCK_A = 0,
TWR_WRAM_BLOCK_B = 1,
TWR_WRAM_BLOCK_C = 2
};
enum TWRWramBlockImageSize
{
TWR_WRAM_BLOCK_IMAGE_SIZE_32K = 0,
TWR_WRAM_BLOCK_IMAGE_SIZE_64K,
TWR_WRAM_BLOCK_IMAGE_SIZE_128K,
TWR_WRAM_BLOCK_IMAGE_SIZE_256K,
};
#define TWR_WRAM_BASE 0x03000000
//WRAM A
#define TWR_WRAM_A_SLOT_SIZE 0x10000
#define TWR_WRAM_A_SLOT_SHIFT 16
#define TWR_WRAM_A_SLOT_COUNT 4
#define TWR_WRAM_A_ADDRESS_MAX 0x03FF0000
#define TWR_WRAM_A_SLOT_OFFSET(i) ((i) << 2)
#define TWR_WRAM_A_SLOT_ENABLE 0x80
enum TWRWramASlotMaster
{
TWR_WRAM_A_SLOT_MASTER_ARM9 = 0,
TWR_WRAM_A_SLOT_MASTER_ARM7 = 1
};
#define TWR_MBK6_START_ADDR_MASK 0x00000FF0
#define TWR_MBK6_START_ADDR_SHIFT 4
#define TWR_MBK6_IMAGE_SIZE_SHIFT 12
#define TWR_MBK6_END_ADDR_SHIFT 20
//WRAM B
#define TWR_WRAM_BC_SLOT_SIZE 0x8000
#define TWR_WRAM_BC_SLOT_SHIFT 15
#define TWR_WRAM_BC_SLOT_COUNT 8
#define TWR_WRAM_BC_ADDRESS_MAX 0x03FF8000
#define TWR_WRAM_BC_SLOT_OFFSET(i) ((i) << 2)
#define TWR_WRAM_BC_SLOT_ENABLE 0x80
enum TWRWramBSlotMaster
{
TWR_WRAM_B_SLOT_MASTER_ARM9 = 0,
TWR_WRAM_B_SLOT_MASTER_ARM7 = 1,
TWR_WRAM_B_SLOT_MASTER_DSP_CODE = 2
};
enum TWRWramCSlotMaster
{
TWR_WRAM_C_SLOT_MASTER_ARM9 = 0,
TWR_WRAM_C_SLOT_MASTER_ARM7 = 1,
TWR_WRAM_C_SLOT_MASTER_DSP_DATA = 2
};
#define TWR_MBK7_START_ADDR_MASK 0x00000FF8
#define TWR_MBK7_START_ADDR_SHIFT 3
#define TWR_MBK7_IMAGE_SIZE_SHIFT 12
#define TWR_MBK7_END_ADDR_SHIFT 19
#define TWR_MBK8_START_ADDR_MASK 0x00000FF8
#define TWR_MBK8_START_ADDR_SHIFT 3
#define TWR_MBK8_IMAGE_SIZE_SHIFT 12
#define TWR_MBK8_END_ADDR_SHIFT 19
u32 twr_getBlockAddress(TWRWramBlock block);
void twr_setBlockMapping(TWRWramBlock block, u32 start, u32 length, TWRWramBlockImageSize imageSize);
void twr_mapWramASlot(int slot, TWRWramASlotMaster master, int offset, bool enable);
void twr_mapWramBSlot(int slot, TWRWramBSlotMaster master, int offset, bool enable);
void twr_mapWramCSlot(int slot, TWRWramCSlotMaster master, int offset, bool enable);

32
arm9/source/main.cpp Normal file
View File

@ -0,0 +1,32 @@
#include <nds.h>
#include <stdio.h>
#include <filesystem.h>
#include <fat.h>
#include "libdsp/DspProcess.h"
static DspProcess* sAudioProc;
int main()
{
fatInitDefault();
consoleDemoInit(); //setup the sub screen for printing
iprintf("DSP Test\n");
sAudioProc = new DspProcess();
if(!sAudioProc->Execute("audio.a", 0xFF, 0xFF))
{
iprintf("DSP Init Fail!\n");
while(1);
}
iprintf("DSP Init OK!\n");
while(true)
{
swiWaitForVBlank();
scanKeys();
if (keysDown()&KEY_START)
break;
}
return 0;
}