2018-12-06 12:14:54 +00:00
|
|
|
// Copyright 2018 Citra Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include "audio_core/lle/lle.h"
|
2018-12-06 12:42:07 +00:00
|
|
|
#include "common/assert.h"
|
|
|
|
#include "common/swap.h"
|
2018-12-06 13:10:00 +00:00
|
|
|
#include "core/hle/service/dsp/dsp_dsp.h"
|
2018-12-06 12:14:54 +00:00
|
|
|
#include "teakra/teakra.h"
|
|
|
|
|
|
|
|
namespace AudioCore {
|
|
|
|
|
2018-12-06 12:42:07 +00:00
|
|
|
struct PipeStatus {
|
|
|
|
u16_le waddress;
|
|
|
|
u16_le bsize;
|
|
|
|
u16_le read_bptr;
|
|
|
|
u16_le write_bptr;
|
|
|
|
u8 slot_index;
|
|
|
|
u8 flags;
|
|
|
|
};
|
|
|
|
|
|
|
|
static_assert(sizeof(PipeStatus) == 10);
|
|
|
|
|
|
|
|
enum class PipeDirection : u8 {
|
|
|
|
DSPtoCPU = 0,
|
|
|
|
CPUtoDSP = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static u8 PipeIndexToSlotIndex(u8 pipe_index, PipeDirection direction) {
|
|
|
|
return (pipe_index << 1) + (u8)direction;
|
|
|
|
}
|
|
|
|
|
2018-12-06 12:14:54 +00:00
|
|
|
struct DspLle::Impl final {
|
|
|
|
Teakra::Teakra teakra;
|
2018-12-06 12:42:07 +00:00
|
|
|
u16 pipe_base_waddr = 0;
|
2018-12-06 12:22:31 +00:00
|
|
|
|
2018-12-06 13:10:00 +00:00
|
|
|
bool semaphore_signaled = false;
|
|
|
|
bool data_signaled = false;
|
|
|
|
|
2018-12-06 12:22:31 +00:00
|
|
|
static constexpr unsigned TeakraSlice = 20000;
|
|
|
|
void RunTeakraSlice() {
|
|
|
|
teakra.Run(TeakraSlice);
|
|
|
|
}
|
2018-12-06 12:42:07 +00:00
|
|
|
|
|
|
|
u8* GetDspDataPointer(u32 baddr) {
|
|
|
|
auto& memory = teakra.GetDspMemory();
|
|
|
|
return &memory[0x40000 + baddr];
|
|
|
|
}
|
|
|
|
|
|
|
|
PipeStatus GetPipeStatus(u8 pipe_index, PipeDirection direction) {
|
|
|
|
u8 slot_index = PipeIndexToSlotIndex(pipe_index, direction);
|
|
|
|
PipeStatus pipe_status;
|
|
|
|
std::memcpy(&pipe_status,
|
|
|
|
GetDspDataPointer(pipe_base_waddr * 2 + slot_index * sizeof(PipeStatus)),
|
|
|
|
sizeof(PipeStatus));
|
|
|
|
ASSERT(pipe_status.slot_index == slot_index);
|
|
|
|
return pipe_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UpdatePipeStatus(const PipeStatus& pipe_status) {
|
|
|
|
u8 slot_index = pipe_status.slot_index;
|
|
|
|
u8* status_address =
|
|
|
|
GetDspDataPointer(pipe_base_waddr * 2 + slot_index * sizeof(PipeStatus));
|
|
|
|
if (slot_index % 2 == 0) {
|
|
|
|
std::memcpy(status_address + 4, &pipe_status.read_bptr, sizeof(u16));
|
|
|
|
} else {
|
|
|
|
std::memcpy(status_address + 6, &pipe_status.write_bptr, sizeof(u16));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WritePipe(u8 pipe_index, const std::vector<u8>& data) {
|
|
|
|
PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::CPUtoDSP);
|
|
|
|
bool need_update = false;
|
|
|
|
const u8* buffer_ptr = data.data();
|
|
|
|
u16 bsize = (u16)data.size();
|
|
|
|
while (bsize != 0) {
|
|
|
|
u16 x = pipe_status.read_bptr ^ pipe_status.write_bptr;
|
|
|
|
ASSERT_MSG(x != 0x8000, "Pipe is Full");
|
|
|
|
u16 write_bend;
|
|
|
|
if (x > 0x8000)
|
|
|
|
write_bend = pipe_status.read_bptr & 0x7FFF;
|
|
|
|
else
|
|
|
|
write_bend = pipe_status.bsize;
|
|
|
|
u16 write_bbegin = pipe_status.write_bptr & 0x7FFF;
|
|
|
|
ASSERT_MSG(write_bend > write_bbegin,
|
|
|
|
"Pipe is in inconsistent state: end {:04X} <= begin {:04X}, size {:04X}",
|
|
|
|
write_bend, write_bbegin, pipe_status.bsize);
|
|
|
|
u16 write_bsize = std::min<u16>(bsize, write_bend - write_bbegin);
|
|
|
|
std::memcpy(GetDspDataPointer(pipe_status.waddress * 2 + write_bbegin), buffer_ptr,
|
|
|
|
write_bsize);
|
|
|
|
buffer_ptr += write_bsize;
|
|
|
|
pipe_status.write_bptr += write_bsize;
|
|
|
|
bsize -= write_bsize;
|
|
|
|
ASSERT_MSG((pipe_status.write_bptr & 0x7FFF) <= pipe_status.bsize,
|
|
|
|
"Pipe is in inconsistent state: write > size");
|
|
|
|
if ((pipe_status.write_bptr & 0x7FFF) == pipe_status.bsize) {
|
|
|
|
pipe_status.write_bptr &= 0x8000;
|
|
|
|
pipe_status.write_bptr ^= 0x8000;
|
|
|
|
}
|
|
|
|
need_update = true;
|
|
|
|
}
|
|
|
|
if (need_update) {
|
|
|
|
UpdatePipeStatus(pipe_status);
|
|
|
|
while (!teakra.SendDataIsEmpty(2))
|
|
|
|
RunTeakraSlice();
|
|
|
|
teakra.SendData(2, pipe_status.slot_index);
|
|
|
|
}
|
|
|
|
}
|
2018-12-06 12:49:07 +00:00
|
|
|
|
|
|
|
std::vector<u8> ReadPipe(u8 pipe_index, u16 bsize) {
|
|
|
|
PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::DSPtoCPU);
|
|
|
|
bool need_update = false;
|
|
|
|
std::vector<u8> data(bsize);
|
|
|
|
u8* buffer_ptr = data.data();
|
|
|
|
while (bsize != 0) {
|
|
|
|
u16 x = pipe_status.read_bptr ^ pipe_status.write_bptr;
|
|
|
|
ASSERT_MSG(x != 0, "Pipe is empty");
|
|
|
|
u16 read_bend;
|
|
|
|
if (x >= 0x8000) {
|
|
|
|
read_bend = pipe_status.bsize;
|
|
|
|
} else {
|
|
|
|
read_bend = pipe_status.write_bptr & 0x7FFF;
|
|
|
|
}
|
|
|
|
u16 read_bbegin = pipe_status.read_bptr & 0x7FFF;
|
|
|
|
ASSERT(read_bend > read_bbegin);
|
|
|
|
u16 read_bsize = std::min<u16>(bsize, read_bend - read_bbegin);
|
|
|
|
std::memcpy(buffer_ptr, GetDspDataPointer(pipe_status.waddress * 2 + read_bbegin),
|
|
|
|
read_bsize);
|
|
|
|
buffer_ptr += read_bsize;
|
|
|
|
pipe_status.read_bptr += read_bsize;
|
|
|
|
bsize -= read_bsize;
|
|
|
|
ASSERT_MSG((pipe_status.read_bptr & 0x7FFF) <= pipe_status.bsize,
|
|
|
|
"Pipe is in inconsistent state: read > size");
|
|
|
|
if ((pipe_status.read_bptr & 0x7FFF) == pipe_status.bsize) {
|
|
|
|
pipe_status.read_bptr &= 0x8000;
|
|
|
|
pipe_status.read_bptr ^= 0x8000;
|
|
|
|
}
|
|
|
|
need_update = true;
|
|
|
|
}
|
|
|
|
if (need_update) {
|
|
|
|
UpdatePipeStatus(pipe_status);
|
|
|
|
while (!teakra.SendDataIsEmpty(2))
|
|
|
|
RunTeakraSlice();
|
|
|
|
teakra.SendData(2, pipe_status.slot_index);
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
u16 GetPipeReadableSize(u8 pipe_index) {
|
|
|
|
PipeStatus pipe_status = GetPipeStatus(pipe_index, PipeDirection::DSPtoCPU);
|
|
|
|
u16 size = pipe_status.write_bptr - pipe_status.read_bptr;
|
|
|
|
if ((pipe_status.read_bptr ^ pipe_status.write_bptr) >= 0x8000) {
|
|
|
|
size += pipe_status.bsize;
|
|
|
|
}
|
|
|
|
return size & 0x7FFF;
|
|
|
|
}
|
2018-12-06 12:14:54 +00:00
|
|
|
};
|
|
|
|
|
2018-12-06 12:22:31 +00:00
|
|
|
u16 DspLle::RecvData(u32 register_number) {
|
|
|
|
while (!impl->teakra.RecvDataIsReady(register_number)) {
|
|
|
|
impl->RunTeakraSlice();
|
|
|
|
}
|
|
|
|
return impl->teakra.RecvData(static_cast<u8>(register_number));
|
|
|
|
}
|
|
|
|
|
2018-12-06 12:30:10 +00:00
|
|
|
bool DspLle::RecvDataIsReady(u32 register_number) const {
|
|
|
|
return impl->teakra.RecvDataIsReady(register_number);
|
|
|
|
}
|
|
|
|
|
2018-12-06 12:35:07 +00:00
|
|
|
void DspLle::SetSemaphore(u16 semaphore_value) {
|
|
|
|
impl->teakra.SetSemaphore(semaphore_value);
|
|
|
|
}
|
|
|
|
|
2018-12-06 12:49:07 +00:00
|
|
|
std::vector<u8> DspLle::PipeRead(DspPipe pipe_number, u32 length) {
|
|
|
|
return impl->ReadPipe(static_cast<u8>(pipe_number), static_cast<u16>(length));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::size_t DspLle::GetPipeReadableSize(DspPipe pipe_number) const {
|
|
|
|
return impl->GetPipeReadableSize(static_cast<u8>(pipe_number));
|
|
|
|
}
|
|
|
|
|
2018-12-06 12:42:07 +00:00
|
|
|
void DspLle::PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
|
|
|
impl->WritePipe(static_cast<u8>(pipe_number), buffer);
|
|
|
|
}
|
|
|
|
|
2018-12-06 13:10:00 +00:00
|
|
|
std::array<u8, Memory::DSP_RAM_SIZE>& DspLle::GetDspMemory() {
|
|
|
|
return impl->teakra.GetDspMemory();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DspLle::SetServiceToInterrupt(std::weak_ptr<Service::DSP::DSP_DSP> dsp) {
|
|
|
|
impl->teakra.SetRecvDataHandler(0, [dsp]() {
|
|
|
|
if (auto locked = dsp.lock()) {
|
|
|
|
locked->SignalInterrupt(Service::DSP::DSP_DSP::InterruptType::Zero,
|
|
|
|
static_cast<DspPipe>(0));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
impl->teakra.SetRecvDataHandler(1, [dsp]() {
|
|
|
|
if (auto locked = dsp.lock()) {
|
|
|
|
locked->SignalInterrupt(Service::DSP::DSP_DSP::InterruptType::One,
|
|
|
|
static_cast<DspPipe>(0));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
auto ProcessPipeEvent = [this, dsp](bool event_from_data) {
|
|
|
|
auto& teakra = impl->teakra;
|
|
|
|
if (event_from_data) {
|
|
|
|
impl->data_signaled = true;
|
|
|
|
} else {
|
|
|
|
if ((teakra.GetSemaphore() & 0x8000) == 0)
|
|
|
|
return;
|
|
|
|
impl->semaphore_signaled = true;
|
|
|
|
}
|
|
|
|
if (impl->semaphore_signaled && impl->data_signaled) {
|
|
|
|
impl->semaphore_signaled = impl->data_signaled = false;
|
|
|
|
u16 slot = teakra.RecvData(2);
|
|
|
|
u16 side = slot % 2;
|
|
|
|
u16 pipe = slot / 2;
|
|
|
|
ASSERT(pipe < 16);
|
|
|
|
if (side != static_cast<u16>(PipeDirection::DSPtoCPU))
|
|
|
|
return;
|
|
|
|
if (pipe == 0) {
|
|
|
|
// pipe 0 is for debug. 3DS automatically drains this pipe and discards the data
|
|
|
|
impl->ReadPipe(pipe, impl->GetPipeReadableSize(pipe));
|
|
|
|
} else {
|
|
|
|
if (auto locked = dsp.lock()) {
|
|
|
|
locked->SignalInterrupt(Service::DSP::DSP_DSP::InterruptType::Pipe,
|
|
|
|
static_cast<DspPipe>(pipe));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
impl->teakra.SetRecvDataHandler(2, [ProcessPipeEvent]() { ProcessPipeEvent(true); });
|
|
|
|
impl->teakra.SetSemaphoreHandler([ProcessPipeEvent]() { ProcessPipeEvent(false); });
|
|
|
|
}
|
|
|
|
|
2018-12-06 12:14:54 +00:00
|
|
|
DspLle::DspLle() : impl(std::make_unique<Impl>()) {}
|
|
|
|
DspLle::~DspLle() = default;
|
|
|
|
|
|
|
|
} // namespace AudioCore
|