1 // Copyright 2017 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "Core/IOS/USB/Host.h"
6
7 #include <algorithm>
8 #include <memory>
9 #include <mutex>
10 #include <set>
11 #include <string>
12 #include <utility>
13
14 #ifdef __LIBUSB__
15 #include <libusb.h>
16 #endif
17
18 #include "Common/Assert.h"
19 #include "Common/ChunkFile.h"
20 #include "Common/CommonTypes.h"
21 #include "Common/Logging/Log.h"
22 #include "Common/Thread.h"
23 #include "Core/ConfigManager.h"
24 #include "Core/Core.h"
25 #include "Core/IOS/USB/Common.h"
26 #include "Core/IOS/USB/LibusbDevice.h"
27
28 namespace IOS::HLE::Device
29 {
USBHost(Kernel & ios,const std::string & device_name)30 USBHost::USBHost(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
31 {
32 }
33
34 USBHost::~USBHost() = default;
35
Open(const OpenRequest & request)36 IPCCommandResult USBHost::Open(const OpenRequest& request)
37 {
38 if (!m_has_initialised && !Core::WantsDeterminism())
39 {
40 GetScanThread().Start();
41 // Force a device scan to complete, because some games (including Your Shape) only care
42 // about the initial device list (in the first GETDEVICECHANGE reply).
43 GetScanThread().WaitForFirstScan();
44 m_has_initialised = true;
45 }
46 return GetDefaultReply(IPC_SUCCESS);
47 }
48
UpdateWantDeterminism(const bool new_want_determinism)49 void USBHost::UpdateWantDeterminism(const bool new_want_determinism)
50 {
51 if (new_want_determinism)
52 GetScanThread().Stop();
53 else if (IsOpened())
54 GetScanThread().Start();
55 }
56
DoState(PointerWrap & p)57 void USBHost::DoState(PointerWrap& p)
58 {
59 if (IsOpened() && p.GetMode() == PointerWrap::MODE_READ)
60 {
61 // After a state has loaded, there may be insertion hooks for devices that were
62 // already plugged in, and which need to be triggered.
63 UpdateDevices(true);
64 }
65 }
66
AddDevice(std::unique_ptr<USB::Device> device)67 bool USBHost::AddDevice(std::unique_ptr<USB::Device> device)
68 {
69 std::lock_guard<std::mutex> lk(m_devices_mutex);
70 if (m_devices.find(device->GetId()) != m_devices.end())
71 return false;
72
73 m_devices[device->GetId()] = std::move(device);
74 return true;
75 }
76
GetDeviceById(const u64 device_id) const77 std::shared_ptr<USB::Device> USBHost::GetDeviceById(const u64 device_id) const
78 {
79 std::lock_guard<std::mutex> lk(m_devices_mutex);
80 const auto it = m_devices.find(device_id);
81 if (it == m_devices.end())
82 return nullptr;
83 return it->second;
84 }
85
OnDeviceChange(ChangeEvent event,std::shared_ptr<USB::Device> changed_device)86 void USBHost::OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> changed_device)
87 {
88 }
89
OnDeviceChangeEnd()90 void USBHost::OnDeviceChangeEnd()
91 {
92 }
93
ShouldAddDevice(const USB::Device & device) const94 bool USBHost::ShouldAddDevice(const USB::Device& device) const
95 {
96 return true;
97 }
98
99 // This is called from the scan thread. Returns false if we failed to update the device list.
UpdateDevices(const bool always_add_hooks)100 bool USBHost::UpdateDevices(const bool always_add_hooks)
101 {
102 if (Core::WantsDeterminism())
103 return true;
104
105 DeviceChangeHooks hooks;
106 std::set<u64> plugged_devices;
107 // If we failed to get a new, up-to-date list of devices, we cannot detect device removals.
108 if (!AddNewDevices(plugged_devices, hooks, always_add_hooks))
109 return false;
110 DetectRemovedDevices(plugged_devices, hooks);
111 DispatchHooks(hooks);
112 return true;
113 }
114
AddNewDevices(std::set<u64> & new_devices,DeviceChangeHooks & hooks,const bool always_add_hooks)115 bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
116 const bool always_add_hooks)
117 {
118 #ifdef __LIBUSB__
119 if (SConfig::GetInstance().m_usb_passthrough_devices.empty())
120 return true;
121
122 if (m_context.IsValid())
123 {
124 m_context.GetDeviceList([&](libusb_device* device) {
125 libusb_device_descriptor descriptor;
126 libusb_get_device_descriptor(device, &descriptor);
127 const std::pair<u16, u16> vid_pid = {descriptor.idVendor, descriptor.idProduct};
128 if (!SConfig::GetInstance().IsUSBDeviceWhitelisted(vid_pid))
129 return true;
130
131 auto usb_device = std::make_unique<USB::LibusbDevice>(m_ios, device, descriptor);
132 if (!ShouldAddDevice(*usb_device))
133 return true;
134
135 const u64 id = usb_device->GetId();
136 new_devices.insert(id);
137 if (AddDevice(std::move(usb_device)) || always_add_hooks)
138 hooks.emplace(GetDeviceById(id), ChangeEvent::Inserted);
139 return true;
140 });
141 }
142 #endif
143 return true;
144 }
145
DetectRemovedDevices(const std::set<u64> & plugged_devices,DeviceChangeHooks & hooks)146 void USBHost::DetectRemovedDevices(const std::set<u64>& plugged_devices, DeviceChangeHooks& hooks)
147 {
148 std::lock_guard<std::mutex> lk(m_devices_mutex);
149 for (auto it = m_devices.begin(); it != m_devices.end();)
150 {
151 if (plugged_devices.find(it->second->GetId()) == plugged_devices.end())
152 {
153 hooks.emplace(it->second, ChangeEvent::Removed);
154 it = m_devices.erase(it);
155 }
156 else
157 {
158 ++it;
159 }
160 }
161 }
162
DispatchHooks(const DeviceChangeHooks & hooks)163 void USBHost::DispatchHooks(const DeviceChangeHooks& hooks)
164 {
165 for (const auto& hook : hooks)
166 {
167 INFO_LOG(IOS_USB, "%s - %s device: %04x:%04x", GetDeviceName().c_str(),
168 hook.second == ChangeEvent::Inserted ? "New" : "Removed", hook.first->GetVid(),
169 hook.first->GetPid());
170 OnDeviceChange(hook.second, hook.first);
171 }
172 if (!hooks.empty())
173 OnDeviceChangeEnd();
174 }
175
~ScanThread()176 USBHost::ScanThread::~ScanThread()
177 {
178 Stop();
179 }
180
WaitForFirstScan()181 void USBHost::ScanThread::WaitForFirstScan()
182 {
183 m_first_scan_complete_event.Wait();
184 }
185
Start()186 void USBHost::ScanThread::Start()
187 {
188 if (Core::WantsDeterminism())
189 return;
190
191 if (m_thread_running.TestAndSet())
192 {
193 m_thread = std::thread([this] {
194 Common::SetCurrentThreadName("USB Scan Thread");
195 while (m_thread_running.IsSet())
196 {
197 if (m_host->UpdateDevices())
198 m_first_scan_complete_event.Set();
199 Common::SleepCurrentThread(50);
200 }
201 });
202 }
203 }
204
Stop()205 void USBHost::ScanThread::Stop()
206 {
207 if (m_thread_running.TestAndClear())
208 m_thread.join();
209
210 // Clear all devices and dispatch removal hooks.
211 DeviceChangeHooks hooks;
212 m_host->DetectRemovedDevices(std::set<u64>(), hooks);
213 m_host->DispatchHooks(hooks);
214 }
215
HandleTransfer(std::shared_ptr<USB::Device> device,u32 request,std::function<s32 ()> submit) const216 IPCCommandResult USBHost::HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
217 std::function<s32()> submit) const
218 {
219 if (!device)
220 return GetDefaultReply(IPC_ENOENT);
221
222 const s32 ret = submit();
223 if (ret == IPC_SUCCESS)
224 return GetNoReply();
225
226 ERROR_LOG(IOS_USB, "[%04x:%04x] Failed to submit transfer (request %u): %s", device->GetVid(),
227 device->GetPid(), request, device->GetErrorName(ret).c_str());
228 return GetDefaultReply(ret <= 0 ? ret : IPC_EINVAL);
229 }
230 } // namespace IOS::HLE::Device
231