Skip to content

Commit 4abd0e0

Browse files
committed
macOS: detach/attach drivers
1 parent 4500bb4 commit 4abd0e0

File tree

14 files changed

+536
-353
lines changed

14 files changed

+536
-353
lines changed

java-does-usb/jextract/macos/gen_macos.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ $JEXTRACT --source --output ../../src/main/java \
5252
--include-function IORegistryEntryGetRegistryEntryID \
5353
--include-function IOServiceAddMatchingNotification \
5454
--include-function IOServiceMatching \
55-
--include-struct IOUSBDeviceStruct182 \
56-
--include-typedef IOUSBDeviceInterface182 \
55+
--include-struct IOUSBDeviceStruct187 \
56+
--include-typedef IOUSBDeviceInterface187 \
5757
--include-constant kIOUSBFindInterfaceDontCare \
5858
--include-typedef IOUSBFindInterfaceRequest \
5959
--include-typedef IOUSBDevRequest \
@@ -62,6 +62,8 @@ $JEXTRACT --source --output ../../src/main/java \
6262
--include-constant kIOUSBTransactionTimeout \
6363
--include-constant kIOReturnAborted \
6464
--include-constant kIOUSBPipeStalled \
65+
--include-constant kUSBReEnumerateCaptureDeviceMask \
66+
--include-constant kUSBReEnumerateReleaseDeviceMask \
6567
iokit_helper.h
6668

6769
# mach.h

java-does-usb/pom.xml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>net.codecrete.usb</groupId>
88
<artifactId>java-does-usb</artifactId>
9-
<version>0.5.0</version>
9+
<version>0.5.1</version>
1010

1111
<properties>
1212
<maven.compiler.source>20</maven.compiler.source>
@@ -54,7 +54,7 @@
5454
<plugin>
5555
<groupId>org.apache.maven.plugins</groupId>
5656
<artifactId>maven-compiler-plugin</artifactId>
57-
<version>3.10.1</version>
57+
<version>3.11.0</version>
5858
<configuration>
5959
<release>20</release>
6060
<compilerArgs>
@@ -67,15 +67,15 @@
6767
<plugin>
6868
<groupId>org.apache.maven.plugins</groupId>
6969
<artifactId>maven-surefire-plugin</artifactId>
70-
<version>3.0.0-M7</version>
70+
<version>3.1.2</version>
7171
<configuration>
7272
<argLine>--enable-preview --enable-native-access=ALL-UNNAMED</argLine>
7373
</configuration>
7474
</plugin>
7575
<plugin>
7676
<groupId>org.apache.maven.plugins</groupId>
7777
<artifactId>maven-javadoc-plugin</artifactId>
78-
<version>3.4.1</version>
78+
<version>3.5.0</version>
7979
<executions>
8080
<execution>
8181
<id>attach-javadocs</id>
@@ -93,7 +93,7 @@
9393
<plugin>
9494
<groupId>org.apache.maven.plugins</groupId>
9595
<artifactId>maven-gpg-plugin</artifactId>
96-
<version>3.0.1</version>
96+
<version>3.1.0</version>
9797
<executions>
9898
<execution>
9999
<id>sign-artifacts</id>
@@ -107,7 +107,7 @@
107107
<plugin>
108108
<groupId>org.apache.maven.plugins</groupId>
109109
<artifactId>maven-source-plugin</artifactId>
110-
<version>3.2.1</version>
110+
<version>3.3.0</version>
111111
<executions>
112112
<execution>
113113
<id>attach-sources</id>
@@ -140,7 +140,7 @@
140140
<dependency>
141141
<groupId>org.junit.jupiter</groupId>
142142
<artifactId>junit-jupiter</artifactId>
143-
<version>5.9.0</version>
143+
<version>5.9.3</version>
144144
<scope>test</scope>
145145
</dependency>
146146
</dependencies>

