1/*-
2 * Copyright (c) 2016-2019 Hans Petter Selasky. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26#ifdef LIBUSB_GLOBAL_INCLUDE_FILE
27#include LIBUSB_GLOBAL_INCLUDE_FILE
28#else
29#include <assert.h>
30#include <errno.h>
31#include <poll.h>
32#include <pthread.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37#include <time.h>
38#include <sys/fcntl.h>
39#include <sys/ioctl.h>
40#include <sys/queue.h>
41#include <sys/endian.h>
42#endif
43
44#define	libusb_device_handle libusb20_device
45
46#include "libusb20.h"
47#include "libusb20_desc.h"
48#include "libusb20_int.h"
49#include "libusb.h"
50#include "libusb10.h"
51
52static int
53libusb_hotplug_equal(libusb_device *_adev, libusb_device *_bdev)
54{
55	struct libusb20_device *adev = _adev->os_priv;
56	struct libusb20_device *bdev = _bdev->os_priv;
57
58	if (adev->bus_number != bdev->bus_number)
59		return (0);
60	if (adev->device_address != bdev->device_address)
61		return (0);
62	if (memcmp(&adev->ddesc, &bdev->ddesc, sizeof(adev->ddesc)))
63		return (0);
64	if (memcmp(&adev->session_data, &bdev->session_data, sizeof(adev->session_data)))
65		return (0);
66	return (1);
67}
68
69static int
70libusb_hotplug_filter(libusb_context *ctx, libusb_hotplug_callback_handle pcbh,
71    libusb_device *dev, libusb_hotplug_event event)
72{
73	if (!(pcbh->events & event))
74		return (0);
75	if (pcbh->vendor != LIBUSB_HOTPLUG_MATCH_ANY &&
76	    pcbh->vendor != libusb20_dev_get_device_desc(dev->os_priv)->idVendor)
77		return (0);
78	if (pcbh->product != LIBUSB_HOTPLUG_MATCH_ANY &&
79	    pcbh->product != libusb20_dev_get_device_desc(dev->os_priv)->idProduct)
80		return (0);
81	if (pcbh->devclass != LIBUSB_HOTPLUG_MATCH_ANY &&
82	    pcbh->devclass != libusb20_dev_get_device_desc(dev->os_priv)->bDeviceClass)
83		return (0);
84	return (pcbh->fn(ctx, dev, event, pcbh->user_data));
85}
86
87static int
88libusb_hotplug_enumerate(libusb_context *ctx, struct libusb_device_head *phead)
89{
90	libusb_device **ppdev;
91	ssize_t count;
92	ssize_t x;
93
94	count = libusb_get_device_list(ctx, &ppdev);
95	if (count < 0)
96		return (-1);
97
98	for (x = 0; x != count; x++)
99		TAILQ_INSERT_TAIL(phead, ppdev[x], hotplug_entry);
100
101	libusb_free_device_list(ppdev, 0);
102	return (0);
103}
104
105static void *
106libusb_hotplug_scan(void *arg)
107{
108	struct libusb_device_head hotplug_devs;
109	libusb_hotplug_callback_handle acbh;
110	libusb_hotplug_callback_handle bcbh;
111	libusb_context *ctx = arg;
112	libusb_device *temp;
113	libusb_device *adev;
114	libusb_device *bdev;
115	unsigned do_loop = 1;
116
117	while (do_loop) {
118		usleep(4000000);
119
120		HOTPLUG_LOCK(ctx);
121
122		TAILQ_INIT(&hotplug_devs);
123
124		if (ctx->hotplug_handler != NO_THREAD) {
125			if (libusb_hotplug_enumerate(ctx, &hotplug_devs) < 0) {
126				HOTPLUG_UNLOCK(ctx);
127				continue;
128			}
129		} else {
130			do_loop = 0;
131		}
132
133		/* figure out which devices are gone */
134		TAILQ_FOREACH_SAFE(adev, &ctx->hotplug_devs, hotplug_entry, temp) {
135			TAILQ_FOREACH(bdev, &hotplug_devs, hotplug_entry) {
136				if (libusb_hotplug_equal(adev, bdev))
137					break;
138			}
139			if (bdev == NULL) {
140				TAILQ_REMOVE(&ctx->hotplug_devs, adev, hotplug_entry);
141				TAILQ_FOREACH_SAFE(acbh, &ctx->hotplug_cbh, entry, bcbh) {
142					if (libusb_hotplug_filter(ctx, acbh, adev,
143					    LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) == 0)
144						continue;
145					TAILQ_REMOVE(&ctx->hotplug_cbh, acbh, entry);
146					free(acbh);
147				}
148				libusb_unref_device(adev);
149			}
150		}
151
152		/* figure out which devices are new */
153		TAILQ_FOREACH_SAFE(adev, &hotplug_devs, hotplug_entry, temp) {
154			TAILQ_FOREACH(bdev, &ctx->hotplug_devs, hotplug_entry) {
155				if (libusb_hotplug_equal(adev, bdev))
156					break;
157			}
158			if (bdev == NULL) {
159				TAILQ_REMOVE(&hotplug_devs, adev, hotplug_entry);
160				TAILQ_INSERT_TAIL(&ctx->hotplug_devs, adev, hotplug_entry);
161				TAILQ_FOREACH_SAFE(acbh, &ctx->hotplug_cbh, entry, bcbh) {
162					if (libusb_hotplug_filter(ctx, acbh, adev,
163					    LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) == 0)
164						continue;
165					TAILQ_REMOVE(&ctx->hotplug_cbh, acbh, entry);
166					free(acbh);
167				}
168			}
169		}
170		HOTPLUG_UNLOCK(ctx);
171
172		/* unref remaining devices */
173		while ((adev = TAILQ_FIRST(&hotplug_devs)) != NULL) {
174			TAILQ_REMOVE(&hotplug_devs, adev, hotplug_entry);
175			libusb_unref_device(adev);
176		}
177	}
178	return (NULL);
179}
180
181int libusb_hotplug_register_callback(libusb_context *ctx,
182    libusb_hotplug_event events, libusb_hotplug_flag flags,
183    int vendor_id, int product_id, int dev_class,
184    libusb_hotplug_callback_fn cb_fn, void *user_data,
185    libusb_hotplug_callback_handle *phandle)
186{
187	libusb_hotplug_callback_handle handle;
188	struct libusb_device *adev;
189
190	ctx = GET_CONTEXT(ctx);
191
192	if (ctx == NULL || cb_fn == NULL || events == 0 ||
193	    vendor_id < -1 || vendor_id > 0xffff ||
194	    product_id < -1 || product_id > 0xffff ||
195	    dev_class < -1 || dev_class > 0xff)
196		return (LIBUSB_ERROR_INVALID_PARAM);
197
198	handle = malloc(sizeof(*handle));
199	if (handle == NULL)
200		return (LIBUSB_ERROR_NO_MEM);
201
202	HOTPLUG_LOCK(ctx);
203	if (ctx->hotplug_handler == NO_THREAD) {
204	  	libusb_hotplug_enumerate(ctx, &ctx->hotplug_devs);
205
206		if (pthread_create(&ctx->hotplug_handler, NULL,
207		    &libusb_hotplug_scan, ctx) != 0)
208			ctx->hotplug_handler = NO_THREAD;
209	}
210	handle->events = events;
211	handle->vendor = vendor_id;
212	handle->product = product_id;
213	handle->devclass = dev_class;
214	handle->fn = cb_fn;
215	handle->user_data = user_data;
216
217	if (flags & LIBUSB_HOTPLUG_ENUMERATE) {
218		TAILQ_FOREACH(adev, &ctx->hotplug_devs, hotplug_entry) {
219			if (libusb_hotplug_filter(ctx, handle, adev,
220			    LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) == 0)
221				continue;
222			free(handle);
223			handle = NULL;
224			break;
225		}
226	}
227	if (handle != NULL)
228		TAILQ_INSERT_TAIL(&ctx->hotplug_cbh, handle, entry);
229	HOTPLUG_UNLOCK(ctx);
230
231	if (phandle != NULL)
232		*phandle = handle;
233	return (LIBUSB_SUCCESS);
234}
235
236void libusb_hotplug_deregister_callback(libusb_context *ctx,
237    libusb_hotplug_callback_handle handle)
238{
239  	ctx = GET_CONTEXT(ctx);
240
241	if (ctx == NULL || handle == NULL)
242		return;
243
244	HOTPLUG_LOCK(ctx);
245	TAILQ_REMOVE(&ctx->hotplug_cbh, handle, entry);
246	HOTPLUG_UNLOCK(ctx);
247
248	free(handle);
249}
250