#include "hid.hpp" #include "platform/hid.hpp" #include "platform/sm.hpp" // Needed for triggering home menu #include #include 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 struct SharedMemorySection; template<> struct SharedMemorySection { 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, entries) ); }; template<> struct SharedMemorySection { 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, entries) ); }; struct SharedMemory { BOOST_HANA_DEFINE_STRUCT(SharedMemory, (SharedMemorySection, binary_state_and_circle_pad), (SharedMemorySection, 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 data_ready_events; // Data is polled and synchronized to shared memory whenever this timer fires HandleTable::Entry 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 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 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(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(thread, srv_session.first, 0x204, 0); // home } else { IPC::SendIPCRequest(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(~Memory::ReadLegacy(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(static_cast(Memory::ReadLegacy(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x104)) - 0x9c); uint16_t circle_pad_y = static_cast(static_cast(Memory::ReadLegacy(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x106)) - 0x9c); uint32_t circle_pad_state = circle_pad_x | (static_cast(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::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::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(thread.GetParentProcess().interpreter_setup.mem, Memory::IO_HID::start + 0x100), Memory::ReadLegacy(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(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 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(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(BindMemFn(&FakeHID::GetGyroscopeSensitivity, this), thread, thread); break; case HIDU::GetGyroscopeCalibrationData::id: IPC::HandleIPCCommand(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 CreateFakeProcessViaContext(OS& os, Interpreter::Setup& setup, uint32_t pid, const std::string& name) { return WrappedFakeProcess::CreateWithContext(os, setup, pid, name); } } // namespace OS } // namespace HLE