Hi,
Is it possible for a macOS (or iOS/ipadOS) app to communicate with a CCID-compliant reader using the CCID interface (i.e., directly sending the PC_TO_RDR_* messages) instead of using the CryptoTokenKit API?
Apple's CCID driver (/System/Library/CryptoTokenKit/usbsmartcardreaderd.slotd) seems to support all the PC_TO_RDR and RDR_TO_PC messages: https://blog.apdu.fr/posts/2023/11/apple-own-ccid-driver-in-sonoma/#enable-my-ccid-driver
The background for this question is that we develop smartcard products and we'd like to use the finer grained settings provided by the CCID specification for testing/demo purposes.
Thank you.
Drivers
RSS for tagUnderstand the role of drivers in bridging the gap between software and hardware, ensuring smooth hardware functionality.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
While developing our driver, we've noticed that the *.ips report that contains the stacktrace of the crash is not always generated. I'm wondering why this report may not get generated, or if there's anything specific to do to guarantee it gets generated.
We've developed a PCIDriverKit driver for the capture card on macOS and have identified an issue: CreateMemoryDescriptorFromClient can only read data from the user space to the driver, but cannot write data back to the user.
typedef struct _MWCAP_EDID_DATA {
uint64_t size;
uint64_t uaddr;
} MWCAP_EDID_DATA;
// App
size_t xxx::GetEdid(void *buff, size_t size)
{
MWCAP_EDID_DATA edid;
edid.size = size;
edid.uaddr = (uint64_t)buff;
kr = IOConnectCallStructMethod(
connect,
kUserGetEdid,
&edid,
sizeof(MWCAP_EDID_DATA),
NULL,
NULL
);
// kr is 0. But However, the data in the buffer remains unchanged;
// it does not reflect the EDID copied from the DEXT.
return size;
}
// Driver
MWCAP_EDID_DATA *edid = (MWCAP_EDID_DATA *)input;
IOMemoryDescriptor *user_buf_mem = NULL;
IOAddressSegment segment;
segment.address = edid->uaddr;
segment.length = edid->size;
// We have verified that the values in edid->uaddr and edid->size are consistent with what was set by the application.
ret = CreateMemoryDescriptorFromClient(kIOMemoryDirectionOutIn, 1, &segment, &user_buf_mem);
if (ret != kIOReturnSuccess) {
os_log(OS_LOG_DEFAULT, "Failed to create memdesc with error: 0x%08x", ret);
break;
}
IOMemoryMap* user_buf_map = nullptr;
ret = user_buf_mem->CreateMapping(0, 0, 0, 0, 0, &user_buf_map);
if (ret != kIOReturnSuccess) {
os_log(OS_LOG_DEFAULT, "Failed to create mapping with error: 0x%08x", ret);
OSSafeReleaseNULL(user_buf_mem);
break;
}
// ... fill the user_buf_map with edid data ...
// For example:
// memcpy(user_buf_map->GetAddress(), source_edid_data, edid->size);
// At this point we have also verified the data in user_buf_map->GetAddress(), which matches our expectations.
OSSafeReleaseNULL(user_buf_map);
OSSafeReleaseNULL(user_buf_mem);
Please help take a look, thank you!
I want to turn off this effect and maintain the style before iOS18. Do I need to add content about iOS26 to the info.plist? Specifically, which content is it? I want to turn off the glass effect on the button
Topic:
App & System Services
SubTopic:
Drivers
Hi everyone,
We are in the process of migrating a legacy KEXT for our external multi-disk RAID enclosure to the modern DriverKit framework. During the performance validation of our KEXT, we observed a large and consistent maximum throughput difference between Intel-based Macs and Apple Silicon-based Macs. We would like to share our findings and hope to discuss with others in the community to see if you have had similar experiences that could confirm or correct our understanding.
The Observation: A Consistent Performance Gap
When using the exact same external RAID hardware (an 8-HDD RAID 5 array), driven by our mature KEXT, we see the following results in high-throughput benchmarks (AJA System Test, large sequential writes):
On a 2020 Intel-based Mac: We consistently achieve a throughput of ~2500 MB/s.
On modern M-series Macs (from M1 to M4): The throughput is consistently capped at ~1500 MB/s.
This performance difference of nearly 40% is significant and is present across the entire Apple Silicon product line.
Our Hypothesis: A Shift in Architectural Design Philosophy
Since the KEXT and external hardware are identical in both tests, we believe this performance difference is not a bug but a fundamental platform architecture distinction. Our hypothesis is as follows:
1. The Intel Mac Era ("Dedicated Throughput")
The Intel-based Macs we tested use a dedicated, discrete Intel Thunderbolt controller chip. This chip has its own dedicated PCIe lanes and resources, and its design appears to be singularly focused on maximizing raw, sustained data throughput for external peripherals.
2. The Apple Silicon Era ("Integrated Efficiency")
In contrast, M-series Macs use a deeply integrated I/O controller inside the SoC. This controller must share resources, such as the total unified memory bandwidth and the chip's overall power budget, with all other functional units (CPU, GPU, etc.).
We speculate that the design priority for this integrated I/O controller has shifted from "maximizing single-task raw throughput" to "maximizing overall system efficiency, multi-task responsiveness, and low latency." As a result, in a pure, single-task storage benchmark, its performance ceiling may be lower than that of the older, dedicated-chip architecture.
Our Question to the Community:
Is our understanding correct? Have other developers of high-performance storage drivers or peripherals also observed a similar performance ceiling for external storage on Apple Silicon Macs, when compared to high-end Intel Macs?
We believe that understanding this as a deliberate architectural trade-off is crucial for setting realistic performance targets for our DEXT. Our current goal has been adjusted to have our DEXT match the KEXT's ~1500 MB/s on the M-series platform.
Any insights, confirmations, or corrections from the community or Apple engineers would be greatly appreciated.
Thank you very much!
Charles
I am developing a DriverKit driver with the goal of sending vendor-specific commands to a USB storage device.
I have successfully created the DriverKit driver, and when I connect the USB storage device, it appears correctly in IORegistryExplorer.
My driver class inherits from IOUserSCSIPeripheralDeviceType00 in the SCSIPeripheralsDriverKit framework.
I also created a UserClient class that inherits from IOUserClient, and from its ExternalMethod I tried sending an INQUIRY command as a basic test to confirm that command transmission works.
However, the device returns an ILLEGAL REQUEST (Sense Key 0x5 / ASC 0x20).
Could someone advise what I might be doing wrong?
Below are the logs output from the driver:
2025-11-14 21:00:43.573730+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] Driver - NewUserClient() - Finished.
2025-11-14 21:00:43.573733+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - Start()
2025-11-14 21:00:43.573807+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - Start() - Finished.
2025-11-14 21:00:43.574249+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - ExternalMethod() called
2025-11-14 21:00:43.574258+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - ----- SCSICmdINQUIRY -----
2025-11-14 21:00:43.574268+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - command.fRequestedByteCountOfTransfer = 512
2025-11-14 21:00:43.575980+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB fCompletionStatus = 0x0
2025-11-14 21:00:43.575988+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB fServiceResponse = 0x2
2025-11-14 21:00:43.575990+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB fSenseDataValid = 0x1
2025-11-14 21:00:43.575992+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB VALID_RESPONSE_CODE = 0x70
2025-11-14 21:00:43.575994+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB SENSE_KEY = 0x5
2025-11-14 21:00:43.575996+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB ADDITIONAL_SENSE_CODE = 0x20
2025-11-14 21:00:43.575998+0900 0x26e9 Default 0x0 0 0 kernel: (SampleDriverKitApp.SampleDriverKitDriver.dext) [DEBUG] UserClient - SCSICmdINQUIRY() UserSendCDB ADDITIONAL_SENSE_CODE_QUALIFIER = 0x0
Here is the UserClient class:
class SampleDriverKitUserClient: public IOUserClient
{
public:
virtual bool init(void) override;
virtual kern_return_t Start(IOService* provider) override;
virtual kern_return_t Stop(IOService* provider) override;
virtual void free(void) override;
virtual kern_return_t ExternalMethod(
uint64_t selector,
IOUserClientMethodArguments* arguments,
const IOUserClientMethodDispatch* dispatch,
OSObject* target,
void* reference) override;
void SCSICmdINQUIRY(SampleDriverKitDriver *driver) LOCALONLY;
};
Here is the part that sends the INQUIRY command:
void SampleDriverKitUserClient::SCSICmdINQUIRY(SampleDriverKitDriver *driver)
{
kern_return_t kr = KERN_SUCCESS;
SCSIType00OutParameters command = {};
UInt8 dataBuffer[512] = {0};
SCSI_Sense_Data senseData = {0};
Log("----- SCSICmdINQUIRY -----");
SetCommandCDB(&command.fCommandDescriptorBlock, 0x12, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
command.fLogicalUnitNumber = 0;
command.fTimeoutDuration = 10000; // milliseconds
command.fRequestedByteCountOfTransfer = sizeof(dataBuffer);
Log("command.fRequestedByteCountOfTransfer = %lld", command.fRequestedByteCountOfTransfer);
command.fBufferDirection = kIOMemoryDirectionIn;
command.fDataTransferDirection = kSCSIDataTransfer_FromTargetToInitiator;
command.fDataBufferAddr = reinterpret_cast<uint64_t>(dataBuffer);
command.fSenseBufferAddr = reinterpret_cast<uint64_t>(&senseData);
command.fSenseLengthRequested = sizeof(senseData);
if( driver ) {
SCSIType00InParameters response = {};
kr = driver->UserSendCDB(command, &response);
if( kr != KERN_SUCCESS ) {
Log("SCSICmdINQUIRY() UserSendCDB failed (0x%x)", kr);
return;
}
Log("SCSICmdINQUIRY() UserSendCDB fCompletionStatus = 0x%x", response.fCompletionStatus);
Log("SCSICmdINQUIRY() UserSendCDB fServiceResponse = 0x%x", response.fServiceResponse);
Log("SCSICmdINQUIRY() UserSendCDB fSenseDataValid = 0x%x", response.fSenseDataValid);
Log("SCSICmdINQUIRY() UserSendCDB VALID_RESPONSE_CODE = 0x%x", senseData.VALID_RESPONSE_CODE);
Log("SCSICmdINQUIRY() UserSendCDB SENSE_KEY = 0x%x", senseData.SENSE_KEY);
Log("SCSICmdINQUIRY() UserSendCDB ADDITIONAL_SENSE_CODE = 0x%x", senseData.ADDITIONAL_SENSE_CODE);
Log("SCSICmdINQUIRY() UserSendCDB ADDITIONAL_SENSE_CODE_QUALIFIER = 0x%x", senseData.ADDITIONAL_SENSE_CODE_QUALIFIER);
if( response.fServiceResponse == kSCSIServiceResponse_TASK_COMPLETE ) {
Log("SCSICmdINQUIRY() UserSendCDB complete success!!");
}
for( int i=0; i < 5; i++ ) {
Log("data [%04d]=0x%x [%04d]=0x%x [%04d]=0x%x [%04d]=0x%x [%04d]=0x%x [%04d]=0x%x [%04d]=0x%x [%04d]=0x%x",
i*8+0, dataBuffer[i*8+0],
i*8+1, dataBuffer[i*8+1],
i*8+2, dataBuffer[i*8+2],
i*8+3, dataBuffer[i*8+3],
i*8+4, dataBuffer[i*8+4],
i*8+5, dataBuffer[i*8+5],
i*8+6, dataBuffer[i*8+6],
i*8+7, dataBuffer[i*8+7] );
}
char vendorID[9] = {0};
memcpy(vendorID, &dataBuffer[8], 8);
Log("vendorID = %s",vendorID);
char productID[17] = {0};
memcpy(productID, &dataBuffer[16], 16);
Log("productID = %s",productID);
}
}
My environment is:
MacBook Pro (M2), macOS 15.6
If anyone has insight into what causes the ILLEGAL REQUEST, or what I am missing when using IOUserSCSIPeripheralDeviceType00 and UserSendCDB, I would greatly appreciate your help.
Thank you.
Topic:
App & System Services
SubTopic:
Drivers
Tags:
SCSIControllerDriverKit
DriverKit
BlockStorageDeviceDriverKit
Hello
I'm testing an 'age range sharing' feature using the AgeRangeService API in an app we service.
I approved 'Age Sharing' during testing.
(For your information, my account is an adult account.)
For repeat testing, I would like to delete the our app from 'Apps that requested user age information' or cancel the sharing status.
However, there doesn't seem to be such a feature.
Is there a way I can't find, or is this a feature that Apple doesn't offer?
We are developing a DriverKit driver on Apple M1. We use the following code to prepare DMA buffer:
IODMACommandSpecification dmaSpecification;
bzero(&dmaSpecification, sizeof(dmaSpecification));
dmaSpecification.options = kIODMACommandSpecificationNoOptions;
dmaSpecification.maxAddressBits = p_dma_mgr->maxAddressBits;
kret = IODMACommand::Create(p_dma_mgr->device,
kIODMACommandCreateNoOptions,
&dmaSpecification,
&impl->dma_cmd
);
if (kret != kIOReturnSuccess) {
os_log(OS_LOG_DEFAULT, "Error: IODMACommand::Create failed! ret=0x%x\n", kret);
impl->user_mem.reset();
IOFree(impl, sizeof(*impl));
return ret;
}
uint64_t flags = 0;
uint32_t segmentsCount = 32;
IOAddressSegment segments[32];
kret = impl->dma_cmd->PrepareForDMA(kIODMACommandPrepareForDMANoOptions,
impl->user_mem.get(),
0,
0, // 0 for entire memory
&flags,
&segmentsCount,
segments
);
if (kret != kIOReturnSuccess) {
OSSafeReleaseNULL(impl->dma_cmd);
impl->user_mem.reset();
IOFree(impl, sizeof(*impl));
os_log(OS_LOG_DEFAULT, "Error: PrepareForDMA failed! ret=0x%x\n", kret);
return kret;
}
I allocated several 8K BGRA video frames, each with a size of 141557760 bytes, and prepared the DMA according to the method mentioned above. The process was successful when the number of frames was 15 or fewer. However, issues arose when allocating 16 frames:
Error: PrepareForDMA failed! ret=0xe00002bd
By calculating, I found that the total size of 16 video frames exceeds 2GB. Is there such a limitation in DriverKit that the total DMA size cannot exceed 2GB? Are there any methods that would allow me to bypass this restriction so I can use more video frame buffers?
Kevin's Guide to DEXT Signing
The question of "How do I sign a DEXT" comes up a lot, so this post is my attempt to describe both what the issue are and the best current solutions are. So...
The Problems:
When DEXTs were originally introduced, the recommended development signing process required disabling SIP and local signing. There is a newer, much simpler process that's built on Xcode's integrated code-signing support; however, that newer process has not yet been integrated into the documentation library. In addition, while the older flow still works, many of the details it describes are no longer correct due to changes to Xcode and the developer portal.
DriverKit's use of individually customized entitlements is different than the other entitlements on our platform, and Xcode's support for it is somewhat incomplete and buggy. The situation has improved considerably over time, particularly from Xcode 15 and Xcode 16, but there are still issues that are not fully resolved.
To address #1, we introduced "development" entitlement variants of all DriverKit entitlements. These entitlement variants are ONLY available in development-signed builds, but they're available on all paid developer accounts without any special approval. They also allow a DEXT to match against any hardware, greatly simplifying working with development or prototype hardware which may not match the configuration of a final product.
Unfortunately, this also means that DEXT developers will always have at least two entitlement variants (the public development variant and the "private" approved entitlement), which is what then causes the problem I mentioned in #2.
The Automatic Solution:
If you're using Xcode 16 or above, then Xcode's Automatic code sign support will work all DEXT Families, with the exception of distribution signing the PCI and USB Families.
For completeness, here is how that Automatic flow should work:
Change the code signing configuration to "Automatic".
Add the capability using Xcode.
If you've been approved for one of these entitlements, the one oddity you'll see is that adding your approved capability will add both the approved AND the development variant, while deleting either will delete both. This is a visual side effect of #2 above; however, aside from the exception described below, it can be ignored.
Similarly, you can sign distribution builds by creating a build archive and then exporting the build using the standard Xcode flow.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware
Have a 2019 Ford Edge w/ Sync 3.4, wired carplay. Worked fine w/ iPhone 16 Pro on iOS 18. Upgraded to iPhone 17 Pro, came w/ iOS 26, carplay hasn't worked since.
I've kept trying throughout new iOS 26 releases, lately with iOS 26.3 Public Beta 1, still not working.
Have a long running issue with updates and system diagnostics as I've tried over the last few months: FB20739050
There is also a Apple support community thread with issues like this (and a ton of others) - my first post there was https://discussions.apple.com/thread/256138283?answerId=261613103022&sortBy=oldest_first#261613103022
I'm hoping here in the developer forums someone can maybe take a look at the feedback item and various system diagnostics to pin-point the issue. I'm a little concerned it's still not fixed this far into the follow-up point releases of iOS 26.
Appreciate any help, thanks!
--Chuck
Why hasn't the watchOS app updated immediately after the iPhone app was updated? It remains stuck in "Installing" status on the Apple Watch.
Topic:
App & System Services
SubTopic:
Drivers
Hello!
I have app (macos and iPadOS platforms) with empbedded DEXT. The DEXT executable runs fine on both platforms (ver 26.2).
Trying to execute from iPad App code:
let sysExtWs = OSSystemExtensionsWorkspace.shared
let sysExts = try sysExtWs.systemExtensions(forApplicationWithBundleID: appBudleId)
but always getting OSSystemExtensionError.Code.missingEntitlement error.
Which entitlement am I missing?
Thank You!
I am developing a macOS virtual audio device using an Audio Server Plug-In (HAL). I want the virtual device to be visible to all applications only when my main app is running, and completely hidden from all apps when the app is closed. The goal is to dynamically control device visibility based on app state without reinstalling the driver.What is the recommended way for the app to notify the HAL plug-in about its running or closed state ? Any guidance on best-practice architecture for this scenario would be appreciated.
Note: This document is specifically focused on what happens after a DEXT has passed its initial code-signing checks. Code-signing issues are dealt with in other posts.
Preliminary Guidance:
Using and understanding DriverKit basically requires understanding IOKit, something which isn't entirely clear in our documentation. The good news here is that IOKit actually does have fairly good "foundational" documentation in the documentation archive. Here are a few of the documents I'd take a look at:
IOKit Fundamentals
IOKit Device Driver Design Guidelines
Accessing Hardware From Applications
Special mention to QA1075: "Making sense of IOKit error codes",, which I happened to notice today and which documents the IOReturn error format (which is a bit weird on first review).
Those documents do not cover the full DEXT loading process, but they are the foundation of how all of this actually works.
Understanding the IOKitPersonalities Dictionary
The first thing to understand here is that the "IOKitPersonalities" is called that because it is in fact a fully valid "IOKitPersonalities" dictionary. That is, what the system actually uses that dictionary "for" is:
Perform a standard IOKit match and load cycle in the kernel.
The final driver in the kernel then uses the DEXT-specific data to launch and run your DEXT process outside the kernel.
So, working through the critical keys in that dictionary:
"IOProviderClass"-> This is the in-kernel class that your in-kernel driver loads "on top" of. The IOKit documentation and naming convention uses the term "Nub", but the naming convention is not consistent enough that it applies to all cases.
"IOClass"-> This is the in-kernel class that your driver loads on top of. This is where things can become a bit confused, as some families work by:
Routing all activity through the provider reference so that the DEXT-specific class does not matter (PCIDriverKit).
Having the DEXT subclass a specific subclass which corresponds to a specific kernel driver (SCSIPeripheralsDriverKit).
This distinction is described in the documentation, but it's easy to overlook if you don't understand what's going on. However, compare PCIDriverKit:
"When the system loads your custom PCI driver, it passes an IOPCIDevice object as the provider to your driver. Use that object to read and write the configuration and memory of your PCI hardware."
Versus SCSIPeripheralsDriverKit:
Develop your driver by subclassing IOUserSCSIPeripheralDeviceType00 or IOUserSCSIPeripheralDeviceType05, depending on whether your device works with SCSI Block Commands (SBC) or SCSI Multimedia Commands (SMC), respectively. In your subclass, override all methods the framework declares as pure virtual.
The reason these differences exist actually comes from the relationship and interactions between the DEXT families. Case in point, PCIDriverKit doesn't require a specific subclass because it wants SCSIControllerDriverKit DEXTs to be able to directly load "above" it.
Note that the common mistake many developers make is leaving "IOUserService" in place when they should have specified a family-specific subclass (case 2 above). This is an undocumented implementation detail, but if there is a mismatch between your DEXT driver ("IOUserSCSIPeripheralDeviceType00") and your kernel driver ("IOUserService"), you end up trying to call unimplemented kernel methods. When a method is "missing" like that, the codegen system ends up handling that by returning kIOReturnUnsupported.
One special case here is the "IOUserResources" provider. This class is the DEXT equivalent of "IOResources" in the kernel. In both cases, these classes exist as an attachment point for objects which don't otherwise have a provider. It's specifically used by the sample "Communicating between a DriverKit extension and a client app" to allow that sample to load on all hardware but is not something the vast majority of DEXT will use.
Following on from that point, most DEXT should NOT include "IOMatchCategory". Quoting IOKit fundamentals:
"Important: Any driver that declares IOResources as the value of its IOProviderClass key must also include in its personality the IOMatchCategory key and a private match category value. This prevents the driver from matching exclusively on the IOResources nub and thereby preventing other drivers from matching on it. It also prevents the driver from having to compete with all other drivers that need to match on IOResources. The value of the IOMatchCategory property should be identical to the value of the driver's IOClass property, which is the driver’s class name in reverse-DNS notation with underbars instead of dots, such as com_MyCompany_driver_MyDriver."
The critical point here is that including IOMatchCategory does this:
"This prevents the driver from matching exclusively on the IOResources nub and thereby preventing other drivers from matching on it."
The problem here is that this is actually the exceptional case. For a typical DEXT, including IOMatchCategory means that a system driver will load "beside" their DEXT, then open the provider blocking DEXT access and breaking the DEXT.
DEXT Launching
The key point here is that the entire process above is the standard IOKit loading process used by all KEXT. Once that process finishes, what actually happens next is the DEXT-specific part of this process:
IOUserServerName-> This key is the bundle ID of your DEXT, which the system uses to find your DEXT target.
IOUserClass-> This is the name of the class the system instantiates after launching your DEXT. Note that this directly mimics how IOKit loading works.
Keep in mind that the second, DEXT-specific, half of this process is the first point your actual code becomes relevant. Any issue before that point will ONLY be visible through kernel logging or possibly the IORegistry.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware
I'm developing a DriverKit USB driver for iPadOS that needs to communicate with a vendor-class USB device (bInterfaceClass = 0xFF) as I need to communicate with a USB device using a custom protocol over IOUSBHostPipe for bulk transfers.
Current Configuration:
Info.plist: IOProviderClass = IOUSBHostDevice
Device: bDeviceClass = 0, bInterfaceClass = 0xFF (vendor-specific)
What Works:
Driver matches and loads successfully
Start_Impl() executes
device->Open() succeeds
device->SetConfiguration() succeeds
The Problem:
uintptr_t iterRef = 0;
kern_return_t ret = device->CreateInterfaceIterator(&iterRef);
Result: ret = kIOReturnSuccess (0x0), but iterRef = 0 (empty iterator)
What I've Tried:
Matching IOUSBHostInterface directly - Driver is loaded, but extension never executed
Current approach (IOUSBHostDevice) - Driver extension loads and starts, but CreateInterfaceIterator returns empty
Question:
Does iPadOS allow third-party DriverKit extensions to access vendor-class (0xFF) USB devices? That is, iPadOS, is there a way for a third-party DriverKit extension to access IOUSBHostInterface objects for vendor-class (0xFF) USB devices?
Topic:
App & System Services
SubTopic:
Drivers
Since macOS 26.4 Beta 1, virtual HID devices created via DriverKit can no longer intercept key events from the built-in MacBook keyboard. External keyboards still work. This is confirmed and tracked here:
https://github.com/pqrs-org/Karabiner-Elements/issues/4402
One possible lead (from LLM-assisted analysis of Apple's open-source IOHIDFamily code and cross-referencing community reports): macOS 26.4 Beta may have introduced or modified a security policy referred to as com.apple.iohid.protectedDeviceAccess, which could block IOHIDDeviceOpen for the Apple Internal Keyboard connected via SPI transport (AppleHIDTransportHIDDevice). This appears related to a "GamePolicy" check in IOHIDDeviceClass.m that gates whether processes can open HID devices. This has not been independently verified and may or may not be the root cause.
This has far-reaching consequences. Karabiner-Elements alone has over 21,000 GitHub stars and is used by hundreds of thousands of macOS users for keyboard customization, accessibility workflows, ergonomic setups, and multilingual input. This change completely breaks its core functionality on any MacBook.
Beyond Karabiner, this affects every developer building keyboard remapping, input customization, or accessibility tooling via DriverKit virtual HID devices — including commercial applications currently in development.
I'd argue that the power and flexibility of keyboard customization on macOS is a genuine competitive advantage for the platform. Developers and power users choose Macs partly because tools like this exist.
Restricting this capability would be detrimental to the ecosystem and to Apple's appeal among professional users.
I'd like to understand: is this an intentional security change or a regression? If intentional, is there a migration path?
Topic:
App & System Services
SubTopic:
Drivers
I have a driver extending IOUserUSBSerial and I want the device to show up as /dev/tty.mycustombasename-123 and /dev/cu. respectively. How can I achieve that?
We are looking for a solution (API, Frameworks) that would allow us to block any type of external device, including storage devices, HIDs, network adapters, and Bluetooth devices according with dynamic rules that comes from management server . This feature is important for endpoint security solutions vendors, and it can be implemented on other platforms and older versions of macOS using the IOKit framework and kexts.
I have found one solution that can control the usage only of "storage" devices with the EndpointSecurity framework in conjunction with the DiskArbitration framework. This involves monitoring the MOUNT and OPEN events for /dev/disk files, checking for devices as they appear, and ejecting them if they need to be blocked.. Also, I have found the ES_EVENT_TYPE_AUTH_IOKIT_OPEN event in EndpointSecurity.framework, but it doesn't seem to be useful, at least not for my purposes, because ES doesn't provide AUTH events for some system daemons, such as configd (it only provides NOTIFY events). Furthermore, there are other ways to communicate with devices and their drivers apart from IOKit.
DriverKit.framework does not provide the necessary functionality either, as it requires specific entitlements that are only available to certain vendors and devices. Therefore, it cannot be used to create universal drivers for all devices, which should be blocked.
Any advice would be greatly appreciated!
Topic:
App & System Services
SubTopic:
Drivers
I am trying to add a few properties to an IOUSBHostDevice but the SetProperties is returning kIOReturnUnsupported. The reason I am trying to modify the IOUSBHostDevice's properties is so we can support a MacBook Air SuperDrive when it is attached to our docking station devices. The MacBook Air SuperDrive needs a high powered port to run and this driver will help the OS realize that our dock can support it.
I see that the documentation for SetProperties says:
The default implementation of this method returns kIOReturnUnsupported. You can override this method and use it to modify the set of properties and values as needed. The changes you make apply only to the current service.
Do I need to override IOUSBHostDevice? This is my current Start implementation (you can also see if in the Xcode project):
kern_return_t
IMPL(MyUserUSBHostDriver, Start)
{
kern_return_t ret = kIOReturnSuccess;
OSDictionary * prop = NULL;
OSDictionary * mergeProperties = NULL;
bool success = true;
os_log(OS_LOG_DEFAULT, "> %s", __FUNCTION__);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = Start(provider, SUPERDISPATCH);
__Require(kIOReturnSuccess == ret, Exit);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ivars->host = OSDynamicCast(IOUSBHostDevice, provider);
__Require_Action(NULL != ivars->host, Exit, ret = kIOReturnNoDevice);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = ivars->host->Open(this, 0, 0);
__Require(kIOReturnSuccess == ret, Exit);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = CopyProperties(&prop);
__Require(kIOReturnSuccess == ret, Exit);
__Require_Action(NULL != prop, Exit, ret = kIOReturnError);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
mergeProperties = OSDynamicCast(OSDictionary, prop->getObject("IOProviderMergeProperties"));
mergeProperties->retain();
__Require_Action(NULL != mergeProperties, Exit, ret = kIOReturnError);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
OSSafeReleaseNULL(prop);
ret = ivars->host->CopyProperties(&prop);
__Require(kIOReturnSuccess == ret, Exit);
__Require_Action(NULL != prop, Exit, ret = kIOReturnError);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
os_log(OS_LOG_DEFAULT, "%s : %s", "USB Product Name", ((OSString *) prop->getObject("USB Product Name"))->getCStringNoCopy());
os_log(OS_LOG_DEFAULT, "%s : %s", "USB Vendor Name", ((OSString *) prop->getObject("USB Vendor Name"))->getCStringNoCopy());
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
success = prop->merge(mergeProperties);
__Require_Action(success, Exit, ret = kIOReturnError);
os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = ivars->host->SetProperties(prop); // this is no working
__Require(kIOReturnSuccess == ret, Exit);
Exit:
OSSafeReleaseNULL(mergeProperties);
OSSafeReleaseNULL(prop);
os_log(OS_LOG_DEFAULT, "err ref %d", kIOReturnUnsupported);
os_log(OS_LOG_DEFAULT, "< %s %d", __FUNCTION__, ret);
return ret;
}
I read that iPadOS supports driverkit, and, presumably, the same serial FTDI UARTs as macOS.
Has this been migrated to USB-C iPhones on iOS 18?
After some searching, the developer doc is not clear, and web responses are contradictory.
We are currently using it for a wired sensor option of our BlueTooth HR sensor. When it is used in wired config, the radios are turned off. This is important to some of our customers. Since Lightning MFI sensors are being discontinued with Apple killing Lightning, we would love to have an alternative for iOS.
-- Harald