From 64641cf9580d48ff6c53f61d8b9a4a0e110dedf0 Mon Sep 17 00:00:00 2001 From: Weiyi Wang Date: Sun, 28 Apr 2019 15:25:46 -0400 Subject: [PATCH] CSND: handle command processing --- src/core/hle/service/csnd/csnd_snd.cpp | 363 +++++++++++++++++++++++-- src/core/hle/service/csnd/csnd_snd.h | 66 ++++- 2 files changed, 398 insertions(+), 31 deletions(-) diff --git a/src/core/hle/service/csnd/csnd_snd.cpp b/src/core/hle/service/csnd/csnd_snd.cpp index 432a405c6..5c9fa13e9 100644 --- a/src/core/hle/service/csnd/csnd_snd.cpp +++ b/src/core/hle/service/csnd/csnd_snd.cpp @@ -10,13 +10,189 @@ namespace Service::CSND { +enum class CommandId : u16 { + Start = 0x000, + Pause = 0x001, + SetEncoding = 0x002, + SetSecondBlock = 0x003, + SetLoopMode = 0x004, + // unknown = 0x005, + SetLinearInterpolation = 0x006, + SetPsgDuty = 0x007, + SetSampleRate = 0x008, + SetVolume = 0x009, + SetFirstBlock = 0x00A, + SetFirstBlockAdpcmState = 0x00B, + SetSecondBlockAdpcmState = 0x00C, + SetSecondBlockAdpcmReload = 0x00D, + ConfigureChannel = 0x00E, + ConfigurePsg = 0x00F, + ConfigurePsgNoise = 0x010, + // 0x10x commands are audio capture related + // unknown = 0x200 + UpdateState = 0x300, +}; + +struct Type0Command { + u16_le next_command_offset; + enum_le command_id; + u8 finished; + INSERT_PADDING_BYTES(3); + union { + struct { + u32_le channel; + u32_le value; + INSERT_PADDING_BYTES(0x10); + } start; + + struct { + u32_le channel; + u32_le value; + INSERT_PADDING_BYTES(0x10); + } pause; + + struct { + u32_le channel; + Encoding value; + INSERT_PADDING_BYTES(0x13); + } set_encoding; + + struct { + u32_le channel; + LoopMode value; + INSERT_PADDING_BYTES(0x13); + } set_loop_mode; + + struct { + u32_le channel; + u32_le value; + INSERT_PADDING_BYTES(0x10); + } set_linear_interpolation; + + struct { + u32_le channel; + u8 value; + INSERT_PADDING_BYTES(0x13); + } set_psg_duty; + + struct { + u32_le channel; + u32_le value; + INSERT_PADDING_BYTES(0x10); + } set_sample_rate; + + struct { + u32_le channel; + u16_le left_channel_volume; + u16_le right_channel_volume; + u16_le left_capture_volume; + u16_le right_capture_volume; + INSERT_PADDING_BYTES(0xC); + } set_volume; + + struct { + u32_le channel; + u32_le address; + u32_le size; + INSERT_PADDING_BYTES(0xC); + } set_block; // for either first block or second block + + struct { + u32_le channel; + s16_le predictor; + u8 step_index; + INSERT_PADDING_BYTES(0x11); + } set_adpcm_state; // for either first block or second block + + struct { + u32_le channel; + u8 value; + INSERT_PADDING_BYTES(0x13); + } set_second_block_adpcm_reload; + + struct { + union { + BitField<0, 6, u32> channel; + BitField<6, 1, u32> linear_interpolation; + + BitField<10, 2, u32> loop_mode; + BitField<12, 2, u32> encoding; + BitField<14, 1, u32> enable_playback; + + BitField<16, 16, u32> sample_rate; + }; + + u16_le left_channel_volume; + u16_le right_channel_volume; + u16_le left_capture_volume; + u16_le right_capture_volume; + u32_le block1_address; + u32_le block2_address; + u32_le size; + } configure_channel; + + struct { + union { + BitField<0, 6, u32> channel; + BitField<14, 1, u32> enable_playback; + BitField<16, 16, u32> sample_rate; + }; + u16_le left_channel_volume; + u16_le right_channel_volume; + u16_le left_capture_volume; + u16_le right_capture_volume; + u32_le duty; + INSERT_PADDING_BYTES(0x8); + } configure_psg; + + struct { + union { + BitField<0, 6, u32> channel; + BitField<14, 1, u32> enable_playback; + }; + u16_le left_channel_volume; + u16_le right_channel_volume; + u16_le left_capture_volume; + u16_le right_capture_volume; + INSERT_PADDING_BYTES(0xC); + } configure_psg_noise; + }; +}; +static_assert(sizeof(Type0Command) == 0x20, "Type0Command structure size is wrong"); + +struct MasterState { + u32_le unknown_channel_flag; + u32_le unknown; +}; +static_assert(sizeof(MasterState) == 0x8, "MasterState structure size is wrong"); + +struct ChannelState { + u8 active; + INSERT_PADDING_BYTES(0x3); + s16_le adpcm_predictor; + u8 adpcm_step_index; + INSERT_PADDING_BYTES(0x1); + + // 3dbrew says this is the current physical address. However the assembly of CSND module + // from 11.3 system shows this is simply assigned as 0, which is also documented on ctrulib. + u32_le zero; +}; +static_assert(sizeof(ChannelState) == 0xC, "ChannelState structure size is wrong"); + +struct CaptureState { + u8 active; + INSERT_PADDING_BYTES(0x3); + u32_le zero; +}; +static_assert(sizeof(CaptureState) == 0x8, "CaptureState structure size is wrong"); + void CSND_SND::Initialize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x01, 5, 0); const u32 size = Common::AlignUp(rp.Pop(), Memory::PAGE_SIZE); - const u32 offset0 = rp.Pop(); - const u32 offset1 = rp.Pop(); - const u32 offset2 = rp.Pop(); - const u32 offset3 = rp.Pop(); + master_state_offset = rp.Pop(); + channel_state_offset = rp.Pop(); + capture_state_offset = rp.Pop(); + type1_command_offset = rp.Pop(); using Kernel::MemoryPermission; mutex = system.Kernel().CreateMutex(false, "CSND:mutex"); @@ -32,8 +208,10 @@ void CSND_SND::Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_CSND, "(STUBBED) called, size=0x{:08X} " - "offset0=0x{:08X} offset1=0x{:08X} offset2=0x{:08X} offset3=0x{:08X}", - size, offset0, offset1, offset2, offset3); + "master_state_offset=0x{:08X} channel_state_offset=0x{:08X} " + "capture_state_offset=0x{:08X} type1_command_offset=0x{:08X}", + size, master_state_offset, channel_state_offset, capture_state_offset, + type1_command_offset); } void CSND_SND::Shutdown(Kernel::HLERequestContext& ctx) { @@ -53,31 +231,178 @@ void CSND_SND::Shutdown(Kernel::HLERequestContext& ctx) { void CSND_SND::ExecuteCommands(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x03, 1, 0); const u32 addr = rp.Pop(); + LOG_WARNING(Service_CSND, "(STUBBED) called, addr=0x{:08X}", addr); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); if (!shared_memory) { - rb.Push(1); + rb.Push(ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CSND, + ErrorSummary::InvalidState, ErrorLevel::Status)); LOG_ERROR(Service_CSND, "called, shared memory not allocated"); - } else { - u8* ptr = shared_memory->GetPointer(addr); - Type0Command command; - - std::memcpy(&command, ptr, sizeof(Type0Command)); - command.finished |= 1; - std::memcpy(ptr, &command, sizeof(Type0Command)); - - rb.Push(RESULT_SUCCESS); + return; } - LOG_WARNING(Service_CSND, "(STUBBED) called, addr=0x{:08X}", addr); + u32 offset = addr; + while (offset != 0xFFFF) { + Type0Command command; + u8* ptr = shared_memory->GetPointer(offset); + std::memcpy(&command, ptr, sizeof(Type0Command)); + offset = command.next_command_offset; + + switch (command.command_id) { + case CommandId::Start: + // TODO: start/stop the sound + break; + case CommandId::Pause: + // TODO: pause/resume the sound + break; + case CommandId::SetEncoding: + channels[command.set_encoding.channel].encoding = command.set_encoding.value; + break; + case CommandId::SetSecondBlock: + channels[command.set_block.channel].block2_address = command.set_block.address; + channels[command.set_block.channel].block2_size = command.set_block.size; + break; + case CommandId::SetLoopMode: + channels[command.set_loop_mode.channel].loop_mode = command.set_loop_mode.value; + break; + case CommandId::SetLinearInterpolation: + channels[command.set_linear_interpolation.channel].linear_interpolation = + command.set_linear_interpolation.value != 0; + break; + case CommandId::SetPsgDuty: + channels[command.set_psg_duty.channel].psg_duty = command.set_psg_duty.value; + break; + case CommandId::SetSampleRate: + channels[command.set_sample_rate.channel].sample_rate = command.set_sample_rate.value; + break; + case CommandId::SetVolume: + channels[command.set_volume.channel].left_channel_volume = + command.set_volume.left_channel_volume; + channels[command.set_volume.channel].right_channel_volume = + command.set_volume.right_channel_volume; + channels[command.set_volume.channel].left_capture_volume = + command.set_volume.left_capture_volume; + channels[command.set_volume.channel].right_capture_volume = + command.set_volume.right_capture_volume; + break; + case CommandId::SetFirstBlock: + channels[command.set_block.channel].block1_address = command.set_block.address; + channels[command.set_block.channel].block1_size = command.set_block.size; + break; + case CommandId::SetFirstBlockAdpcmState: + channels[command.set_adpcm_state.channel].block1_adpcm_state = { + command.set_adpcm_state.predictor, command.set_adpcm_state.step_index}; + channels[command.set_adpcm_state.channel].block2_adpcm_state = {}; + channels[command.set_adpcm_state.channel].block2_adpcm_reload = false; + break; + case CommandId::SetSecondBlockAdpcmState: + channels[command.set_adpcm_state.channel].block2_adpcm_state = { + command.set_adpcm_state.predictor, command.set_adpcm_state.step_index}; + channels[command.set_adpcm_state.channel].block2_adpcm_reload = true; + break; + case CommandId::SetSecondBlockAdpcmReload: + channels[command.set_second_block_adpcm_reload.channel].block2_adpcm_reload = + command.set_second_block_adpcm_reload.value != 0; + break; + case CommandId::ConfigureChannel: { + auto& configure = command.configure_channel; + auto& channel = channels[configure.channel]; + channel.linear_interpolation = configure.linear_interpolation != 0; + channel.loop_mode = static_cast(configure.loop_mode.Value()); + channel.encoding = static_cast(configure.encoding.Value()); + channel.sample_rate = configure.sample_rate; + channel.left_channel_volume = configure.left_channel_volume; + channel.right_channel_volume = configure.right_channel_volume; + channel.left_capture_volume = configure.left_capture_volume; + channel.right_capture_volume = configure.right_capture_volume; + channel.block1_address = configure.block1_address; + channel.block2_address = configure.block2_address; + channel.block1_size = channel.block2_size = configure.size; + if (configure.enable_playback) { + // TODO: startthe sound + } + break; + } + case CommandId::ConfigurePsg: { + auto& configure = command.configure_psg; + auto& channel = channels[configure.channel]; + channel.encoding = Encoding::Psg; + channel.psg_duty = configure.duty; + channel.sample_rate = configure.sample_rate; + channel.left_channel_volume = configure.left_channel_volume; + channel.right_channel_volume = configure.right_channel_volume; + channel.left_capture_volume = configure.left_capture_volume; + channel.right_capture_volume = configure.right_capture_volume; + if (configure.enable_playback) { + // TODO: startthe sound + } + break; + } + case CommandId::ConfigurePsgNoise: { + auto& configure = command.configure_psg_noise; + auto& channel = channels[configure.channel]; + channel.encoding = Encoding::Psg; + channel.left_channel_volume = configure.left_channel_volume; + channel.right_channel_volume = configure.right_channel_volume; + channel.left_capture_volume = configure.left_capture_volume; + channel.right_capture_volume = configure.right_capture_volume; + if (configure.enable_playback) { + // TODO: startthe sound + } + break; + } + case CommandId::UpdateState: { + MasterState master{0, 0}; + std::memcpy(shared_memory->GetPointer(master_state_offset), &master, sizeof(master)); + + u32 output_index = 0; + for (u32 i = 0; i < ChannelCount; ++i) { + if ((acquired_channel_mask & (1 << i)) == 0) + continue; + ChannelState state; + state.active = false; + state.adpcm_predictor = channels[i].block1_adpcm_state.predictor; + state.adpcm_predictor = channels[i].block1_adpcm_state.step_index; + state.zero = 0; + std::memcpy( + shared_memory->GetPointer(channel_state_offset + sizeof(state) * output_index), + &state, sizeof(state)); + ++output_index; + } + + for (u32 i = 0; i < MaxCaptureUnits; ++i) { + if (!capture_units[i]) + continue; + CaptureState state; + state.active = false; + state.zero = 0; + std::memcpy(shared_memory->GetPointer(capture_state_offset + sizeof(state) * i), + &state, sizeof(state)); + } + + break; + } + default: + LOG_ERROR(Service_CSND, "Unimplemented command ID 0x{:X}", + static_cast(command.command_id)); + } + } + + *shared_memory->GetPointer(addr + offsetof(Type0Command, finished)) = 1; + + rb.Push(RESULT_SUCCESS); } void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x05, 0, 0); + // This is "almost" hardcoded, as in CSND initializes this with some code during sysmodule + // startup, but it always compute to the same value. + acquired_channel_mask = 0xFFFFFF00; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - rb.Push(0xFFFFFF00); + rb.Push(acquired_channel_mask); LOG_WARNING(Service_CSND, "(STUBBED) called"); } @@ -85,6 +410,8 @@ void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) { void CSND_SND::ReleaseSoundChannels(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x06, 0, 0); + acquired_channel_mask = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/csnd/csnd_snd.h b/src/core/hle/service/csnd/csnd_snd.h index 1d01b3117..afdc3b1a8 100644 --- a/src/core/hle/service/csnd/csnd_snd.h +++ b/src/core/hle/service/csnd/csnd_snd.h @@ -15,6 +15,45 @@ class System; namespace Service::CSND { +enum class Encoding : u8 { + Pcm8 = 0, + Pcm16 = 1, + Adpcm = 2, + Psg = 3, +}; + +enum class LoopMode : u8 { + Manual = 0, // Play block 1 endlessly ignoring the size + Normal = 1, // Play block 1 once, then repeat with block 2. Block size is reloaded every time a + // new block is started + OneShot = 2, // Play block 1 once and stop + ConstantSize = 3, // Similar to Normal, but only load block size once at the beginning +}; + +struct AdpcmState { + s16 predictor = 0; + u8 step_index = 0; +}; + +struct Channel { + PAddr block1_address = 0; + PAddr block2_address = 0; + u32 block1_size = 0; + u32 block2_size = 0; + AdpcmState block1_adpcm_state; + AdpcmState block2_adpcm_state; + bool block2_adpcm_reload = false; + u16 left_channel_volume = 0; + u16 right_channel_volume = 0; + u16 left_capture_volume = 0; + u16 right_capture_volume = 0; + u32 sample_rate = 0; + bool linear_interpolation = false; + LoopMode loop_mode = LoopMode::Manual; + Encoding encoding = Encoding::Pcm8; + u8 psg_duty = 0; +}; + class CSND_SND final : public ServiceFramework { public: explicit CSND_SND(Core::System& system); @@ -26,10 +65,10 @@ private: * Inputs: * 0 : Header Code[0x00010140] * 1 : Shared memory block size, for mem-block creation - * 2 : Offset0 located in the shared-memory, region size=8 - * 3 : Offset1 located in the shared-memory, region size=12*num_channels - * 4 : Offset2 located in the shared-memory, region size=8*num_capturedevices - * 5 : Offset3 located in the shared-memory. + * 2 : offset to master state located in the shared-memory, region size=8 + * 3 : offset to channel state located in the shared-memory, region size=12*num_channels + * 4 : offset to capture state located in the shared-memory, region size=8*num_captures + * 5 : offset to type 1 commands (?) located in the shared-memory. * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : Handle-list header @@ -166,15 +205,6 @@ private: */ void Reset(Kernel::HLERequestContext& ctx); - struct Type0Command { - // command id and next command offset - u32 command_id; - u32 finished; - u32 flags; - u8 parameters[20]; - }; - static_assert(sizeof(Type0Command) == 0x20, "Type0Command structure size is wrong"); - Core::System& system; std::shared_ptr mutex = nullptr; @@ -182,6 +212,16 @@ private: static constexpr u32 MaxCaptureUnits = 2; std::array capture_units = {false, false}; + + static constexpr u32 ChannelCount = 32; + std::array channels; + + u32 master_state_offset = 0; + u32 channel_state_offset = 0; + u32 capture_state_offset = 0; + u32 type1_command_offset = 0; + + u32 acquired_channel_mask = 0; }; /// Initializes the CSND_SND Service