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