Complete hotplug support, with full support for deregistration.

This commit is contained in:
Luca Longinotti 2013-07-17 15:24:45 +02:00
parent a66013a393
commit ef88482436
4 changed files with 140 additions and 67 deletions

View File

@ -26,3 +26,4 @@ m4
missing
ar-lib
compile
.cproject

View File

@ -1272,14 +1272,14 @@ static void LIBUSB_CALL triggerPollfdAdded(int fd, short events, void *user_data
{
THREAD_BEGIN(env)
jclass fdcls = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID constructor = (*env)->GetMethodID(env, fdcls, "<init>", "(I)V");
jobject object = (*env)->NewObject(env, fdcls, constructor, fd);
jclass fdCls = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID fdConstructor = (*env)->GetMethodID(env, fdCls, "<init>", "(I)V");
jobject fdObject = (*env)->NewObject(env, fdCls, fdConstructor, fd);
jclass cls = (*env)->FindClass(env, PACKAGE_DIR"/LibUsb");
jmethodID method = (*env)->GetStaticMethodID(env, cls,
"triggerPollfdAdded", "(Ljava/io/FileDescriptor;IJ)V");
(*env)->CallStaticVoidMethod(env, cls, method, object, (jint) events,
(*env)->CallStaticVoidMethod(env, cls, method, fdObject, (jint) events,
(jlong) (intptr_t) user_data);
THREAD_END
@ -1289,38 +1289,37 @@ static void LIBUSB_CALL triggerPollfdRemoved(int fd, void *user_data)
{
THREAD_BEGIN(env)
jclass fdcls = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID constructor = (*env)->GetMethodID(env, fdcls, "<init>", "(I)V");
jobject object = (*env)->NewObject(env, fdcls, constructor, fd);
jclass fdCls = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID fdConstructor = (*env)->GetMethodID(env, fdCls, "<init>", "(I)V");
jobject fdObject = (*env)->NewObject(env, fdCls, fdConstructor, fd);
jclass cls = (*env)->FindClass(env, PACKAGE_DIR"/LibUsb");
jmethodID method = (*env)->GetStaticMethodID(env, cls,
"triggerPollfdRemoved", "(Ljava/io/FileDescriptor;J)V");
(*env)->CallStaticVoidMethod(env, cls, method, object,
(*env)->CallStaticVoidMethod(env, cls, method, fdObject,
(jlong) (intptr_t) user_data);
THREAD_END
}
/**
* void setPollfdNotifiers(Context, long)
* void setPollfdNotifiersNative(Context, long)
*/
JNIEXPORT void JNICALL METHOD_NAME(LibUsb, setPollfdNotifiers)
JNIEXPORT void JNICALL METHOD_NAME(LibUsb, setPollfdNotifiersNative)
(
JNIEnv *env, jclass class, jobject context, jlong context_id
)
{
libusb_context *ctx = unwrapContext(env, context);
if (!ctx && context) return;
libusb_set_pollfd_notifiers(ctx, &triggerPollfdAdded, &triggerPollfdRemoved,
(void *) (intptr_t) context_id);
}
/**
* void unsetPollfdNotifiers(Context)
* void unsetPollfdNotifiersNative(Context)
*/
JNIEXPORT void JNICALL METHOD_NAME(LibUsb, unsetPollfdNotifiers)
JNIEXPORT void JNICALL METHOD_NAME(LibUsb, unsetPollfdNotifiersNative)
(
JNIEnv *env, jclass class, jobject context
)
@ -1409,62 +1408,55 @@ JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, cancelTransfer)
return libusb_cancel_transfer(transfer);
}
struct hotplug_data
{
jobject callbackObject;
jmethodID callbackObjectMethod;
jobject callbackUserDataObject;
};
static int LIBUSB_CALL hotplugCallback(libusb_context *ctx,
static int LIBUSB_CALL hotplugCallback(libusb_context *context,
libusb_device *device, libusb_hotplug_event event, void *user_data)
{
THREAD_BEGIN(env)
// TODO: check for return value and free memory appropriately.
jobject ctx = wrapContext(env, context);
jobject dev = wrapDevice(env, device);
jclass cls = (*env)->FindClass(env, PACKAGE_DIR"/LibUsb");
jmethodID method = (*env)->GetStaticMethodID(env, cls,
"hotplugCallback", "(L"PACKAGE_DIR"/Context;L"PACKAGE_DIR"/Device;IJ)I");
int result = (*env)->CallStaticIntMethod(env, cls, method, ctx, dev,
(jint) event, (jlong) (intptr_t) user_data);
THREAD_END
return result;
}
/**
* int hotplugRegisterCallback(Context, int, int, short, short, byte,
* HotplugCallback, Object, HotplugCallbackHandle)
* int hotplugRegisterCallbackNative(Context, int, int, short, short, byte,
* HotplugCallbackHandle, long)
*/
JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, hotplugRegisterCallback)
JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, hotplugRegisterCallbackNative)
(
JNIEnv *env, jclass class, jobject context, jint events, jint flags,
jshort vendorId, jshort productId, jbyte deviceClass,
jobject callback, jobject userData, jobject callbackHandle
jobject callbackHandle, jlong hotplugId
)
{
libusb_context *ctx = unwrapContext(env, context);
if (!ctx && context) return 0;
NOT_NULL(env, callback, return 0);
if (callbackHandle) {
if (callbackHandle != NULL)
{
// If callbackHandle is set, the Java object must be fresh/empty.
NOT_SET(env, callbackHandle, "hotplugCallbackHandleValue", return 0);
}
// Allocate memory to hold the references to the callback on the C side.
struct hotplug_data *hotplugData = calloc(1, sizeof(*hotplugData));
if (!hotplugData)
{
return LIBUSB_ERROR_NO_MEM;
}
// Setup the needed global references to the callback objects.
hotplugData->callbackObject = NULL;
hotplugData->callbackObjectMethod = NULL;
hotplugData->callbackUserDataObject = userData;
// Register the callback.
libusb_hotplug_callback_handle handle = 0;
libusb_hotplug_callback_handle handle;
int result = libusb_hotplug_register_callback(ctx, events, flags,
vendorId, productId, deviceClass, &hotplugCallback, hotplugData, &handle);
vendorId, productId, deviceClass, &hotplugCallback,
(void *) (intptr_t) hotplugId, &handle);
// If callbackHandle is set and registering was successful, we set the handle
// to the value we've gotten from libusb.
if (callbackHandle && (result == LIBUSB_SUCCESS)) {
if ((callbackHandle != NULL) && (result == LIBUSB_SUCCESS))
{
setHotplugCallbackHandle(env, handle, callbackHandle);
}
@ -1472,22 +1464,25 @@ JNIEXPORT jint JNICALL METHOD_NAME(LibUsb, hotplugRegisterCallback)
}
/*
* void hotplugDeregisterCallback(Context, HotplugCallbackHandle)
* long hotplugDeregisterCallbackNative(Context, HotplugCallbackHandle)
*/
JNIEXPORT void JNICALL METHOD_NAME(LibUsb, hotplugDeregisterCallback)
JNIEXPORT jlong JNICALL METHOD_NAME(LibUsb, hotplugDeregisterCallbackNative)
(
JNIEnv *env, jclass class, jobject context, jobject callbackHandle
)
{
libusb_context *ctx = unwrapContext(env, context);
if (!ctx && context) return;
NOT_NULL(env, callbackHandle, return);
if (!ctx && context) return 0;
NOT_NULL(env, callbackHandle, return 0);
libusb_hotplug_callback_handle handle =
unwrapHotplugCallbackHandle(env, callbackHandle);
if (!handle) return;
if (!handle) return 0;
// Deregister the callback.
libusb_hotplug_deregister_callback(ctx, handle);
resetHotplugCallbackHandle(env, callbackHandle);
return handle;
}

View File

@ -617,7 +617,15 @@ public final class LibUsb
public static final byte HOTPLUG_MATCH_ANY = -1;
/**
* pollfd listeners (to support different listeners for different contexts).
* Hotplug callbacks (to correctly manage calls and additional data).
*/
private static long globalHotplugId = 1;
private static final ConcurrentMap<Long, ImmutablePair<HotplugCallback, Object>> hotplugCallbacks =
new ConcurrentHashMap<Long, ImmutablePair<HotplugCallback, Object>>();
/**
* Pollfd listeners (to support different listeners for different contexts).
*/
private static final ConcurrentMap<Long, ImmutablePair<PollfdListener, Object>> pollfdListeners =
new ConcurrentHashMap<Long, ImmutablePair<PollfdListener, Object>>();
@ -2228,7 +2236,7 @@ public final class LibUsb
* User data to be passed back to callbacks (useful for passing
* context information).
*/
public static void setPollfdNotifiers(final Context context,
public static synchronized void setPollfdNotifiers(final Context context,
final PollfdListener listener, final Object userData)
{
long contextId;
@ -2244,13 +2252,13 @@ public final class LibUsb
if (listener == null)
{
unsetPollfdNotifiers(context);
unsetPollfdNotifiersNative(context);
pollfdListeners.remove(contextId);
}
else
{
setPollfdNotifiers(context, contextId);
setPollfdNotifiersNative(context, contextId);
pollfdListeners.put(contextId,
new ImmutablePair<PollfdListener, Object>(listener, userData));
@ -2309,7 +2317,7 @@ public final class LibUsb
* @param contextId
* A unique identifier for the given context.
*/
static native void setPollfdNotifiers(final Context context,
static native void setPollfdNotifiersNative(final Context context,
final long contextId);
/**
@ -2319,7 +2327,7 @@ public final class LibUsb
* @param context
* The context to operate on, or NULL for the default context
*/
static native void unsetPollfdNotifiers(final Context context);
static native void unsetPollfdNotifiersNative(final Context context);
/**
* Allocate a libusb transfer without support for isochronous transfers.
@ -2545,14 +2553,39 @@ public final class LibUsb
isoDescriptors[packet].length());
}
static int hotplugCallback(final Context context, final Device device,
final int event, final long hotplugId)
{
final ImmutablePair<HotplugCallback, Object> callback = hotplugCallbacks
.get(hotplugId);
int result = 0;
if (callback != null)
{
result = callback.left.processEvent(context, device, event,
callback.right);
}
// If callback indicates it is finished, it will get deregistered
// automatically. As such, we have to remove it from the Java
// map, like when deregistering manually.
if (result == 1)
{
hotplugCallbacks.remove(hotplugId);
}
return result;
}
/**
* Register a hotplug callback function.
*
* Register a callback with the {@link Context}. The callback will fire
* when a matching event occurs on a matching device. The callback is
* armed until either it is deregistered with
* {@link #hotplugDeregisterCallback(Context, HotplugCallbackHandle)}
* or the supplied callback returns 1 to indicate it is finished processing
* {@link #hotplugDeregisterCallback(Context, HotplugCallbackHandle)} or the
* supplied callback returns 1 to indicate it is finished processing
* events.
*
* @param context
@ -2577,11 +2610,37 @@ public final class LibUsb
*
* @return LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure
*/
public static native int hotplugRegisterCallback(final Context context,
public static synchronized int hotplugRegisterCallback(
final Context context, final int events, final int flags,
final short vendorId, final short productId, final byte deviceClass,
final HotplugCallback callback, final Object userData,
final HotplugCallbackHandle callbackHandle)
{
if (callback == null)
{
throw new IllegalArgumentException("callback must not be null");
}
final int result = hotplugRegisterCallbackNative(context, events,
flags, vendorId, productId, deviceClass, callbackHandle,
globalHotplugId);
if (result == LibUsb.SUCCESS)
{
hotplugCallbacks.put(globalHotplugId,
new ImmutablePair<HotplugCallback, Object>(callback, userData));
// Increment globalHotplugId by one, like the libusb handle.
globalHotplugId++;
}
return result;
}
static native int hotplugRegisterCallbackNative(final Context context,
final int events, final int flags, final short vendorId,
final short productId, final byte deviceClass,
final HotplugCallback callback, final Object userData,
final HotplugCallbackHandle callbackHandle);
final HotplugCallbackHandle callbackHandle, final long hotplugId);
/**
* Deregisters a hotplug callback.
@ -2594,6 +2653,24 @@ public final class LibUsb
* @param handle
* the handle of the callback to deregister
*/
public static native void hotplugDeregisterCallback(final Context context,
public static void hotplugDeregisterCallback(final Context context,
final HotplugCallbackHandle callbackHandle)
{
final long handle = hotplugDeregisterCallbackNative(context,
callbackHandle);
// When a handle is assigned by a register call, its value is the same
// as the one of globalHotplugId at that moment, which is what's used
// to identify data in the hotplugCallbacks map.
// This is because globalHotplugId pretty much mirrors the behavior of
// the handle: integer starting at 1, incremented each time by one.
// Problems could arise from concurrency, but are completely avoided by
// fully serializing register calls.
// As such, we can use the handle value from callbackHandle to
// correctly remove the data from the hotplugCallbacks map.
hotplugCallbacks.remove(handle);
}
static native long hotplugDeregisterCallbackNative(final Context context,
final HotplugCallbackHandle callbackHandle);
}

View File

@ -1160,27 +1160,27 @@ public class LibUSBTest
}
/**
* Tests {@link LibUsb#setPollfdNotifiers(Context, long)} with uninitialized
* USB context.
* Tests {@link LibUsb#setPollfdNotifiersNative(Context, long)}
* with uninitialized USB context.
*/
@Test(expected = IllegalStateException.class)
public void testSetPollfdNotifiersWithUninitializedContext()
{
assumeUsbTestsEnabled();
final Context context = new Context();
LibUsb.setPollfdNotifiers(context, context.getPointer());
LibUsb.setPollfdNotifiersNative(context, context.getPointer());
}
/**
* Tests {@link LibUsb#unsetPollfdNotifiers(Context)} with uninitialized USB
* context.
* Tests {@link LibUsb#unsetPollfdNotifiersNative(Context)} with
* uninitialized USB context.
*/
@Test(expected = IllegalStateException.class)
public void testUnsetPollfdNotifiersWithUninitializedContext()
{
assumeUsbTestsEnabled();
final Context context = new Context();
LibUsb.unsetPollfdNotifiers(context);
LibUsb.unsetPollfdNotifiersNative(context);
}
/**