mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-11-03 23:28:48 +00:00 
			
		
		
		
	Merge pull request #3304 from wwylele/hid-new-framework
HID: convert to ServiceFramework
This commit is contained in:
		
						commit
						a66e4585a0
					
				
					 7 changed files with 260 additions and 258 deletions
				
			
		| 
						 | 
				
			
			@ -3,15 +3,12 @@
 | 
			
		|||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <cmath>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/3ds.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/frontend/input.h"
 | 
			
		||||
#include "core/hle/ipc.h"
 | 
			
		||||
#include "core/hle/ipc_helpers.h"
 | 
			
		||||
#include "core/hle/kernel/event.h"
 | 
			
		||||
#include "core/hle/kernel/handle_table.h"
 | 
			
		||||
#include "core/hle/kernel/shared_memory.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -23,27 +20,7 @@
 | 
			
		|||
namespace Service {
 | 
			
		||||
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 CoreTiming::EventType* pad_update_event;
 | 
			
		||||
static CoreTiming::EventType* accelerometer_update_event;
 | 
			
		||||
static CoreTiming::EventType* gyroscope_update_event;
 | 
			
		||||
static std::weak_ptr<Module> current_module;
 | 
			
		||||
 | 
			
		||||
// 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -53,13 +30,6 @@ 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;
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +59,7 @@ DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
 | 
			
		|||
    return state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void LoadInputDevices() {
 | 
			
		||||
void Module::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>);
 | 
			
		||||
| 
						 | 
				
			
			@ -99,16 +69,7 @@ static void LoadInputDevices() {
 | 
			
		|||
    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) {
 | 
			
		||||
void Module::UpdatePadCallback(u64 userdata, int cycles_late) {
 | 
			
		||||
    SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
 | 
			
		||||
 | 
			
		||||
    if (is_device_reload_pending.exchange(false))
 | 
			
		||||
| 
						 | 
				
			
			@ -198,7 +159,7 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) {
 | 
			
		|||
    CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
 | 
			
		||||
void Module::UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
 | 
			
		||||
    SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
 | 
			
		||||
 | 
			
		||||
    mem->accelerometer.index = next_accelerometer_index;
 | 
			
		||||
| 
						 | 
				
			
			@ -240,7 +201,7 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
 | 
			
		|||
    CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
 | 
			
		||||
void Module::UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
 | 
			
		||||
    SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
 | 
			
		||||
 | 
			
		||||
    mem->gyroscope.index = next_gyroscope_index;
 | 
			
		||||
| 
						 | 
				
			
			@ -273,134 +234,122 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
 | 
			
		|||
    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 Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0xA, 0, 0};
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(1, 7);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.PushCopyObjects(hid->shared_mem, hid->event_pad_or_touch_1, hid->event_pad_or_touch_2,
 | 
			
		||||
                       hid->event_accelerometer, hid->event_gyroscope, hid->event_debug_pad);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EnableAccelerometer(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x11, 0, 0};
 | 
			
		||||
 | 
			
		||||
    ++enable_accelerometer_count;
 | 
			
		||||
    ++hid->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);
 | 
			
