xref: /aosp_15_r20/external/libusb/tests/stress_mt.c (revision 86b64dcb59b3a0b37502ecd56e119234366a6f7e)
1 /*
2  * libusb multi-thread test program
3  * Copyright 2022-2023 Tormod Volden
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #include <config.h>
21 
22 #include <libusb.h>
23 #include <stdio.h>
24 #include <stdbool.h>
25 
26 #if defined(PLATFORM_POSIX)
27 
28 #include <pthread.h>
29 typedef pthread_t thread_t;
30 typedef void * thread_return_t;
31 #define THREAD_RETURN_VALUE NULL
32 #define THREAD_CALL_TYPE
33 
thread_create(thread_t * thread,thread_return_t (* thread_entry)(void * arg),void * arg)34 static inline int thread_create(thread_t *thread,
35 	thread_return_t (*thread_entry)(void *arg), void *arg)
36 {
37 	return pthread_create(thread, NULL, thread_entry, arg) == 0 ? 0 : -1;
38 }
39 
thread_join(thread_t thread)40 static inline void thread_join(thread_t thread)
41 {
42 	(void)pthread_join(thread, NULL);
43 }
44 
45 #include <stdatomic.h>
46 
47 #elif defined(PLATFORM_WINDOWS)
48 
49 typedef HANDLE thread_t;
50 #define THREAD_RETURN_VALUE 0
51 #define THREAD_CALL_TYPE __stdcall
52 
53 #if defined(__CYGWIN__)
54 typedef DWORD thread_return_t;
55 #else
56 #include <process.h>
57 typedef unsigned thread_return_t;
58 #endif
59 
thread_create(thread_t * thread,thread_return_t (__stdcall * thread_entry)(void * arg),void * arg)60 static inline int thread_create(thread_t *thread,
61 	thread_return_t (__stdcall *thread_entry)(void *arg), void *arg)
62 {
63 #if defined(__CYGWIN__)
64 	*thread = CreateThread(NULL, 0, thread_entry, arg, 0, NULL);
65 #else
66 	*thread = (HANDLE)_beginthreadex(NULL, 0, thread_entry, arg, 0, NULL);
67 #endif
68 	return *thread != NULL ? 0 : -1;
69 }
70 
thread_join(thread_t thread)71 static inline void thread_join(thread_t thread)
72 {
73 	(void)WaitForSingleObject(thread, INFINITE);
74 	(void)CloseHandle(thread);
75 }
76 
77 typedef volatile LONG atomic_bool;
78 
79 #define atomic_exchange InterlockedExchange
80 #endif /* PLATFORM_WINDOWS */
81 
82 /* Test that creates and destroys contexts repeatedly */
83 
84 #define NTHREADS 8
85 #define ITERS 64
86 #define MAX_DEVCOUNT 128
87 
88 struct thread_info {
89 	int number;
90 	int enumerate;
91 	ssize_t devcount;
92 	int err;
93 	int iteration;
94 } tinfo[NTHREADS];
95 
96 atomic_bool no_access[MAX_DEVCOUNT];
97 
98 /* Function called by backend during device initialization to convert
99  * multi-byte fields in the device descriptor to host-endian format.
100  * Copied from libusbi.h as we want test to be realistic and not depend on internals.
101  */
usbi_localize_device_descriptor(struct libusb_device_descriptor * desc)102 static inline void usbi_localize_device_descriptor(struct libusb_device_descriptor *desc)
103 {
104 	desc->bcdUSB = libusb_le16_to_cpu(desc->bcdUSB);
105 	desc->idVendor = libusb_le16_to_cpu(desc->idVendor);
106 	desc->idProduct = libusb_le16_to_cpu(desc->idProduct);
107 	desc->bcdDevice = libusb_le16_to_cpu(desc->bcdDevice);
108 }
109 
init_and_exit(void * arg)110 static thread_return_t THREAD_CALL_TYPE init_and_exit(void * arg)
111 {
112 	struct thread_info *ti = (struct thread_info *) arg;
113 
114 	for (ti->iteration = 0; ti->iteration < ITERS && !ti->err; ti->iteration++) {
115 		libusb_context *ctx = NULL;
116 
117 		ti->err = libusb_init_context(&ctx, /*options=*/NULL, /*num_options=*/0);
118 		if (ti->err != 0) {
119 			break;
120 		}
121 		if (ti->enumerate) {
122 			libusb_device **devs;
123 			ti->devcount = libusb_get_device_list(ctx, &devs);
124 			if (ti->devcount < 0) {
125 				ti->err = (int)ti->devcount;
126 				break;
127 			}
128 			for (int i = 0; i < ti->devcount && ti->err == 0; i++) {
129 				libusb_device *dev = devs[i];
130 				struct libusb_device_descriptor desc;
131 				ti->err = libusb_get_device_descriptor(dev, &desc);
132 				if (ti->err != 0) {
133 					break;
134 				}
135 				if (no_access[i]) {
136 					continue;
137 				}
138 				libusb_device_handle *dev_handle;
139 				int open_err = libusb_open(dev, &dev_handle);
140 				if (open_err == LIBUSB_ERROR_ACCESS
141 #if defined(PLATFORM_WINDOWS)
142 				    || open_err == LIBUSB_ERROR_NOT_SUPPORTED
143 				    || open_err == LIBUSB_ERROR_NOT_FOUND
144 #endif
145 						) {
146 					/* Use atomic swap to ensure we print warning only once across all threads.
147 					   This is a warning and not a hard error because it should be fine to run tests
148 					   even if we don't have access to some devices. */
149 					if (!atomic_exchange(&no_access[i], true)) {
150 						fprintf(stderr, "No access to device %04x:%04x, skipping transfer tests.\n", desc.idVendor, desc.idProduct);
151 					}
152 					continue;
153 				}
154 				if (open_err != 0) {
155 					ti->err = open_err;
156 					break;
157 				}
158 				/* Request raw descriptor via control transfer.
159 				   This tests opening, transferring and closing from multiple threads in parallel. */
160 				struct libusb_device_descriptor raw_desc;
161 				int raw_desc_len = libusb_get_descriptor(dev_handle, LIBUSB_DT_DEVICE, 0, (unsigned char *)&raw_desc, sizeof(raw_desc));
162 				if (raw_desc_len < 0) {
163 					ti->err = raw_desc_len;
164 					goto close;
165 				}
166 				if (raw_desc_len != sizeof(raw_desc)) {
167 					fprintf(stderr, "Thread %d: device %d: unexpected raw descriptor length %d\n",
168 						ti->number, i, raw_desc_len);
169 					ti->err = LIBUSB_ERROR_OTHER;
170 					goto close;
171 				}
172 				usbi_localize_device_descriptor(&raw_desc);
173 #define ASSERT_EQ(field) if (raw_desc.field != desc.field) { \
174 	fprintf(stderr, "Thread %d: device %d: mismatch in field " #field ": %d != %d\n", \
175 		ti->number, i, raw_desc.field, desc.field); \
176 		ti->err = LIBUSB_ERROR_OTHER; \
177 		goto close; \
178 }
179 				ASSERT_EQ(bLength);
180 				ASSERT_EQ(bDescriptorType);
181 #if !defined(PLATFORM_WINDOWS)
182 				/* these are hardcoded by the winusbx HID backend */
183 				ASSERT_EQ(bcdUSB);
184 				ASSERT_EQ(bDeviceClass);
185 				ASSERT_EQ(bDeviceSubClass);
186 				ASSERT_EQ(bDeviceProtocol);
187 				ASSERT_EQ(bMaxPacketSize0);
188 				ASSERT_EQ(bcdDevice);
189 #endif
190 				ASSERT_EQ(idVendor);
191 				ASSERT_EQ(idProduct);
192 				ASSERT_EQ(iManufacturer);
193 				ASSERT_EQ(iProduct);
194 				ASSERT_EQ(iSerialNumber);
195 				ASSERT_EQ(bNumConfigurations);
196 			close:
197 				libusb_close(dev_handle);
198 			}
199 			libusb_free_device_list(devs, 1);
200 		}
201 
202 		libusb_exit(ctx);
203 	}
204 	return (thread_return_t) THREAD_RETURN_VALUE;
205 }
206 
test_multi_init(int enumerate)207 static int test_multi_init(int enumerate)
208 {
209 	thread_t threadId[NTHREADS];
210 	int errs = 0;
211 	int t, i;
212 	ssize_t last_devcount = 0;
213 	int devcount_mismatch = 0;
214 	int access_failures = 0;
215 
216 	printf("Starting %d threads\n", NTHREADS);
217 	for (t = 0; t < NTHREADS; t++) {
218 		tinfo[t].err = 0;
219 		tinfo[t].number = t;
220 		tinfo[t].enumerate = enumerate;
221 		thread_create(&threadId[t], &init_and_exit, (void *) &tinfo[t]);
222 	}
223 
224 	for (t = 0; t < NTHREADS; t++) {
225 		thread_join(threadId[t]);
226 		if (tinfo[t].err) {
227 			errs++;
228 			fprintf(stderr,
229 				"Thread %d failed (iteration %d): %s\n",
230 				tinfo[t].number,
231 				tinfo[t].iteration,
232 				libusb_error_name(tinfo[t].err));
233 		} else if (enumerate) {
234 			if (t > 0 && tinfo[t].devcount != last_devcount) {
235 				devcount_mismatch++;
236 				printf("Device count mismatch: Thread %d discovered %ld devices instead of %ld\n",
237 				       tinfo[t].number,
238 				       (long int) tinfo[t].devcount,
239 				       (long int) last_devcount);
240 			}
241 			last_devcount = tinfo[t].devcount;
242 		}
243 	}
244 
245 	for (i = 0; i < MAX_DEVCOUNT; i++)
246 		if (no_access[i])
247 			access_failures++;
248 
249 	if (enumerate && !devcount_mismatch)
250 		printf("All threads discovered %ld devices (%i not opened)\n",
251 		       (long int) last_devcount, access_failures);
252 
253 	return errs + devcount_mismatch;
254 }
255 
main(void)256 int main(void)
257 {
258 	int errs = 0;
259 
260 	printf("Running multithreaded init/exit test...\n");
261 	errs += test_multi_init(0);
262 	printf("Running multithreaded init/exit test with enumeration...\n");
263 	errs += test_multi_init(1);
264 	printf("All done, %d errors\n", errs);
265 
266 	return errs != 0;
267 }
268