Beispiel #1
0
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);
}
Beispiel #2
0
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);
}
Beispiel #3
0
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;
}
Beispiel #4
0
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");
}
Beispiel #5
0
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);
}
Beispiel #6
0
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");
}
Beispiel #7
0
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");
}
Beispiel #9
0
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
}
Beispiel #10
0
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();
}
Beispiel #11
0
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);
}
Beispiel #12
0
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);
}
Beispiel #13
0
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;
}
Beispiel #14
0
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");
}
Beispiel #15
0
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);
}
Beispiel #16
0
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
Beispiel #17
0
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], &param, 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
Beispiel #18
0
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
Beispiel #19
0
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
Beispiel #20
0
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
Beispiel #21
0
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;
}
Beispiel #22
0
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
Beispiel #23
0
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();
}
Beispiel #24
0
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
Beispiel #25
0
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