1/*
2 * libusb synchronization on Microsoft Windows
3 *
4 * Copyright © 2010 Michael Plante <michael.plante@gmail.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#include <config.h>
22
23#include <objbase.h>
24#include <errno.h>
25
26#include "libusbi.h"
27
28struct usbi_cond_perthread {
29	struct list_head list;
30	DWORD tid;
31	HANDLE event;
32};
33
34int usbi_mutex_static_lock(usbi_mutex_static_t *mutex)
35{
36	if (!mutex)
37		return EINVAL;
38	while (InterlockedExchange(mutex, 1) == 1)
39		SleepEx(0, TRUE);
40	return 0;
41}
42
43int usbi_mutex_static_unlock(usbi_mutex_static_t *mutex)
44{
45	if (!mutex)
46		return EINVAL;
47	InterlockedExchange(mutex, 0);
48	return 0;
49}
50
51int usbi_mutex_init(usbi_mutex_t *mutex)
52{
53	if (!mutex)
54		return EINVAL;
55	*mutex = CreateMutex(NULL, FALSE, NULL);
56	if (!*mutex)
57		return ENOMEM;
58	return 0;
59}
60
61int usbi_mutex_lock(usbi_mutex_t *mutex)
62{
63	DWORD result;
64
65	if (!mutex)
66		return EINVAL;
67	result = WaitForSingleObject(*mutex, INFINITE);
68	if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED)
69		return 0; // acquired (ToDo: check that abandoned is ok)
70	else
71		return EINVAL; // don't know how this would happen
72			       //   so don't know proper errno
73}
74
75int usbi_mutex_unlock(usbi_mutex_t *mutex)
76{
77	if (!mutex)
78		return EINVAL;
79	if (ReleaseMutex(*mutex))
80		return 0;
81	else
82		return EPERM;
83}
84
85int usbi_mutex_trylock(usbi_mutex_t *mutex)
86{
87	DWORD result;
88
89	if (!mutex)
90		return EINVAL;
91	result = WaitForSingleObject(*mutex, 0);
92	if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED)
93		return 0; // acquired (ToDo: check that abandoned is ok)
94	else if (result == WAIT_TIMEOUT)
95		return EBUSY;
96	else
97		return EINVAL; // don't know how this would happen
98			       //   so don't know proper error
99}
100
101int usbi_mutex_destroy(usbi_mutex_t *mutex)
102{
103	// It is not clear if CloseHandle failure is due to failure to unlock.
104	//   If so, this should be errno=EBUSY.
105	if (!mutex || !CloseHandle(*mutex))
106		return EINVAL;
107	*mutex = NULL;
108	return 0;
109}
110
111int usbi_cond_init(usbi_cond_t *cond)
112{
113	if (!cond)
114		return EINVAL;
115	list_init(&cond->waiters);
116	list_init(&cond->not_waiting);
117	return 0;
118}
119
120int usbi_cond_destroy(usbi_cond_t *cond)
121{
122	// This assumes no one is using this anymore.  The check MAY NOT BE safe.
123	struct usbi_cond_perthread *pos, *next_pos;
124
125	if(!cond)
126		return EINVAL;
127	if (!list_empty(&cond->waiters))
128		return EBUSY; // (!see above!)
129	list_for_each_entry_safe(pos, next_pos, &cond->not_waiting, list, struct usbi_cond_perthread) {
130		CloseHandle(pos->event);
131		list_del(&pos->list);
132		free(pos);
133	}
134	return 0;
135}
136
137int usbi_cond_broadcast(usbi_cond_t *cond)
138{
139	// Assumes mutex is locked; this is not in keeping with POSIX spec, but
140	//   libusb does this anyway, so we simplify by not adding more sync
141	//   primitives to the CV definition!
142	int fail = 0;
143	struct usbi_cond_perthread *pos;
144
145	if (!cond)
146		return EINVAL;
147	list_for_each_entry(pos, &cond->waiters, list, struct usbi_cond_perthread) {
148		if (!SetEvent(pos->event))
149			fail = 1;
150	}
151	// The wait function will remove its respective item from the list.
152	return fail ? EINVAL : 0;
153}
154
155__inline static int usbi_cond_intwait(usbi_cond_t *cond,
156	usbi_mutex_t *mutex, DWORD timeout_ms)
157{
158	struct usbi_cond_perthread *pos;
159	int r, found = 0;
160	DWORD r2, tid = GetCurrentThreadId();
161
162	if (!cond || !mutex)
163		return EINVAL;
164	list_for_each_entry(pos, &cond->not_waiting, list, struct usbi_cond_perthread) {
165		if(tid == pos->tid) {
166			found = 1;
167			break;
168		}
169	}
170
171	if (!found) {
172		pos = calloc(1, sizeof(struct usbi_cond_perthread));
173		if (!pos)
174			return ENOMEM; // This errno is not POSIX-allowed.
175		pos->tid = tid;
176		pos->event = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset.
177		if (!pos->event) {
178			free(pos);
179			return ENOMEM;
180		}
181		list_add(&pos->list, &cond->not_waiting);
182	}
183
184	list_del(&pos->list); // remove from not_waiting list.
185	list_add(&pos->list, &cond->waiters);
186
187	r  = usbi_mutex_unlock(mutex);
188	if (r)
189		return r;
190
191	r2 = WaitForSingleObject(pos->event, timeout_ms);
192	r = usbi_mutex_lock(mutex);
193	if (r)
194		return r;
195
196	list_del(&pos->list);
197	list_add(&pos->list, &cond->not_waiting);
198
199	if (r2 == WAIT_OBJECT_0)
200		return 0;
201	else if (r2 == WAIT_TIMEOUT)
202		return ETIMEDOUT;
203	else
204		return EINVAL;
205}
206// N.B.: usbi_cond_*wait() can also return ENOMEM, even though pthread_cond_*wait cannot!
207int usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex)
208{
209	return usbi_cond_intwait(cond, mutex, INFINITE);
210}
211
212int usbi_cond_timedwait(usbi_cond_t *cond,
213	usbi_mutex_t *mutex, const struct timeval *tv)
214{
215	DWORD millis;
216
217	millis = (DWORD)(tv->tv_sec * 1000) + (tv->tv_usec / 1000);
218	/* round up to next millisecond */
219	if (tv->tv_usec % 1000)
220		millis++;
221	return usbi_cond_intwait(cond, mutex, millis);
222}
223
224int usbi_tls_key_create(usbi_tls_key_t *key)
225{
226	if (!key)
227		return EINVAL;
228	*key = TlsAlloc();
229	if (*key == TLS_OUT_OF_INDEXES)
230		return ENOMEM;
231	else
232		return 0;
233}
234
235void *usbi_tls_key_get(usbi_tls_key_t key)
236{
237	return TlsGetValue(key);
238}
239
240int usbi_tls_key_set(usbi_tls_key_t key, void *value)
241{
242	if (TlsSetValue(key, value))
243		return 0;
244	else
245		return EINVAL;
246}
247
248int usbi_tls_key_delete(usbi_tls_key_t key)
249{
250	if (TlsFree(key))
251		return 0;
252	else
253		return EINVAL;
254}
255
256int usbi_get_tid(void)
257{
258	return (int)GetCurrentThreadId();
259}
260