mikage-dev/source/processes/hid.cpp

392 lines
17 KiB
C++
Raw Normal View History

2024-03-07 22:05:16 +01:00
#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