diff --git a/libctru/include/3ds.h b/libctru/include/3ds.h index c2d3974..5d2f6d5 100644 --- a/libctru/include/3ds.h +++ b/libctru/include/3ds.h @@ -22,6 +22,7 @@ extern "C" { #include <3ds/services/cfgnor.h> #include <3ds/services/cfgu.h> #include <3ds/services/csnd.h> +#include <3ds/services/dsp.h> #include <3ds/services/fs.h> #include <3ds/services/gsp.h> #include <3ds/services/hid.h> diff --git a/libctru/include/3ds/services/dsp.h b/libctru/include/3ds/services/dsp.h new file mode 100644 index 0000000..045c88d --- /dev/null +++ b/libctru/include/3ds/services/dsp.h @@ -0,0 +1,137 @@ +/** + * @file dsp.h + * @brief DSP Service to access the DSP processor commands (sound) + * + * The DSP has access to the Linear memory region, and to the DSP memory region if allowed in the exheader. + */ +#pragma once +#include <3ds/types.h> + +typedef enum { + DSP_INTERRUPT_PIPE = 2 +} DSP_InterruptType; + + +typedef enum { + DSP_PIPE_INPUT = 0, ///< DSP to ARM + DSP_PIPE_OUTPUT = 1 ///< ARM to DSP +} DSP_PipeDirection; + +/** + * @brief Initializes the dsp service. + * + * Call this before calling any DSP_* function. + * @note This will also unload any previously loaded DSP binary. + * It is done this way since you have to provide your binary when the 3DS leaves sleep mode anyway. + */ +Result dspInit(void); + +/** + * @brief Closes the dsp service. + * @note This will also unload the DSP binary. + */ +Result dspExit(void); + +///Checks if a headphone is inserted. +Result DSP_GetHeadphoneStatus(bool* is_inserted); + + +/** + * @brief Flushes the cache + * @param address Beginning of the memory range to flush, inside the Linear or DSP memory regions + * @param size Size of the memory range to flush + * + * Flushes the cache for the specified memory range and invalidates the cache + */ +Result DSP_FlushDataCache(u32 address, u32 size); + +/** + * @brief Invalidates the cache + * @param address Beginning of the memory range to invalidate, inside the Linear or DSP memory regions + * @param size Size of the memory range to flush + * + * Invalidates the cache for the specified memory range + */ +Result DSP_InvalidateDataCache(u32 address, u32 size); + +///Retrieves the handle of the DSP semaphore +Result DSP_GetSemaphoreHandle(Handle* semaphore); + +///Sets the DSP hardware semaphore value +Result DSP_SetSemaphore(u16 value); + +///Masks the DSP hardware semaphore value +Result DSP_SetSemaphoreMask(u16 mask); + +/** + * @brief Loads a DSP binary and starts the DSP + * @param component The program file address in memory + * @param size The size of the program + * @param prog_mask DSP memory block related ? Default is 0xff. + * @param data_mask DSP memory block related ? Default is 0xff. + * @param is_loaded Indicates if the DSP was succesfully loaded. + * + * @note The binary must be signed (http://3dbrew.org/wiki/DSP_Binary) + * @note Seems to be called when the 3ds leaves the Sleep mode + */ +Result DSP_LoadComponent(u8 const* component,u32 size,u16 prog_mask,u16 data_mask,bool * is_loaded); + +///Stops the DSP by unloading the binary +Result DSP_UnloadComponent(void); + +/** + * @brief Registers an event handle with the DSP through IPC + * @param interrut The type of interrupt that will trigger the event. Usual value is DSP_INTERRUPT_PIPE. + * @param channel The pipe channel. Usual value is 2 + * + * @note It is possible that interrupt are inverted + */ +Result DSP_RegisterInterruptEvents(Handle handle,u32 interrupt,u32 channel); + + +/** + * @param channel ?????? TODO usually 2 + * @param buffer The buffer that will store the values read from the pipe + * @param length Length of the buffer + * @param length_read Number of bytes read by the command + */ +Result DSP_ReadPipeIfPossible(u32 channel, u8 const *buffer, u16 length, u16* length_read); + +/** + * @param channel ?????? TODO usually 2 + * @param buffer The message to send to the DSP process + * @param length Length of the message + */ +Result DSP_WriteProcessPipe(u32 channel,u8 const* buffer,u32 length); + + +///Converts a DSP memory to a virtual address usable by the process +Result DSP_ConvertProcessAddressFromDspDram(u32 dsp_address, u32 *arm_address); + +/** + * @brief Reads a DSP register + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + */ +Result DSP_RecvData(u16 regNo, u16 * value); + +/** + * @brief Checks if you can read a DSP register + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + * + * @warning This call might hang if the data is not ready. See @ref DSP_SendDataIsEmpty. + */ +Result DSP_RecvDataIsReady(u16 regNo, bool * is_ready); + +/** + * @brief Writes to a DSP register + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + * + * @warning This call might hang if the SendData is not empty. See @ref DSP_SendDataIsEmpty. + */ +Result DSP_SendData(u16 regNo, u16 value); + +/** + * @brief Checks if you can write to a DSP register ? + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + */ +Result DSP_SendDataIsEmpty(u16 regNo, bool * is_empty); diff --git a/libctru/source/services/dsp.c b/libctru/source/services/dsp.c new file mode 100644 index 0000000..05e006b --- /dev/null +++ b/libctru/source/services/dsp.c @@ -0,0 +1,254 @@ +#include <3ds/types.h> +#include <3ds/svc.h> +#include <3ds/srv.h> +#include <3ds/ipc.h> +#include <3ds/services/dsp.h> + +Handle dspHandle = 0; + + +Result dspInit(void) +{ + Result ret = 0; + + if (dspHandle == 0) + { + ret = srvGetServiceHandle(&dspHandle, "dsp::DSP"); + if (ret < 0) return ret; + } + if (ret < 0) return ret; + DSP_UnloadComponent(); + return 0; +} + +Result dspExit(void) +{ + Result ret = 0; +//No need to call unload, it will be done automatically by closing the handle + if (dspHandle != 0) + { + ret = svcCloseHandle(dspHandle); + if (ret < 0) return ret; + dspHandle = 0; + } + + return 0; +} + + +Result DSP_GetHeadphoneStatus(bool* is_inserted) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x001F,0,0); + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *is_inserted = cmdbuf[2] & 0xFF; + return cmdbuf[1]; +} + + +Result DSP_FlushDataCache(u32 address, u32 size) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x13,2,2); + cmdbuf[1] = address; + cmdbuf[2] = size; + cmdbuf[3] = IPC_Desc_SharedHandles(1); + cmdbuf[4] = CUR_PROCESS_HANDLE; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + + +Result DSP_InvalidateDataCache(u32 address, u32 size) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x14,2,2); + cmdbuf[1] = address; + cmdbuf[2] = size; + cmdbuf[3] = IPC_Desc_SharedHandles(1); + cmdbuf[4] = CUR_PROCESS_HANDLE; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + + + +Result DSP_SetSemaphore(u16 value) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x7,1,0); + cmdbuf[1] = value; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + + + +Result DSP_SetSemaphoreMask(u16 mask) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x17,1,0); + cmdbuf[1] = mask; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + +Result DSP_GetSemaphoreHandle(Handle* semaphore) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x16,0,0); + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *semaphore = cmdbuf[3]; + return cmdbuf[1]; +} + +Result DSP_LoadComponent(u8 const* component,u32 size,u16 prog_mask,u16 data_mask,bool * is_loaded) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x11,3,2); + cmdbuf[1] = size; + cmdbuf[2] = prog_mask; + cmdbuf[3] = data_mask; + cmdbuf[4] = IPC_Desc_Buffer(size,IPC_BUFFER_R); + cmdbuf[5] = (u32) component; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *is_loaded = cmdbuf[2] & 0xFF; + return cmdbuf[1]; +} + + + +Result DSP_UnloadComponent(void) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x12,0,0); + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + +Result DSP_RegisterInterruptEvents(Handle handle, u32 interrupt, u32 channel) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x15,2,2); + cmdbuf[1] = interrupt; + cmdbuf[2] = channel; + cmdbuf[3] = IPC_Desc_SharedHandles(1); + cmdbuf[4] = handle; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + + +Result DSP_ReadPipeIfPossibleEx(u32 channel,u32 unk1, u8 const *buffer, u16 length, u16* length_read) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x10,3,0); + cmdbuf[1] = channel; + cmdbuf[2] = unk1; + cmdbuf[3] = length; + + u32 * staticbufs = cmdbuf + 0x100; + + u32 saved1 = staticbufs[0x0]; + u32 saved2 = staticbufs[0x4]; + + staticbufs[0] = (length<<14) | 2; + staticbufs[4] = (u32)buffer; + + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + + staticbufs[0] = saved1; + staticbufs[4] = saved2; + + *length_read = cmdbuf[2] & 0xFFFF; + return cmdbuf[1]; +} + +//TODO change DSP_ReadPipeIfPossibleEx into DSP_ReadPipeIfPossible once unk1 is figured out +//However it seems that it is always used with value 0 +Result DSP_ReadPipeIfPossible(u32 channel, u8 const *buffer, u16 length, u16* length_read) +{ + return DSP_ReadPipeIfPossibleEx(channel,0,buffer,length, length_read); +} + +Result DSP_WriteProcessPipe(u32 channel, u8 const *buffer, u32 length) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0xd,2,2); + cmdbuf[1] = channel; + cmdbuf[2] = length; + cmdbuf[3] = IPC_Desc_StaticBuffer(length,1); + cmdbuf[4] = (u32) buffer; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + +Result DSP_ConvertProcessAddressFromDspDram(u32 dsp_address, u32 *arm_address) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0xc,1,0); + cmdbuf[1] = dsp_address; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *arm_address = cmdbuf[2]; + return cmdbuf[1]; +} + +Result DSP_RecvData(u16 regNo, u16 * value) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x1,1,0) ; + cmdbuf[1] = regNo; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *value = cmdbuf[2] & 0xFFFF; + return cmdbuf[1]; +} + +Result DSP_RecvDataIsReady(u16 regNo, bool * is_ready) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x2,1,0); + cmdbuf[1] = regNo; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *is_ready = cmdbuf[2] & 0xFF; + return cmdbuf[1]; +} + + +// Writes data to the reg regNo +// *(_WORD *)(8 * regNo + 0x1ED03024) = value +Result DSP_SendData(u16 regNo, u16 value) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x3,2,0); + cmdbuf[1] = regNo; + cmdbuf[1] = value; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + return cmdbuf[1]; +} + +Result DSP_SendDataIsEmpty(u16 regNo, bool * is_empty) +{ + Result ret = 0; + u32* cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = IPC_MakeHeader(0x4,1,0); + cmdbuf[1] = regNo; + if ((ret = svcSendSyncRequest(dspHandle)) != 0) return ret; + *is_empty = cmdbuf[2] & 0xFF; + return cmdbuf[1]; +} +