java-does-usb/src/main/java/net/codecrete/usb/USBDevice.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,56 @@ public interface USBDevice {
9999
*/
100100
Version deviceVersion();
101101

102+
/**
103+
* Detaches the standard operating-system drivers of this device.
104+
* <p>
105+
* By detaching the standard drivers, the operating system releases the exclusive access to the device
106+
* and/or some or all of the device's interfaces. This allows the application to open the device and claim
107+
* interfaces. It is relevant for device and interfaces implementing standard USB classes, such as HID, CDC
108+
* and mass storage.
109+
* </p>
110+
* <p>
111+
* This method should be called before the device is opened. After the device has been closed,
112+
* {@link #attachStandardDrivers()} should be called to restore the previous state.
113+
* </p>
114+
* <p>
115+
* On macOS, all device drivers are immediately detached from the device. To execute it, the application must
116+
* be run as <i>root</i>. Without <i>root</i> privileges, the method does nothing.
117+
* </p>
118+
* <p>
119+
* On Linux, this method changes the behavior of {@link #claimInterface(int)} for this device. The standard drivers
120+
* will be detached interface by interface when the interface is claimed.
121+
* </p>
122+
* <p>
123+
* On Windows, this method does nothing. It is not possible to temporarily change the drivers.
124+
* </p>
125+
*/
126+
void detachStandardDrivers();
127+
128+
/**
129+
* Reattaches the standard operating-system drivers to this device.
130+
* <p>
131+
* By attaching the standard drivers, the operating system claims the device and/or its interfaces if they
132+
* implement standard USB classes, such as HID, CDC and mass storage. It is used to restore the state before
133+
* calling {@link #detachStandardDrivers()}.
134+
* </p>
135+
* <p>
136+
* This method should be called after the device has been closed.
137+
* </p>
138+
* <p>
139+
* On macOS, the application must be run as <i>root</i>. Without <i>root</i> privileges, the method does nothing.
140+
* </p>
141+
* <p>
142+
* On Linux, this method changes the behavior of {@link #claimInterface(int)}. Standard drivers will no longer be
143+
* detached when the interface is claimed. Standard drivers are automatically reattached when the interfaces
144+
* are released, at the lasted when the device is closed.
145+
* </p>
146+
* <p>
147+
* On Windows, this method does nothing.
148+
* </p>
149+
*/
150+
void attachStandardDrivers();
151+
102152
/**
103153
* Opens the device for communication.
104154
*/

java-does-usb/src/main/java/net/codecrete/usb/common/USBDeviceImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ protected USBDeviceImpl(Object id, int vendorId, int productId) {
6161
pid = productId;
6262
}
6363

64+
@Override
65+
public void detachStandardDrivers() {
66+
// Default implementation: do nothing
67+
}
68+
69+
@Override
70+
public void attachStandardDrivers() {
71+
// Default implementation: do nothing
72+
}
73+
6474
@Override
6575
public abstract void open();
6676

java-does-usb/src/main/java/net/codecrete/usb/linux/LinuxUSBDevice.java

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public class LinuxUSBDevice extends USBDeviceImpl {
4343

4444
private final LinuxAsyncTask asyncTask;
4545

46+
private boolean detachDrivers = false;
47+
4648
LinuxUSBDevice(Object id, int vendorId, int productId) {
4749
super(id, vendorId, productId);
4850
asyncTask = LinuxAsyncTask.instance();
@@ -64,6 +66,16 @@ private void loadDescription(String path) {
6466
setConfigurationDescriptor(descriptorsSegment.asSlice(DeviceDescriptor.LAYOUT.byteSize()));
6567
}
6668

69+
@Override
70+
public void detachStandardDrivers() {
71+
detachDrivers = true;
72+
}
73+
74+
@Override
75+
public void attachStandardDrivers() {
76+
detachDrivers = false;
77+
}
78+
6779
@Override
6880
public boolean isOpen() {
6981
return fd != -1;
@@ -113,14 +125,22 @@ public synchronized void claimInterface(int interfaceNumber) {
113125

114126
try (var arena = Arena.openConfined()) {
115127
var errnoState = arena.allocate(Linux.ERRNO_STATE.layout());
116-
117-
// claim interface (possibly disconnecting kernel driver)
118-
var disconnectClaim = usbdevfs_disconnect_claim.allocate(arena);
119-
usbdevfs_disconnect_claim.interface_$set(disconnectClaim, interfaceNumber);
120-
usbdevfs_disconnect_claim.flags$set(disconnectClaim, usbdevice_fs.USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER());
121-
byte[] driverName = { 'u', 's', 'b', 'f', 's', 0 };
122-
usbdevfs_disconnect_claim.driver$slice(disconnectClaim).copyFrom(MemorySegment.ofArray(driverName));
123-
int ret = IO.ioctl(fd, USBDevFS.DISCONNECT_CLAIM, disconnectClaim, errnoState);
128+
int ret;
129+
130+
if (detachDrivers) {
131+
// claim interface (detaching kernel driver)
132+
var disconnectClaim = usbdevfs_disconnect_claim.allocate(arena);
133+
usbdevfs_disconnect_claim.interface_$set(disconnectClaim, interfaceNumber);
134+
usbdevfs_disconnect_claim.flags$set(disconnectClaim, usbdevice_fs.USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER());
135+
byte[] driverName = {'u', 's', 'b', 'f', 's', 0};
136+
usbdevfs_disconnect_claim.driver$slice(disconnectClaim).copyFrom(MemorySegment.ofArray(driverName));
137+
ret = IO.ioctl(fd, USBDevFS.DISCONNECT_CLAIM, disconnectClaim, errnoState);
138+
139+
} else {
140+
// claim interface (without detaching kernel driver)
141+
var intfNumSegment = arena.allocate(JAVA_INT, interfaceNumber);
142+
ret = IO.ioctl(fd, USBDevFS.CLAIMINTERFACE, intfNumSegment, errnoState);
143+
}
124144

125145
if (ret != 0)
126146
throwLastError(errnoState, "Cannot claim USB interface");
@@ -176,12 +196,14 @@ public synchronized void releaseInterface(int interfaceNumber) {
176196

177197
setClaimed(interfaceNumber, false);
178198

179-
// reattach kernel driver
180-
var request = usbdevfs_ioctl.allocate(arena);
181-
usbdevfs_ioctl.ifno$set(request, interfaceNumber);
182-
usbdevfs_ioctl.ioctl_code$set(request, USBDevFS.CONNECT);
183-
usbdevfs_ioctl.data$set(request, MemorySegment.NULL);
184-
IO.ioctl(fd, USBDevFS.IOCTL, request, errnoState);
199+
if (detachDrivers) {
200+
// reattach kernel driver
201+
var request = usbdevfs_ioctl.allocate(arena);
202+
usbdevfs_ioctl.ifno$set(request, interfaceNumber);
203+
usbdevfs_ioctl.ioctl_code$set(request, USBDevFS.CONNECT);
204+
usbdevfs_ioctl.data$set(request, MemorySegment.NULL);
205+
IO.ioctl(fd, USBDevFS.IOCTL, request, errnoState);
206+
}
185207
}
186208
}
187209

java-does-usb/src/main/java/net/codecrete/usb/linux/USBDevFS.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
public class USBDevFS {
1414

15+
public static final long CLAIMINTERFACE = 0x8004550FL;
1516
public static final long RELEASEINTERFACE = 0x80045510L;
1617
public static final long SETINTERFACE = 0x80085504L;
1718
public static final long CLEAR_HALT = 0x80045515L;

java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ public class IoKitHelper {
2828
public static final MemorySegment kIOUSBInterfaceUserClientTypeID = UUID.CreateCFUUID(new byte[]{(byte) 0x2d,
2929
(byte) 0x97, (byte) 0x86, (byte) 0xc6, (byte) 0x9e, (byte) 0xf3, (byte) 0x11, (byte) 0xD4, (byte) 0xad,
3030
(byte) 0x51, (byte) 0x00, (byte) 0x0a, (byte) 0x27, (byte) 0x05, (byte) 0x28, (byte) 0x61});
31-
public static final MemorySegment kIOUSBDeviceInterfaceID182 = UUID.CreateCFUUID(new byte[]{(byte) 0x15,
32-
(byte) 0x2f, (byte) 0xc4, (byte) 0x96, (byte) 0x48, (byte) 0x91, (byte) 0x11, (byte) 0xD5, (byte) 0x9d,
33-
(byte) 0x52, (byte) 0x00, (byte) 0x0a, (byte) 0x27, (byte) 0x80, (byte) 0x1e, (byte) 0x86});
31+
public static final MemorySegment kIOUSBDeviceInterfaceID187 = UUID.CreateCFUUID(new byte[]{(byte) 0x3c,
32+
(byte) 0x9e, (byte) 0xe1, (byte) 0xeb, (byte) 0x24, (byte) 0x02, (byte) 0x11, (byte) 0xb2, (byte) 0x8e,
33+
(byte) 0x7e, (byte) 0x00, (byte) 0x0a, (byte) 0x27, (byte) 0x80, (byte) 0x1e, (byte) 0x86});
3434
public static final MemorySegment kIOUSBInterfaceInterfaceID190 = UUID.CreateCFUUID(new byte[]{(byte) 0x8f,
3535
(byte) 0xdb, (byte) 0x84, (byte) 0x55, (byte) 0x74, (byte) 0xa6, (byte) 0x11, (byte) 0xD6, (byte) 0x97,
3636
(byte) 0xb1, (byte) 0x00, (byte) 0x30, (byte) 0x65, (byte) 0xd3, (byte) 0x60, (byte) 0x8e});

java-does-usb/src/main/java/net/codecrete/usb/macos/IoKitUSB.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
package net.codecrete.usb.macos;
99

1010
import net.codecrete.usb.common.ForeignMemory;
11-
import net.codecrete.usb.macos.gen.iokit.IOUSBDeviceInterface182;
11+
import net.codecrete.usb.macos.gen.iokit.IOUSBDeviceInterface187;
1212
import net.codecrete.usb.macos.gen.iokit.IOUSBInterfaceInterface190;
1313

1414
import java.lang.foreign.MemorySegment;
@@ -29,60 +29,65 @@ private static MemorySegment getVtable(MemorySegment self) {
2929

3030
// HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *thisPointer, REFIID iid, LPVOID *ppv);
3131
public static int QueryInterface(MemorySegment self, MemorySegment iid, MemorySegment ppv) {
32-
return IOUSBDeviceInterface182.QueryInterface(getVtable(self), SegmentScope.global()).apply(self, iid, ppv);
32+
return IOUSBDeviceInterface187.QueryInterface(getVtable(self), SegmentScope.global()).apply(self, iid, ppv);
3333
}
3434

3535
// ULONG (STDMETHODCALLTYPE *AddRef)(void *thisPointer);
3636
public static int AddRef(MemorySegment self) {
37-
return IOUSBDeviceInterface182.AddRef(getVtable(self), SegmentScope.global()).apply(self);
37+
return IOUSBDeviceInterface187.AddRef(getVtable(self), SegmentScope.global()).apply(self);
3838
}
3939

4040
// ULONG (STDMETHODCALLTYPE *Release)(void *thisPointer)
4141
public static int Release(MemorySegment self) {
42-
return IOUSBDeviceInterface182.Release(getVtable(self), SegmentScope.global()).apply(self);
42+
return IOUSBDeviceInterface187.Release(getVtable(self), SegmentScope.global()).apply(self);
4343
}
4444

4545
// IOReturn (* CreateDeviceAsyncEventSource)(void* self, CFRunLoopSourceRef* source);
4646
public static int CreateDeviceAsyncEventSource(MemorySegment self, MemorySegment source) {
47-
return IOUSBDeviceInterface182.CreateDeviceAsyncEventSource(getVtable(self), SegmentScope.global()).apply(self, source);
47+
return IOUSBDeviceInterface187.CreateDeviceAsyncEventSource(getVtable(self), SegmentScope.global()).apply(self, source);
4848
}
4949

5050
// CFRunLoopSourceRef (* GetDeviceAsyncEventSource)(void* self);
5151
public static MemorySegment GetDeviceAsyncEventSource(MemorySegment self) {
52-
return IOUSBDeviceInterface182.GetDeviceAsyncEventSource(getVtable(self), SegmentScope.global()).apply(self);
52+
return IOUSBDeviceInterface187.GetDeviceAsyncEventSource(getVtable(self), SegmentScope.global()).apply(self);
5353
}
5454

5555
// IOReturn (*USBDeviceOpenSeize)(void *self);
5656
public static int USBDeviceOpenSeize(MemorySegment self) {
57-
return IOUSBDeviceInterface182.USBDeviceOpenSeize(getVtable(self), SegmentScope.global()).apply(self);
57+
return IOUSBDeviceInterface187.USBDeviceOpenSeize(getVtable(self), SegmentScope.global()).apply(self);
5858
}
5959

6060
// IOReturn (*USBDeviceClose)(void *self);
6161
public static int USBDeviceClose(MemorySegment self) {
62-
return IOUSBDeviceInterface182.USBDeviceClose(getVtable(self), SegmentScope.global()).apply(self);
62+
return IOUSBDeviceInterface187.USBDeviceClose(getVtable(self), SegmentScope.global()).apply(self);
63+
}
64+
65+
// IOReturn (* USBDeviceReEnumerate)(void* self, UInt32 options);
66+
public static int USBDeviceReEnumerate(MemorySegment self, int options) {
67+
return IOUSBDeviceInterface187.USBDeviceReEnumerate(getVtable(self), SegmentScope.global()).apply(self, options);
6368
}
6469

6570
// IOReturn (*GetConfigurationDescriptorPtr)(void *self, UInt8 configIndex, IOUSBConfigurationDescriptorPtr *desc);
6671
public static int GetConfigurationDescriptorPtr(MemorySegment self, byte configIndex, MemorySegment descHolder) {
67-
return IOUSBDeviceInterface182.GetConfigurationDescriptorPtr(getVtable(self), SegmentScope.global()).apply(self, configIndex, descHolder);
72+
return IOUSBDeviceInterface187.GetConfigurationDescriptorPtr(getVtable(self), SegmentScope.global()).apply(self, configIndex, descHolder);
6873
}
6974

7075
// IOReturn (*SetConfiguration)(void *self, UInt8 configNum);
7176
public static int SetConfiguration(MemorySegment self, byte configValue) {
72-
return IOUSBDeviceInterface182.SetConfiguration(getVtable(self), SegmentScope.global()).apply(self,
77+
return IOUSBDeviceInterface187.SetConfiguration(getVtable(self), SegmentScope.global()).apply(self,
7378
configValue);
7479
}
7580

7681
// IOReturn (*CreateInterfaceIterator)(void *self, IOUSBFindInterfaceRequest *req, io_iterator_t *iter);
7782
public static int CreateInterfaceIterator(MemorySegment self, MemorySegment req, MemorySegment iter) {
78-
return IOUSBDeviceInterface182.CreateInterfaceIterator(getVtable(self), SegmentScope.global()).apply(self,
83+
return IOUSBDeviceInterface187.CreateInterfaceIterator(getVtable(self), SegmentScope.global()).apply(self,
7984
req, iter);
8085
}
8186

8287
// IOReturn (* DeviceRequestAsync)(void* self, IOUSBDevRequest* req, IOAsyncCallback1 callback, void* refCon);
8388
public static int DeviceRequestAsync(MemorySegment self, MemorySegment deviceRequest, MemorySegment callback,
8489
MemorySegment refCon) {
85-
return IOUSBDeviceInterface182.DeviceRequestAsync(getVtable(self), SegmentScope.global()).apply(self,
90+
return IOUSBDeviceInterface187.DeviceRequestAsync(getVtable(self), SegmentScope.global()).apply(self,
8691
deviceRequest, callback, refCon);
8792
}
8893

java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUSBDevice.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ public class MacosUSBDevice extends USBDeviceImpl {
6969
IoKitUSB.AddRef(device);
7070
}
7171

72+
@Override
73+
public void detachStandardDrivers() {
74+
int ret = IoKitUSB.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateCaptureDeviceMask());
75+
if (ret != 0)
76+
throwException(ret, "failed to detach kernel drivers");
77+
}
78+
79+
@Override
80+
public void attachStandardDrivers() {
81+
int ret = IoKitUSB.USBDeviceReEnumerate(device, IOKit.kUSBReEnumerateReleaseDeviceMask());
82+
if (ret != 0)
83+
throwException(ret, "failed to attach kernel drivers");
84+
}
85+
7286
@Override
7387
public boolean isOpen() {
7488
return claimedInterfaces != null;

java-does-usb/src/main/java/net/codecrete/usb/macos/MacosUSBDeviceRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private void iterateDevices(int iterator, IOKitDeviceConsumer consumer) {
118118
cleanup.add(() -> IOKit.IOObjectRelease(service));
119119

120120
var device = IoKitHelper.getInterface(service, IoKitHelper.kIOUSBDeviceUserClientTypeID,
121-
IoKitHelper.kIOUSBDeviceInterfaceID182);
121+
IoKitHelper.kIOUSBDeviceInterfaceID187);
122122
if (device != null)
123123
cleanup.add(() -> IoKitUSB.Release(device));
124124

0 commit comments

Comments
 (0)