CSND: handle command processing

This commit is contained in:
Weiyi Wang 2019-04-28 15:25:46 -04:00
parent 7ea82e7941
commit 64641cf958
2 changed files with 398 additions and 31 deletions

View file

@ -10,13 +10,189 @@
namespace Service::CSND { 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<CommandId> 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) { void CSND_SND::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x01, 5, 0); IPC::RequestParser rp(ctx, 0x01, 5, 0);
const u32 size = Common::AlignUp(rp.Pop<u32>(), Memory::PAGE_SIZE); const u32 size = Common::AlignUp(rp.Pop<u32>(), Memory::PAGE_SIZE);
const u32 offset0 = rp.Pop<u32>(); master_state_offset = rp.Pop<u32>();
const u32 offset1 = rp.Pop<u32>(); channel_state_offset = rp.Pop<u32>();
const u32 offset2 = rp.Pop<u32>(); capture_state_offset = rp.Pop<u32>();
const u32 offset3 = rp.Pop<u32>(); type1_command_offset = rp.Pop<u32>();
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
mutex = system.Kernel().CreateMutex(false, "CSND:mutex"); mutex = system.Kernel().CreateMutex(false, "CSND:mutex");
@ -32,8 +208,10 @@ void CSND_SND::Initialize(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_CSND, LOG_WARNING(Service_CSND,
"(STUBBED) called, size=0x{:08X} " "(STUBBED) called, size=0x{:08X} "
"offset0=0x{:08X} offset1=0x{:08X} offset2=0x{:08X} offset3=0x{:08X}", "master_state_offset=0x{:08X} channel_state_offset=0x{:08X} "
size, offset0, offset1, offset2, offset3); "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) { 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) { void CSND_SND::ExecuteCommands(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x03, 1, 0); IPC::RequestParser rp(ctx, 0x03, 1, 0);
const u32 addr = rp.Pop<u32>(); const u32 addr = rp.Pop<u32>();
LOG_WARNING(Service_CSND, "(STUBBED) called, addr=0x{:08X}", addr);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (!shared_memory) { if (!shared_memory) {
rb.Push<u32>(1); rb.Push(ResultCode(ErrorDescription::InvalidResultValue, ErrorModule::CSND,
ErrorSummary::InvalidState, ErrorLevel::Status));
LOG_ERROR(Service_CSND, "called, shared memory not allocated"); LOG_ERROR(Service_CSND, "called, shared memory not allocated");
} else { return;
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);
} }
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<LoopMode>(configure.loop_mode.Value());
channel.encoding = static_cast<Encoding>(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<u16>(command.command_id));
}
}
*shared_memory->GetPointer(addr + offsetof(Type0Command, finished)) = 1;
rb.Push(RESULT_SUCCESS);
} }
void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) { void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x05, 0, 0); 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0xFFFFFF00); rb.Push(acquired_channel_mask);
LOG_WARNING(Service_CSND, "(STUBBED) called"); LOG_WARNING(Service_CSND, "(STUBBED) called");
} }
@ -85,6 +410,8 @@ void CSND_SND::AcquireSoundChannels(Kernel::HLERequestContext& ctx) {
void CSND_SND::ReleaseSoundChannels(Kernel::HLERequestContext& ctx) { void CSND_SND::ReleaseSoundChannels(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x06, 0, 0); IPC::RequestParser rp(ctx, 0x06, 0, 0);
acquired_channel_mask = 0;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);

View file

@ -15,6 +15,45 @@ class System;
namespace Service::CSND { 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<CSND_SND> { class CSND_SND final : public ServiceFramework<CSND_SND> {
public: public:
explicit CSND_SND(Core::System& system); explicit CSND_SND(Core::System& system);
@ -26,10 +65,10 @@ private:
* Inputs: * Inputs:
* 0 : Header Code[0x00010140] * 0 : Header Code[0x00010140]
* 1 : Shared memory block size, for mem-block creation * 1 : Shared memory block size, for mem-block creation
* 2 : Offset0 located in the shared-memory, region size=8 * 2 : offset to master state located in the shared-memory, region size=8
* 3 : Offset1 located in the shared-memory, region size=12*num_channels * 3 : offset to channel state located in the shared-memory, region size=12*num_channels
* 4 : Offset2 located in the shared-memory, region size=8*num_capturedevices * 4 : offset to capture state located in the shared-memory, region size=8*num_captures
* 5 : Offset3 located in the shared-memory. * 5 : offset to type 1 commands (?) located in the shared-memory.
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* 2 : Handle-list header * 2 : Handle-list header
@ -166,15 +205,6 @@ private:
*/ */
void Reset(Kernel::HLERequestContext& ctx); 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; Core::System& system;
std::shared_ptr<Kernel::Mutex> mutex = nullptr; std::shared_ptr<Kernel::Mutex> mutex = nullptr;
@ -182,6 +212,16 @@ private:
static constexpr u32 MaxCaptureUnits = 2; static constexpr u32 MaxCaptureUnits = 2;
std::array<bool, MaxCaptureUnits> capture_units = {false, false}; std::array<bool, MaxCaptureUnits> capture_units = {false, false};
static constexpr u32 ChannelCount = 32;
std::array<Channel, ChannelCount> 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 /// Initializes the CSND_SND Service