diff --git a/.github/BUILDING.md b/.github/BUILDING.md index 231a21c25..c120a19de 100644 --- a/.github/BUILDING.md +++ b/.github/BUILDING.md @@ -4,20 +4,20 @@ ### The dependencies for Debian-like distributions. ``` -sudo apt install build-essential cmake libunwind-dev libglfw3-dev libvulkan-dev vulkan-validationlayers-dev spirv-tools glslang-tools libspirv-cross-c-shared-dev libsox-dev git +sudo apt install build-essential cmake libunwind-dev libglfw3-dev libvulkan-dev vulkan-validationlayers-dev spirv-tools glslang-tools libspirv-cross-c-shared-dev libsox-dev git libasound2-dev ``` # git is only needed for ubuntu 22.04 ### The dependencies for Fedora distributions: ``` -sudo dnf install cmake libunwind-devel glfw-devel vulkan-devel vulkan-validation-layers-devel spirv-tools glslang-devel gcc-c++ gcc spirv-tools-devel xbyak-devel sox-devel +sudo dnf install cmake libunwind-devel glfw-devel vulkan-devel vulkan-validation-layers-devel spirv-tools glslang-devel gcc-c++ gcc spirv-tools-devel xbyak-devel sox-devel alsa-lib-devel ``` ### The dependencies for Arch distributions: ``` -sudo pacman -S libunwind glfw-x11 vulkan-devel sox glslang git cmake +sudo pacman -S libunwind glfw-x11 vulkan-devel sox glslang git cmake alsa-lib ``` > Side note you will need to pull ``spirv-cross`` from the AUR for now so do the following ``` diff --git a/.github/workflows/rpcsx.yml b/.github/workflows/rpcsx.yml index 4ebec57fc..9843c63a8 100644 --- a/.github/workflows/rpcsx.yml +++ b/.github/workflows/rpcsx.yml @@ -26,7 +26,7 @@ jobs: sudo apt update sudo apt install -y cmake build-essential libunwind-dev \ libglfw3-dev libvulkan-dev vulkan-validationlayers \ - libsox-dev + libsox-dev libasound2-dev echo "deb http://azure.archive.ubuntu.com/ubuntu noble main universe" | sudo tee /etc/apt/sources.list sudo apt update sudo apt install g++-14 ninja-build diff --git a/rpcsx-os/CMakeLists.txt b/rpcsx-os/CMakeLists.txt index 745ba1716..c4aa07c5e 100644 --- a/rpcsx-os/CMakeLists.txt +++ b/rpcsx-os/CMakeLists.txt @@ -6,6 +6,9 @@ target_include_directories(standalone-config INTERFACE orbis-kernel-config) add_library(orbis::kernel::config ALIAS standalone-config) add_executable(rpcsx-os + audio/AudioDevice.cpp + audio/AlsaDevice.cpp + iodev/ajm.cpp iodev/blockpool.cpp iodev/bt.cpp @@ -66,7 +69,7 @@ add_executable(rpcsx-os ) target_include_directories(rpcsx-os PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(rpcsx-os PUBLIC orbis::kernel amdgpu::bridge rx libcrypto libunwind::unwind-x86_64 xbyak::xbyak sox::sox) +target_link_libraries(rpcsx-os PUBLIC orbis::kernel amdgpu::bridge rx libcrypto libunwind::unwind-x86_64 xbyak::xbyak sox::sox asound) target_base_address(rpcsx-os 0x0000010000000000) target_compile_options(rpcsx-os PRIVATE "-mfsgsbase") diff --git a/rpcsx-os/audio/AlsaDevice.cpp b/rpcsx-os/audio/AlsaDevice.cpp new file mode 100644 index 000000000..024a668a1 --- /dev/null +++ b/rpcsx-os/audio/AlsaDevice.cpp @@ -0,0 +1,242 @@ +#include "AlsaDevice.hpp" +#include "orbis/utils/Logs.hpp" +#include "rx/hexdump.hpp" + +AlsaDevice::AlsaDevice() {} + +void AlsaDevice::start() { + setAlsaFormat(); + int err; + if ((err = snd_pcm_open(&mPCMHandle, "default", SND_PCM_STREAM_PLAYBACK, + 0)) < 0) { + ORBIS_LOG_FATAL("Cannot open audio device", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_malloc(&mHWParams)) < 0) { + ORBIS_LOG_FATAL("Cannot allocate hardware parameter structure", + snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_any(mPCMHandle, mHWParams)) < 0) { + ORBIS_LOG_FATAL("Cannot initialize hardware parameter structure", + snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_set_rate_resample(mPCMHandle, mHWParams, + 0)) < 0) { + ORBIS_LOG_FATAL("Cannot disable rate resampling", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_set_access(mPCMHandle, mHWParams, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + ORBIS_LOG_FATAL("Cannot set access type", snd_strerror(err)); + std::abort(); + } + if ((err = snd_pcm_hw_params_set_format(mPCMHandle, mHWParams, + mAlsaFormat)) < 0) { + ORBIS_LOG_FATAL("Cannot set sample format", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_set_rate(mPCMHandle, mHWParams, mFrequency, + 0)) < 0) { + ORBIS_LOG_FATAL("Cannot set sample rate", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_set_channels(mPCMHandle, mHWParams, mChannels)) < + 0) { + ORBIS_LOG_FATAL("cannot set channel count", snd_strerror(err), mChannels); + std::abort(); + } + + uint periods = mSampleCount; + if ((err = snd_pcm_hw_params_set_periods_max(mPCMHandle, mHWParams, &periods, NULL)) < 0) { + ORBIS_LOG_FATAL("Cannot set periods count", snd_strerror(err)); + std::abort(); + } + + int frameBytes = snd_pcm_format_physical_width(mAlsaFormat) * mChannels / 8; + + snd_pcm_uframes_t size = mSampleSize / frameBytes; + + // TODO: it shouldn't work like this + + if ((err = snd_pcm_hw_params_set_buffer_size(mPCMHandle, mHWParams, size)) < 0) { + ORBIS_LOG_FATAL("Cannot set buffer size", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_hw_params_set_period_size(mPCMHandle, mHWParams, size / 2, 0)) < 0) { + ORBIS_LOG_FATAL("Cannot set period size", snd_strerror(err)); + std::abort(); + } + + snd_pcm_uframes_t periodSize; + if ((err = snd_pcm_hw_params_get_period_size(mHWParams, &periodSize, NULL)) < 0) { + ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err)); + std::abort(); + } + + snd_pcm_uframes_t bufferSize; + if ((err = snd_pcm_hw_params_get_buffer_size(mHWParams, &bufferSize)) < 0) { + ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err)); + std::abort(); + } + + ORBIS_LOG_TODO("period and buffer", periodSize, bufferSize); + + if ((err = snd_pcm_hw_params(mPCMHandle, mHWParams)) < 0) { + ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_sw_params_malloc(&mSWParams)) < 0) { + ORBIS_LOG_FATAL("Cannot allocate software parameter structure", + snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_sw_params_current(mPCMHandle, mSWParams)) < 0) { + ORBIS_LOG_FATAL("cannot sw params current", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_sw_params_set_start_threshold(mPCMHandle, mSWParams, periodSize)) < 0) { + ORBIS_LOG_FATAL("cannot set start threshold", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_sw_params_set_stop_threshold(mPCMHandle, mSWParams, bufferSize)) < 0) { + ORBIS_LOG_FATAL("cannot set stop threshold", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_sw_params(mPCMHandle, mSWParams)) < 0) { + ORBIS_LOG_FATAL("cannot set parameters", snd_strerror(err)); + std::abort(); + } + + if ((err = snd_pcm_prepare(mPCMHandle)) < 0) { + ORBIS_LOG_FATAL("cannot prepare audio interface for use", + snd_strerror(err)); + std::abort(); + } + mWorking = true; +} + +int AlsaDevice::fixXRun() +{ + switch (snd_pcm_state(mPCMHandle)) { + case SND_PCM_STATE_XRUN: + return snd_pcm_prepare(mPCMHandle); + case SND_PCM_STATE_DRAINING: + if (snd_pcm_stream(mPCMHandle) == SND_PCM_STREAM_CAPTURE) + return snd_pcm_prepare(mPCMHandle); + break; + default: + break; + } + return -EIO; +} + +int AlsaDevice::resumeFromSupsend() +{ + int res; + while ((res = snd_pcm_resume(mPCMHandle)) == -EAGAIN) + std::this_thread::sleep_for(std::chrono::seconds(1)); + if (!res) + return 0; + return snd_pcm_prepare(mPCMHandle); +} + +long AlsaDevice::write(void *buf, long len) { + if (!mWorking) return 0; + ssize_t r; + int frameBytes = snd_pcm_format_physical_width(mAlsaFormat) * mChannels / 8; + snd_pcm_uframes_t frames = len / frameBytes; + + r = snd_pcm_writei(mPCMHandle, buf, frames); + if (r == -EPIPE) { + if (!(r = fixXRun())) + return write(buf, len); + } else if (r == -ESTRPIPE) { + if (!(r = resumeFromSupsend())) + return write(buf, len); + } + r *= frameBytes; + return r; +} + +void AlsaDevice::stop() { + snd_pcm_hw_params_free(mHWParams); + snd_pcm_sw_params_free(mSWParams); + snd_pcm_drain(mPCMHandle); + snd_pcm_drop(mPCMHandle); + mWorking = false; +} + +void AlsaDevice::reset() { + if (!mWorking) return; + int err; + err = snd_pcm_drop(mPCMHandle); + if (err >= 0) + err = snd_pcm_prepare(mPCMHandle); + if (err < 0) + err = err; +} + +audio_buf_info AlsaDevice::getOSpace() { + int err; + snd_pcm_uframes_t periodSize; + if ((err = snd_pcm_hw_params_get_period_size(mHWParams, &periodSize, NULL)) < 0) { + ORBIS_LOG_FATAL("cannot get period size", snd_strerror(err)); + std::abort(); + } + + snd_pcm_uframes_t bufferSize; + if ((err = snd_pcm_hw_params_get_buffer_size(mHWParams, &bufferSize)) < 0) { + ORBIS_LOG_FATAL("cannot get buffer size", snd_strerror(err)); + std::abort(); + } + int frameBytes = snd_pcm_format_physical_width(mAlsaFormat) * mChannels / 8; + + snd_pcm_sframes_t avail, delay; + audio_buf_info info; + avail = snd_pcm_avail_update(mPCMHandle); + if (avail < 0 || (snd_pcm_uframes_t)avail > bufferSize) + avail = bufferSize; + info.fragsize = periodSize * frameBytes; + info.fragstotal = mSampleCount; + info.bytes = avail * frameBytes; + info.fragments = avail / periodSize; + return info; +} + +void AlsaDevice::setAlsaFormat() { + if (mWorking) + return; + _snd_pcm_format fmt; + switch (mFormat) { + case FMT_S32_LE: + fmt = SND_PCM_FORMAT_S32_LE; + break; + case FMT_S16_LE: + fmt = SND_PCM_FORMAT_S16_LE; + break; + case FMT_AC3: + default: + ORBIS_LOG_FATAL("Format is not supported", mFormat); + std::abort(); + break; + } + mAlsaFormat = fmt; +} + +AlsaDevice::~AlsaDevice() { + stop(); +} \ No newline at end of file diff --git a/rpcsx-os/audio/AlsaDevice.hpp b/rpcsx-os/audio/AlsaDevice.hpp new file mode 100644 index 000000000..12328fa92 --- /dev/null +++ b/rpcsx-os/audio/AlsaDevice.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "AudioDevice.hpp" +#include +#include +#include + +class AlsaDevice : public AudioDevice { +private: + snd_pcm_format_t mAlsaFormat; + snd_pcm_t *mPCMHandle; + snd_pcm_hw_params_t *mHWParams; + snd_pcm_sw_params_t *mSWParams; + +public: + AlsaDevice(); + ~AlsaDevice() override; + + void init() override {}; + void start() override; + long write(void *, long) override; + void stop() override; + void reset() override; + + void setAlsaFormat(); + + int fixXRun(); + int resumeFromSupsend(); + + audio_buf_info getOSpace() override; +}; diff --git a/rpcsx-os/audio/AudioDevice.cpp b/rpcsx-os/audio/AudioDevice.cpp new file mode 100644 index 000000000..24a5778a7 --- /dev/null +++ b/rpcsx-os/audio/AudioDevice.cpp @@ -0,0 +1,55 @@ +#include "AudioDevice.hpp" +#include "orbis/utils/Logs.hpp" +#include "rx/hexdump.hpp" + +AudioDevice::AudioDevice() {} + +void AudioDevice::init() {} + +void AudioDevice::start() {} + +long AudioDevice::write(void *buf, long len) { + return -1; +} + +void AudioDevice::stop() { +} + +void AudioDevice::reset() {} + +void AudioDevice::setFormat(orbis::uint format) { + if (mWorking) + return; + mFormat = format; +} + +void AudioDevice::setFrequency(orbis::uint frequency) { + if (mWorking) + return; + mFrequency = frequency; +} + +void AudioDevice::setChannels(orbis::ushort channels) { + if (mWorking) + return; + if (channels > 8) { + ORBIS_LOG_FATAL("Channels count is not supported", channels); + std::abort(); + } + mChannels = channels; +} + +void AudioDevice::setSampleSize(orbis::uint sampleSize, orbis::uint sampleCount) { + if (mWorking) + return; + mSampleSize = sampleSize; + mSampleCount = sampleCount; +} + +audio_buf_info AudioDevice::getOSpace() { + audio_buf_info info; + return info; +} + + +AudioDevice::~AudioDevice() {} \ No newline at end of file diff --git a/rpcsx-os/audio/AudioDevice.hpp b/rpcsx-os/audio/AudioDevice.hpp new file mode 100644 index 000000000..95dfe2629 --- /dev/null +++ b/rpcsx-os/audio/AudioDevice.hpp @@ -0,0 +1,44 @@ +#pragma once + +#define FMT_S16_LE 0x10 +#define FMT_AC3 0x400 +#define FMT_S32_LE 0x1000 + +#include +#include + +struct audio_buf_info { + int fragments; + int fragstotal; + int fragsize; + int bytes; +}; + +class AudioDevice { +protected: + bool mWorking = false; + orbis::uint mFormat{}; + orbis::uint mFrequency{}; + orbis::ushort mChannels{}; + orbis::ushort mSampleSize{}; + orbis::ushort mSampleCount{}; + +private: + +public: + AudioDevice(); + virtual ~AudioDevice(); + + virtual void init(); + virtual void start(); + virtual long write(void *buf, long len); + virtual void stop(); + virtual void reset(); + + void setFormat(orbis::uint format); + void setFrequency(orbis::uint frequency); + void setChannels(orbis::ushort channels); + void setSampleSize(orbis::uint sampleSize = 0, orbis::uint sampleCount = 0); + + virtual audio_buf_info getOSpace(); +}; diff --git a/rpcsx-os/io-devices.hpp b/rpcsx-os/io-devices.hpp index b10045a87..99f11fd50 100644 --- a/rpcsx-os/io-devices.hpp +++ b/rpcsx-os/io-devices.hpp @@ -31,7 +31,7 @@ IoDevice *createXptCharacterDevice(); IoDevice *createCdCharacterDevice(); IoDevice *createMetaDbgCharacterDevice(); IoDevice *createHddCharacterDevice(std::uint64_t size); -IoDevice *createAoutCharacterDevice(); +IoDevice *createAoutCharacterDevice(std::int8_t id); IoDevice *createAVControlCharacterDevice(); IoDevice *createHDMICharacterDevice(); IoDevice *createMBusAVCharacterDevice(); diff --git a/rpcsx-os/iodev/aout.cpp b/rpcsx-os/iodev/aout.cpp index 3ac39cc94..4d036c01a 100644 --- a/rpcsx-os/iodev/aout.cpp +++ b/rpcsx-os/iodev/aout.cpp @@ -1,3 +1,4 @@ +#include "audio/AlsaDevice.hpp" #include "io-device.hpp" #include "iodev/mbus_av.hpp" #include "orbis/KernelAllocator.hpp" @@ -8,20 +9,168 @@ #include "orbis/uio.hpp" #include "orbis/utils/Logs.hpp" #include +// #include + +#define SNDCTL_DSP_RESET 0x20005000 +#define SNDCTL_DSP_SETFRAGMENT 0xc004500a +#define SNDCTL_DSP_SETFMT 0xc0045005 +#define SNDCTL_DSP_SPEED 0xc0045002 +#define SNDCTL_DSP_CHANNELS 0xc0045006 +#define ORBIS_AUDIO_UPDATE_TICK_PARAMS 0xc004505c +#define SNDCTL_DSP_SYNCGROUP 0xc048501c +#define ORBIS_AUDIO_CONFIG_SPDIF 0xc0085063 +#define SNDCTL_DSP_GETBLKSIZE 0x40045004 +#define SOUND_PCM_READ_BITS 0x40045005 +#define SNDCTL_DSP_GETOSPACE 0x4010500c +#define SNDCTL_DSP_SYNCSTART 0x8004501d +#define ORBIS_AUDIO_IOCTL_SETCONTROL 0x80085062 struct AoutFile : orbis::File {}; +struct AoutDevice : public IoDevice { + std::int8_t id; + AudioDevice *audioDevice; + + AoutDevice(std::int8_t id) : id(id) {} + + orbis::ErrorCode open(orbis::Ref *file, const char *path, + std::uint32_t flags, std::uint32_t mode, + orbis::Thread *thread) override; +}; + static orbis::ErrorCode aout_ioctl(orbis::File *file, std::uint64_t request, void *argp, orbis::Thread *thread) { - ORBIS_LOG_FATAL("Unhandled aout ioctl", request); - thread->where(); + auto device = static_cast(file->device.get()); + switch (request) { + case SNDCTL_DSP_RESET: { + ORBIS_LOG_TODO("SNDCTL_DSP_RESET"); + if (auto audioDevice = device->audioDevice) { + audioDevice->reset(); + } + return {}; + } + case SNDCTL_DSP_SETFRAGMENT: { + struct Args { + std::uint32_t fragment; + }; + auto args = reinterpret_cast(argp); + ORBIS_LOG_NOTICE("SNDCTL_DSP_SETFRAGMENT", args->fragment & 0xF, (args->fragment >> 16) & 0xF); + if (auto audioDevice = device->audioDevice) { + audioDevice->setSampleSize(1 << (args->fragment & 0xF), (args->fragment >> 16) & 0xF); + } + return {}; + } + case SNDCTL_DSP_SETFMT: { + struct Args { + std::uint32_t fmt; + }; + auto args = reinterpret_cast(argp); + ORBIS_LOG_NOTICE("SNDCTL_DSP_SETFMT", args->fmt); + if (auto audioDevice = device->audioDevice) { + audioDevice->setFormat(args->fmt); + } + return {}; + } + case SNDCTL_DSP_SPEED: { + struct Args { + std::uint32_t speed; + }; + auto args = reinterpret_cast(argp); + if (auto audioDevice = device->audioDevice) { + audioDevice->setFrequency(args->speed); + } + return {}; + } + case SNDCTL_DSP_CHANNELS: { + struct Args { + std::uint32_t channels; + }; + auto args = reinterpret_cast(argp); + if (auto audioDevice = device->audioDevice) { + audioDevice->setChannels(args->channels); + } + return {}; + } + case ORBIS_AUDIO_UPDATE_TICK_PARAMS: { + struct Args { + std::uint32_t tick; + }; + auto args = reinterpret_cast(argp); + ORBIS_LOG_NOTICE("ORBIS_AUDIO_UPDATE_TICK_PARAMS", args->tick); + return {}; + } + case SNDCTL_DSP_SYNCGROUP: { + ORBIS_LOG_NOTICE("SNDCTL_DSP_SYNCGROUP"); + return {}; + } + case ORBIS_AUDIO_CONFIG_SPDIF: { + struct Args { + std::uint64_t unk0; + }; + auto args = reinterpret_cast(argp); + args->unk0 = 0x100000000; // Disable SPDIF output + return {}; + } + case SNDCTL_DSP_GETBLKSIZE: { + struct Args { + std::uint32_t blksize; + }; + auto args = reinterpret_cast(argp); + ORBIS_LOG_NOTICE("SNDCTL_DSP_GETBLKSIZE", args->blksize); + return {}; + } + case SOUND_PCM_READ_BITS: { + struct Args { + std::uint32_t bits; + }; + auto args = reinterpret_cast(argp); + ORBIS_LOG_NOTICE("SOUND_PCM_READ_BITS", args->bits); + return {}; + } + case SNDCTL_DSP_GETOSPACE: { + auto args = reinterpret_cast(argp); + if (auto audioDevice = device->audioDevice) { + auto info = audioDevice->getOSpace(); + args->fragments = info.fragments; + args->fragstotal = info.fragstotal; + args->fragsize = info.fragsize; + args->bytes = info.bytes; + } + ORBIS_LOG_TODO("SNDCTL_DSP_GETOSPACE", args->fragments, args->fragstotal, args->fragsize, args->bytes); + return {}; + } + case SNDCTL_DSP_SYNCSTART: { + ORBIS_LOG_NOTICE("SNDCTL_DSP_SYNCSTART"); + if (auto audioDevice = device->audioDevice) { + audioDevice->start(); + } + return {}; + } + case ORBIS_AUDIO_IOCTL_SETCONTROL: { + struct Args { + std::uint64_t unk0; + }; + auto args = reinterpret_cast(argp); + ORBIS_LOG_NOTICE("ORBIS_AUDIO_IOCTL_SETCONTROL", args->unk0); + return {}; + } + default: + ORBIS_LOG_FATAL("Unhandled aout ioctl", request); + thread->where(); + break; + } return {}; } static orbis::ErrorCode aout_write(orbis::File *file, orbis::Uio *uio, - orbis::Thread *) { - for (auto entry : std::span(uio->iov, uio->iovcnt)) { - uio->offset += entry.len; + orbis::Thread *thread) { + auto device = static_cast(file->device.get()); + if (auto audioDevice = device->audioDevice) { + for (auto vec : std::span(uio->iov, uio->iovcnt)) { + audioDevice->write(vec.base, vec.len); + // rx::hexdump({(std::byte*)vec.base, vec.len}); + uio->offset += vec.len; + } } return {}; } @@ -31,19 +180,24 @@ static const orbis::FileOps fileOps = { .write = aout_write, }; -struct AoutDevice : IoDevice { - orbis::ErrorCode open(orbis::Ref *file, const char *path, - std::uint32_t flags, std::uint32_t mode, - orbis::Thread *thread) override { - ORBIS_LOG_FATAL("aout device open", path, flags, mode); - auto newFile = orbis::knew(); - newFile->ops = &fileOps; - newFile->device = this; - thread->where(); +orbis::ErrorCode AoutDevice::open(orbis::Ref *file, + const char *path, std::uint32_t flags, + std::uint32_t mode, orbis::Thread *thread) { + ORBIS_LOG_FATAL("aout device open", path, flags, mode); + auto newFile = orbis::knew(); + newFile->ops = &fileOps; + newFile->device = this; + thread->where(); - *file = newFile; - return {}; + *file = newFile; + // create audio device only for hdmi output, 0 - hdmi, 1 - analog, 2 - spdif + if (id == 0) { + // TODO: use factory to more backends support + audioDevice = new AlsaDevice(); } -}; + return {}; +} -IoDevice *createAoutCharacterDevice() { return orbis::knew(); } +IoDevice *createAoutCharacterDevice(std::int8_t id) { + return orbis::knew(id); +} diff --git a/rpcsx-os/main.cpp b/rpcsx-os/main.cpp index 60e26c326..db2e06feb 100644 --- a/rpcsx-os/main.cpp +++ b/rpcsx-os/main.cpp @@ -404,9 +404,9 @@ static void ps4InitDev() { rx::vfs::addDevice("notification3", createNotificationCharacterDevice(3)); rx::vfs::addDevice("notification4", createNotificationCharacterDevice(4)); rx::vfs::addDevice("notification5", createNotificationCharacterDevice(5)); - rx::vfs::addDevice("aout0", createAoutCharacterDevice()); - rx::vfs::addDevice("aout1", createAoutCharacterDevice()); - rx::vfs::addDevice("aout2", createAoutCharacterDevice()); + rx::vfs::addDevice("aout0", createAoutCharacterDevice(0)); + rx::vfs::addDevice("aout1", createAoutCharacterDevice(1)); + rx::vfs::addDevice("aout2", createAoutCharacterDevice(2)); rx::vfs::addDevice("av_control", createAVControlCharacterDevice()); rx::vfs::addDevice("hdmi", createHDMICharacterDevice()); rx::vfs::addDevice("mbus_av", mbusAv); @@ -464,6 +464,189 @@ static void ps4InitFd(orbis::Thread *mainThread) { mainThread->tproc->fileDescriptors.insert(stderrFile); } +static orbis::Process *createGuestProcess() { + auto pid = orbis::g_context.allocatePid() * 10000 + 1; + return orbis::g_context.createProcess(pid); +} + +static orbis::Thread *createGuestThread() { + auto process = createGuestProcess(); + auto [baseId, thread] = process->threadsMap.emplace(); + thread->tproc = process; + thread->tid = process->pid + baseId; + thread->state = orbis::ThreadState::RUNNING; + return thread; +} + +template struct GuestAlloc { + orbis::ptr guestAddress; + + GuestAlloc(std::size_t size) { + if (size == 0) { + guestAddress = nullptr; + } else { + guestAddress = orbis::ptr(rx::vm::map( + nullptr, size, rx::vm::kMapProtCpuRead | rx::vm::kMapProtCpuWrite, + rx::vm::kMapFlagPrivate | rx::vm::kMapFlagAnonymous)); + } + } + + GuestAlloc() : GuestAlloc(sizeof(T)) {} + + GuestAlloc(const T &data) : GuestAlloc() { + if (orbis::uwrite(guestAddress, data) != orbis::ErrorCode{}) { + std::abort(); + } + } + + GuestAlloc(const void *data, std::size_t size) : GuestAlloc(size) { + if (orbis::uwriteRaw(guestAddress, data, size) != orbis::ErrorCode{}) { + std::abort(); + } + } + + GuestAlloc(const GuestAlloc &) = delete; + + GuestAlloc(GuestAlloc &&other) : guestAddress(other.guestAddress) { + other.guestAddress = 0; + } + GuestAlloc &operator=(GuestAlloc &&other) { + std::swap(guestAddress, other.guestAddress); + } + + ~GuestAlloc() { + if (guestAddress != 0) { + rx::vm::unmap(guestAddress, sizeof(T)); + } + } + + operator orbis::ptr() { return guestAddress; } + T *operator->() { return guestAddress; } + operator T &() { return *guestAddress; } +}; + +struct IpmiClient { + orbis::Ref clientImpl; + orbis::uint kid; + orbis::Thread *thread; + + orbis::sint + sendSyncMessageRaw(std::uint32_t method, + const std::vector> &inData, + std::vector> &outBuf) { + GuestAlloc serverResult; + GuestAlloc guestInDataArray{ + sizeof(orbis::IpmiDataInfo) * inData.size()}; + GuestAlloc guestOutBufArray{ + sizeof(orbis::IpmiBufferInfo) * outBuf.size()}; + + std::vector> guestAllocs; + guestAllocs.reserve(inData.size() + outBuf.size()); + + for (auto &data : inData) { + auto pointer = + guestAllocs.emplace_back(data.data(), data.size()).guestAddress; + + guestInDataArray.guestAddress[&data - inData.data()] = { + .data = pointer, .size = data.size()}; + } + + for (auto &buf : outBuf) { + auto pointer = + guestAllocs.emplace_back(buf.data(), buf.size()).guestAddress; + + guestOutBufArray.guestAddress[&buf - outBuf.data()] = { + .data = pointer, .capacity = buf.size()}; + } + + GuestAlloc params = orbis::IpmiSyncCallParams{ + .method = method, + .numInData = static_cast(inData.size()), + .numOutData = static_cast(outBuf.size()), + .pInData = guestInDataArray, + .pOutData = guestOutBufArray, + .pResult = serverResult, + .flags = (inData.size() >= 1 || outBuf.size() >= 1) ? 1u : 0u, + }; + + GuestAlloc errorCode; + orbis::sysIpmiClientInvokeSyncMethod(thread, errorCode, kid, params, + sizeof(orbis::IpmiSyncCallParams)); + + for (auto &buf : outBuf) { + auto size = guestOutBufArray.guestAddress[inData.data() - &buf].size; + buf.resize(size); + } + return serverResult; + } + + template + orbis::sint sendSyncMessage(std::uint32_t method, + const InputTypes &...input) { + std::vector> outBuf; + return sendSyncMessageRaw(method, {toBytes(input)...}, outBuf); + } + + template + requires((sizeof...(OutputTypes) > 0) || sizeof...(InputTypes) == 0) + std::tuple sendSyncMessage(std::uint32_t method, + InputTypes... input) { + std::vector> outBuf{sizeof(OutputTypes)...}; + sendSyncMessageRaw(method, {toBytes(input)...}, outBuf); + std::tuple output; + + auto unpack = [&](std::index_sequence) { + ((std::get(output) = *reinterpret_cast(outBuf.data())), + ...); + }; + unpack(std::make_index_sequence{}); + return output; + } +}; + +static IpmiClient audioIpmiClient; + +static IpmiClient createIpmiClient(orbis::Thread *thread, const char *name) { + orbis::Ref client; + GuestAlloc config = orbis::IpmiCreateClientConfig{ + .size = sizeof(orbis::IpmiCreateClientConfig), + }; + + orbis::uint kid; + + { + GuestAlloc guestName{name, std::strlen(name)}; + GuestAlloc params = orbis::IpmiCreateClientParams{ + .name = guestName, + .config = config, + }; + + GuestAlloc result; + GuestAlloc guestKid; + orbis::sysIpmiCreateClient(thread, guestKid, params, + sizeof(orbis::IpmiCreateClientParams)); + kid = guestKid; + } + + { + GuestAlloc status; + GuestAlloc params = orbis::IpmiClientConnectParams{.status = status}; + + GuestAlloc result; + while (true) { + auto errc = orbis::sysIpmiClientConnect( + thread, result, kid, params, sizeof(orbis::IpmiClientConnectParams)); + if (errc.value() == 0) { + break; + } + + std::this_thread::sleep_for(std::chrono::microseconds(300)); + } + } + + return {std::move(client), kid, thread}; +} + struct ExecEnv { std::uint64_t entryPoint; std::uint64_t interpBase; @@ -1281,7 +1464,7 @@ static void createAudioSystemObjects(orbis::Process *process) { } struct SceMbusIpcAddHandleByUserIdMethodArgs { - orbis::uint32_t unk; // 0 + orbis::uint32_t deviceType; // 0 - pad, 1 - aout, 2 - ain, 4 - camera, 6 - kb, 7 - mouse, 8 - vr orbis::uint32_t deviceId; orbis::uint32_t userId; orbis::uint32_t type; @@ -1301,9 +1484,36 @@ static void createSysCoreObjects(orbis::Process *process) { createIpmiServer(process, "SceMbusIpc") .addSyncMethod( 0xce110007, [](const auto &args) -> std::int32_t { - ORBIS_LOG_TODO("IPMI: SceMbusIpcAddHandleByUserId", args.unk, + ORBIS_LOG_TODO("IPMI: SceMbusIpcAddHandleByUserId", args.deviceType, args.deviceId, args.userId, args.type, args.index, args.reserved, args.pid); + if (args.deviceType == 1) { + struct HandleA { + int32_t pid; + int32_t port; + int32_t unk0 = 0x20100000; + int32_t unk1 = 1; + } handleA; + handleA.pid = args.pid; + handleA.port = args.deviceId; + audioIpmiClient.sendSyncMessage(0x1234000a, handleA); + struct HandleC { + int32_t pid; + int32_t port; + int32_t unk0 = 1; + int32_t unk1 = 0; + int32_t unk2 = 1; + int32_t unk3 = 0; + int32_t unk4 = 0; + int32_t unk5 = 0; + int32_t unk6 = 0; + int32_t unk7 = 1; + int32_t unk8 = 0; + } handleC; + handleC.pid = args.pid; + handleC.port = args.deviceId; + audioIpmiClient.sendSyncMessage(0x1234000c, handleC); + } return 0; }); createIpmiServer(process, "SceSysCoreApp"); @@ -1619,180 +1829,6 @@ static void createShellCoreObjects(orbis::Process *process) { createSemaphore("SceNpTpip 0", 0x101, 0, 1); } -static orbis::Process *createGuestProcess() { - auto pid = orbis::g_context.allocatePid() * 10000 + 1; - return orbis::g_context.createProcess(pid); -} - -static orbis::Thread *createGuestThread() { - auto process = createGuestProcess(); - auto [baseId, thread] = process->threadsMap.emplace(); - thread->tproc = process; - thread->tid = process->pid + baseId; - thread->state = orbis::ThreadState::RUNNING; - return thread; -} - -template struct GuestAlloc { - orbis::ptr guestAddress; - - GuestAlloc(std::size_t size) { - if (size == 0) { - guestAddress = nullptr; - } else { - guestAddress = orbis::ptr(rx::vm::map( - nullptr, size, rx::vm::kMapProtCpuRead | rx::vm::kMapProtCpuWrite, - rx::vm::kMapFlagPrivate | rx::vm::kMapFlagAnonymous)); - } - } - - GuestAlloc() : GuestAlloc(sizeof(T)) {} - - GuestAlloc(const T &data) : GuestAlloc() { - if (orbis::uwrite(guestAddress, data) != orbis::ErrorCode{}) { - std::abort(); - } - } - - GuestAlloc(const void *data, std::size_t size) : GuestAlloc(size) { - if (orbis::uwriteRaw(guestAddress, data, size) != orbis::ErrorCode{}) { - std::abort(); - } - } - - GuestAlloc(const GuestAlloc &) = delete; - - GuestAlloc(GuestAlloc &&other) : guestAddress(other.guestAddress) { - other.guestAddress = 0; - } - GuestAlloc &operator=(GuestAlloc &&other) { - std::swap(guestAddress, other.guestAddress); - } - - ~GuestAlloc() { - if (guestAddress != 0) { - rx::vm::unmap(guestAddress, sizeof(T)); - } - } - - operator orbis::ptr() { return guestAddress; } - T *operator->() { return guestAddress; } - operator T &() { return *guestAddress; } -}; - -struct IpmiClient { - orbis::Ref clientImpl; - orbis::uint kid; - orbis::Thread *thread; - - orbis::sint - sendSyncMessageRaw(std::uint32_t method, - const std::vector> &inData, - std::vector> &outBuf) { - GuestAlloc serverResult; - GuestAlloc guestInDataArray{ - sizeof(orbis::IpmiDataInfo) * inData.size()}; - GuestAlloc guestOutBufArray{ - sizeof(orbis::IpmiBufferInfo) * outBuf.size()}; - - std::vector> guestAllocs; - guestAllocs.reserve(inData.size() + outBuf.size()); - - for (auto &data : inData) { - auto pointer = - guestAllocs.emplace_back(data.data(), data.size()).guestAddress; - - guestInDataArray.guestAddress[&data - inData.data()] = { - .data = pointer, .size = data.size()}; - } - - for (auto &buf : outBuf) { - auto pointer = - guestAllocs.emplace_back(buf.data(), buf.size()).guestAddress; - - guestOutBufArray.guestAddress[&buf - outBuf.data()] = { - .data = pointer, .capacity = buf.size()}; - } - - GuestAlloc params = orbis::IpmiSyncCallParams{ - .method = method, - .numInData = static_cast(inData.size()), - .numOutData = static_cast(outBuf.size()), - .pInData = guestInDataArray, - .pOutData = guestOutBufArray, - .pResult = serverResult, - .flags = (inData.size() > 1 || outBuf.size() > 1) ? 1u : 0u, - }; - - GuestAlloc errorCode; - orbis::sysIpmiClientInvokeSyncMethod(thread, errorCode, kid, params, - sizeof(orbis::IpmiSyncCallParams)); - - for (auto &buf : outBuf) { - auto size = guestOutBufArray.guestAddress[inData.data() - &buf].size; - buf.resize(size); - } - return serverResult; - } - - template - orbis::sint sendSyncMessage(std::uint32_t method, - const InputTypes &...input) { - std::vector> outBuf; - return sendSyncMessageRaw(method, {toBytes(input)...}, outBuf); - } - - template - requires((sizeof...(OutputTypes) > 0) || sizeof...(InputTypes) == 0) - std::tuple sendSyncMessage(std::uint32_t method, - InputTypes... input) { - std::vector> outBuf{sizeof(OutputTypes)...}; - sendSyncMessageRaw(method, {toBytes(input)...}, outBuf); - std::tuple output; - - auto unpack = [&](std::index_sequence) { - ((std::get(output) = *reinterpret_cast(outBuf.data())), - ...); - }; - unpack(std::make_index_sequence{}); - return output; - } -}; - -static IpmiClient createIpmiClient(orbis::Thread *thread, const char *name) { - orbis::Ref client; - GuestAlloc config = orbis::IpmiCreateClientConfig{ - .size = sizeof(orbis::IpmiCreateClientConfig), - }; - - orbis::uint kid; - - { - GuestAlloc guestName{name, std::strlen(name)}; - GuestAlloc params = orbis::IpmiCreateClientParams{ - .name = guestName, - .config = config, - }; - - GuestAlloc result; - GuestAlloc guestKid; - orbis::sysIpmiCreateClient(thread, guestKid, params, - sizeof(orbis::IpmiCreateClientParams)); - kid = guestKid; - } - - { - GuestAlloc status; - GuestAlloc params = orbis::IpmiClientConnectParams{.status = status}; - - GuestAlloc result; - orbis::sysIpmiClientConnect(thread, result, kid, params, - sizeof(orbis::IpmiClientConnectParams)); - } - - return {std::move(client), kid, thread}; -} - static orbis::SysResult launchDaemon(orbis::Thread *thread, std::string path, std::vector argv, std::vector envv, @@ -2186,6 +2222,34 @@ int main(int argc, const char *argv[]) { .titleId = "NPXS20973", .unk4 = orbis::slong(0x80000000'00000000), }); + // confirmed to work and known method of initialization since 5.05 version + if (orbis::g_context.fwSdkVersion >= 0x5050000) { + auto fakeIpmiThread = createGuestThread(); + audioIpmiClient = createIpmiClient(fakeIpmiThread, "SceSysAudioSystemIpc"); + // HACK: here is a bug in audiod because we send this very early and audiod has time to reset the state due to initialization + // so we wait for a second, during this time audiod should have time to initialize on most systems + std::this_thread::sleep_for(std::chrono::seconds(1)); + struct Data1 { + int32_t pid = 0; + int32_t someSwitch = 0x14; // 0x14 for init, 0x19 for mute + int32_t someFlag = 0; + } data1; + data1.pid = fakeIpmiThread->tproc->pid; + struct Data2 { + void* unk0 = 0; + int32_t unk1 = 0x105; + int32_t unk2 = 0x10000; + int64_t unk3 = 0; + int32_t unk4 = 0; + int32_t unk5 = 0; + int32_t unk6 = 0; + int64_t unk7 = 0; + int32_t unk8 = 0x2; + char unk9[24]{0}; + } data2; + std::uint32_t method = orbis::g_context.fwSdkVersion >= 0x8000000 ? 0x1234002c : 0x1234002b; + audioIpmiClient.sendSyncMessage(method, data1, data2); + } } }