static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); mem->gyroscope.index = next_gyroscope_index; next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; Math::Vec3<float> gyro; std::tie(std::ignore, gyro) = motion_device->GetStatus(); double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); gyro *= gyroscope_coef * static_cast<float>(stretch); gyroscope_entry.x = static_cast<s16>(gyro.x); gyroscope_entry.y = static_cast<s16>(gyro.y); gyroscope_entry.z = static_cast<s16>(gyro.z); // Make up "raw" entry mem->gyroscope.raw_entry.x = gyroscope_entry.x; mem->gyroscope.raw_entry.z = -gyroscope_entry.y; mem->gyroscope.raw_entry.y = gyroscope_entry.z; // If we just updated index 0, provide a new timestamp if (mem->gyroscope.index == 0) { mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); } event_gyroscope->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event); }
static void UpdateCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_memory->GetPointer()); if (is_device_reload_pending.exchange(false)) LoadInputDevices(); PadState state; state.zl.Assign(zl_button->GetStatus()); state.zr.Assign(zr_button->GetStatus()); // Get current c-stick position and update c-stick direction float c_stick_x_f, c_stick_y_f; std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius const s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS); const s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS); if (!raw_c_stick) { const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); state.c_stick_up.Assign(direction.up); state.c_stick_down.Assign(direction.down); state.c_stick_left.Assign(direction.left); state.c_stick_right.Assign(direction.right); } // TODO (wwylele): implement raw C-stick data for raw_c_stick = true const u32 last_entry_index = mem->index; mem->index = next_pad_index; next_pad_index = (next_pad_index + 1) % mem->entries.size(); // Get the previous Pad state PadState old_state{mem->entries[last_entry_index].current_state}; // Compute bitmask with 1s for bits different from the old state PadState changed = {state.hex ^ old_state.hex}; // Get the current Pad entry PadDataEntry& pad_entry = mem->entries[mem->index]; // Update entry properties pad_entry.current_state.hex = state.hex; pad_entry.delta_additions.hex = changed.hex & state.hex; pad_entry.delta_removals.hex = changed.hex & old_state.hex; pad_entry.c_stick_x = c_stick_x; pad_entry.c_stick_y = c_stick_y; // If we just updated index 0, provide a new timestamp if (mem->index == 0) { mem->index_reset_ticks_previous = mem->index_reset_ticks; mem->index_reset_ticks = CoreTiming::GetTicks(); } update_event->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(msToCycles(update_period) - cycles_late, update_callback_id); }
static ResultCode GetProcessInfo(s64* out, Handle process_handle, u32 type) { LOG_TRACE(Kernel_SVC, "called process=0x%08X type=%u", process_handle, type); using Kernel::Process; Kernel::SharedPtr<Process> process = Kernel::g_handle_table.Get<Process>(process_handle); if (process == nullptr) return ERR_INVALID_HANDLE; switch (type) { case 0: case 2: // TODO(yuriks): Type 0 returns a slightly higher number than type 2, but I'm not sure // what's the difference between them. *out = process->heap_used + process->linear_heap_used + process->misc_memory_used; if(*out % Memory::PAGE_SIZE != 0) { LOG_ERROR(Kernel_SVC, "called, memory size not page-aligned"); return ERR_MISALIGNED_SIZE; } break; case 1: case 3: case 4: case 5: case 6: case 7: case 8: // These are valid, but not implemented yet LOG_ERROR(Kernel_SVC, "unimplemented GetProcessInfo type=%u", type); break; case 20: *out = Memory::FCRAM_PADDR - process->GetLinearHeapBase(); break; default: LOG_ERROR(Kernel_SVC, "unknown GetProcessInfo type=%u", type); if (type >= 21 && type <= 23) { return ResultCode( // 0xE0E01BF4 ErrorDescription::NotImplemented, ErrorModule::OS, ErrorSummary::InvalidArgument, ErrorLevel::Usage); } else { return ResultCode( // 0xD8E007ED ErrorDescription::InvalidEnumValue, ErrorModule::Kernel, ErrorSummary::InvalidArgument, ErrorLevel::Permanent); } break; } return RESULT_SUCCESS; }
static void DriverInitialize(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.input_format = InputFormat::YUV422_Indiv8; conversion.output_format = OutputFormat::RGBA8; conversion.rotation = Rotation::None; conversion.block_alignment = BlockAlignment::Linear; conversion.coefficients.fill(0); conversion.SetInputLineWidth(1024); conversion.SetInputLines(1024); conversion.alpha = 0; ConversionBuffer zero_buffer = {}; conversion.src_Y = zero_buffer; conversion.src_U = zero_buffer; conversion.src_V = zero_buffer; conversion.dst = zero_buffer; completion_event->Clear(); cmd_buff[0] = IPC::MakeHeader(0x2B, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); }
void NotifyToWait(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; // TODO(Subv): Verify this, it seems to get SWKBD and Home Menu further. start_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); }
void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_HID, "(STUBBED) called"); }
void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_HID, "(STUBBED) called"); }
void RequireConnection(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conn_status_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_IR, "(STUBBED) called"); }
void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); // TODO(bunnei): Check if these are created in Initialize or on APT process startup. notification_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Notification"); pause_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Pause"); cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(pause_event).MoveFrom(); // TODO(bunnei): Check if these events are cleared/signaled every time Initialize is called. notification_event->Clear(); pause_event->Signal(); // Fire start event ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error }
void SignalInterrupt() { // TODO(bunnei): This is just a stub, it does not do anything other than signal to the emulated // application that a DSP interrupt occurred, without specifying which one. Since we do not // emulate the DSP yet (and how it works is largely unknown), this is a work around to get games // that check the DSP interrupt signal event to run. We should figure out the different types of // DSP interrupts, and trigger them at the appropriate times. if (interrupt_event != 0) interrupt_event->Signal(); }
void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; u32 flags = cmd_buff[2]; cmd_buff[2] = 0x04000000; // According to 3dbrew, this value should be 0x04000000 cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(start_event).MoveFrom(); // TODO(bunnei): Check if these events are cleared every time Initialize is called. notification_event->Clear(); start_event->Clear(); ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_TRACE(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); }
static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); mem->accelerometer.index = next_accelerometer_index; next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); Math::Vec3<float> accel; std::tie(accel, std::ignore) = motion_device->GetStatus(); accel *= accelerometer_coef; // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback // The time stretch formula should be like // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; accelerometer_entry.x = static_cast<s16>(accel.x); accelerometer_entry.y = static_cast<s16>(accel.y); accelerometer_entry.z = static_cast<s16>(accel.z); // Make up "raw" entry // TODO(wwylele): // From hardware testing, the raw_entry values are approximately, but not exactly, as twice as // corresponding entries (or with a minus sign). It may caused by system calibration to the // accelerometer. Figure out how it works, or, if no game reads raw_entry, the following three // lines can be removed and leave raw_entry unimplemented. mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; // If we just updated index 0, provide a new timestamp if (mem->accelerometer.index == 0) { mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); } event_accelerometer->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event); }
static void StartConversion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); HW::Y2R::PerformConversion(conversion); // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( u32 total_output_size = conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap); VideoCore::g_renderer->hw_rasterizer->NotifyFlush( Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); LOG_DEBUG(Service_Y2R, "called"); completion_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; }
static void StartConversion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( u32 total_output_size = conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap); Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); HW::Y2R::PerformConversion(conversion); completion_event->Signal(); cmd_buff[0] = IPC::MakeHeader(0x26, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); }
static void UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); if (is_device_reload_pending.exchange(false)) LoadInputDevices(); PadState state; using namespace Settings::NativeButton; state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS); s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); state.circle_up.Assign(direction.up); state.circle_down.Assign(direction.down); state.circle_left.Assign(direction.left); state.circle_right.Assign(direction.right); mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); // Get the previous Pad state u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); PadState old_state = mem->pad.entries[last_entry_index].current_state; // Compute bitmask with 1s for bits different from the old state PadState changed = {{(state.hex ^ old_state.hex)}}; // Get the current Pad entry PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; // Update entry properties pad_entry.current_state.hex = state.hex; pad_entry.delta_additions.hex = changed.hex & state.hex; pad_entry.delta_removals.hex = changed.hex & old_state.hex; pad_entry.circle_pad_x = circle_pad_x; pad_entry.circle_pad_y = circle_pad_y; // If we just updated index 0, provide a new timestamp if (mem->pad.index == 0) { mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); } mem->touch.index = next_touch_index; next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); // Get the current touch entry TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; bool pressed = false; float x, y; std::tie(x, y, pressed) = touch_device->GetStatus(); touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth); touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight); touch_entry.valid.Assign(pressed ? 1 : 0); // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). // If we just updated index 0, provide a new timestamp if (mem->touch.index == 0) { mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); } // Signal both handles when there's an update to Pad or touch event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); }
namespace IR { static Kernel::SharedPtr<Kernel::Event> handle_event; static Kernel::SharedPtr<Kernel::Event> conn_status_event; static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; void GetHandles(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0x4000000; cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); } void InitializeIrNopShared(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 transfer_buff_size = cmd_buff[1]; u32 recv_buff_size = cmd_buff[2]; u32 unk1 = cmd_buff[3]; u32 send_buff_size = cmd_buff[4]; u32 unk2 = cmd_buff[5]; u8 baud_rate = cmd_buff[6] & 0xFF; Handle handle = cmd_buff[8]; if(Kernel::g_handle_table.IsValid(handle)) { transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); transfer_shared_memory->name = "IR:TransferSharedMemory"; } cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); } void RequireConnection(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conn_status_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_IR, "(STUBBED) called"); } void Disconnect(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_IR, "(STUBBED) called"); } void GetConnectionStatusEvent(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); LOG_WARNING(Service_IR, "(STUBBED) called"); } void FinalizeIrNop(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_IR, "(STUBBED) called"); } void Init() { using namespace Kernel; AddService(new IR_RST_Interface); AddService(new IR_U_Interface); AddService(new IR_User_Interface); using Kernel::MemoryPermission; shared_memory = SharedMemory::Create(0x1000, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::ReadWrite, "IR:SharedMemory"); transfer_shared_memory = nullptr; // Create event handle(s) handle_event = Event::Create(RESETTYPE_ONESHOT, "IR:HandleEvent"); conn_status_event = Event::Create(RESETTYPE_ONESHOT, "IR:ConnectionStatusEvent"); } void Shutdown() { transfer_shared_memory = nullptr; shared_memory = nullptr; handle_event = nullptr; conn_status_event = nullptr; } } // namespace IR
namespace HID { // Handle to shared memory region designated to HID_User service static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; // Event handles static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1; static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2; static Kernel::SharedPtr<Kernel::Event> event_accelerometer; static Kernel::SharedPtr<Kernel::Event> event_gyroscope; static Kernel::SharedPtr<Kernel::Event> event_debug_pad; static u32 next_pad_index; static u32 next_touch_index; static u32 next_accelerometer_index; static u32 next_gyroscope_index; static int enable_accelerometer_count; // positive means enabled static int enable_gyroscope_count; // positive means enabled static int pad_update_event; static int accelerometer_update_event; static int gyroscope_update_event; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call static std::atomic<bool> is_device_reload_pending; static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> buttons; static std::unique_ptr<Input::AnalogDevice> circle_pad; static std::unique_ptr<Input::MotionDevice> motion_device; static std::unique_ptr<Input::TouchDevice> touch_device; DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; constexpr float TAN60 = 1 / TAN30; // a circle pad radius greater than 40 will trigger circle pad direction constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; DirectionState state{false, false, false, false}; if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x); if (circle_pad_x != 0 && t < TAN60) { if (circle_pad_x > 0) state.right = true; else state.left = true; } if (circle_pad_x == 0 || t > TAN30) { if (circle_pad_y > 0) state.up = true; else state.down = true; } } return state; } static void LoadInputDevices() { std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); circle_pad = Input::CreateDevice<Input::AnalogDevice>( Settings::values.analogs[Settings::NativeAnalog::CirclePad]); motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device); touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); } static void UnloadInputDevices() { for (auto& button : buttons) { button.reset(); } circle_pad.reset(); motion_device.reset(); touch_device.reset(); } static void UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); if (is_device_reload_pending.exchange(false)) LoadInputDevices(); PadState state; using namespace Settings::NativeButton; state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); // Get current circle pad position and update circle pad direction float circle_pad_x_f, circle_pad_y_f; std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS); s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); state.circle_up.Assign(direction.up); state.circle_down.Assign(direction.down); state.circle_left.Assign(direction.left); state.circle_right.Assign(direction.right); mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); // Get the previous Pad state u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); PadState old_state = mem->pad.entries[last_entry_index].current_state; // Compute bitmask with 1s for bits different from the old state PadState changed = {{(state.hex ^ old_state.hex)}}; // Get the current Pad entry PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; // Update entry properties pad_entry.current_state.hex = state.hex; pad_entry.delta_additions.hex = changed.hex & state.hex; pad_entry.delta_removals.hex = changed.hex & old_state.hex; pad_entry.circle_pad_x = circle_pad_x; pad_entry.circle_pad_y = circle_pad_y; // If we just updated index 0, provide a new timestamp if (mem->pad.index == 0) { mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); } mem->touch.index = next_touch_index; next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); // Get the current touch entry TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; bool pressed = false; float x, y; std::tie(x, y, pressed) = touch_device->GetStatus(); touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth); touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight); touch_entry.valid.Assign(pressed ? 1 : 0); // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). // If we just updated index 0, provide a new timestamp if (mem->touch.index == 0) { mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); } // Signal both handles when there's an update to Pad or touch event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); mem->accelerometer.index = next_accelerometer_index; next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); Math::Vec3<float> accel; std::tie(accel, std::ignore) = motion_device->GetStatus(); accel *= accelerometer_coef; // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback // The time stretch formula should be like // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; accelerometer_entry.x = static_cast<s16>(accel.x); accelerometer_entry.y = static_cast<s16>(accel.y); accelerometer_entry.z = static_cast<s16>(accel.z); // Make up "raw" entry // TODO(wwylele): // From hardware testing, the raw_entry values are approximately, but not exactly, as twice as // corresponding entries (or with a minus sign). It may caused by system calibration to the // accelerometer. Figure out how it works, or, if no game reads raw_entry, the following three // lines can be removed and leave raw_entry unimplemented. mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x; mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y; mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z; // If we just updated index 0, provide a new timestamp if (mem->accelerometer.index == 0) { mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks; mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks(); } event_accelerometer->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event); } static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); mem->gyroscope.index = next_gyroscope_index; next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; Math::Vec3<float> gyro; std::tie(std::ignore, gyro) = motion_device->GetStatus(); double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); gyro *= gyroscope_coef * static_cast<float>(stretch); gyroscope_entry.x = static_cast<s16>(gyro.x); gyroscope_entry.y = static_cast<s16>(gyro.y); gyroscope_entry.z = static_cast<s16>(gyro.z); // Make up "raw" entry mem->gyroscope.raw_entry.x = gyroscope_entry.x; mem->gyroscope.raw_entry.z = -gyroscope_entry.y; mem->gyroscope.raw_entry.y = gyroscope_entry.z; // If we just updated index 0, provide a new timestamp if (mem->gyroscope.index == 0) { mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks; mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks(); } event_gyroscope->Signal(); // Reschedule recurrent event CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event); } void GetIPCHandles(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = 0; // No error cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).Unwrap(); cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).Unwrap(); cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).Unwrap(); cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).Unwrap(); cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).Unwrap(); cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).Unwrap(); } void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ++enable_accelerometer_count; // Schedules the accelerometer update event if the accelerometer was just enabled if (enable_accelerometer_count == 1) { CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event); } cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_HID, "called"); } void DisableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); --enable_accelerometer_count; // Unschedules the accelerometer update event if the accelerometer was just disabled if (enable_accelerometer_count == 0) { CoreTiming::UnscheduleEvent(accelerometer_update_event, 0); } cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_HID, "called"); } void EnableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); ++enable_gyroscope_count; // Schedules the gyroscope update event if the gyroscope was just enabled if (enable_gyroscope_count == 1) { CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event); } cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_HID, "called"); } void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); --enable_gyroscope_count; // Unschedules the gyroscope update event if the gyroscope was just disabled if (enable_gyroscope_count == 0) { CoreTiming::UnscheduleEvent(gyroscope_update_event, 0); } cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_HID, "called"); } void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; f32 coef = gyroscope_coef; memcpy(&cmd_buff[2], &coef, 4); } void GetGyroscopeLowCalibrateParam(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; const s16 param_unit = 6700; // an approximate value taken from hw GyroscopeCalibrateParam param = { {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, }; memcpy(&cmd_buff[2], ¶m, sizeof(param)); LOG_WARNING(Service_HID, "(STUBBED) called"); } void GetSoundVolume(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = volume; LOG_WARNING(Service_HID, "(STUBBED) called"); } void Init() { using namespace Kernel; AddService(new HID_U_Interface); AddService(new HID_SPVR_Interface); is_device_reload_pending.store(true); using Kernel::MemoryPermission; shared_mem = SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); next_pad_index = 0; next_touch_index = 0; next_accelerometer_index = 0; next_gyroscope_index = 0; enable_accelerometer_count = 0; enable_gyroscope_count = 0; // Create event handles event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); event_pad_or_touch_2 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch2"); event_accelerometer = Event::Create(ResetType::OneShot, "HID:EventAccelerometer"); event_gyroscope = Event::Create(ResetType::OneShot, "HID:EventGyroscope"); event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad"); // Register update callbacks pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback); accelerometer_update_event = CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback); gyroscope_update_event = CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback); CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } void Shutdown() { shared_mem = nullptr; event_pad_or_touch_1 = nullptr; event_pad_or_touch_2 = nullptr; event_accelerometer = nullptr; event_gyroscope = nullptr; event_debug_pad = nullptr; UnloadInputDevices(); } void ReloadInputDevices() { is_device_reload_pending.store(true); } } // namespace HID
namespace Y2R { struct ConversionParameters { InputFormat input_format; OutputFormat output_format; Rotation rotation; BlockAlignment block_alignment; u16 input_line_width; u16 input_lines; StandardCoefficient standard_coefficient; u8 padding; u16 alpha; }; static_assert(sizeof(ConversionParameters) == 12, "ConversionParameters struct has incorrect size"); static Kernel::SharedPtr<Kernel::Event> completion_event; static ConversionConfiguration conversion; static DitheringWeightParams dithering_weight_params; static u32 temporal_dithering_enabled = 0; static u32 transfer_end_interrupt_enabled = 0; static u32 spacial_dithering_enabled = 0; static const CoefficientSet standard_coefficients[4] = { {{0x100, 0x166, 0xB6, 0x58, 0x1C5, -0x166F, 0x10EE, -0x1C5B}}, // ITU_Rec601 {{0x100, 0x193, 0x77, 0x2F, 0x1DB, -0x1933, 0xA7C, -0x1D51}}, // ITU_Rec709 {{0x12A, 0x198, 0xD0, 0x64, 0x204, -0x1BDE, 0x10F2, -0x229B}}, // ITU_Rec601_Scaling {{0x12A, 0x1CA, 0x88, 0x36, 0x21C, -0x1F04, 0x99C, -0x2421}}, // ITU_Rec709_Scaling }; ResultCode ConversionConfiguration::SetInputLineWidth(u16 width) { if (width == 0 || width > 1024 || width % 8 != 0) { return ResultCode(ErrorDescription::OutOfRange, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053FD } // Note: The hardware uses the register value 0 to represent a width of 1024, so for a width of // 1024 the `camera` module would set the value 0 here, but we don't need to emulate this // internal detail. this->input_line_width = width; return RESULT_SUCCESS; } ResultCode ConversionConfiguration::SetInputLines(u16 lines) { if (lines == 0 || lines > 1024) { return ResultCode(ErrorDescription::OutOfRange, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053FD } // Note: In what appears to be a bug, the `camera` module does not set the hardware register at // all if `lines` is 1024, so the conversion uses the last value that was set. The intention // was probably to set it to 0 like in SetInputLineWidth. if (lines != 1024) { this->input_lines = lines; } return RESULT_SUCCESS; } ResultCode ConversionConfiguration::SetStandardCoefficient( StandardCoefficient standard_coefficient) { size_t index = static_cast<size_t>(standard_coefficient); if (index >= ARRAY_SIZE(standard_coefficients)) { return ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053ED } std::memcpy(coefficients.data(), standard_coefficients[index].data(), sizeof(coefficients)); return RESULT_SUCCESS; } static void SetInputFormat(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.input_format = static_cast<InputFormat>(cmd_buff[1]); cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called input_format=%hhu", conversion.input_format); } static void GetInputFormat(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x2, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = static_cast<u32>(conversion.input_format); LOG_DEBUG(Service_Y2R, "called input_format=%hhu", conversion.input_format); } static void SetOutputFormat(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.output_format = static_cast<OutputFormat>(cmd_buff[1]); cmd_buff[0] = IPC::MakeHeader(0x3, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called output_format=%hhu", conversion.output_format); } static void GetOutputFormat(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x4, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = static_cast<u32>(conversion.output_format); LOG_DEBUG(Service_Y2R, "called output_format=%hhu", conversion.output_format); } static void SetRotation(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.rotation = static_cast<Rotation>(cmd_buff[1]); cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called rotation=%hhu", conversion.rotation); } static void GetRotation(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x6, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = static_cast<u32>(conversion.rotation); LOG_DEBUG(Service_Y2R, "called rotation=%hhu", conversion.rotation); } static void SetBlockAlignment(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.block_alignment = static_cast<BlockAlignment>(cmd_buff[1]); cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", conversion.block_alignment); } static void GetBlockAlignment(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = static_cast<u32>(conversion.block_alignment); LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", conversion.block_alignment); } /** * Y2R_U::SetSpacialDithering service function * Inputs: * 1 : u8, 0 = Disabled, 1 = Enabled * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void SetSpacialDithering(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); spacial_dithering_enabled = cmd_buff[1] & 0xF; cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::GetSpacialDithering service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetSpacialDithering(Interface* self) { IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0xA, 2, 0); rb.Push(RESULT_SUCCESS); rb.Push(spacial_dithering_enabled != 0); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::SetTemporalDithering service function * Inputs: * 1 : u8, 0 = Disabled, 1 = Enabled * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void SetTemporalDithering(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); temporal_dithering_enabled = cmd_buff[1] & 0xF; cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::GetTemporalDithering service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetTemporalDithering(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = temporal_dithering_enabled; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::SetTransferEndInterrupt service function * Inputs: * 1 : u8, 0 = Disabled, 1 = Enabled * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void SetTransferEndInterrupt(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); transfer_end_interrupt_enabled = cmd_buff[1] & 0xf; cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::GetTransferEndInterrupt service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetTransferEndInterrupt(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0xE, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = transfer_end_interrupt_enabled; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::GetTransferEndEvent service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 3 : The handle of the completion event */ static void GetTransferEndEvent(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); LOG_DEBUG(Service_Y2R, "called"); } static void SetSendingY(Interface* self) { // The helper should be passed by argument to the function IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00100102); conversion.src_Y.address = rp.Pop<u32>(); conversion.src_Y.image_size = rp.Pop<u32>(); conversion.src_Y.transfer_unit = rp.Pop<u32>(); conversion.src_Y.gap = rp.Pop<u32>(); Kernel::Handle src_process_handle = rp.PopHandle(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_Y.image_size, conversion.src_Y.transfer_unit, conversion.src_Y.gap, src_process_handle); } static void SetSendingU(Interface* self) { // The helper should be passed by argument to the function IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00110102); conversion.src_U.address = rp.Pop<u32>(); conversion.src_U.image_size = rp.Pop<u32>(); conversion.src_U.transfer_unit = rp.Pop<u32>(); conversion.src_U.gap = rp.Pop<u32>(); Kernel::Handle src_process_handle = rp.PopHandle(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_U.image_size, conversion.src_U.transfer_unit, conversion.src_U.gap, src_process_handle); } static void SetSendingV(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.src_V.address = cmd_buff[1]; conversion.src_V.image_size = cmd_buff[2]; conversion.src_V.transfer_unit = cmd_buff[3]; conversion.src_V.gap = cmd_buff[4]; cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap, cmd_buff[6]); } static void SetSendingYUYV(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.src_YUYV.address = cmd_buff[1]; conversion.src_YUYV.image_size = cmd_buff[2]; conversion.src_YUYV.transfer_unit = cmd_buff[3]; conversion.src_YUYV.gap = cmd_buff[4]; cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_YUYV.image_size, conversion.src_YUYV.transfer_unit, conversion.src_YUYV.gap, cmd_buff[6]); } /** * Y2R::IsFinishedSendingYuv service function * Output: * 1 : Result of the function, 0 on success, otherwise error code * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingYuv(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x14, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R::IsFinishedSendingY service function * Output: * 1 : Result of the function, 0 on success, otherwise error code * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingY(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x15, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R::IsFinishedSendingU service function * Output: * 1 : Result of the function, 0 on success, otherwise error code * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingU(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x16, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } /** * Y2R::IsFinishedSendingV service function * Output: * 1 : Result of the function, 0 on success, otherwise error code * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingV(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x17, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void SetReceiving(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.dst.address = cmd_buff[1]; conversion.dst.image_size = cmd_buff[2]; conversion.dst.transfer_unit = cmd_buff[3]; conversion.dst.gap = cmd_buff[4]; cmd_buff[0] = IPC::MakeHeader(0x18, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "dst_process_handle=0x%08X", conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap, cmd_buff[6]); } /** * Y2R::IsFinishedReceiving service function * Output: * 1 : Result of the function, 0 on success, otherwise error code * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedReceiving(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x19, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 1; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void SetInputLineWidth(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x1A, 1, 0); cmd_buff[1] = conversion.SetInputLineWidth(cmd_buff[1]).raw; LOG_DEBUG(Service_Y2R, "called input_line_width=%u", cmd_buff[1]); } static void GetInputLineWidth(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x1B, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = conversion.input_line_width; LOG_DEBUG(Service_Y2R, "called input_line_width=%u", conversion.input_line_width); } static void SetInputLines(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x1C, 1, 0); cmd_buff[1] = conversion.SetInputLines(cmd_buff[1]).raw; LOG_DEBUG(Service_Y2R, "called input_lines=%u", cmd_buff[1]); } static void GetInputLines(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x1D, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = static_cast<u32>(conversion.input_lines); LOG_DEBUG(Service_Y2R, "called input_lines=%u", conversion.input_lines); } static void SetCoefficient(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); const u16* coefficients = reinterpret_cast<const u16*>(&cmd_buff[1]); std::memcpy(conversion.coefficients.data(), coefficients, sizeof(CoefficientSet)); cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]", coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4], coefficients[5], coefficients[6], coefficients[7]); } static void GetCoefficient(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x1F, 5, 0); cmd_buff[1] = RESULT_SUCCESS.raw; std::memcpy(&cmd_buff[2], conversion.coefficients.data(), sizeof(CoefficientSet)); LOG_DEBUG(Service_Y2R, "called"); } static void SetStandardCoefficient(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 index = cmd_buff[1]; cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0); cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)index).raw; LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", index); } static void GetStandardCoefficient(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 index = cmd_buff[1]; if (index < ARRAY_SIZE(standard_coefficients)) { cmd_buff[0] = IPC::MakeHeader(0x21, 5, 0); cmd_buff[1] = RESULT_SUCCESS.raw; std::memcpy(&cmd_buff[2], &standard_coefficients[index], sizeof(CoefficientSet)); LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u ", index); } else { cmd_buff[0] = IPC::MakeHeader(0x21, 1, 0); cmd_buff[1] = ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage) .raw; LOG_ERROR(Service_Y2R, "called standard_coefficient=%u The argument is invalid!", index); } } static void SetAlpha(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.alpha = cmd_buff[1]; cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); } static void GetAlpha(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x23, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = conversion.alpha; LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); } static void SetDitheringWeightParams(Interface* self) { IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x24, 8, 0); // 0x240200 rp.PopRaw(dithering_weight_params); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } static void GetDitheringWeightParams(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x25, 9, 0); cmd_buff[1] = RESULT_SUCCESS.raw; std::memcpy(&cmd_buff[2], &dithering_weight_params, sizeof(DitheringWeightParams)); LOG_DEBUG(Service_Y2R, "called"); } static void StartConversion(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( u32 total_output_size = conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap); Memory::RasterizerFlushAndInvalidateRegion( Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); HW::Y2R::PerformConversion(conversion); completion_event->Signal(); cmd_buff[0] = IPC::MakeHeader(0x26, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } static void StopConversion(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x27, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } /** * Y2R_U::IsBusyConversion service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : 1 if there's a conversion running, otherwise 0. */ static void IsBusyConversion(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x28, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; // StartConversion always finishes immediately LOG_DEBUG(Service_Y2R, "called"); } /** * Y2R_U::SetPackageParameter service function */ static void SetPackageParameter(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); auto params = reinterpret_cast<const ConversionParameters*>(&cmd_buff[1]); conversion.input_format = params->input_format; conversion.output_format = params->output_format; conversion.rotation = params->rotation; conversion.block_alignment = params->block_alignment; ResultCode result = conversion.SetInputLineWidth(params->input_line_width); if (result.IsError()) goto cleanup; result = conversion.SetInputLines(params->input_lines); if (result.IsError()) goto cleanup; result = conversion.SetStandardCoefficient(params->standard_coefficient); if (result.IsError()) goto cleanup; conversion.padding = params->padding; conversion.alpha = params->alpha; cleanup: cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0); cmd_buff[1] = result.raw; LOG_DEBUG( Service_Y2R, "called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu " "input_line_width=%hu input_lines=%hu standard_coefficient=%hhu reserved=%hhu alpha=%hX", params->input_format, params->output_format, params->rotation, params->block_alignment, params->input_line_width, params->input_lines, params->standard_coefficient, params->padding, params->alpha); } static void PingProcess(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x2A, 2, 0); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void DriverInitialize(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.input_format = InputFormat::YUV422_Indiv8; conversion.output_format = OutputFormat::RGBA8; conversion.rotation = Rotation::None; conversion.block_alignment = BlockAlignment::Linear; conversion.coefficients.fill(0); conversion.SetInputLineWidth(1024); conversion.SetInputLines(1024); conversion.alpha = 0; ConversionBuffer zero_buffer = {}; conversion.src_Y = zero_buffer; conversion.src_U = zero_buffer; conversion.src_V = zero_buffer; conversion.dst = zero_buffer; completion_event->Clear(); cmd_buff[0] = IPC::MakeHeader(0x2B, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } static void DriverFinalize(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x2C, 1, 0); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } static void GetPackageParameter(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = IPC::MakeHeader(0x2D, 4, 0); cmd_buff[1] = RESULT_SUCCESS.raw; std::memcpy(&cmd_buff[2], &conversion, sizeof(ConversionParameters)); LOG_DEBUG(Service_Y2R, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010040, SetInputFormat, "SetInputFormat"}, {0x00020000, GetInputFormat, "GetInputFormat"}, {0x00030040, SetOutputFormat, "SetOutputFormat"}, {0x00040000, GetOutputFormat, "GetOutputFormat"}, {0x00050040, SetRotation, "SetRotation"}, {0x00060000, GetRotation, "GetRotation"}, {0x00070040, SetBlockAlignment, "SetBlockAlignment"}, {0x00080000, GetBlockAlignment, "GetBlockAlignment"}, {0x00090040, SetSpacialDithering, "SetSpacialDithering"}, {0x000A0000, GetSpacialDithering, "GetSpacialDithering"}, {0x000B0040, SetTemporalDithering, "SetTemporalDithering"}, {0x000C0000, GetTemporalDithering, "GetTemporalDithering"}, {0x000D0040, SetTransferEndInterrupt, "SetTransferEndInterrupt"}, {0x000E0000, GetTransferEndInterrupt, "GetTransferEndInterrupt"}, {0x000F0000, GetTransferEndEvent, "GetTransferEndEvent"}, {0x00100102, SetSendingY, "SetSendingY"}, {0x00110102, SetSendingU, "SetSendingU"}, {0x00120102, SetSendingV, "SetSendingV"}, {0x00130102, SetSendingYUYV, "SetSendingYUYV"}, {0x00140000, IsFinishedSendingYuv, "IsFinishedSendingYuv"}, {0x00150000, IsFinishedSendingY, "IsFinishedSendingY"}, {0x00160000, IsFinishedSendingU, "IsFinishedSendingU"}, {0x00170000, IsFinishedSendingV, "IsFinishedSendingV"}, {0x00180102, SetReceiving, "SetReceiving"}, {0x00190000, IsFinishedReceiving, "IsFinishedReceiving"}, {0x001A0040, SetInputLineWidth, "SetInputLineWidth"}, {0x001B0000, GetInputLineWidth, "GetInputLineWidth"}, {0x001C0040, SetInputLines, "SetInputLines"}, {0x001D0000, GetInputLines, "GetInputLines"}, {0x001E0100, SetCoefficient, "SetCoefficient"}, {0x001F0000, GetCoefficient, "GetCoefficient"}, {0x00200040, SetStandardCoefficient, "SetStandardCoefficient"}, {0x00210040, GetStandardCoefficient, "GetStandardCoefficient"}, {0x00220040, SetAlpha, "SetAlpha"}, {0x00230000, GetAlpha, "GetAlpha"}, {0x00240200, SetDitheringWeightParams, "SetDitheringWeightParams"}, {0x00250000, GetDitheringWeightParams, "GetDitheringWeightParams"}, {0x00260000, StartConversion, "StartConversion"}, {0x00270000, StopConversion, "StopConversion"}, {0x00280000, IsBusyConversion, "IsBusyConversion"}, {0x002901C0, SetPackageParameter, "SetPackageParameter"}, {0x002A0000, PingProcess, "PingProcess"}, {0x002B0000, DriverInitialize, "DriverInitialize"}, {0x002C0000, DriverFinalize, "DriverFinalize"}, {0x002D0000, GetPackageParameter, "GetPackageParameter"}, }; Y2R_U::Y2R_U() { completion_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "Y2R:Completed"); std::memset(&conversion, 0, sizeof(conversion)); Register(FunctionTable); } Y2R_U::~Y2R_U() { completion_event = nullptr; } } // namespace Y2R
namespace APT_U { // Address used for shared font (as observed on HW) // TODO(bunnei): This is the hard-coded address where we currently dump the shared font from via // https://github.com/citra-emu/3dsutils. This is technically a hack, and will not work at any // address other than 0x18000000 due to internal pointers in the shared font dump that would need to // be relocated. This might be fixed by dumping the shared font @ address 0x00000000 and then // correctly mapping it in Citra, however we still do not understand how the mapping is determined. static const VAddr SHARED_FONT_VADDR = 0x18000000; /// Handle to shared memory region designated to for shared system font static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; static Kernel::SharedPtr<Kernel::Mutex> lock; static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event static Kernel::SharedPtr<Kernel::Event> pause_event = 0; ///< APT pause event static std::vector<u8> shared_font; /// Signals used by APT functions enum class SignalType : u32 { None = 0x0, AppJustStarted = 0x1, ReturningToApp = 0xB, ExitingApp = 0xC, }; /// App Id's used by APT functions enum class AppID : u32 { HomeMenu = 0x101, AlternateMenu = 0x103, Camera = 0x110, FriendsList = 0x112, GameNotes = 0x113, InternetBrowser = 0x114, InstructionManual = 0x115, Notifications = 0x116, Miiverse = 0x117, SoftwareKeyboard1 = 0x201, Ed = 0x202, PnoteApp = 0x204, SnoteApp = 0x205, Error = 0x206, Mint = 0x207, Extrapad = 0x208, Memolib = 0x209, Application = 0x300, SoftwareKeyboard2 = 0x401, }; void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); // TODO(bunnei): Check if these are created in Initialize or on APT process startup. notification_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Notification"); pause_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Pause"); cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(pause_event).MoveFrom(); // TODO(bunnei): Check if these events are cleared/signaled every time Initialize is called. notification_event->Clear(); pause_event->Signal(); // Fire start event ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error } /** * APT_U::NotifyToWait service function * Inputs: * 1 : AppID * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ void NotifyToWait(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; // TODO(Subv): Verify this, it seems to get SWKBD and Home Menu further. pause_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); } void GetLockHandle(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 flags = cmd_buff[1]; // TODO(bunnei): Figure out the purpose of the flag field cmd_buff[1] = RESULT_SUCCESS.raw; // No error // Not sure what these parameters are used for, but retail apps check that they are 0 after // GetLockHandle has been called. cmd_buff[2] = 0; cmd_buff[3] = 0; cmd_buff[4] = 0; cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); LOG_TRACE(Service_APT, "called handle=0x%08X", cmd_buff[5]); } void Enable(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 unk = cmd_buff[1]; // TODO(bunnei): What is this field used for? cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } /** * APT_U::GetAppletManInfo service function. * Inputs: * 1 : Unknown * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : Unknown u32 value * 3 : Unknown u8 value * 4 : Home Menu AppId * 5 : AppID of currently active app */ void GetAppletManInfo(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 unk = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; cmd_buff[3] = 0; cmd_buff[4] = static_cast<u32>(AppID::HomeMenu); // Home menu AppID cmd_buff[5] = static_cast<u32>(AppID::Application); // TODO(purpasmart96): Do this correctly LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } /** * APT_U::IsRegistered service function. This returns whether the specified AppID is registered with NS yet. * An AppID is "registered" once the process associated with the AppID uses APT:Enable. Home Menu uses this * command to determine when the launched process is running and to determine when to stop using GSP etc, * while displaying the "Nintendo 3DS" loading screen. * Inputs: * 1 : AppID * Outputs: * 0 : Return header * 1 : Result of function, 0 on success, otherwise error code * 2 : Output, 0 = not registered, 1 = registered. */ static void IsRegistered(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 1; // Set to registered LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void InquireNotification(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } /** * APT_U::SendParameter service function. This sets the parameter data state. * Inputs: * 1 : Source AppID * 2 : Destination AppID * 3 : Signal type * 4 : Parameter buffer size, max size is 0x1000 (this can be zero) * 5 : Value * 6 : Handle to the destination process, likely used for shared memory (this can be zero) * 7 : (Size<<14) | 2 * 8 : Input parameter buffer ptr * Outputs: * 0 : Return Header * 1 : Result of function, 0 on success, otherwise error code */ static void SendParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 src_app_id = cmd_buff[1]; u32 dst_app_id = cmd_buff[2]; u32 signal_type = cmd_buff[3]; u32 buffer_size = cmd_buff[4]; u32 value = cmd_buff[5]; u32 handle = cmd_buff[6]; u32 size = cmd_buff[7]; u32 in_param_buffer_ptr = cmd_buff[8]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, in_param_buffer_ptr); } /** * APT_U::ReceiveParameter service function. This returns the current parameter data from NS state, * from the source process which set the parameters. Once finished, NS will clear a flag in the NS * state so that this command will return an error if this command is used again if parameters were * not set again. This is called when the second Initialize event is triggered. It returns a signal * type indicating why it was triggered. * Inputs: * 1 : AppID * 2 : Parameter buffer size, max size is 0x1000 * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : AppID of the process which sent these parameters * 3 : Signal type * 4 : Actual parameter buffer size, this is <= to the the input size * 5 : Value * 6 : Handle from the source process which set the parameters, likely used for shared memory * 7 : Size * 8 : Output parameter buffer ptr */ void ReceiveParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; u32 buffer_size = cmd_buff[2]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type cmd_buff[4] = 0x10; // Parameter buffer size (16) cmd_buff[5] = 0; cmd_buff[6] = 0; cmd_buff[7] = 0; LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); } /** * APT_U::GlanceParameter service function. This is exactly the same as APT_U::ReceiveParameter * (except for the word value prior to the output handle), except this will not clear the flag * (except when responseword[3]==8 || responseword[3]==9) in NS state. * Inputs: * 1 : AppID * 2 : Parameter buffer size, max size is 0x1000 * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : Unknown, for now assume AppID of the process which sent these parameters * 3 : Unknown, for now assume Signal type * 4 : Actual parameter buffer size, this is <= to the the input size * 5 : Value * 6 : Handle from the source process which set the parameters, likely used for shared memory * 7 : Size * 8 : Output parameter buffer ptr */ void GlanceParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; u32 buffer_size = cmd_buff[2]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type cmd_buff[4] = 0x10; // Parameter buffer size (16) cmd_buff[5] = 0; cmd_buff[6] = 0; cmd_buff[7] = 0; LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); } /** * APT_U::CancelParameter service function. When the parameter data is available, and when the above * specified fields match the ones in NS state(for the ones where the checks are enabled), this * clears the flag which indicates that parameter data is available * (same flag cleared by APT:ReceiveParameter). * Inputs: * 1 : Flag, when non-zero NS will compare the word after this one with a field in the NS state. * 2 : Unknown, this is the same as the first unknown field returned by APT:ReceiveParameter. * 3 : Flag, when non-zero NS will compare the word after this one with a field in the NS state. * 4 : AppID * Outputs: * 0 : Return header * 1 : Result of function, 0 on success, otherwise error code * 2 : Status flag, 0 = failure due to no parameter data being available, or the above enabled * fields don't match the fields in NS state. 1 = success. */ static void CancelParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 flag1 = cmd_buff[1]; u32 unk = cmd_buff[2]; u32 flag2 = cmd_buff[3]; u32 app_id = cmd_buff[4]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 1; // Set to Success LOG_WARNING(Service_APT, "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", flag1, unk, flag2, app_id); } /** * APT_U::AppletUtility service function * Inputs: * 1 : Unknown, but clearly used for something * 2 : Buffer 1 size (purpose is unknown) * 3 : Buffer 2 size (purpose is unknown) * 5 : Buffer 1 address (purpose is unknown) * 65 : Buffer 2 address (purpose is unknown) * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ void AppletUtility(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); // These are from 3dbrew - I'm not really sure what they're used for. u32 unk = cmd_buff[1]; u32 buffer1_size = cmd_buff[2]; u32 buffer2_size = cmd_buff[3]; u32 buffer1_addr = cmd_buff[5]; u32 buffer2_addr = cmd_buff[65]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X, buffer1_size=0x%08x, buffer2_size=0x%08x, " "buffer1_addr=0x%08x, buffer2_addr=0x%08x", unk, buffer1_size, buffer2_size, buffer1_addr, buffer2_addr); } /** * APT_U::GetSharedFont service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : Virtual address of where shared font will be loaded in memory * 4 : Handle to shared font memory */ void GetSharedFont(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); if (!shared_font.empty()) { // TODO(bunnei): This function shouldn't copy the shared font every time it's called. // Instead, it should probably map the shared font as RO memory. We don't currently have // an easy way to do this, but the copy should be sufficient for now. memcpy(Memory::GetPointer(SHARED_FONT_VADDR), shared_font.data(), shared_font.size()); cmd_buff[0] = 0x00440082; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = SHARED_FONT_VADDR; cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); } else { cmd_buff[1] = -1; // Generic error (not really possible to verify this on hardware) LOG_ERROR(Kernel_SVC, "called, but %s has not been loaded!", SHARED_FONT); } } /** * APT_U::SetAppCpuTimeLimit service function * Inputs: * 1 : Value, must be one * 2 : Percentage of CPU time from 5 to 80 * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void SetAppCpuTimeLimit(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 value = cmd_buff[1]; u32 percent = cmd_buff[2]; if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called percent=0x%08X, value=0x%08x", percent, value); } /** * APT_U::GetAppCpuTimeLimit service function * Inputs: * 1 : Value, must be one * Outputs: * 0 : Return header * 1 : Result of function, 0 on success, otherwise error code * 2 : System core CPU time percentage */ static void GetAppCpuTimeLimit(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 value = cmd_buff[1]; if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } // TODO(purpasmart96): This is incorrect, I'm pretty sure the percentage should // be set by the application. cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0x80; // Set to 80% LOG_WARNING(Service_APT, "(STUBBED) called value=0x%08x", value); } const Interface::FunctionInfo FunctionTable[] = { {0x00010040, GetLockHandle, "GetLockHandle"}, {0x00020080, Initialize, "Initialize"}, {0x00030040, Enable, "Enable"}, {0x00040040, nullptr, "Finalize"}, {0x00050040, GetAppletManInfo, "GetAppletManInfo"}, {0x00060040, nullptr, "GetAppletInfo"}, {0x00070000, nullptr, "GetLastSignaledAppletId"}, {0x00080000, nullptr, "CountRegisteredApplet"}, {0x00090040, IsRegistered, "IsRegistered"}, {0x000A0040, nullptr, "GetAttribute"}, {0x000B0040, InquireNotification, "InquireNotification"}, {0x000C0104, SendParameter, "SendParameter"}, {0x000D0080, ReceiveParameter, "ReceiveParameter"}, {0x000E0080, GlanceParameter, "GlanceParameter"}, {0x000F0100, CancelParameter, "CancelParameter"}, {0x001000C2, nullptr, "DebugFunc"}, {0x001100C0, nullptr, "MapProgramIdForDebug"}, {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, {0x00130000, nullptr, "GetPreparationState"}, {0x00140040, nullptr, "SetPreparationState"}, {0x00150140, nullptr, "PrepareToStartApplication"}, {0x00160040, nullptr, "PreloadLibraryApplet"}, {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, {0x00180040, nullptr, "PrepareToStartLibraryApplet"}, {0x00190040, nullptr, "PrepareToStartSystemApplet"}, {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, {0x001B00C4, nullptr, "StartApplication"}, {0x001C0000, nullptr, "WakeupApplication"}, {0x001D0000, nullptr, "CancelApplication"}, {0x001E0084, nullptr, "StartLibraryApplet"}, {0x001F0084, nullptr, "StartSystemApplet"}, {0x00200044, nullptr, "StartNewestHomeMenu"}, {0x00210000, nullptr, "OrderToCloseApplication"}, {0x00220040, nullptr, "PrepareToCloseApplication"}, {0x00230040, nullptr, "PrepareToJumpToApplication"}, {0x00240044, nullptr, "JumpToApplication"}, {0x002500C0, nullptr, "PrepareToCloseLibraryApplet"}, {0x00260000, nullptr, "PrepareToCloseSystemApplet"}, {0x00270044, nullptr, "CloseApplication"}, {0x00280044, nullptr, "CloseLibraryApplet"}, {0x00290044, nullptr, "CloseSystemApplet"}, {0x002A0000, nullptr, "OrderToCloseSystemApplet"}, {0x002B0000, nullptr, "PrepareToJumpToHomeMenu"}, {0x002C0044, nullptr, "JumpToHomeMenu"}, {0x002D0000, nullptr, "PrepareToLeaveHomeMenu"}, {0x002E0044, nullptr, "LeaveHomeMenu"}, {0x002F0040, nullptr, "PrepareToLeaveResidentApplet"}, {0x00300044, nullptr, "LeaveResidentApplet"}, {0x00310100, nullptr, "PrepareToDoApplicationJump"}, {0x00320084, nullptr, "DoApplicationJump"}, {0x00330000, nullptr, "GetProgramIdOnApplicationJump"}, {0x00340084, nullptr, "SendDeliverArg"}, {0x00350080, nullptr, "ReceiveDeliverArg"}, {0x00360040, nullptr, "LoadSysMenuArg"}, {0x00370042, nullptr, "StoreSysMenuArg"}, {0x00380040, nullptr, "PreloadResidentApplet"}, {0x00390040, nullptr, "PrepareToStartResidentApplet"}, {0x003A0044, nullptr, "StartResidentApplet"}, {0x003B0040, nullptr, "CancelLibraryApplet"}, {0x003C0042, nullptr, "SendDspSleep"}, {0x003D0042, nullptr, "SendDspWakeUp"}, {0x003E0080, nullptr, "ReplySleepQuery"}, {0x003F0040, nullptr, "ReplySleepNotificationComplete"}, {0x00400042, nullptr, "SendCaptureBufferInfo"}, {0x00410040, nullptr, "ReceiveCaptureBufferInfo"}, {0x00420080, nullptr, "SleepSystem"}, {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, {0x00460104, nullptr, "Wrap"}, {0x00470104, nullptr, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, {0x004B00C2, AppletUtility, "AppletUtility"}, {0x004C0000, nullptr, "SetFatalErrDispMode"}, {0x004D0080, nullptr, "GetAppletProgramInfo"}, {0x004E0000, nullptr, "HardwareResetAsync"}, {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Interface class Interface::Interface() { // Load the shared system font (if available). // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file // "shared_font.bin" in the Citra "sysdata" directory. shared_font.clear(); std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; FileUtil::CreateFullPath(filepath); // Create path if not already created FileUtil::IOFile file(filepath, "rb"); if (file.IsOpen()) { // Read shared font data shared_font.resize((size_t)file.GetSize()); file.ReadBytes(shared_font.data(), (size_t)file.GetSize()); // Create shared font memory object shared_font_mem = Kernel::SharedMemory::Create("APT_U:shared_font_mem"); } else { LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str()); shared_font_mem = nullptr; } lock = Kernel::Mutex::Create(false, "APT_U:Lock"); Register(FunctionTable); } } // namespace
namespace DSP_DSP { static u32 read_pipe_count; static Kernel::SharedPtr<Kernel::Event> semaphore_event; static Kernel::SharedPtr<Kernel::Event> interrupt_event; void SignalInterrupt() { // TODO(bunnei): This is just a stub, it does not do anything other than signal to the emulated // application that a DSP interrupt occurred, without specifying which one. Since we do not // emulate the DSP yet (and how it works is largely unknown), this is a work around to get games // that check the DSP interrupt signal event to run. We should figure out the different types of // DSP interrupts, and trigger them at the appropriate times. if (interrupt_event != 0) interrupt_event->Signal(); } /** * DSP_DSP::ConvertProcessAddressFromDspDram service function * Inputs: * 1 : Address * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : (inaddr << 1) + 0x1FF40000 (where 0x1FF00000 is the DSP RAM address) */ static void ConvertProcessAddressFromDspDram(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 addr = cmd_buff[1]; cmd_buff[1] = 0; // No error cmd_buff[2] = (addr << 1) + (Memory::DSP_RAM_VADDR + 0x40000); LOG_WARNING(Service_DSP, "(STUBBED) called with address 0x%08X", addr); } /** * DSP_DSP::LoadComponent service function * Inputs: * 1 : Size * 2 : Unknown (observed only half word used) * 3 : Unknown (observed only half word used) * 4 : (size << 4) | 0xA * 5 : Buffer address * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : Component loaded, 0 on not loaded, 1 on loaded */ static void LoadComponent(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 size = cmd_buff[1]; u32 unk1 = cmd_buff[2]; u32 unk2 = cmd_buff[3]; u32 new_size = cmd_buff[4]; u32 buffer = cmd_buff[5]; cmd_buff[1] = 0; // No error cmd_buff[2] = 1; // Pretend that we actually loaded the DSP firmware // TODO(bunnei): Implement real DSP firmware loading LOG_WARNING(Service_DSP, "(STUBBED) called size=0x%X, unk1=0x%08X, unk2=0x%08X, new_size=0x%X, buffer=0x%08X", size, unk1, unk2, new_size, buffer); } /** * DSP_DSP::GetSemaphoreEventHandle service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 3 : Semaphore event handle */ static void GetSemaphoreEventHandle(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).MoveFrom(); // Event handle LOG_WARNING(Service_DSP, "(STUBBED) called"); } /** * DSP_DSP::FlushDataCache service function * * This Function is a no-op, We aren't emulating the CPU cache any time soon. * * Inputs: * 1 : Address * 2 : Size * 3 : Value 0, some descriptor for the KProcess Handle * 4 : KProcess handle * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void FlushDataCache(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 address = cmd_buff[1]; u32 size = cmd_buff[2]; u32 process = cmd_buff[4]; // TODO(purpasmart96): Verify return header on HW cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_DEBUG(Service_DSP, "(STUBBED) called address=0x%08X, size=0x%X, process=0x%08X", address, size, process); } /** * DSP_DSP::RegisterInterruptEvents service function * Inputs: * 1 : Parameter 0 (purpose unknown) * 2 : Parameter 1 (purpose unknown) * 4 : Interrupt event handle * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void RegisterInterruptEvents(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 param0 = cmd_buff[1]; u32 param1 = cmd_buff[2]; u32 event_handle = cmd_buff[4]; auto evt = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]); if (evt != nullptr) { interrupt_event = evt; cmd_buff[1] = 0; // No error } else { LOG_ERROR(Service_DSP, "called with invalid handle=%08X", cmd_buff[4]); // TODO(yuriks): An error should be returned from SendSyncRequest, not in the cmdbuf cmd_buff[1] = -1; } LOG_WARNING(Service_DSP, "(STUBBED) called param0=%u, param1=%u, event_handle=0x%08X", param0, param1, event_handle); } /** * DSP_DSP::SetSemaphore service function * Inputs: * 1 : Unknown (observed only half word used) * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void SetSemaphore(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); SignalInterrupt(); cmd_buff[1] = 0; // No error LOG_WARNING(Service_DSP, "(STUBBED) called"); } /** * DSP_DSP::WriteProcessPipe service function * Inputs: * 1 : Number * 2 : Size * 3 : (size <<14) | 0x402 * 4 : Buffer * Outputs: * 0 : Return header * 1 : Result of function, 0 on success, otherwise error code */ static void WriteProcessPipe(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 number = cmd_buff[1]; u32 size = cmd_buff[2]; u32 new_size = cmd_buff[3]; u32 buffer = cmd_buff[4]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_DSP, "(STUBBED) called number=%u, size=0x%X, new_size=0x%X, buffer=0x%08X", number, size, new_size, buffer); } /** * DSP_DSP::ReadPipeIfPossible service function * Inputs: * 1 : Unknown * 2 : Unknown * 3 : Size in bytes of read (observed only lower half word used) * 0x41 : Virtual address to read from DSP pipe to in memory * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : Number of bytes read from pipe */ static void ReadPipeIfPossible(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 unk1 = cmd_buff[1]; u32 unk2 = cmd_buff[2]; u32 size = cmd_buff[3] & 0xFFFF;// Lower 16 bits are size VAddr addr = cmd_buff[0x41]; // Canned DSP responses that games expect. These were taken from HW by 3dmoo team. // TODO: Remove this hack :) static const std::array<u16, 16> canned_read_pipe = {{ 0x000F, 0xBFFF, 0x9E8E, 0x8680, 0xA78E, 0x9430, 0x8400, 0x8540, 0x948E, 0x8710, 0x8410, 0xA90E, 0xAA0E, 0xAACE, 0xAC4E, 0xAC58 } }; u32 initial_size = read_pipe_count; for (unsigned offset = 0; offset < size; offset += sizeof(u16)) { if (read_pipe_count < canned_read_pipe.size()) { Memory::Write16(addr + offset, canned_read_pipe[read_pipe_count]); read_pipe_count++; } else { LOG_ERROR(Service_DSP, "canned read pipe log exceeded!"); break; } } cmd_buff[1] = 0; // No error cmd_buff[2] = (read_pipe_count - initial_size) * sizeof(u16); LOG_WARNING(Service_DSP, "(STUBBED) called unk1=0x%08X, unk2=0x%08X, size=0x%X, buffer=0x%08X", unk1, unk2, size, addr); } /** * DSP_DSP::SetSemaphoreMask service function * Inputs: * 1 : Mask * Outputs: * 1 : Result of function, 0 on success, otherwise error code */ static void SetSemaphoreMask(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 mask = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_DSP, "(STUBBED) called mask=0x%08X", mask); } /** * DSP_DSP::GetHeadphoneStatus service function * Inputs: * 1 : None * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : The headphone status response, 0 = Not using headphones?, * 1 = using headphones? */ static void GetHeadphoneStatus(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; // Not using headphones? LOG_DEBUG(Service_DSP, "(STUBBED) called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010040, nullptr, "RecvData"}, {0x00020040, nullptr, "RecvDataIsReady"}, {0x00030080, nullptr, "SendData"}, {0x00040040, nullptr, "SendDataIsEmpty"}, {0x000500C2, nullptr, "SendFifoEx"}, {0x000600C0, nullptr, "RecvFifoEx"}, {0x00070040, SetSemaphore, "SetSemaphore"}, {0x00080000, nullptr, "GetSemaphore"}, {0x00090040, nullptr, "ClearSemaphore"}, {0x000A0040, nullptr, "MaskSemaphore"}, {0x000B0000, nullptr, "CheckSemaphoreRequest"}, {0x000C0040, ConvertProcessAddressFromDspDram, "ConvertProcessAddressFromDspDram"}, {0x000D0082, WriteProcessPipe, "WriteProcessPipe"}, {0x000E00C0, nullptr, "ReadPipe"}, {0x000F0080, nullptr, "GetPipeReadableSize"}, {0x001000C0, ReadPipeIfPossible, "ReadPipeIfPossible"}, {0x001100C2, LoadComponent, "LoadComponent"}, {0x00120000, nullptr, "UnloadComponent"}, {0x00130082, FlushDataCache, "FlushDataCache"}, {0x00140082, nullptr, "InvalidateDCache"}, {0x00150082, RegisterInterruptEvents, "RegisterInterruptEvents"}, {0x00160000, GetSemaphoreEventHandle, "GetSemaphoreEventHandle"}, {0x00170040, SetSemaphoreMask, "SetSemaphoreMask"}, {0x00180040, nullptr, "GetPhysicalAddress"}, {0x00190040, nullptr, "GetVirtualAddress"}, {0x001A0042, nullptr, "SetIirFilterI2S1_cmd1"}, {0x001B0042, nullptr, "SetIirFilterI2S1_cmd2"}, {0x001C0082, nullptr, "SetIirFilterEQ"}, {0x001D00C0, nullptr, "ReadMultiEx_SPI2"}, {0x001E00C2, nullptr, "WriteMultiEx_SPI2"}, {0x001F0000, GetHeadphoneStatus, "GetHeadphoneStatus"}, {0x00200040, nullptr, "ForceHeadphoneOut"}, {0x00210000, nullptr, "GetIsDspOccupied"}, }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Interface class Interface::Interface() { semaphore_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "DSP_DSP::semaphore_event"); interrupt_event = nullptr; read_pipe_count = 0; Register(FunctionTable); } Interface::~Interface() { semaphore_event = nullptr; interrupt_event = nullptr; } } // namespace
ResultStatus AppLoader_NCCH::LoadExec(Kernel::SharedPtr<Kernel::Process>& process) { using Kernel::CodeSet; using Kernel::SharedPtr; if (!is_loaded) return ResultStatus::ErrorNotLoaded; std::vector<u8> code; u64_le program_id; if (ResultStatus::Success == ReadCode(code) && ResultStatus::Success == ReadProgramId(program_id)) { std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( (const char*)overlay_ncch->exheader_header.codeset_info.name, 8); SharedPtr<CodeSet> codeset = CodeSet::Create(process_name, program_id); codeset->code.offset = 0; codeset->code.addr = overlay_ncch->exheader_header.codeset_info.text.address; codeset->code.size = overlay_ncch->exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; codeset->rodata.offset = codeset->code.offset + codeset->code.size; codeset->rodata.addr = overlay_ncch->exheader_header.codeset_info.ro.address; codeset->rodata.size = overlay_ncch->exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just // to the regular size. Playing it safe for now. u32 bss_page_size = (overlay_ncch->exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; code.resize(code.size() + bss_page_size, 0); codeset->data.offset = codeset->rodata.offset + codeset->rodata.size; codeset->data.addr = overlay_ncch->exheader_header.codeset_info.data.address; codeset->data.size = overlay_ncch->exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + bss_page_size; codeset->entrypoint = codeset->code.addr; codeset->memory = std::make_shared<std::vector<u8>>(std::move(code)); process = Kernel::Process::Create(std::move(codeset)); // Attach a resource limit to the process based on the resource limit category process->resource_limit = Kernel::ResourceLimit::GetForCategory(static_cast<Kernel::ResourceLimitCategory>( overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category)); // Set the default CPU core for this process process->ideal_processor = overlay_ncch->exheader_header.arm11_system_local_caps.ideal_processor; // Copy data while converting endianness std::array<u32, ARRAY_SIZE(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors)> kernel_caps; std::copy_n(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps)); process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; process->Run(priority, stack_size); return ResultStatus::Success; } return ResultStatus::Error; }
namespace HID { static const int MAX_CIRCLEPAD_POS = 0x9C; ///< Max value for a circle pad position // Handle to shared memory region designated to HID_User service static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; // Event handles static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1; static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2; static Kernel::SharedPtr<Kernel::Event> event_accelerometer; static Kernel::SharedPtr<Kernel::Event> event_gyroscope; static Kernel::SharedPtr<Kernel::Event> event_debug_pad; static u32 next_pad_index; static u32 next_touch_index; const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = {{ Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y, Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR, Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE, Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT, Service::HID::PAD_CIRCLE_UP, Service::HID::PAD_CIRCLE_DOWN, Service::HID::PAD_CIRCLE_LEFT, Service::HID::PAD_CIRCLE_RIGHT, Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT }}; // TODO(peachum): // Add a method for setting analog input from joystick device for the circle Pad. // // This method should: // * Be called after both PadButton<Press, Release>(). // * Be called before PadUpdateComplete() // * Set current PadEntry.circle_pad_<axis> using analog data // * Set PadData.raw_circle_pad_data // * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_x >= 41 // * Set PadData.current_state.circle_up = 1 if current PadEntry.circle_pad_y >= 41 // * Set PadData.current_state.circle_left = 1 if current PadEntry.circle_pad_x <= -41 // * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_y <= -41 void Update() { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); const PadState state = VideoCore::g_emu_window->GetPadState(); if (mem == nullptr) { LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); return; } mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; next_touch_index = (next_touch_index + 1) % mem->pad.entries.size(); // Get the previous Pad state u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); PadState old_state = mem->pad.entries[last_entry_index].current_state; // Compute bitmask with 1s for bits different from the old state PadState changed = { { (state.hex ^ old_state.hex) } }; // Get the current Pad entry PadDataEntry* pad_entry = &mem->pad.entries[mem->pad.index]; // Update entry properties pad_entry->current_state.hex = state.hex; pad_entry->delta_additions.hex = changed.hex & state.hex; pad_entry->delta_removals.hex = changed.hex & old_state.hex;; // Set circle Pad pad_entry->circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : state.circle_right ? MAX_CIRCLEPAD_POS : 0x0; pad_entry->circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : state.circle_up ? MAX_CIRCLEPAD_POS : 0x0; // If we just updated index 0, provide a new timestamp if (mem->pad.index == 0) { mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); } mem->touch.index = next_touch_index; next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); // Get the current touch entry TouchDataEntry* touch_entry = &mem->touch.entries[mem->touch.index]; bool pressed = false; std::tie(touch_entry->x, touch_entry->y, pressed) = VideoCore::g_emu_window->GetTouchState(); touch_entry->valid = pressed ? 1 : 0; // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). // If we just updated index 0, provide a new timestamp if (mem->touch.index == 0) { mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); } // Signal both handles when there's an update to Pad or touch event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); } void GetIPCHandles(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = 0; // No error cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom(); cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom(); cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom(); cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom(); cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom(); } void EnableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_HID, "(STUBBED) called"); } void DisableAccelerometer(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); event_accelerometer->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_HID, "(STUBBED) called"); } void EnableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_HID, "(STUBBED) called"); } void DisableGyroscopeLow(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); event_gyroscope->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; LOG_WARNING(Service_HID, "(STUBBED) called"); } void GetSoundVolume(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = volume; LOG_WARNING(Service_HID, "(STUBBED) called"); } void Init() { using namespace Kernel; AddService(new HID_U_Interface); AddService(new HID_SPVR_Interface); using Kernel::MemoryPermission; shared_mem = SharedMemory::Create(0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, "HID:SharedMem"); next_pad_index = 0; next_touch_index = 0; // Create event handles event_pad_or_touch_1 = Event::Create(RESETTYPE_ONESHOT, "HID:EventPadOrTouch1"); event_pad_or_touch_2 = Event::Create(RESETTYPE_ONESHOT, "HID:EventPadOrTouch2"); event_accelerometer = Event::Create(RESETTYPE_ONESHOT, "HID:EventAccelerometer"); event_gyroscope = Event::Create(RESETTYPE_ONESHOT, "HID:EventGyroscope"); event_debug_pad = Event::Create(RESETTYPE_ONESHOT, "HID:EventDebugPad"); } void Shutdown() { shared_mem = nullptr; event_pad_or_touch_1 = nullptr; event_pad_or_touch_2 = nullptr; event_accelerometer = nullptr; event_gyroscope = nullptr; event_debug_pad = nullptr; } } // namespace HID
void Update() { SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); const PadState state = VideoCore::g_emu_window->GetPadState(); if (mem == nullptr) { LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); return; } mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; next_touch_index = (next_touch_index + 1) % mem->pad.entries.size(); // Get the previous Pad state u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); PadState old_state = mem->pad.entries[last_entry_index].current_state; // Compute bitmask with 1s for bits different from the old state PadState changed = { { (state.hex ^ old_state.hex) } }; // Get the current Pad entry PadDataEntry* pad_entry = &mem->pad.entries[mem->pad.index]; // Update entry properties pad_entry->current_state.hex = state.hex; pad_entry->delta_additions.hex = changed.hex & state.hex; pad_entry->delta_removals.hex = changed.hex & old_state.hex;; // Set circle Pad pad_entry->circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : state.circle_right ? MAX_CIRCLEPAD_POS : 0x0; pad_entry->circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : state.circle_up ? MAX_CIRCLEPAD_POS : 0x0; // If we just updated index 0, provide a new timestamp if (mem->pad.index == 0) { mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); } mem->touch.index = next_touch_index; next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); // Get the current touch entry TouchDataEntry* touch_entry = &mem->touch.entries[mem->touch.index]; bool pressed = false; std::tie(touch_entry->x, touch_entry->y, pressed) = VideoCore::g_emu_window->GetTouchState(); touch_entry->valid = pressed ? 1 : 0; // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). // If we just updated index 0, provide a new timestamp if (mem->touch.index == 0) { mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); } // Signal both handles when there's an update to Pad or touch event_pad_or_touch_1->Signal(); event_pad_or_touch_2->Signal(); }
namespace Y2R_U { struct ConversionParameters { InputFormat input_format; OutputFormat output_format; Rotation rotation; BlockAlignment block_alignment; u16 input_line_width; u16 input_lines; StandardCoefficient standard_coefficient; u8 reserved; u16 alpha; }; static_assert(sizeof(ConversionParameters) == 12, "ConversionParameters struct has incorrect size"); static Kernel::SharedPtr<Kernel::Event> completion_event; static ConversionConfiguration conversion; static const CoefficientSet standard_coefficients[4] = { {{ 0x100, 0x166, 0xB6, 0x58, 0x1C5, -0x166F, 0x10EE, -0x1C5B }}, // ITU_Rec601 {{ 0x100, 0x193, 0x77, 0x2F, 0x1DB, -0x1933, 0xA7C, -0x1D51 }}, // ITU_Rec709 {{ 0x12A, 0x198, 0xD0, 0x64, 0x204, -0x1BDE, 0x10F2, -0x229B }}, // ITU_Rec601_Scaling {{ 0x12A, 0x1CA, 0x88, 0x36, 0x21C, -0x1F04, 0x99C, -0x2421 }}, // ITU_Rec709_Scaling }; ResultCode ConversionConfiguration::SetInputLineWidth(u16 width) { if (width == 0 || width > 1024 || width % 8 != 0) { return ResultCode(ErrorDescription::OutOfRange, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053FD } // Note: The hardware uses the register value 0 to represent a width of 1024, so for a width of // 1024 the `camera` module would set the value 0 here, but we don't need to emulate this // internal detail. this->input_line_width = width; return RESULT_SUCCESS; } ResultCode ConversionConfiguration::SetInputLines(u16 lines) { if (lines == 0 || lines > 1024) { return ResultCode(ErrorDescription::OutOfRange, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053FD } // Note: In what appears to be a bug, the `camera` module does not set the hardware register at // all if `lines` is 1024, so the conversion uses the last value that was set. The intention // was probably to set it to 0 like in SetInputLineWidth. if (lines != 1024) { this->input_lines = lines; } return RESULT_SUCCESS; } ResultCode ConversionConfiguration::SetStandardCoefficient(StandardCoefficient standard_coefficient) { size_t index = static_cast<size_t>(standard_coefficient); if (index >= 4) { return ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM, ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E053ED } std::memcpy(coefficients.data(), standard_coefficients[index].data(), sizeof(coefficients)); return RESULT_SUCCESS; } static void SetInputFormat(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.input_format = static_cast<InputFormat>(cmd_buff[1]); LOG_DEBUG(Service_Y2R, "called input_format=%hhu", conversion.input_format); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetOutputFormat(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.output_format = static_cast<OutputFormat>(cmd_buff[1]); LOG_DEBUG(Service_Y2R, "called output_format=%hhu", conversion.output_format); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetRotation(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.rotation = static_cast<Rotation>(cmd_buff[1]); LOG_DEBUG(Service_Y2R, "called rotation=%hhu", conversion.rotation); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetBlockAlignment(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.block_alignment = static_cast<BlockAlignment>(cmd_buff[1]); LOG_DEBUG(Service_Y2R, "called alignment=%hhu", conversion.block_alignment); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetTransferEndInterrupt(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = 0x000D0040; cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "(STUBBED) called"); } /** * Y2R_U::GetTransferEndEvent service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 3 : The handle of the completion event */ static void GetTransferEndEvent(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); LOG_DEBUG(Service_Y2R, "called"); } static void SetSendingY(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.src_Y.address = cmd_buff[1]; conversion.src_Y.image_size = cmd_buff[2]; conversion.src_Y.transfer_unit = cmd_buff[3]; conversion.src_Y.gap = cmd_buff[4]; u32 src_process_handle = cmd_buff[6]; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_Y.image_size, conversion.src_Y.transfer_unit, conversion.src_Y.gap, src_process_handle); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetSendingU(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.src_U.address = cmd_buff[1]; conversion.src_U.image_size = cmd_buff[2]; conversion.src_U.transfer_unit = cmd_buff[3]; conversion.src_U.gap = cmd_buff[4]; u32 src_process_handle = cmd_buff[6]; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_U.image_size, conversion.src_U.transfer_unit, conversion.src_U.gap, src_process_handle); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetSendingV(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.src_V.address = cmd_buff[1]; conversion.src_V.image_size = cmd_buff[2]; conversion.src_V.transfer_unit = cmd_buff[3]; conversion.src_V.gap = cmd_buff[4]; u32 src_process_handle = cmd_buff[6]; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap, src_process_handle); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetSendingYUYV(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.src_YUYV.address = cmd_buff[1]; conversion.src_YUYV.image_size = cmd_buff[2]; conversion.src_YUYV.transfer_unit = cmd_buff[3]; conversion.src_YUYV.gap = cmd_buff[4]; u32 src_process_handle = cmd_buff[6]; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_YUYV.image_size, conversion.src_YUYV.transfer_unit, conversion.src_YUYV.gap, src_process_handle); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetReceiving(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.dst.address = cmd_buff[1]; conversion.dst.image_size = cmd_buff[2]; conversion.dst.transfer_unit = cmd_buff[3]; conversion.dst.gap = cmd_buff[4]; u32 dst_process_handle = cmd_buff[6]; LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "dst_process_handle=0x%08X", conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap, dst_process_handle); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetInputLineWidth(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); LOG_DEBUG(Service_Y2R, "called input_line_width=%u", cmd_buff[1]); cmd_buff[1] = conversion.SetInputLineWidth(cmd_buff[1]).raw; } static void SetInputLines(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); LOG_DEBUG(Service_Y2R, "called input_line_number=%u", cmd_buff[1]); cmd_buff[1] = conversion.SetInputLines(cmd_buff[1]).raw; } static void SetCoefficient(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); const u16* coefficients = reinterpret_cast<const u16*>(&cmd_buff[1]); std::memcpy(conversion.coefficients.data(), coefficients, sizeof(CoefficientSet)); LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]", coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4], coefficients[5], coefficients[6], coefficients[7]); cmd_buff[1] = RESULT_SUCCESS.raw; } static void SetStandardCoefficient(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", cmd_buff[1]); cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)cmd_buff[1]).raw; } static void SetAlpha(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.alpha = cmd_buff[1]; LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); cmd_buff[1] = RESULT_SUCCESS.raw; } static void StartConversion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); HW::Y2R::PerformConversion(conversion); // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( u32 total_output_size = conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap); VideoCore::g_renderer->hw_rasterizer->NotifyFlush( Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); LOG_DEBUG(Service_Y2R, "called"); completion_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; } static void StopConversion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = 0x00270040; cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } /** * Y2R_U::IsBusyConversion service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code * 2 : 1 if there's a conversion running, otherwise 0. */ static void IsBusyConversion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; // StartConversion always finishes immediately LOG_DEBUG(Service_Y2R, "called"); } /** * Y2R_U::SetConversionParams service function */ static void SetConversionParams(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); auto params = reinterpret_cast<const ConversionParameters*>(&cmd_buff[1]); LOG_DEBUG(Service_Y2R, "called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu " "input_line_width=%hu input_lines=%hu standard_coefficient=%hhu " "reserved=%hhu alpha=%hX", params->input_format, params->output_format, params->rotation, params->block_alignment, params->input_line_width, params->input_lines, params->standard_coefficient, params->reserved, params->alpha); ResultCode result = RESULT_SUCCESS; conversion.input_format = params->input_format; conversion.output_format = params->output_format; conversion.rotation = params->rotation; conversion.block_alignment = params->block_alignment; result = conversion.SetInputLineWidth(params->input_line_width); if (result.IsError()) goto cleanup; result = conversion.SetInputLines(params->input_lines); if (result.IsError()) goto cleanup; result = conversion.SetStandardCoefficient(params->standard_coefficient); if (result.IsError()) goto cleanup; conversion.alpha = params->alpha; cleanup: cmd_buff[0] = 0x00290040; // TODO verify cmd_buff[1] = result.raw; } static void PingProcess(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[2] = 0; LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void DriverInitialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); conversion.input_format = InputFormat::YUV422_Indiv8; conversion.output_format = OutputFormat::RGBA8; conversion.rotation = Rotation::None; conversion.block_alignment = BlockAlignment::Linear; conversion.coefficients.fill(0); conversion.SetInputLineWidth(1024); conversion.SetInputLines(1024); conversion.alpha = 0; ConversionBuffer zero_buffer = {}; conversion.src_Y = zero_buffer; conversion.src_U = zero_buffer; conversion.src_V = zero_buffer; conversion.dst = zero_buffer; completion_event->Clear(); cmd_buff[0] = 0x002B0040; cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } static void DriverFinalize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[0] = 0x002C0040; cmd_buff[1] = RESULT_SUCCESS.raw; LOG_DEBUG(Service_Y2R, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010040, SetInputFormat, "SetInputFormat"}, {0x00030040, SetOutputFormat, "SetOutputFormat"}, {0x00050040, SetRotation, "SetRotation"}, {0x00070040, SetBlockAlignment, "SetBlockAlignment"}, {0x000D0040, SetTransferEndInterrupt, "SetTransferEndInterrupt"}, {0x000F0000, GetTransferEndEvent, "GetTransferEndEvent"}, {0x00100102, SetSendingY, "SetSendingY"}, {0x00110102, SetSendingU, "SetSendingU"}, {0x00120102, SetSendingV, "SetSendingV"}, {0x00130102, SetSendingYUYV, "SetSendingYUYV"}, {0x00180102, SetReceiving, "SetReceiving"}, {0x001A0040, SetInputLineWidth, "SetInputLineWidth"}, {0x001C0040, SetInputLines, "SetInputLines"}, {0x001E0100, SetCoefficient, "SetCoefficient"}, {0x00200040, SetStandardCoefficient, "SetStandardCoefficient"}, {0x00220040, SetAlpha, "SetAlpha"}, {0x00260000, StartConversion, "StartConversion"}, {0x00270000, StopConversion, "StopConversion"}, {0x00280000, IsBusyConversion, "IsBusyConversion"}, {0x002901C0, SetConversionParams, "SetConversionParams"}, {0x002A0000, PingProcess, "PingProcess"}, {0x002B0000, DriverInitialize, "DriverInitialize"}, {0x002C0000, DriverFinalize, "DriverFinalize"}, }; //////////////////////////////////////////////////////////////////////////////////////////////////// // Interface class Interface::Interface() { completion_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "Y2R:Completed"); std::memset(&conversion, 0, sizeof(conversion)); Register(FunctionTable); } } // namespace
namespace APT { // Address used for shared font (as observed on HW) // TODO(bunnei): This is the hard-coded address where we currently dump the shared font from via // https://github.com/citra-emu/3dsutils. This is technically a hack, and will not work at any // address other than 0x18000000 due to internal pointers in the shared font dump that would need to // be relocated. This might be fixed by dumping the shared font @ address 0x00000000 and then // correctly mapping it in Citra, however we still do not understand how the mapping is determined. static const VAddr SHARED_FONT_VADDR = 0x18000000; /// Handle to shared memory region designated to for shared system font static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; static Kernel::SharedPtr<Kernel::Mutex> lock; static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event static Kernel::SharedPtr<Kernel::Event> start_event; ///< APT start event static std::vector<u8> shared_font; static u32 cpu_percent; ///< CPU time available to the running application void Initialize(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; u32 flags = cmd_buff[2]; cmd_buff[2] = 0x04000000; // According to 3dbrew, this value should be 0x04000000 cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(start_event).MoveFrom(); // TODO(bunnei): Check if these events are cleared every time Initialize is called. notification_event->Clear(); start_event->Clear(); ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); lock->Release(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_TRACE(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); } void GetSharedFont(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); if (!shared_font.empty()) { // TODO(bunnei): This function shouldn't copy the shared font every time it's called. // Instead, it should probably map the shared font as RO memory. We don't currently have // an easy way to do this, but the copy should be sufficient for now. memcpy(Memory::GetPointer(SHARED_FONT_VADDR), shared_font.data(), shared_font.size()); cmd_buff[0] = 0x00440082; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = SHARED_FONT_VADDR; cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); } else { cmd_buff[1] = -1; // Generic error (not really possible to verify this on hardware) LOG_ERROR(Kernel_SVC, "called, but %s has not been loaded!", SHARED_FONT); } } void NotifyToWait(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; // TODO(Subv): Verify this, it seems to get SWKBD and Home Menu further. start_event->Signal(); cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) app_id=%u", app_id); } void GetLockHandle(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 flags = cmd_buff[1]; // TODO(bunnei): Figure out the purpose of the flag field cmd_buff[1] = RESULT_SUCCESS.raw; // No error // Not sure what these parameters are used for, but retail apps check that they are 0 after // GetLockHandle has been called. cmd_buff[2] = 0; cmd_buff[3] = 0; cmd_buff[4] = 0; cmd_buff[5] = Kernel::g_handle_table.Create(lock).MoveFrom(); LOG_TRACE(Service_APT, "called handle=0x%08X", cmd_buff[5]); } void Enable(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 unk = cmd_buff[1]; // TODO(bunnei): What is this field used for? cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } void GetAppletManInfo(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 unk = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; cmd_buff[3] = 0; cmd_buff[4] = static_cast<u32>(AppID::HomeMenu); // Home menu AppID cmd_buff[5] = static_cast<u32>(AppID::Application); // TODO(purpasmart96): Do this correctly LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X", unk); } void IsRegistered(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 1; // Set to registered LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void InquireNotification(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = static_cast<u32>(SignalType::None); // Signal type LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); } void SendParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 src_app_id = cmd_buff[1]; u32 dst_app_id = cmd_buff[2]; u32 signal_type = cmd_buff[3]; u32 buffer_size = cmd_buff[4]; u32 value = cmd_buff[5]; u32 handle = cmd_buff[6]; u32 size = cmd_buff[7]; u32 in_param_buffer_ptr = cmd_buff[8]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," "buffer_size=0x%08X, value=0x%08X, handle=0x%08X, size=0x%08X, in_param_buffer_ptr=0x%08X", src_app_id, dst_app_id, signal_type, buffer_size, value, handle, size, in_param_buffer_ptr); } void ReceiveParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; u32 buffer_size = cmd_buff[2]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type cmd_buff[4] = 0x10; // Parameter buffer size (16) cmd_buff[5] = 0; cmd_buff[6] = 0; cmd_buff[7] = 0; LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); } void GlanceParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 app_id = cmd_buff[1]; u32 buffer_size = cmd_buff[2]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 0; cmd_buff[3] = static_cast<u32>(SignalType::AppJustStarted); // Signal type cmd_buff[4] = 0x10; // Parameter buffer size (16) cmd_buff[5] = 0; cmd_buff[6] = 0; cmd_buff[7] = 0; LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size); } void CancelParameter(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 flag1 = cmd_buff[1]; u32 unk = cmd_buff[2]; u32 flag2 = cmd_buff[3]; u32 app_id = cmd_buff[4]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = 1; // Set to Success LOG_WARNING(Service_APT, "(STUBBED) called flag1=0x%08X, unk=0x%08X, flag2=0x%08X, app_id=0x%08X", flag1, unk, flag2, app_id); } void PrepareToStartApplication(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 title_info1 = cmd_buff[1]; u32 title_info2 = cmd_buff[2]; u32 title_info3 = cmd_buff[3]; u32 title_info4 = cmd_buff[4]; u32 flags = cmd_buff[5]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X," "title_info4=0x%08X, flags=0x%08X", title_info1, title_info2, title_info3, title_info4, flags); } void StartApplication(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 buffer1_size = cmd_buff[1]; u32 buffer2_size = cmd_buff[2]; u32 flag = cmd_buff[3]; u32 size1 = cmd_buff[4]; u32 buffer1_ptr = cmd_buff[5]; u32 size2 = cmd_buff[6]; u32 buffer2_ptr = cmd_buff[7]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called buffer1_size=0x%08X, buffer2_size=0x%08X, flag=0x%08X," "size1=0x%08X, buffer1_ptr=0x%08X, size2=0x%08X, buffer2_ptr=0x%08X", buffer1_size, buffer2_size, flag, size1, buffer1_ptr, size2, buffer2_ptr); } void AppletUtility(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); // These are from 3dbrew - I'm not really sure what they're used for. u32 unk = cmd_buff[1]; u32 buffer1_size = cmd_buff[2]; u32 buffer2_size = cmd_buff[3]; u32 buffer1_addr = cmd_buff[5]; u32 buffer2_addr = cmd_buff[65]; cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called unk=0x%08X, buffer1_size=0x%08X, buffer2_size=0x%08X, " "buffer1_addr=0x%08X, buffer2_addr=0x%08X", unk, buffer1_size, buffer2_size, buffer1_addr, buffer2_addr); } void SetAppCpuTimeLimit(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 value = cmd_buff[1]; cpu_percent = cmd_buff[2]; if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } cmd_buff[1] = RESULT_SUCCESS.raw; // No error LOG_WARNING(Service_APT, "(STUBBED) called cpu_percent=%u, value=%u", cpu_percent, value); } void GetAppCpuTimeLimit(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); u32 value = cmd_buff[1]; if (value != 1) { LOG_ERROR(Service_APT, "This value should be one, but is actually %u!", value); } cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[2] = cpu_percent; LOG_WARNING(Service_APT, "(STUBBED) called value=%u", value); } void Init() { AddService(new APT_A_Interface); AddService(new APT_S_Interface); AddService(new APT_U_Interface); // Load the shared system font (if available). // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file // "shared_font.bin" in the Citra "sysdata" directory. shared_font.clear(); std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; FileUtil::CreateFullPath(filepath); // Create path if not already created FileUtil::IOFile file(filepath, "rb"); if (file.IsOpen()) { // Read shared font data shared_font.resize((size_t)file.GetSize()); file.ReadBytes(shared_font.data(), (size_t)file.GetSize()); // Create shared font memory object using Kernel::MemoryPermission; shared_font_mem = Kernel::SharedMemory::Create(3 * 1024 * 1024, // 3MB MemoryPermission::ReadWrite, MemoryPermission::Read, "APT_U:shared_font_mem"); } else { LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str()); shared_font_mem = nullptr; } lock = Kernel::Mutex::Create(false, "APT_U:Lock"); cpu_percent = 0; // TODO(bunnei): Check if these are created in Initialize or on APT process startup. notification_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Notification"); start_event = Kernel::Event::Create(RESETTYPE_ONESHOT, "APT_U:Start"); } void Shutdown() { shared_font.clear(); shared_font_mem = nullptr; lock = nullptr; notification_event = nullptr; start_event = nullptr; } } // namespace APT