summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/service/ir/extra_hid.cpp231
-rw-r--r--src/core/hle/service/ir/extra_hid.h48
-rw-r--r--src/core/hle/service/ir/ir_user.cpp489
-rw-r--r--src/core/hle/service/ir/ir_user.h33
-rw-r--r--src/core/settings.cpp2
6 files changed, 761 insertions, 44 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 61a0b1cc3..a2866fdd8 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -109,6 +109,7 @@ set(SRCS
109 hle/service/hid/hid_spvr.cpp 109 hle/service/hid/hid_spvr.cpp
110 hle/service/hid/hid_user.cpp 110 hle/service/hid/hid_user.cpp
111 hle/service/http_c.cpp 111 hle/service/http_c.cpp
112 hle/service/ir/extra_hid.cpp
112 hle/service/ir/ir.cpp 113 hle/service/ir/ir.cpp
113 hle/service/ir/ir_rst.cpp 114 hle/service/ir/ir_rst.cpp
114 hle/service/ir/ir_u.cpp 115 hle/service/ir/ir_u.cpp
@@ -297,6 +298,7 @@ set(HEADERS
297 hle/service/hid/hid_spvr.h 298 hle/service/hid/hid_spvr.h
298 hle/service/hid/hid_user.h 299 hle/service/hid/hid_user.h
299 hle/service/http_c.h 300 hle/service/http_c.h
301 hle/service/ir/extra_hid.h
300 hle/service/ir/ir.h 302 hle/service/ir/ir.h
301 hle/service/ir/ir_rst.h 303 hle/service/ir/ir_rst.h
302 hle/service/ir/ir_u.h 304 hle/service/ir/ir_u.h
diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp
new file mode 100644
index 000000000..e7acc17a5
--- /dev/null
+++ b/src/core/hle/service/ir/extra_hid.cpp
@@ -0,0 +1,231 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/alignment.h"
6#include "common/bit_field.h"
7#include "common/string_util.h"
8#include "core/core_timing.h"
9#include "core/hle/service/ir/extra_hid.h"
10#include "core/settings.h"
11
12namespace Service {
13namespace IR {
14
15enum class RequestID : u8 {
16 /**
17 * ConfigureHIDPolling request
18 * Starts HID input polling, or changes the polling interval if it is already started.
19 * Inputs:
20 * byte 0: request ID
21 * byte 1: polling interval in ms
22 * byte 2: unknown
23 */
24 ConfigureHIDPolling = 1,
25
26 /**
27 * ReadCalibrationData request
28 * Reads the calibration data stored in circle pad pro.
29 * Inputs:
30 * byte 0: request ID
31 * byte 1: expected response time in ms?
32 * byte 2-3: data offset (aligned to 0x10)
33 * byte 4-5: data size (aligned to 0x10)
34 */
35 ReadCalibrationData = 2,
36
37 // TODO(wwylele): there are three more request types (id = 3, 4 and 5)
38};
39
40enum class ResponseID : u8 {
41
42 /**
43 * PollHID response
44 * Sends current HID status
45 * Output:
46 * byte 0: response ID
47 * byte 1-3: Right circle pad position. This three bytes are two little-endian 12-bit
48 * fields. The first one is for x-axis and the second one is for y-axis.
49 * byte 4: bit[0:4] battery level; bit[5] ZL button; bit[6] ZR button; bit[7] R button
50 * Note that for the three button fields, the bit is set when the button is NOT pressed.
51 * byte 5: unknown
52 */
53 PollHID = 0x10,
54
55 /**
56 * ReadCalibrationData response
57 * Sends the calibration data reads from circle pad pro.
58 * Output:
59 * byte 0: resonse ID
60 * byte 1-2: data offset (aligned to 0x10)
61 * byte 3-4: data size (aligned to 0x10)
62 * byte 5-...: calibration data
63 */
64 ReadCalibrationData = 0x11,
65};
66
67ExtraHID::ExtraHID(SendFunc send_func) : IRDevice(send_func) {
68 LoadInputDevices();
69
70 // The data below was retrieved from a New 3DS
71 // TODO(wwylele): this data is probably writable (via request 3?) and thus should be saved to
72 // and loaded from somewhere.
73 calibration_data = std::array<u8, 0x40>{{
74 // 0x00
75 0x00, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F,
76 // 0x08
77 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0xF5,
78 // 0x10
79 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F,
80 // 0x18
81 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65,
82 // 0x20
83 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F,
84 // 0x28
85 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65,
86 // 0x30
87 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F,
88 // 0x38
89 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65,
90 }};
91
92 hid_polling_callback_id =
93 CoreTiming::RegisterEvent("ExtraHID::SendHIDStatus", [this](u64, int cycles_late) {
94 SendHIDStatus();
95 CoreTiming::ScheduleEvent(msToCycles(hid_period) - cycles_late,
96 hid_polling_callback_id);
97 });
98}
99
100ExtraHID::~ExtraHID() {
101 OnDisconnect();
102}
103
104void ExtraHID::OnConnect() {}
105
106void ExtraHID::OnDisconnect() {
107 CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0);
108}
109
110void ExtraHID::HandleConfigureHIDPollingRequest(const std::vector<u8>& request) {
111 if (request.size() != 3) {
112 LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request.size(),
113 Common::ArrayToString(request.data(), request.size()).c_str());
114 return;
115 }
116
117 // Change HID input polling interval
118 CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0);
119 hid_period = request[1];
120 CoreTiming::ScheduleEvent(msToCycles(hid_period), hid_polling_callback_id);
121}
122
123void ExtraHID::HandleReadCalibrationDataRequest(const std::vector<u8>& request_buf) {
124 struct ReadCalibrationDataRequest {
125 RequestID request_id;
126 u8 expected_response_time;
127 u16_le offset;
128 u16_le size;
129 };
130 static_assert(sizeof(ReadCalibrationDataRequest) == 6,
131 "ReadCalibrationDataRequest has wrong size");
132
133 if (request_buf.size() != sizeof(ReadCalibrationDataRequest)) {
134 LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request_buf.size(),
135 Common::ArrayToString(request_buf.data(), request_buf.size()).c_str());
136 return;
137 }
138
139 ReadCalibrationDataRequest request;
140 std::memcpy(&request, request_buf.data(), sizeof(request));
141
142 const u16 offset = Common::AlignDown(request.offset, 16);
143 const u16 size = Common::AlignDown(request.size, 16);
144
145 if (offset + size > calibration_data.size()) {
146 LOG_ERROR(Service_IR, "Read beyond the end of calibration data! (offset=%u, size=%u)",
147 offset, size);
148 return;
149 }
150
151 std::vector<u8> response(5);
152 response[0] = static_cast<u8>(ResponseID::ReadCalibrationData);
153 std::memcpy(&response[1], &request.offset, sizeof(request.offset));
154 std::memcpy(&response[3], &request.size, sizeof(request.size));
155 response.insert(response.end(), calibration_data.begin() + offset,
156 calibration_data.begin() + offset + size);
157 Send(response);
158}
159
160void ExtraHID::OnReceive(const std::vector<u8>& data) {
161 switch (static_cast<RequestID>(data[0])) {
162 case RequestID::ConfigureHIDPolling:
163 HandleConfigureHIDPollingRequest(data);
164 break;
165 case RequestID::ReadCalibrationData:
166 HandleReadCalibrationDataRequest(data);
167 break;
168 default:
169 LOG_ERROR(Service_IR, "Unknown request: %s",
170 Common::ArrayToString(data.data(), data.size()).c_str());
171 break;
172 }
173}
174
175void ExtraHID::SendHIDStatus() {
176 if (is_device_reload_pending.exchange(false))
177 LoadInputDevices();
178
179 struct {
180 union {
181 BitField<0, 8, u32_le> header;
182 BitField<8, 12, u32_le> c_stick_x;
183 BitField<20, 12, u32_le> c_stick_y;
184 } c_stick;
185 union {
186 BitField<0, 5, u8> battery_level;
187 BitField<5, 1, u8> zl_not_held;
188 BitField<6, 1, u8> zr_not_held;
189 BitField<7, 1, u8> r_not_held;
190 } buttons;
191 u8 unknown;
192 } response;
193 static_assert(sizeof(response) == 6, "HID status response has wrong size!");
194
195 constexpr int C_STICK_CENTER = 0x800;
196 // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can
197 // take values in the whole range of a 12-bit integer.
198 constexpr int C_STICK_RADIUS = 0x7FF;
199
200 float x, y;
201 std::tie(x, y) = c_stick->GetStatus();
202
203 response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
204 response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
205 response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
206 response.buttons.battery_level.Assign(0x1F);
207 response.buttons.zl_not_held.Assign(!zl->GetStatus());
208 response.buttons.zr_not_held.Assign(!zr->GetStatus());
209 response.buttons.r_not_held.Assign(1);
210 response.unknown = 0;
211
212 std::vector<u8> response_buffer(sizeof(response));
213 memcpy(response_buffer.data(), &response, sizeof(response));
214 Send(response_buffer);
215}
216
217void ExtraHID::RequestInputDevicesReload() {
218 is_device_reload_pending.store(true);
219}
220
221void ExtraHID::LoadInputDevices() {
222 zl = Input::CreateDevice<Input::ButtonDevice>(
223 Settings::values.buttons[Settings::NativeButton::ZL]);
224 zr = Input::CreateDevice<Input::ButtonDevice>(
225 Settings::values.buttons[Settings::NativeButton::ZR]);
226 c_stick = Input::CreateDevice<Input::AnalogDevice>(
227 Settings::values.analogs[Settings::NativeAnalog::CStick]);
228}
229
230} // namespace IR
231} // namespace Service
diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h
new file mode 100644
index 000000000..a2459a73a
--- /dev/null
+++ b/src/core/hle/service/ir/extra_hid.h
@@ -0,0 +1,48 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <atomic>
9#include "core/frontend/input.h"
10#include "core/hle/service/ir/ir_user.h"
11
12namespace Service {
13namespace IR {
14
15/**
16 * An IRDevice emulating Circle Pad Pro or New 3DS additional HID hardware.
17 * This device sends periodic udates at a rate configured by the 3DS, and sends calibration data if
18 * requested.
19 */
20class ExtraHID final : public IRDevice {
21public:
22 explicit ExtraHID(SendFunc send_func);
23 ~ExtraHID();
24
25 void OnConnect() override;
26 void OnDisconnect() override;
27 void OnReceive(const std::vector<u8>& data) override;
28
29 /// Requests input devices reload from current settings. Called when the input settings change.
30 void RequestInputDevicesReload();
31
32private:
33 void SendHIDStatus();
34 void HandleConfigureHIDPollingRequest(const std::vector<u8>& request);
35 void HandleReadCalibrationDataRequest(const std::vector<u8>& request);
36 void LoadInputDevices();
37
38 u8 hid_period;
39 int hid_polling_callback_id;
40 std::array<u8, 0x40> calibration_data;
41 std::unique_ptr<Input::ButtonDevice> zl;
42 std::unique_ptr<Input::ButtonDevice> zr;
43 std::unique_ptr<Input::AnalogDevice> c_stick;
44 std::atomic<bool> is_device_reload_pending;
45};
46
47} // namespace IR
48} // namespace Service
diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp
index b326d7fc7..bccf6bce7 100644
--- a/src/core/hle/service/ir/ir_user.cpp
+++ b/src/core/hle/service/ir/ir_user.cpp
@@ -2,110 +2,481 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <memory>
6#include <boost/crc.hpp>
7#include <boost/optional.hpp>
8#include "common/string_util.h"
9#include "common/swap.h"
5#include "core/hle/kernel/event.h" 10#include "core/hle/kernel/event.h"
6#include "core/hle/kernel/shared_memory.h" 11#include "core/hle/kernel/shared_memory.h"
12#include "core/hle/service/ir/extra_hid.h"
7#include "core/hle/service/ir/ir.h" 13#include "core/hle/service/ir/ir.h"
8#include "core/hle/service/ir/ir_user.h" 14#include "core/hle/service/ir/ir_user.h"
9 15
10namespace Service { 16namespace Service {
11namespace IR { 17namespace IR {
12 18
13static Kernel::SharedPtr<Kernel::Event> conn_status_event; 19// This is a header that will present in the ir:USER shared memory if it is initialized with
14static Kernel::SharedPtr<Kernel::SharedMemory> transfer_shared_memory; 20// InitializeIrNopShared service function. Otherwise the shared memory doesn't have this header if
21// it is initialized with InitializeIrNop service function.
22struct SharedMemoryHeader {
23 u32_le latest_receive_error_result;
24 u32_le latest_send_error_result;
25 // TODO(wwylele): for these fields below, make them enum when the meaning of values is known.
26 u8 connection_status;
27 u8 trying_to_connect_status;
28 u8 connection_role;
29 u8 machine_id;
30 u8 connected;
31 u8 network_id;
32 u8 initialized;
33 u8 unknown;
34
35 // This is not the end of the shared memory. It is followed by a receive buffer and a send
36 // buffer. We handle receive buffer in the BufferManager class. For the send buffer, because
37 // games usually don't access it, we don't emulate it.
38};
39static_assert(sizeof(SharedMemoryHeader) == 16, "SharedMemoryHeader has wrong size!");
40
41/**
42 * A manager of the send/receive buffers in the shared memory. Currently it is only used for the
43 * receive buffer.
44 *
45 * A buffer consists of three parts:
46 * - BufferInfo: stores available count of packets, and their position in the PacketInfo
47 * circular queue.
48 * - PacketInfo circular queue: stores the position of each avaiable packets in the Packet data
49 * buffer. Each entry is a pair of {offset, size}.
50 * - Packet data circular buffer: stores the actual data of packets.
51 *
52 * IR packets can be put into and get from the buffer.
53 *
54 * When a new packet is put into the buffer, its data is put into the data circular buffer,
55 * following the end of previous packet data. A new entry is also added to the PacketInfo circular
56 * queue pointing to the added packet data. Then BufferInfo is updated.
57 *
58 * Packets can be released from the other end of the buffer. When releasing a packet, the front
59 * entry in thePacketInfo circular queue is removed, and as a result the corresponding memory in the
60 * data circular buffer is also released. BufferInfo is updated as well.
61 *
62 * The client application usually has a similar manager constructed over the same shared memory
63 * region, performing the same put/get/release operation. This way the client and the service
64 * communicate via a pair of manager of the same buffer.
65 *
66 * TODO(wwylele): implement Get function, which is used by ReceiveIrnop service function.
67 */
68class BufferManager {
69public:
70 BufferManager(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_, u32 info_offset_,
71 u32 buffer_offset_, u32 max_packet_count_, u32 buffer_size)
72 : shared_memory(shared_memory_), info_offset(info_offset_), buffer_offset(buffer_offset_),
73 max_packet_count(max_packet_count_),
74 max_data_size(buffer_size - sizeof(PacketInfo) * max_packet_count_) {
75 UpdateBufferInfo();
76 }
77
78 /**
79 * Puts a packet to the head of the buffer.
80 * @params packet The data of the packet to put.
81 * @returns whether the operation is successful.
82 */
83 bool Put(const std::vector<u8>& packet) {
84 if (info.packet_count == max_packet_count)
85 return false;
86
87 u32 write_offset;
88
89 // finds free space offset in data buffer
90 if (info.packet_count == 0) {
91 write_offset = 0;
92 if (packet.size() > max_data_size)
93 return false;
94 } else {
95 const u32 last_index = (info.end_index + max_packet_count - 1) % max_packet_count;
96 const PacketInfo first = GetPacketInfo(info.begin_index);
97 const PacketInfo last = GetPacketInfo(last_index);
98 write_offset = (last.offset + last.size) % max_data_size;
99 const u32 free_space = (first.offset + max_data_size - write_offset) % max_data_size;
100 if (packet.size() > free_space)
101 return false;
102 }
103
104 // writes packet info
105 PacketInfo packet_info{write_offset, static_cast<u32>(packet.size())};
106 SetPacketInfo(info.end_index, packet_info);
107
108 // writes packet data
109 for (size_t i = 0; i < packet.size(); ++i) {
110 *GetDataBufferPointer((write_offset + i) % max_data_size) = packet[i];
111 }
112
113 // updates buffer info
114 info.end_index++;
115 info.end_index %= max_packet_count;
116 info.packet_count++;
117 UpdateBufferInfo();
118 return true;
119 }
120
121 /**
122 * Release packets from the tail of the buffer
123 * @params count Numbers of packets to release.
124 * @returns whether the operation is successful.
125 */
126 bool Release(u32 count) {
127 if (info.packet_count < count)
128 return false;
129
130 info.packet_count -= count;
131 info.begin_index += count;
132 info.begin_index %= max_packet_count;
133 UpdateBufferInfo();
134 return true;
135 }
136
137private:
138 struct BufferInfo {
139 u32_le begin_index;
140 u32_le end_index;
141 u32_le packet_count;
142 u32_le unknown;
143 };
144 static_assert(sizeof(BufferInfo) == 16, "BufferInfo has wrong size!");
145
146 struct PacketInfo {
147 u32_le offset;
148 u32_le size;
149 };
150 static_assert(sizeof(PacketInfo) == 8, "PacketInfo has wrong size!");
151
152 u8* GetPacketInfoPointer(u32 index) {
153 return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * index);
154 }
155
156 void SetPacketInfo(u32 index, const PacketInfo& packet_info) {
157 memcpy(GetPacketInfoPointer(index), &packet_info, sizeof(PacketInfo));
158 }
159
160 PacketInfo GetPacketInfo(u32 index) {
161 PacketInfo packet_info;
162 memcpy(&packet_info, GetPacketInfoPointer(index), sizeof(PacketInfo));
163 return packet_info;
164 }
165
166 u8* GetDataBufferPointer(u32 offset) {
167 return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * max_packet_count +
168 offset);
169 }
170
171 void UpdateBufferInfo() {
172 if (info_offset) {
173 memcpy(shared_memory->GetPointer(info_offset), &info, sizeof(info));
174 }
175 }
176
177 BufferInfo info{0, 0, 0, 0};
178 Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
179 u32 info_offset;
180 u32 buffer_offset;
181 u32 max_packet_count;
182 u32 max_data_size;
183};
184
185static Kernel::SharedPtr<Kernel::Event> conn_status_event, send_event, receive_event;
186static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
187static std::unique_ptr<ExtraHID> extra_hid;
188static IRDevice* connected_device;
189static boost::optional<BufferManager> receive_buffer;
190
191/// Wraps the payload into packet and puts it to the receive buffer
192static void PutToReceive(const std::vector<u8>& payload) {
193 LOG_TRACE(Service_IR, "called, data=%s",
194 Common::ArrayToString(payload.data(), payload.size()).c_str());
195 size_t size = payload.size();
196
197 std::vector<u8> packet;
198
199 // Builds packet header. For the format info:
200 // https://www.3dbrew.org/wiki/IRUSER_Shared_Memory#Packet_structure
201
202 // fixed value
203 packet.push_back(0xA5);
204 // destination network ID
205 u8 network_id = *(shared_memory->GetPointer(offsetof(SharedMemoryHeader, network_id)));
206 packet.push_back(network_id);
207
208 // puts the size info.
209 // The highest bit of the first byte is unknown, which is set to zero here. The second highest
210 // bit is a flag that determines whether the size info is in extended form. If the packet size
211 // can be represent within 6 bits, the short form (1 byte) of size info is chosen, the size is
212 // put to the lower bits of this byte, and the flag is clear. If the packet size cannot be
213 // represent within 6 bits, the extended form (2 bytes) is chosen, the lower 8 bits of the size
214 // is put to the second byte, the higher bits of the size is put to the lower bits of the first
215 // byte, and the flag is set. Note that the packet size must be within 14 bits due to this
216 // format restriction, or it will overlap with the flag bit.
217 if (size < 0x40) {
218 packet.push_back(static_cast<u8>(size));
219 } else if (size < 0x4000) {
220 packet.push_back(static_cast<u8>(size >> 8) | 0x40);
221 packet.push_back(static_cast<u8>(size));
222 } else {
223 ASSERT(false);
224 }
225
226 // puts the payload
227 packet.insert(packet.end(), payload.begin(), payload.end());
228
229 // calculates CRC and puts to the end
230 packet.push_back(boost::crc<8, 0x07, 0, 0, false, false>(packet.data(), packet.size()));
231
232 if (receive_buffer->Put(packet)) {
233 receive_event->Signal();
234 } else {
235 LOG_ERROR(Service_IR, "receive buffer is full!");
236 }
237}
15 238
16/** 239/**
17 * IR::InitializeIrNopShared service function 240 * IR::InitializeIrNopShared service function
241 * Initializes ir:USER service with a user provided shared memory. The shared memory is configured
242 * to shared mode (with SharedMemoryHeader at the beginning of the shared memory).
18 * Inputs: 243 * Inputs:
19 * 1 : Size of transfer buffer 244 * 1 : Size of shared memory
20 * 2 : Recv buffer size 245 * 2 : Recv buffer size
21 * 3 : unknown 246 * 3 : Recv buffer packet count
22 * 4 : Send buffer size 247 * 4 : Send buffer size
23 * 5 : unknown 248 * 5 : Send buffer packet count
24 * 6 : BaudRate (u8) 249 * 6 : BaudRate (u8)
25 * 7 : 0 250 * 7 : 0 (Handle descriptor)
26 * 8 : Handle of transfer shared memory 251 * 8 : Handle of shared memory
27 * Outputs: 252 * Outputs:
28 * 1 : Result of function, 0 on success, otherwise error code 253 * 1 : Result of function, 0 on success, otherwise error code
29 */ 254 */
30static void InitializeIrNopShared(Interface* self) { 255static void InitializeIrNopShared(Interface* self) {
31 u32* cmd_buff = Kernel::GetCommandBuffer(); 256 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 6, 2);
257 const u32 shared_buff_size = rp.Pop<u32>();
258 const u32 recv_buff_size = rp.Pop<u32>();
259 const u32 recv_buff_packet_count = rp.Pop<u32>();
260 const u32 send_buff_size = rp.Pop<u32>();
261 const u32 send_buff_packet_count = rp.Pop<u32>();
262 const u8 baud_rate = rp.Pop<u8>();
263 const Kernel::Handle handle = rp.PopHandle();
32 264
33 u32 transfer_buff_size = cmd_buff[1]; 265 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
34 u32 recv_buff_size = cmd_buff[2];
35 u32 unk1 = cmd_buff[3];
36 u32 send_buff_size = cmd_buff[4];
37 u32 unk2 = cmd_buff[5];
38 u8 baud_rate = cmd_buff[6] & 0xFF;
39 Kernel::Handle handle = cmd_buff[8];
40 266
41 if (Kernel::g_handle_table.IsValid(handle)) { 267 shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle);
42 transfer_shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle); 268 if (!shared_memory) {
43 transfer_shared_memory->name = "IR:TransferSharedMemory"; 269 LOG_CRITICAL(Service_IR, "invalid shared memory handle 0x%08X", handle);
270 rb.Push(ResultCode(ErrorDescription::InvalidHandle, ErrorModule::OS,
271 ErrorSummary::WrongArgument, ErrorLevel::Permanent));
272 return;
44 } 273 }
274 shared_memory->name = "IR_USER: shared memory";
45 275
46 cmd_buff[1] = RESULT_SUCCESS.raw; 276 receive_buffer =
277 BufferManager(shared_memory, 0x10, 0x20, recv_buff_packet_count, recv_buff_size);
278 SharedMemoryHeader shared_memory_init{};
279 shared_memory_init.initialized = 1;
280 std::memcpy(shared_memory->GetPointer(), &shared_memory_init, sizeof(SharedMemoryHeader));
47 281
48 LOG_WARNING(Service_IR, "(STUBBED) called, transfer_buff_size=%d, recv_buff_size=%d, " 282 rb.Push(RESULT_SUCCESS);
49 "unk1=%d, send_buff_size=%d, unk2=%d, baud_rate=%u, handle=0x%08X", 283
50 transfer_buff_size, recv_buff_size, unk1, send_buff_size, unk2, baud_rate, handle); 284 LOG_INFO(Service_IR, "called, shared_buff_size=%u, recv_buff_size=%u, "
285 "recv_buff_packet_count=%u, send_buff_size=%u, "
286 "send_buff_packet_count=%u, baud_rate=%u, handle=0x%08X",
287 shared_buff_size, recv_buff_size, recv_buff_packet_count, send_buff_size,
288 send_buff_packet_count, baud_rate, handle);
51} 289}
52 290
53/** 291/**
54 * IR::RequireConnection service function 292 * IR::RequireConnection service function
293 * Searches for an IR device and connects to it. After connecting to the device, applications can
294 * use SendIrNop function, ReceiveIrNop function (or read from the buffer directly) to communicate
295 * with the device.
55 * Inputs: 296 * Inputs:
56 * 1 : unknown (u8), looks like always 1 297 * 1 : device ID? always 1 for circle pad pro
57 * Outputs: 298 * Outputs:
58 * 1 : Result of function, 0 on success, otherwise error code 299 * 1 : Result of function, 0 on success, otherwise error code
59 */ 300 */
60static void RequireConnection(Interface* self) { 301static void RequireConnection(Interface* self) {
61 u32* cmd_buff = Kernel::GetCommandBuffer(); 302 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 0);
303 const u8 device_id = rp.Pop<u8>();
304
305 u8* shared_memory_ptr = shared_memory->GetPointer();
306 if (device_id == 1) {
307 // These values are observed on a New 3DS. The meaning of them is unclear.
308 // TODO (wwylele): should assign network_id a (random?) number
309 shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 2;
310 shared_memory_ptr[offsetof(SharedMemoryHeader, connection_role)] = 2;
311 shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 1;
312
313 connected_device = extra_hid.get();
314 connected_device->OnConnect();
315 conn_status_event->Signal();
316 } else {
317 LOG_WARNING(Service_IR, "unknown device id %u. Won't connect.", device_id);
318 shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 1;
319 shared_memory_ptr[offsetof(SharedMemoryHeader, trying_to_connect_status)] = 2;
320 }
321
322 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
323 rb.Push(RESULT_SUCCESS);
324
325 LOG_INFO(Service_IR, "called, device_id = %u", device_id);
326}
327
328/**
329 * IR::GetReceiveEvent service function
330 * Gets an event that is signaled when a packet is received from the IR device.
331 * Outputs:
332 * 1 : Result of function, 0 on success, otherwise error code
333 * 2 : 0 (Handle descriptor)
334 * 3 : Receive event handle
335 */
336void GetReceiveEvent(Interface* self) {
337 IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2);
62 338
63 conn_status_event->Signal(); 339 rb.Push(RESULT_SUCCESS);
340 rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).MoveFrom());
341
342 LOG_INFO(Service_IR, "called");
343}
344
345/**
346 * IR::GetSendEvent service function
347 * Gets an event that is signaled when the sending of a packet is complete
348 * Outputs:
349 * 1 : Result of function, 0 on success, otherwise error code
350 * 2 : 0 (Handle descriptor)
351 * 3 : Send event handle
352 */
353void GetSendEvent(Interface* self) {
354 IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2);
64 355
65 cmd_buff[1] = RESULT_SUCCESS.raw; 356 rb.Push(RESULT_SUCCESS);
357 rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).MoveFrom());
66 358
67 LOG_WARNING(Service_IR, "(STUBBED) called"); 359 LOG_INFO(Service_IR, "called");
68} 360}
69 361
70/** 362/**
71 * IR::Disconnect service function 363 * IR::Disconnect service function
364 * Disconnects from the current connected IR device.
72 * Outputs: 365 * Outputs:
73 * 1 : Result of function, 0 on success, otherwise error code 366 * 1 : Result of function, 0 on success, otherwise error code
74 */ 367 */
75static void Disconnect(Interface* self) { 368static void Disconnect(Interface* self) {
76 u32* cmd_buff = Kernel::GetCommandBuffer(); 369 if (connected_device) {
370 connected_device->OnDisconnect();
371 connected_device = nullptr;
372 conn_status_event->Signal();
373 }
374
375 u8* shared_memory_ptr = shared_memory->GetPointer();
376 shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 0;
377 shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 0;
77 378
78 cmd_buff[1] = RESULT_SUCCESS.raw; 379 IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x09, 1, 0);
380 rb.Push(RESULT_SUCCESS);
79 381
80 LOG_WARNING(Service_IR, "(STUBBED) called"); 382 LOG_INFO(Service_IR, "called");
81} 383}
82 384
83/** 385/**
84 * IR::GetConnectionStatusEvent service function 386 * IR::GetConnectionStatusEvent service function
387 * Gets an event that is signaled when the connection status is changed
85 * Outputs: 388 * Outputs:
86 * 1 : Result of function, 0 on success, otherwise error code 389 * 1 : Result of function, 0 on success, otherwise error code
87 * 2 : Connection Status Event handle 390 * 2 : 0 (Handle descriptor)
391 * 3 : Connection Status Event handle
88 */ 392 */
89static void GetConnectionStatusEvent(Interface* self) { 393static void GetConnectionStatusEvent(Interface* self) {
90 u32* cmd_buff = Kernel::GetCommandBuffer(); 394 IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2);
91 395
92 cmd_buff[1] = RESULT_SUCCESS.raw; 396 rb.Push(RESULT_SUCCESS);
93 cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom(); 397 rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom());
94 398
95 LOG_WARNING(Service_IR, "(STUBBED) called"); 399 LOG_INFO(Service_IR, "called");
96} 400}
97 401
98/** 402/**
99 * IR::FinalizeIrNop service function 403 * IR::FinalizeIrNop service function
404 * Finalize ir:USER service.
100 * Outputs: 405 * Outputs:
101 * 1 : Result of function, 0 on success, otherwise error code 406 * 1 : Result of function, 0 on success, otherwise error code
102 */ 407 */
103static void FinalizeIrNop(Interface* self) { 408static void FinalizeIrNop(Interface* self) {
104 u32* cmd_buff = Kernel::GetCommandBuffer(); 409 if (connected_device) {
410 connected_device->OnDisconnect();
411 connected_device = nullptr;
412 }
413
414 shared_memory = nullptr;
415 receive_buffer = boost::none;
416
417 IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x02, 1, 0);
418 rb.Push(RESULT_SUCCESS);
419
420 LOG_INFO(Service_IR, "called");
421}
422
423/**
424 * IR::SendIrNop service function
425 * Sends a packet to the connected IR device
426 * Inpus:
427 * 1 : Size of data to send
428 * 2 : 2 + (size << 14) (Static buffer descriptor)
429 * 3 : Data buffer address
430 * Outputs:
431 * 1 : Result of function, 0 on success, otherwise error code
432 */
433static void SendIrNop(Interface* self) {
434 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0D, 1, 2);
435 const u32 size = rp.Pop<u32>();
436 const VAddr address = rp.PopStaticBuffer();
437
438 std::vector<u8> buffer(size);
439 Memory::ReadBlock(address, buffer.data(), size);
440
441 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
442 if (connected_device) {
443 connected_device->OnReceive(buffer);
444 send_event->Signal();
445 rb.Push(RESULT_SUCCESS);
446 } else {
447 LOG_ERROR(Service_IR, "not connected");
448 rb.Push(ResultCode(static_cast<ErrorDescription>(13), ErrorModule::IR,
449 ErrorSummary::InvalidState, ErrorLevel::Status));
450 }
451
452 LOG_TRACE(Service_IR, "called, data=%s", Common::ArrayToString(buffer.data(), size).c_str());
453}
454
455/**
456 * IR::ReleaseReceivedData function
457 * Release a specified amount of packet from the receive buffer. This is called after the
458 * application reads received packet from the buffer directly, to release the buffer space for
459 * future packets.
460 * Inpus:
461 * 1 : Number of packets to release
462 * Outputs:
463 * 1 : Result of function, 0 on success, otherwise error code
464 */
465static void ReleaseReceivedData(Interface* self) {
466 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 1, 0);
467 u32 count = rp.Pop<u32>();
468
469 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
105 470
106 cmd_buff[1] = RESULT_SUCCESS.raw; 471 if (receive_buffer->Release(count)) {
472 rb.Push(RESULT_SUCCESS);
473 } else {
474 LOG_ERROR(Service_IR, "failed to release %u packets", count);
475 rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::IR, ErrorSummary::NotFound,
476 ErrorLevel::Status));
477 }
107 478
108 LOG_WARNING(Service_IR, "(STUBBED) called"); 479 LOG_TRACE(Service_IR, "called, count=%u", count);
109} 480}
110 481
111const Interface::FunctionInfo FunctionTable[] = { 482const Interface::FunctionInfo FunctionTable[] = {
@@ -118,10 +489,10 @@ const Interface::FunctionInfo FunctionTable[] = {
118 {0x000702C0, nullptr, "AutoConnection"}, 489 {0x000702C0, nullptr, "AutoConnection"},
119 {0x00080000, nullptr, "AnyConnection"}, 490 {0x00080000, nullptr, "AnyConnection"},
120 {0x00090000, Disconnect, "Disconnect"}, 491 {0x00090000, Disconnect, "Disconnect"},
121 {0x000A0000, nullptr, "GetReceiveEvent"}, 492 {0x000A0000, GetReceiveEvent, "GetReceiveEvent"},
122 {0x000B0000, nullptr, "GetSendEvent"}, 493 {0x000B0000, GetSendEvent, "GetSendEvent"},
123 {0x000C0000, GetConnectionStatusEvent, "GetConnectionStatusEvent"}, 494 {0x000C0000, GetConnectionStatusEvent, "GetConnectionStatusEvent"},
124 {0x000D0042, nullptr, "SendIrNop"}, 495 {0x000D0042, SendIrNop, "SendIrNop"},
125 {0x000E0042, nullptr, "SendIrNopLarge"}, 496 {0x000E0042, nullptr, "SendIrNopLarge"},
126 {0x000F0040, nullptr, "ReceiveIrnop"}, 497 {0x000F0040, nullptr, "ReceiveIrnop"},
127 {0x00100042, nullptr, "ReceiveIrnopLarge"}, 498 {0x00100042, nullptr, "ReceiveIrnopLarge"},
@@ -133,7 +504,7 @@ const Interface::FunctionInfo FunctionTable[] = {
133 {0x00160000, nullptr, "GetSendSizeFreeAndUsed"}, 504 {0x00160000, nullptr, "GetSendSizeFreeAndUsed"},
134 {0x00170000, nullptr, "GetConnectionRole"}, 505 {0x00170000, nullptr, "GetConnectionRole"},
135 {0x00180182, InitializeIrNopShared, "InitializeIrNopShared"}, 506 {0x00180182, InitializeIrNopShared, "InitializeIrNopShared"},
136 {0x00190040, nullptr, "ReleaseReceivedData"}, 507 {0x00190040, ReleaseReceivedData, "ReleaseReceivedData"},
137 {0x001A0040, nullptr, "SetOwnMachineId"}, 508 {0x001A0040, nullptr, "SetOwnMachineId"},
138}; 509};
139 510
@@ -144,13 +515,43 @@ IR_User_Interface::IR_User_Interface() {
144void InitUser() { 515void InitUser() {
145 using namespace Kernel; 516 using namespace Kernel;
146 517
147 transfer_shared_memory = nullptr; 518 shared_memory = nullptr;
519
148 conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent"); 520 conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent");
521 send_event = Event::Create(ResetType::OneShot, "IR:SendEvent");
522 receive_event = Event::Create(ResetType::OneShot, "IR:ReceiveEvent");
523
524 receive_buffer = boost::none;
525
526 extra_hid = std::make_unique<ExtraHID>(PutToReceive);
527
528 connected_device = nullptr;
149} 529}
150 530
151void ShutdownUser() { 531void ShutdownUser() {
152 transfer_shared_memory = nullptr; 532 if (connected_device) {
533 connected_device->OnDisconnect();
534 connected_device = nullptr;
535 }
536
537 extra_hid = nullptr;
538 receive_buffer = boost::none;
539 shared_memory = nullptr;
153 conn_status_event = nullptr; 540 conn_status_event = nullptr;
541 send_event = nullptr;
542 receive_event = nullptr;
543}
544
545void ReloadInputDevices() {
546 if (extra_hid)
547 extra_hid->RequestInputDevicesReload();
548}
549
550IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {}
551IRDevice::~IRDevice() = default;
552
553void IRDevice::Send(const std::vector<u8>& data) {
554 send_func(data);
154} 555}
155 556
156} // namespace IR 557} // namespace IR
diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h
index 3849bd923..2401346e8 100644
--- a/src/core/hle/service/ir/ir_user.h
+++ b/src/core/hle/service/ir/ir_user.h
@@ -4,11 +4,41 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <functional>
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8 9
9namespace Service { 10namespace Service {
10namespace IR { 11namespace IR {
11 12
13/// An interface representing a device that can communicate with 3DS via ir:USER service
14class IRDevice {
15public:
16 /**
17 * A function object that implements the method to send data to the 3DS, which takes a vector of
18 * data to send.
19 */
20 using SendFunc = std::function<void(const std::vector<u8>& data)>;
21
22 explicit IRDevice(SendFunc send_func);
23 virtual ~IRDevice();
24
25 /// Called when connected with 3DS
26 virtual void OnConnect() = 0;
27
28 /// Called when disconnected from 3DS
29 virtual void OnDisconnect() = 0;
30
31 /// Called when data is received from the 3DS. This is invoked by the ir:USER send function.
32 virtual void OnReceive(const std::vector<u8>& data) = 0;
33
34protected:
35 /// Sends data to the 3DS. The actual sending method is specified in the constructor
36 void Send(const std::vector<u8>& data);
37
38private:
39 const SendFunc send_func;
40};
41
12class IR_User_Interface : public Service::Interface { 42class IR_User_Interface : public Service::Interface {
13public: 43public:
14 IR_User_Interface(); 44 IR_User_Interface();
@@ -21,5 +51,8 @@ public:
21void InitUser(); 51void InitUser();
22void ShutdownUser(); 52void ShutdownUser();
23 53
54/// Reload input devices. Used when input configuration changed
55void ReloadInputDevices();
56
24} // namespace IR 57} // namespace IR
25} // namespace Service 58} // namespace Service
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index a598f9f2f..3d22c0afa 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -5,6 +5,7 @@
5#include "audio_core/audio_core.h" 5#include "audio_core/audio_core.h"
6#include "core/gdbstub/gdbstub.h" 6#include "core/gdbstub/gdbstub.h"
7#include "core/hle/service/hid/hid.h" 7#include "core/hle/service/hid/hid.h"
8#include "core/hle/service/ir/ir_user.h"
8#include "settings.h" 9#include "settings.h"
9#include "video_core/video_core.h" 10#include "video_core/video_core.h"
10 11
@@ -32,6 +33,7 @@ void Apply() {
32 AudioCore::EnableStretching(values.enable_audio_stretching); 33 AudioCore::EnableStretching(values.enable_audio_stretching);
33 34
34 Service::HID::ReloadInputDevices(); 35 Service::HID::ReloadInputDevices();
36 Service::IR::ReloadInputDevices();
35} 37}
36 38
37} // namespace 39} // namespace