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