1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22
23 #ifdef SDL_JOYSTICK_HIDAPI
24
25 /* Handle rumble on a separate thread so it doesn't block the application */
26
27 #include "SDL_assert.h"
28 #include "SDL_thread.h"
29 #include "SDL_hidapijoystick_c.h"
30 #include "SDL_hidapi_rumble.h"
31 #include "../../thread/SDL_systhread.h"
32
33
34 typedef struct SDL_HIDAPI_RumbleRequest
35 {
36 SDL_HIDAPI_Device *device;
37 Uint8 data[2*USB_PACKET_LENGTH]; /* need enough space for the biggest report: dualshock4 is 78 bytes */
38 int size;
39 struct SDL_HIDAPI_RumbleRequest *prev;
40
41 } SDL_HIDAPI_RumbleRequest;
42
43 typedef struct SDL_HIDAPI_RumbleContext
44 {
45 SDL_atomic_t initialized;
46 SDL_atomic_t running;
47 SDL_Thread *thread;
48 SDL_mutex *lock;
49 SDL_sem *request_sem;
50 SDL_HIDAPI_RumbleRequest *requests_head;
51 SDL_HIDAPI_RumbleRequest *requests_tail;
52 } SDL_HIDAPI_RumbleContext;
53
54 static SDL_HIDAPI_RumbleContext rumble_context;
55
SDL_HIDAPI_RumbleThread(void * data)56 static int SDL_HIDAPI_RumbleThread(void *data)
57 {
58 SDL_HIDAPI_RumbleContext *ctx = (SDL_HIDAPI_RumbleContext *)data;
59
60 SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
61
62 while (SDL_AtomicGet(&ctx->running)) {
63 SDL_HIDAPI_RumbleRequest *request = NULL;
64
65 SDL_SemWait(ctx->request_sem);
66
67 SDL_LockMutex(ctx->lock);
68 request = ctx->requests_tail;
69 if (request) {
70 if (request == ctx->requests_head) {
71 ctx->requests_head = NULL;
72 }
73 ctx->requests_tail = request->prev;
74 }
75 SDL_UnlockMutex(ctx->lock);
76
77 if (request) {
78 SDL_LockMutex(request->device->dev_lock);
79 if (request->device->dev) {
80 hid_write( request->device->dev, request->data, request->size );
81 }
82 SDL_UnlockMutex(request->device->dev_lock);
83 (void)SDL_AtomicDecRef(&request->device->rumble_pending);
84 SDL_free(request);
85 }
86 }
87 return 0;
88 }
89
90 static void
SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext * ctx)91 SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
92 {
93 SDL_AtomicSet(&ctx->running, SDL_FALSE);
94
95 if (ctx->thread) {
96 int result;
97
98 SDL_SemPost(ctx->request_sem);
99 SDL_WaitThread(ctx->thread, &result);
100 ctx->thread = NULL;
101 }
102
103 /* This should always be called with an empty queue */
104 SDL_assert(!ctx->requests_head);
105 SDL_assert(!ctx->requests_tail);
106
107 if (ctx->request_sem) {
108 SDL_DestroySemaphore(ctx->request_sem);
109 ctx->request_sem = NULL;
110 }
111
112 if (ctx->lock) {
113 SDL_DestroyMutex(ctx->lock);
114 ctx->lock = NULL;
115 }
116
117 SDL_AtomicSet(&ctx->initialized, SDL_FALSE);
118 }
119
120 static int
SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext * ctx)121 SDL_HIDAPI_StartRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
122 {
123 ctx->lock = SDL_CreateMutex();
124 if (!ctx->lock) {
125 SDL_HIDAPI_StopRumbleThread(ctx);
126 return -1;
127 }
128
129 ctx->request_sem = SDL_CreateSemaphore(0);
130 if (!ctx->request_sem) {
131 SDL_HIDAPI_StopRumbleThread(ctx);
132 return -1;
133 }
134
135 SDL_AtomicSet(&ctx->running, SDL_TRUE);
136 ctx->thread = SDL_CreateThreadInternal(SDL_HIDAPI_RumbleThread, "HIDAPI Rumble", 0, ctx);
137 if (!ctx->thread) {
138 SDL_HIDAPI_StopRumbleThread(ctx);
139 return -1;
140 }
141 return 0;
142 }
143
SDL_HIDAPI_LockRumble(void)144 int SDL_HIDAPI_LockRumble(void)
145 {
146 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
147
148 if (SDL_AtomicCAS(&ctx->initialized, SDL_FALSE, SDL_TRUE)) {
149 if (SDL_HIDAPI_StartRumbleThread(ctx) < 0) {
150 return -1;
151 }
152 }
153
154 return SDL_LockMutex(ctx->lock);
155 }
156
SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device * device,Uint8 ** data,int ** size,int * maximum_size)157 SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size)
158 {
159 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
160 SDL_HIDAPI_RumbleRequest *request;
161
162 for (request = ctx->requests_tail; request; request = request->prev) {
163 if (request->device == device) {
164 *data = request->data;
165 *size = &request->size;
166 *maximum_size = sizeof(request->data);
167 return SDL_TRUE;
168 }
169 }
170 return SDL_FALSE;
171 }
172
SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device * device,const Uint8 * data,int size)173 int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
174 {
175 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
176 SDL_HIDAPI_RumbleRequest *request;
177
178 if (size > sizeof(request->data)) {
179 SDL_HIDAPI_UnlockRumble();
180 return SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, (int)sizeof(request->data));
181 }
182
183 request = (SDL_HIDAPI_RumbleRequest *)SDL_calloc(1, sizeof(*request));
184 if (!request) {
185 SDL_HIDAPI_UnlockRumble();
186 return SDL_OutOfMemory();
187 }
188 request->device = device;
189 SDL_memcpy(request->data, data, size);
190 request->size = size;
191
192 SDL_AtomicIncRef(&device->rumble_pending);
193
194 if (ctx->requests_head) {
195 ctx->requests_head->prev = request;
196 } else {
197 ctx->requests_tail = request;
198 }
199 ctx->requests_head = request;
200
201 /* Make sure we unlock before posting the semaphore so the rumble thread can run immediately */
202 SDL_HIDAPI_UnlockRumble();
203
204 SDL_SemPost(ctx->request_sem);
205
206 return size;
207 }
208
SDL_HIDAPI_UnlockRumble(void)209 void SDL_HIDAPI_UnlockRumble(void)
210 {
211 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
212
213 SDL_UnlockMutex(ctx->lock);
214 }
215
SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device * device,const Uint8 * data,int size)216 int SDL_HIDAPI_SendRumble(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
217 {
218 Uint8 *pending_data;
219 int *pending_size;
220 int maximum_size;
221
222 if (SDL_HIDAPI_LockRumble() < 0) {
223 return -1;
224 }
225
226 /* check if there is a pending request for the device and update it */
227 if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size)) {
228 if (size > maximum_size) {
229 SDL_HIDAPI_UnlockRumble();
230 return SDL_SetError("Couldn't send rumble, size %d is greater than %d", size, maximum_size);
231 }
232
233 SDL_memcpy(pending_data, data, size);
234 *pending_size = size;
235 SDL_HIDAPI_UnlockRumble();
236 return size;
237 }
238
239 return SDL_HIDAPI_SendRumbleAndUnlock(device, data, size);
240 }
241
SDL_HIDAPI_QuitRumble(void)242 void SDL_HIDAPI_QuitRumble(void)
243 {
244 SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
245
246 if (SDL_AtomicGet(&ctx->running)) {
247 SDL_HIDAPI_StopRumbleThread(ctx);
248 }
249 }
250
251 #endif /* SDL_JOYSTICK_HIDAPI */
252
253 /* vi: set ts=4 sw=4 expandtab: */
254