		||||
    if (hid->enable_accelerometer_count == 1) {
 | 
			
		||||
        CoreTiming::ScheduleEvent(accelerometer_update_ticks, hid->accelerometer_update_event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cmd_buff[1] = RESULT_SUCCESS.raw;
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG(Service_HID, "called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisableAccelerometer(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x12, 0, 0};
 | 
			
		||||
 | 
			
		||||
    --enable_accelerometer_count;
 | 
			
		||||
    --hid->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);
 | 
			
		||||
    if (hid->enable_accelerometer_count == 0) {
 | 
			
		||||
        CoreTiming::UnscheduleEvent(hid->accelerometer_update_event, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cmd_buff[1] = RESULT_SUCCESS.raw;
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG(Service_HID, "called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EnableGyroscopeLow(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x13, 0, 0};
 | 
			
		||||
 | 
			
		||||
    ++enable_gyroscope_count;
 | 
			
		||||
    ++hid->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);
 | 
			
		||||
    if (hid->enable_gyroscope_count == 1) {
 | 
			
		||||
        CoreTiming::ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cmd_buff[1] = RESULT_SUCCESS.raw;
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG(Service_HID, "called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DisableGyroscopeLow(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x14, 0, 0};
 | 
			
		||||
 | 
			
		||||
    --enable_gyroscope_count;
 | 
			
		||||
    --hid->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);
 | 
			
		||||
    if (hid->enable_gyroscope_count == 0) {
 | 
			
		||||
        CoreTiming::UnscheduleEvent(hid->gyroscope_update_event, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    cmd_buff[1] = RESULT_SUCCESS.raw;
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG(Service_HID, "called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x15, 0, 0};
 | 
			
		||||
 | 
			
		||||
    cmd_buff[1] = RESULT_SUCCESS.raw;
 | 
			
		||||
 | 
			
		||||
    f32 coef = gyroscope_coef;
 | 
			
		||||
    memcpy(&cmd_buff[2], &coef, 4);
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.PushRaw<f32>(gyroscope_coef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GetGyroscopeLowCalibrateParam(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x16, 0, 0};
 | 
			
		||||
 | 
			
		||||
    cmd_buff[1] = RESULT_SUCCESS.raw;
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 | 
			
		||||
    const s16 param_unit = 6700; // an approximate value taken from hw
 | 
			
		||||
    GyroscopeCalibrateParam param = {
 | 
			
		||||
        {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit},
 | 
			
		||||
    };
 | 
			
		||||
    memcpy(&cmd_buff[2], ¶m, sizeof(param));
 | 
			
		||||
    rb.PushRaw(param);
 | 
			
		||||
 | 
			
		||||
    LOG_WARNING(Service_HID, "(STUBBED) called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GetSoundVolume(Service::Interface* self) {
 | 
			
		||||
    u32* cmd_buff = Kernel::GetCommandBuffer();
 | 
			
		||||
void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x17, 0, 0};
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.Push(volume);
 | 
			
		||||
 | 
			
		||||
    LOG_WARNING(Service_HID, "(STUBBED) called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Init() {
 | 
			
		||||
Module::Interface::Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session)
 | 
			
		||||
    : ServiceFramework(name, max_session), hid(std::move(hid)) {}
 | 
			
		||||
 | 
			
		||||
Module::Module() {
 | 
			
		||||
    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;
 | 
			
		||||
                             0, MemoryRegion::BASE, "HID:SharedMemory");
 | 
			
		||||
 | 
			
		||||
    // Create event handles
 | 
			
		||||
    event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1");
 | 
			
		||||
| 
						 | 
				
			
			@ -410,27 +359,35 @@ void Init() {
 | 
			
		|||
    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);
 | 
			
		||||
    pad_update_event =
 | 
			
		||||
        CoreTiming::RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) {
 | 
			
		||||
            UpdatePadCallback(userdata, cycles_late);
 | 
			
		||||
        });
 | 
			
		||||
    accelerometer_update_event = CoreTiming::RegisterEvent(
 | 
			
		||||
        "HID::UpdateAccelerometerCallback", [this](u64 userdata, int cycles_late) {
 | 
			
		||||
            UpdateAccelerometerCallback(userdata, cycles_late);
 | 
			
		||||
        });
 | 
			
		||||
    gyroscope_update_event = CoreTiming::RegisterEvent(
 | 
			
		||||
        "HID::UpdateGyroscopeCallback",
 | 
			
		||||
        [this](u64 userdata, int cycles_late) { UpdateGyroscopeCallback(userdata, cycles_late); });
 | 
			
		||||
 | 
			
		||||
    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 Module::ReloadInputDevices() {
 | 
			
		||||
    is_device_reload_pending.store(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ReloadInputDevices() {
 | 
			
		||||
    is_device_reload_pending.store(true);
 | 
			
		||||
    if (auto hid = current_module.lock())
 | 
			
		||||
        hid->ReloadInputDevices();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
 | 
			
		||||
    auto hid = std::make_shared<Module>();
 | 
			
		||||
    std::make_shared<User>(hid)->InstallAsService(service_manager);
 | 
			
		||||
    std::make_shared<Spvr>(hid)->InstallAsService(service_manager);
 | 
			
		||||
    current_module = hid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace HID
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,17 +5,29 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#ifndef _MSC_VER
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#endif
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include "common/bit_field.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/frontend/input.h"
 | 
			
		||||
#include "core/hle/kernel/kernel.h"
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
 | 
			
		||||
namespace Service {
 | 
			
		||||
namespace Kernel {
 | 
			
		||||
class Event;
 | 
			
		||||
class SharedMemory;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Interface;
 | 
			
		||||
namespace CoreTiming {
 | 
			
		||||
class EventType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
namespace Service {
 | 
			
		||||
 | 
			
		||||
namespace HID {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -186,93 +198,140 @@ struct DirectionState {
 | 
			
		|||
/// Translates analog stick axes to directions. This is exposed for ir_rst module to use.
 | 
			
		||||
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::GetIPCHandles service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 *      2 : IPC Command Structure translate-header
 | 
			
		||||
 *      3 : Handle to HID shared memory
 | 
			
		||||
 *      4 : Event signaled by HID
 | 
			
		||||
 *      5 : Event signaled by HID
 | 
			
		||||
 *      6 : Event signaled by HID
 | 
			
		||||
 *      7 : Gyroscope event
 | 
			
		||||
 *      8 : Event signaled by HID
 | 
			
		||||
 */
 | 
			
		||||
void GetIPCHandles(Interface* self);
 | 
			
		||||
class Module final {
 | 
			
		||||
public:
 | 
			
		||||
    Module();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::EnableAccelerometer service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 */
 | 
			
		||||
void EnableAccelerometer(Interface* self);
 | 
			
		||||
    class Interface : public ServiceFramework<Interface> {
 | 
			
		||||
    public:
 | 
			
		||||
        Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::DisableAccelerometer service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 */
 | 
			
		||||
void DisableAccelerometer(Interface* self);
 | 
			
		||||
    protected:
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::GetIPCHandles service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         *      2 : IPC Command Structure translate-header
 | 
			
		||||
         *      3 : Handle to HID shared memory
 | 
			
		||||
         *      4 : Event signaled by HID
 | 
			
		||||
         *      5 : Event signaled by HID
 | 
			
		||||
         *      6 : Event signaled by HID
 | 
			
		||||
         *      7 : Gyroscope event
 | 
			
		||||
         *      8 : Event signaled by HID
 | 
			
		||||
         */
 | 
			
		||||
        void GetIPCHandles(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::EnableGyroscopeLow service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 */
 | 
			
		||||
void EnableGyroscopeLow(Interface* self);
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::EnableAccelerometer service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         */
 | 
			
		||||
        void EnableAccelerometer(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::DisableGyroscopeLow service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 */
 | 
			
		||||
void DisableGyroscopeLow(Interface* self);
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::DisableAccelerometer service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         */
 | 
			
		||||
        void DisableAccelerometer(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::GetSoundVolume service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 *      2 : u8 output value
 | 
			
		||||
 */
 | 
			
		||||
void GetSoundVolume(Interface* self);
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::EnableGyroscopeLow service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         */
 | 
			
		||||
        void EnableGyroscopeLow(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::GetGyroscopeLowRawToDpsCoefficient service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 *      2 : float output value
 | 
			
		||||
 */
 | 
			
		||||
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self);
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::DisableGyroscopeLow service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         */
 | 
			
		||||
        void DisableGyroscopeLow(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID::GetGyroscopeLowCalibrateParam service function
 | 
			
		||||
 *  Inputs:
 | 
			
		||||
 *      None
 | 
			
		||||
 *  Outputs:
 | 
			
		||||
 *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
 *      2~6 (18 bytes) : struct GyroscopeCalibrateParam
 | 
			
		||||
 */
 | 
			
		||||
void GetGyroscopeLowCalibrateParam(Service::Interface* self);
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::GetSoundVolume service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         *      2 : u8 output value
 | 
			
		||||
         */
 | 
			
		||||
        void GetSoundVolume(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/// Initialize HID service
 | 
			
		||||
void Init();
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::GetGyroscopeLowRawToDpsCoefficient service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         *      2 : float output value
 | 
			
		||||
         */
 | 
			
		||||
        void GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
/// Shutdown HID service
 | 
			
		||||
void Shutdown();
 | 
			
		||||
        /**
 | 
			
		||||
         * HID::GetGyroscopeLowCalibrateParam service function
 | 
			
		||||
         *  Inputs:
 | 
			
		||||
         *      None
 | 
			
		||||
         *  Outputs:
 | 
			
		||||
         *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
         *      2~6 (18 bytes) : struct GyroscopeCalibrateParam
 | 
			
		||||
         */
 | 
			
		||||
        void GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        std::shared_ptr<Module> hid;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    void ReloadInputDevices();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void LoadInputDevices();
 | 
			
		||||
    void UpdatePadCallback(u64 userdata, int cycles_late);
 | 
			
		||||
    void UpdateAccelerometerCallback(u64 userdata, int cycles_late);
 | 
			
		||||
    void UpdateGyroscopeCallback(u64 userdata, int cycles_late);
 | 
			
		||||
 | 
			
		||||
    // Handle to shared memory region designated to HID_User service
 | 
			
		||||
    Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
 | 
			
		||||
 | 
			
		||||
    // Event handles
 | 
			
		||||
    Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1;
 | 
			
		||||
    Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2;
 | 
			
		||||
    Kernel::SharedPtr<Kernel::Event> event_accelerometer;
 | 
			
		||||
    Kernel::SharedPtr<Kernel::Event> event_gyroscope;
 | 
			
		||||
    Kernel::SharedPtr<Kernel::Event> event_debug_pad;
 | 
			
		||||
 | 
			
		||||
    u32 next_pad_index = 0;
 | 
			
		||||
    u32 next_touch_index = 0;
 | 
			
		||||
    u32 next_accelerometer_index = 0;
 | 
			
		||||
    u32 next_gyroscope_index = 0;
 | 
			
		||||
 | 
			
		||||
    int enable_accelerometer_count = 0; // positive means enabled
 | 
			
		||||
    int enable_gyroscope_count = 0;     // positive means enabled
 | 
			
		||||
 | 
			
		||||
    CoreTiming::EventType* pad_update_event;
 | 
			
		||||
    CoreTiming::EventType* accelerometer_update_event;
 | 
			
		||||
    CoreTiming::EventType* gyroscope_update_event;
 | 
			
		||||
 | 
			
		||||
    std::atomic<bool> is_device_reload_pending{true};
 | 
			
		||||
    std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
 | 
			
		||||
        buttons;
 | 
			
		||||
    std::unique_ptr<Input::AnalogDevice> circle_pad;
 | 
			
		||||
    std::unique_ptr<Input::MotionDevice> motion_device;
 | 
			
		||||
    std::unique_ptr<Input::TouchDevice> touch_device;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void InstallInterfaces(SM::ServiceManager& service_manager);
 | 
			
		||||
 | 
			
		||||
/// Reload input devices. Used when input configuration changed
 | 
			
		||||
void ReloadInputDevices();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,27 +2,26 @@
 | 
			
		|||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "core/hle/service/hid/hid.h"
 | 
			
		||||
#include "core/hle/service/hid/hid_spvr.h"
 | 
			
		||||
 | 
			
		||||
namespace Service {
 | 
			
		||||
namespace HID {
 | 
			
		||||
 | 
			
		||||
const Interface::FunctionInfo FunctionTable[] = {
 | 
			
		||||
    {0x000A0000, GetIPCHandles, "GetIPCHandles"},
 | 
			
		||||
    {0x000B0000, nullptr, "StartAnalogStickCalibration"},
 | 
			
		||||
    {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
 | 
			
		||||
    {0x00110000, EnableAccelerometer, "EnableAccelerometer"},
 | 
			
		||||
    {0x00120000, DisableAccelerometer, "DisableAccelerometer"},
 | 
			
		||||
    {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"},
 | 
			
		||||
    {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"},
 | 
			
		||||
    {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"},
 | 
			
		||||
    {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
 | 
			
		||||
    {0x00170000, GetSoundVolume, "GetSoundVolume"},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
HID_SPVR_Interface::HID_SPVR_Interface() {
 | 
			
		||||
    Register(FunctionTable);
 | 
			
		||||
Spvr::Spvr(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:SPVR", 6) {
 | 
			
		||||
    static const FunctionInfo functions[] = {
 | 
			
		||||
        {0x000A0000, &Spvr::GetIPCHandles, "GetIPCHandles"},
 | 
			
		||||
        {0x000B0000, nullptr, "StartAnalogStickCalibration"},
 | 
			
		||||
        {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
 | 
			
		||||
        {0x00110000, &Spvr::EnableAccelerometer, "EnableAccelerometer"},
 | 
			
		||||
        {0x00120000, &Spvr::DisableAccelerometer, "DisableAccelerometer"},
 | 
			
		||||
        {0x00130000, &Spvr::EnableGyroscopeLow, "EnableGyroscopeLow"},
 | 
			
		||||
        {0x00140000, &Spvr::DisableGyroscopeLow, "DisableGyroscopeLow"},
 | 
			
		||||
        {0x00150000, &Spvr::GetGyroscopeLowRawToDpsCoefficient,
 | 
			
		||||
         "GetGyroscopeLowRawToDpsCoefficient"},
 | 
			
		||||
        {0x00160000, &Spvr::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
 | 
			
		||||
        {0x00170000, &Spvr::GetSoundVolume, "GetSoundVolume"},
 | 
			
		||||
    };
 | 
			
		||||
    RegisterHandlers(functions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace HID
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,19 +4,15 @@
 | 
			
		|||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
#include "core/hle/service/hid/hid.h"
 | 
			
		||||
 | 
			
		||||
namespace Service {
 | 
			
		||||
namespace HID {
 | 
			
		||||
 | 
			
		||||
class HID_SPVR_Interface : public Service::Interface {
 | 
			
		||||
class Spvr final : public Module::Interface {
 | 
			
		||||
public:
 | 
			
		||||
    HID_SPVR_Interface();
 | 
			
		||||
 | 
			
		||||
    std::string GetPortName() const override {
 | 
			
		||||
        return "hid:SPVR";
 | 
			
		||||
    }
 | 
			
		||||
    explicit Spvr(std::shared_ptr<Module> hid);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace HID
 | 
			
		||||
} // namespace Service
 | 
			
		||||
} // namespace Service
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,27 +2,26 @@
 | 
			
		|||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "core/hle/service/hid/hid.h"
 | 
			
		||||
#include "core/hle/service/hid/hid_user.h"
 | 
			
		||||
 | 
			
		||||
namespace Service {
 | 
			
		||||
namespace HID {
 | 
			
		||||
 | 
			
		||||
const Interface::FunctionInfo FunctionTable[] = {
 | 
			
		||||
    {0x000A0000, GetIPCHandles, "GetIPCHandles"},
 | 
			
		||||
    {0x000B0000, nullptr, "StartAnalogStickCalibration"},
 | 
			
		||||
    {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
 | 
			
		||||
    {0x00110000, EnableAccelerometer, "EnableAccelerometer"},
 | 
			
		||||
    {0x00120000, DisableAccelerometer, "DisableAccelerometer"},
 | 
			
		||||
    {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"},
 | 
			
		||||
    {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"},
 | 
			
		||||
    {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"},
 | 
			
		||||
    {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
 | 
			
		||||
    {0x00170000, GetSoundVolume, "GetSoundVolume"},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
HID_U_Interface::HID_U_Interface() {
 | 
			
		||||
    Register(FunctionTable);
 | 
			
		||||
User::User(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:USER", 6) {
 | 
			
		||||
    static const FunctionInfo functions[] = {
 | 
			
		||||
        {0x000A0000, &User::GetIPCHandles, "GetIPCHandles"},
 | 
			
		||||
        {0x000B0000, nullptr, "StartAnalogStickCalibration"},
 | 
			
		||||
        {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
 | 
			
		||||
        {0x00110000, &User::EnableAccelerometer, "EnableAccelerometer"},
 | 
			
		||||
        {0x00120000, &User::DisableAccelerometer, "DisableAccelerometer"},
 | 
			
		||||
        {0x00130000, &User::EnableGyroscopeLow, "EnableGyroscopeLow"},
 | 
			
		||||
        {0x00140000, &User::DisableGyroscopeLow, "DisableGyroscopeLow"},
 | 
			
		||||
        {0x00150000, &User::GetGyroscopeLowRawToDpsCoefficient,
 | 
			
		||||
         "GetGyroscopeLowRawToDpsCoefficient"},
 | 
			
		||||
        {0x00160000, &User::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
 | 
			
		||||
        {0x00170000, &User::GetSoundVolume, "GetSoundVolume"},
 | 
			
		||||
    };
 | 
			
		||||
    RegisterHandlers(functions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace HID
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
#include "core/hle/service/hid/hid.h"
 | 
			
		||||
 | 
			
		||||
// This service is used for interfacing to physical user controls.
 | 
			
		||||
// Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad.
 | 
			
		||||
| 
						 | 
				
			
			@ -12,17 +12,10 @@
 | 
			
		|||
namespace Service {
 | 
			
		||||
namespace HID {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * HID service interface.
 | 
			
		||||
 */
 | 
			
		||||
class HID_U_Interface : public Service::Interface {
 | 
			
		||||
class User final : public Module::Interface {
 | 
			
		||||
public:
 | 
			
		||||
    HID_U_Interface();
 | 
			
		||||
 | 
			
		||||
    std::string GetPortName() const override {
 | 
			
		||||
        return "hid:USER";
 | 
			
		||||
    }
 | 
			
		||||
    explicit User(std::shared_ptr<Module> hid);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace HID
 | 
			
		||||
} // namespace Service
 | 
			
		||||
} // namespace Service
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -277,7 +277,7 @@ void Init() {
 | 
			
		|||
    DLP::Init();
 | 
			
		||||
    FRD::Init();
 | 
			
		||||
    GSP::InstallInterfaces(*SM::g_service_manager);
 | 
			
		||||
    HID::Init();
 | 
			
		||||
    HID::InstallInterfaces(*SM::g_service_manager);
 | 
			
		||||
    IR::InstallInterfaces(*SM::g_service_manager);
 | 
			
		||||
    MVD::Init();
 | 
			
		||||
    NDM::Init();
 | 
			
		||||
| 
						 | 
				
			
			@ -307,7 +307,6 @@ void Shutdown() {
 | 
			
		|||
    NIM::Shutdown();
 | 
			
		||||
    NEWS::Shutdown();
 | 
			
		||||
    NDM::Shutdown();
 | 
			
		||||
    HID::Shutdown();
 | 
			
		||||
    FRD::Shutdown();
 | 
			
		||||
    DLP::Shutdown();
 | 
			
		||||
    CFG::Shutdown();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue