mirror of
https://github.com/mikage-emu/mikage-dev.git
synced 2025-01-09 15:01:00 +01:00
392 lines
17 KiB
C++
392 lines
17 KiB
C++
|
#include "hid.hpp"
|
||
|
#include "platform/hid.hpp"
|
||
|
#include "platform/sm.hpp" // Needed for triggering home menu
|
||
|
|
||
|
#include <framework/exceptions.hpp>
|
||
|
|
||
|
#include <range/v3/view/take.hpp>
|
||
|
|
||
|
namespace HLE {
|
||
|
|
||
|
namespace OS {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
struct BinaryState {
|
||
|
uint32_t raw;
|
||
|
};
|
||
|
|
||
|
struct BinaryStateAndCirclePad {
|
||
|
BOOST_HANA_DEFINE_STRUCT(BinaryStateAndCirclePad,
|
||
|
(BinaryState, current_binary), // E.g. button presses
|
||
|
(BinaryState, activated_binary), // E.g. button pushes (previously unpressed)
|
||
|
(BinaryState, deactivated_binary), // E.g. button releases (previously pressed)
|
||
|
(uint32_t, circle_pad)
|
||
|
);
|
||
|
};
|
||
|
|
||
|
struct TouchState {
|
||
|
BOOST_HANA_DEFINE_STRUCT(TouchState,
|
||
|
(uint16_t, x), // Pixel coordinate (0 = left edge, 320 = right edge, subject to proper hardware calibration)
|
||
|
(uint16_t, y), // Pixel coordinate (0 = top edge, 240 = bottom edge, subject to proper hardware calibration)
|
||
|
(uint32_t, flags) // Non-zero if touch is pressed (TODOTEST: Which value is used specifically?)
|
||
|
);
|
||
|
};
|
||
|
|
||
|
template<typename T>
|
||
|
struct SharedMemorySection;
|
||
|
|
||
|
template<>
|
||
|
struct SharedMemorySection<BinaryStateAndCirclePad> {
|
||
|
BOOST_HANA_DEFINE_STRUCT(SharedMemorySection,
|
||
|
(uint64_t, timestamp), // Value obtained from SVCGetSystemTick
|
||
|
(uint64_t, previous_timestamp), // Timestamp of previous update
|
||
|
(uint32_t, active_entry), // Index to the last element in entries updated by HID
|
||
|
(uint32_t, unknown1),
|
||
|
(uint32_t, slider3d_percentage), // Float value from 0.0 - 100.0
|
||
|
(BinaryState, current_data),
|
||
|
(uint32_t, circle_pad),
|
||
|
(uint32_t, unknown3),
|
||
|
(std::array<BinaryStateAndCirclePad, 8>, entries)
|
||
|
);
|
||
|
};
|
||
|
|
||
|
template<>
|
||
|
struct SharedMemorySection<TouchState> {
|
||
|
BOOST_HANA_DEFINE_STRUCT(SharedMemorySection,
|
||
|
(uint64_t, timestamp), // Value obtained from SVCGetSystemTick
|
||
|
(uint64_t, previous_timestamp), // Timestamp of previous update
|
||
|
(uint32_t, active_entry), // Index to the last element in entries updated by HID
|
||
|
(uint32_t, unknown1),
|
||
|
(uint64_t, unknown2), // Raw touch state
|
||
|
(std::array<TouchState, 8>, entries)
|
||
|
);
|
||
|
};
|
||
|
|
||
|
struct SharedMemory {
|
||
|
BOOST_HANA_DEFINE_STRUCT(SharedMemory,
|
||
|
(SharedMemorySection<BinaryStateAndCirclePad>, binary_state_and_circle_pad),
|
||
|
(SharedMemorySection<TouchState>, touch_state)
|
||
|
);
|
||
|
};
|
||
|
|
||
|
} // anonymous namespace
|
||
|
|
||
|
struct FakeHID : TagMapHIDMMIO {
|
||
|
public:
|
||
|
FakeHID(FakeThread& thread);
|
||
|
virtual ~FakeHID() = default;
|
||
|
|
||
|
spdlog::logger& logger;
|
||
|
|
||
|
VAddr shared_mem_vaddr;
|
||
|
static constexpr const uint32_t shared_mem_size = 0x1000;
|
||
|
|
||
|
Handle shared_memory;
|
||
|
|
||
|
// NOTE: The last of these events refers to the debug pad, which we
|
||
|
// don't currently emulate. Hence, it never gets signaled.
|
||
|
std::array<Handle, 5> data_ready_events;
|
||
|
|
||
|
// Data is polled and synchronized to shared memory whenever this timer fires
|
||
|
HandleTable::Entry<Timer> data_polling_timer;
|
||
|
static constexpr const uint32_t data_polling_timer_period = 4'000'000; // Every 4 milliseconds
|
||
|
|
||
|
// Previous state of binary values, used to store change masks in shared memory
|
||
|
// TODO: What's the initial value of this? Since the pad state includes
|
||
|
// button changes, this value affects the first entry written
|
||
|
// during hid execution
|
||
|
BinaryState previous_binary_state = { 0 };
|
||
|
|
||
|
decltype(ServiceHelper::SendReply) OnIPCRequest(FakeThread& thread, const IPC::CommandHeader& header);
|
||
|
|
||
|
OS::ResultAnd<> EnableGyroscope(FakeThread& thread) {
|
||
|
return RESULT_OK;
|
||
|
}
|
||
|
|
||
|
OS::ResultAnd<uint32_t> GetGyroscopeSensitivity(FakeThread& thread) {
|
||
|
float dps = 14.375; // degrees per second
|
||
|
uint32_t dps_uint;
|
||
|
memcpy(&dps_uint, &dps, sizeof(dps));
|
||
|
return { RESULT_OK, dps_uint };
|
||
|
}
|
||
|
|
||
|
OS::ResultAnd<Platform::HID::User::GyroscopeCalibrationData> GetGyroscopeCalibrationData(FakeThread& thread) {
|
||
|
return { RESULT_OK, Platform::HID::User::GyroscopeCalibrationData {} };
|
||
|
}
|
||
|
|
||
|
// Called when data_polling_timer is signaled
|
||
|
void OnDataPollingTimer(FakeThread& thread) {
|
||
|
// TODO: Actually, the mcu module should read the Home button status via device 3 and publish this notification
|
||
|
|
||
|
static bool latch = false;
|
||
|
// Latch to ensure we don't send out multiple notifications... TODO: Should be moved to notifier!
|
||
|
auto pressed = Memory::ReadLegacy<uint16_t>(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x108);
|
||
|
|
||
|
if (!latch && pressed) {
|
||
|
auto [result, srv_session] = thread.CallSVC(&OS::SVCConnectToPort, "srv:");
|
||
|
if (result != RESULT_OK) {
|
||
|
throw std::runtime_error("FAILED to get srv:pm handle");
|
||
|
}
|
||
|
|
||
|
// Publish notification 0x204: Home button pressed
|
||
|
static int lol = 0;
|
||
|
if (lol++ == 0) {
|
||
|
IPC::SendIPCRequest<Platform::SM::SRV::PublishToSubscriber>(thread, srv_session.first, 0x204, 0); // home
|
||
|
} else {
|
||
|
IPC::SendIPCRequest<Platform::SM::SRV::PublishToSubscriber>(thread, srv_session.first, 0x202, 0); // power down
|
||
|
}
|
||
|
|
||
|
thread.CallSVC(&OS::SVCCloseHandle, srv_session.first);
|
||
|
}
|
||
|
latch = pressed;
|
||
|
|
||
|
// TODO: cdc:HID GetTouchData (Gets touch screen and circle pad data from SPI device 3)
|
||
|
|
||
|
auto [result] = thread.CallSVC(&OS::SVCSetTimer, *data_polling_timer.second, data_polling_timer_period, 0);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
|
||
|
// TODO: Currently, thread.ReadMemory16 does a byte-wise read rather than a 16-bit one, so we need to use an overly complicated Read instead...
|
||
|
// NOTE: HID button state is a negative mask, i.e. bits that are set indicate *unpressed* buttons
|
||
|
// TODO: Add circle pad info to the topmost bits of pad_state
|
||
|
BinaryState binary_state = { static_cast<uint16_t>(~Memory::ReadLegacy<uint16_t>(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start)) };
|
||
|
|
||
|
// TODO: These are supposed to be retrieved from cdc:HID:GetTouchData
|
||
|
uint16_t circle_pad_x = static_cast<uint16_t>(static_cast<int16_t>(Memory::ReadLegacy<uint16_t>(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x104)) - 0x9c);
|
||
|
uint16_t circle_pad_y = static_cast<uint16_t>(static_cast<int16_t>(Memory::ReadLegacy<uint16_t>(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x106)) - 0x9c);
|
||
|
uint32_t circle_pad_state = circle_pad_x | (static_cast<uint32_t>(circle_pad_y) << 16);
|
||
|
|
||
|
auto [tick] = thread.CallSVC(&OS::SVCGetSystemTick);
|
||
|
|
||
|
// Button and circle pad state
|
||
|
{
|
||
|
uint32_t entry_index = 1 + thread.ReadMemory32(shared_mem_vaddr + 0x10);
|
||
|
entry_index = entry_index % std::tuple_size_v<decltype(SharedMemorySection<BinaryStateAndCirclePad>::entries)>;
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x10, entry_index);
|
||
|
|
||
|
if (entry_index == 0) {
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x8, thread.ReadMemory32(shared_mem_vaddr));
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0xc, thread.ReadMemory32(shared_mem_vaddr) + 4);
|
||
|
// TODO: Tick is only supposed to be updated when writing the first entry
|
||
|
// TODO: Ok, we do that now
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x0, tick & 0xFFFFFFFF);
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x4, tick >> 32);
|
||
|
}
|
||
|
|
||
|
thread.GetLogger()->error("PAD State: {:#x}, index {}", binary_state.raw, entry_index);
|
||
|
|
||
|
float slider3d_percentage = 100.0;
|
||
|
uint32_t slider3d_percentage_raw;
|
||
|
memcpy(&slider3d_percentage_raw, &slider3d_percentage, sizeof(slider3d_percentage));
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x18, slider3d_percentage_raw);
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x1c, binary_state.raw);
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x20, circle_pad_state);
|
||
|
|
||
|
auto entry = BinaryStateAndCirclePad {
|
||
|
binary_state,
|
||
|
BinaryState { ~previous_binary_state.raw & binary_state.raw },
|
||
|
BinaryState { previous_binary_state.raw & ~binary_state.raw },
|
||
|
circle_pad_state
|
||
|
};
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x28 + entry_index * sizeof(entry), entry.current_binary.raw);
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x2c + entry_index * sizeof(entry), entry.activated_binary.raw);
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x30 + entry_index * sizeof(entry), entry.deactivated_binary.raw);
|
||
|
thread.WriteMemory32(shared_mem_vaddr + 0x34 + entry_index * sizeof(entry), circle_pad_state);
|
||
|
|
||
|
previous_binary_state = binary_state;
|
||
|
}
|
||
|
|
||
|
// Touch state
|
||
|
// TODO: Actually, HID should query this data from cdc:HID's GetData
|
||
|
// (which in turn queries it from SPI device number 3)
|
||
|
// and convert it to screen coordinates using the touch
|
||
|
// calibration data in config block 0x40000 (which in turn
|
||
|
// originate in ctrnand:/ro/sys/HWCAL0.dat and HWCAL1.dat)
|
||
|
{
|
||
|
auto block_start = shared_mem_vaddr + 0xa8;
|
||
|
uint32_t entry_index = 1 + thread.ReadMemory32(block_start + 0x10);
|
||
|
entry_index = entry_index % std::tuple_size_v<decltype(SharedMemorySection<TouchState>::entries)>;
|
||
|
thread.WriteMemory32(block_start + 0x10, entry_index);
|
||
|
|
||
|
if (entry_index == 0) {
|
||
|
thread.WriteMemory32(block_start + 0x8, thread.ReadMemory32(block_start));
|
||
|
thread.WriteMemory32(block_start + 0xc, thread.ReadMemory32(block_start) + 4);
|
||
|
// TODO: Tick is only supposed to be updated when writing the first entry
|
||
|
// TODO: Ok, we do that now
|
||
|
thread.WriteMemory32(block_start + 0x0, tick & 0xFFFFFFFF);
|
||
|
thread.WriteMemory32(block_start + 0x4, tick >> 32);
|
||
|
}
|
||
|
|
||
|
auto entry = TouchState {
|
||
|
Memory::ReadLegacy<uint16_t>(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x100),
|
||
|
Memory::ReadLegacy<uint16_t>(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x102),
|
||
|
0
|
||
|
};
|
||
|
const bool touched = (0xffff != entry.x || 0xffff != entry.y);
|
||
|
entry.flags = touched;
|
||
|
if (!touched) {
|
||
|
entry.x = 0;
|
||
|
entry.y = 0;
|
||
|
}
|
||
|
thread.WriteMemory32(block_start + 0x20 + entry_index * sizeof(entry), (static_cast<uint32_t>(entry.y) << 16) | entry.x);
|
||
|
thread.WriteMemory32(block_start + 0x24 + entry_index * sizeof(entry), entry.flags);
|
||
|
}
|
||
|
|
||
|
// TODO: Read gyroscope data using mcu::HID
|
||
|
|
||
|
// TODO: Read accelerometer data
|
||
|
|
||
|
for (auto& data_ready_event : ranges::view::take(4)(data_ready_events)) {
|
||
|
std::tie(result) = thread.CallSVC(&OS::SVCSignalEvent, data_ready_event);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
|
||
|
// TODO: Accelerometer (index 2) and gyroscope (index 3) should be
|
||
|
// signaled at a lower frequency (about every 4-5 iterations)
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
FakeHID::FakeHID(FakeThread& thread) : logger(*thread.GetLogger()) {
|
||
|
// Create shared memory block (0x1000 bytes, matching the native HID module)
|
||
|
// TODO: Should we zero-initialize this?
|
||
|
OS::Result result;
|
||
|
std::tie(result,shared_mem_vaddr) = thread.CallSVC(&OS::SVCControlMemory, 0, 0, shared_mem_size, 3/*ALLOC*/, 0x3/*RW*/);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
|
||
|
{
|
||
|
auto [result, shared_memory_entry] = thread.CallSVC(&OS::SVCCreateMemoryBlock, shared_mem_vaddr, shared_mem_size, 0x3/*RW*/, 0x1/*Read-only*/);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
shared_memory = shared_memory_entry.first;
|
||
|
}
|
||
|
|
||
|
// TODO: Read calibration data from cfg blocks 0x40000, 0x40002, and 0x40003
|
||
|
|
||
|
for (auto& handle : data_ready_events) {
|
||
|
auto [result, event_entry] = thread.CallSVC(&OS::SVCCreateEvent, ResetType::OneShot);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
handle = event_entry.first;
|
||
|
}
|
||
|
|
||
|
std::tie(result, data_polling_timer) = thread.CallSVC(&OS::SVCCreateTimer, ResetType::OneShot);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
|
||
|
std::tie(result) = thread.CallSVC(&OS::SVCSetTimer, *data_polling_timer.second, data_polling_timer_period, 0);
|
||
|
if (result != RESULT_OK)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
|
||
|
ServiceHelper service;
|
||
|
service.Append(ServiceUtil::SetupService(thread, "hid:USER", 5));
|
||
|
service.Append(ServiceUtil::SetupService(thread, "hid:SPVR", 5)); // Same as hid:USER; used by Home Menu and other system software
|
||
|
service.Append(data_polling_timer);
|
||
|
|
||
|
auto InvokeCommandHandler = [&](FakeThread& thread, uint32_t signalled_handle_index) {
|
||
|
Platform::IPC::CommandHeader header = { thread.ReadTLS(0x80) };
|
||
|
auto signalled_handle = service.handles[signalled_handle_index];
|
||
|
if (signalled_handle == data_polling_timer.first) {
|
||
|
OnDataPollingTimer(thread);
|
||
|
return ServiceHelper::DoNothing;
|
||
|
} else {
|
||
|
return OnIPCRequest(thread, header);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
service.Run(thread, std::move(InvokeCommandHandler));
|
||
|
}
|
||
|
|
||
|
template<typename Class, typename Func>
|
||
|
static auto BindMemFn(Func f, Class* c) {
|
||
|
return [f,c](auto&&... args) { return std::mem_fn(f)(c, args...); };
|
||
|
}
|
||
|
|
||
|
decltype(ServiceHelper::SendReply) FakeHID::OnIPCRequest(FakeThread& thread, const IPC::CommandHeader& header) {
|
||
|
namespace HIDU = Platform::HID::User;
|
||
|
|
||
|
switch (header.command_id) {
|
||
|
case 0xa: // GetIPCHandles
|
||
|
{
|
||
|
logger.info("{} received GetIPCHandles", ThreadPrinter{thread});
|
||
|
|
||
|
// Validate command
|
||
|
if (header.raw != 0x000A0000)
|
||
|
thread.CallSVC(&OS::SVCBreak, OS::BreakReason::Panic);
|
||
|
|
||
|
thread.WriteTLS(0x80, IPC::CommandHeader::Make(0, 1, 7).raw);
|
||
|
thread.WriteTLS(0x84, RESULT_OK);
|
||
|
thread.WriteTLS(0x88, IPC::TranslationDescriptor::MakeHandles(6).raw);
|
||
|
thread.WriteTLS(0x8c, shared_memory.value);
|
||
|
thread.WriteTLS(0x90, data_ready_events[0].value);
|
||
|
thread.WriteTLS(0x94, data_ready_events[1].value);
|
||
|
thread.WriteTLS(0x98, data_ready_events[2].value);
|
||
|
thread.WriteTLS(0x9c, data_ready_events[3].value);
|
||
|
thread.WriteTLS(0xa0, data_ready_events[4].value);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 0x11: // EnableAccelerometer
|
||
|
logger.info("{}received EnableAccelerometer", ThreadPrinter{thread});
|
||
|
|
||
|
// Sure, whatever
|
||
|
// LogStub(header);
|
||
|
thread.WriteTLS(0x80, IPC::CommandHeader::Make(0, 1, 0).raw);
|
||
|
thread.WriteTLS(0x84, RESULT_OK);
|
||
|
break;
|
||
|
|
||
|
case 0x12: // DisableAccelerometer
|
||
|
logger.info("{}received DisableAccelerometer", ThreadPrinter{thread});
|
||
|
|
||
|
// Sure, whatever
|
||
|
// LogStub(header);
|
||
|
thread.WriteTLS(0x80, IPC::CommandHeader::Make(0, 1, 0).raw);
|
||
|
thread.WriteTLS(0x84, RESULT_OK);
|
||
|
break;
|
||
|
|
||
|
case HIDU::EnableGyroscope::id:
|
||
|
IPC::HandleIPCCommand<HIDU::EnableGyroscope>(BindMemFn(&FakeHID::EnableGyroscope, this), thread, thread);
|
||
|
break;
|
||
|
|
||
|
case 0x14: // DisableGyroscope
|
||
|
logger.info("{}received DisableGyroscope", ThreadPrinter{thread});
|
||
|
|
||
|
// Sure, whatever
|
||
|
// LogStub(header);
|
||
|
thread.WriteTLS(0x80, IPC::CommandHeader::Make(0, 1, 0).raw);
|
||
|
thread.WriteTLS(0x84, RESULT_OK);
|
||
|
break;
|
||
|
|
||
|
case HIDU::GetGyroscopeSensitivity::id:
|
||
|
IPC::HandleIPCCommand<HIDU::GetGyroscopeSensitivity>(BindMemFn(&FakeHID::GetGyroscopeSensitivity, this), thread, thread);
|
||
|
break;
|
||
|
|
||
|
case HIDU::GetGyroscopeCalibrationData::id:
|
||
|
IPC::HandleIPCCommand<HIDU::GetGyroscopeCalibrationData>(BindMemFn(&FakeHID::GetGyroscopeCalibrationData, this), thread, thread);
|
||
|
break;
|
||
|
|
||
|
case 0x17: // GetSoundVolume
|
||
|
logger.info("{}received GetSoundVolume", ThreadPrinter{thread});
|
||
|
|
||
|
thread.WriteTLS(0x80, IPC::CommandHeader::Make(0, 2, 0).raw);
|
||
|
thread.WriteTLS(0x84, RESULT_OK);
|
||
|
thread.WriteTLS(0x88, 0x3f); // Maximum volume
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw std::runtime_error(fmt::format("Unknown HID IPC request with header {:#x}", header.raw));
|
||
|
}
|
||
|
return ServiceHelper::SendReply;
|
||
|
}
|
||
|
|
||
|
template<> std::shared_ptr<WrappedFakeProcess> CreateFakeProcessViaContext<FakeHID>(OS& os, Interpreter::Setup& setup, uint32_t pid, const std::string& name) {
|
||
|
return WrappedFakeProcess::CreateWithContext<FakeHID>(os, setup, pid, name);
|
||
|
}
|
||
|
|
||
|
} // namespace OS
|
||
|
|
||
|
} // namespace HLE
|