Understand the role of drivers in bridging the gap between software and hardware, ensuring smooth hardware functionality.

Drivers Documentation

Posts under Drivers subtopic

Post

Replies

Boosts

Views

Activity

INQUIRY command is ILLEGAL REQUEST
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.
1
0
50
Nov ’25
UserSendCDB fails due to permissions
I created a custom class that inherits from IOUserSCSIPeripheralDeviceType00 in the DriverKit SCSIPeripheralsDriverKit framework. When I attempted to send a vendor-specific command to a USB storage device using the UserSendCDB function of this class instance, the function returned the error: kIOReturnNotPrivileged (iokit_common_err(0x2c1)) // privilege violation However, when using UserSendCDB in the same way to issue standard SCSI commands such as INQUIRY or Test Unit Ready, no error occurred and the returned sense data was valid. Why is UserSendCDB able to send standard SCSI commands successfully, but vendor-specific commands return kIOReturnNotPrivileged? Is there any required entitlement, DriverKit capability, or implementation detail needed to allow vendor-specific CDBs? Below are the entitlements of my DriverKit extension: <dict> <key>com.apple.developer.driverkit.transport.usb</key> <array> <dict> <key>idVendor</key> <integer>[number of vendorid]</integer> </dict> </array> <key>com.apple.developer.driverkit</key> <true/> <key>com.apple.developer.driverkit.allow-any-userclient-access</key> <true/> <key>com.apple.developer.driverkit.allow-third-party-userclients</key> <true/> <key>com.apple.developer.driverkit.communicates-with-drivers</key> <true/> <key>com.apple.developer.driverkit.family.scsicontroller</key> <true/> </dict> If there is any additional configuration or requirement to enable vendor-specific SCSI commands, I would appreciate your guidance. Environment: macOS15.6 M2 MacBook Pro
1
0
54
4w
Disable ISO15693Tag Popup
Dear Apple CS, I’m working with NFC ISO15693 tags using NFCTagReaderSession / NFCISO15693Tag, and I’d like to read these tags in the background if possible. Is there any way to read this tag type without triggering the system NFC popup that iOS normally shows? Please note it will not be a public app, the app is meant for internal use for our employees only. is there an option to submit a special request for this use case? Thank you in advance!
2
0
106
2w
CPActionSheetTemplate not detected in presentedTemplate while CPAlertTemplate is
I'm developing a CarPlay app and encountered an inconsistent behavior with template detection. When I present a CPActionSheetTemplate and then print the presentedTemplate property, it returns nil. However, when I present a CPAlertTemplate, the presentedTemplate property correctly returns the template object. This inconsistency is causing issues in my app where I need to check if there's already a presented template before showing another one to avoid conflicts. Why does CPActionSheetTemplate not get detected in presentedTemplate while CPAlertTemplate does? Is this intended behavior or a bug? Any guidance on how to properly detect if a CPActionSheetTemplate is currently presented would be greatly appreciated.
1
0
80
2w
Cancel 'Share age range in app'
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?
1
0
380
2w
The total DMA size in DriverKit cannot exceed 2G?
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?
1
0
51
2w
CarPlay not working on iOS 26 beta
Just wanted to check here to see if anyone else is running into the issue of CarPlay not working at all on iOS 26 Beta 1, even with the update on Friday. I plug my phone in (wired) and CarPlay never shows up. I've seen a Reddit thread where other folks are seeing the same thing.
4
1
329
2w
DEXT (IOUserSCSIParallelInterfaceController): Direct I/O Succeeds, but Buffered I/O Fails with Data Corruption on Large File Copies
Hi all, We are migrating a SCSI HBA driver from KEXT to DriverKit (DEXT), with our DEXT inheriting from IOUserSCSIParallelInterfaceController. We've encountered a data corruption issue that is reliably reproducible under specific conditions and are hoping for some assistance from the community. Hardware and Driver Configuration: Controller: LSI 3108 DEXT Configuration: We are reporting our hardware limitations to the framework via the UserReportHBAConstraints function, with the following key settings: // UserReportHBAConstraints... addConstraint(kIOMaximumSegmentAddressableBitCountKey, 0x20); // 32-bit addConstraint(kIOMaximumSegmentCountWriteKey, 129); addConstraint(kIOMaximumByteCountWriteKey, 0x80000); // 512KB Observed Behavior: Direct I/O vs. Buffered I/O We've observed that the I/O behavior differs drastically depending on whether it goes through the system file cache: 1. Direct I/O (Bypassing System Cache) -> 100% Successful When we use fio with the direct=1 flag, our read/write and data verification tests pass perfectly for all file sizes, including 20GB+. 2. Buffered I/O (Using System Cache) -> 100% Failure at >128MB Whether we use the standard cp command or fio with the direct=1 option removed to simulate buffered I/O, we observe the exact same, clear failure threshold: Test Results: File sizes ≤ 128MB: Success. Data checksums match perfectly. File sizes ≥ 256MB: Failure. Checksums do not match, and the destination file is corrupted. Evidence of failure reproduced with fio (buffered_integrity_test.fio, with direct=1 removed): fio --size=128M buffered_integrity_test.fio -> Test Succeeded (err=0). fio --size=256M buffered_integrity_test.fio -> Test Failed (err=92), reporting the following error, which proves a data mismatch during the verification phase: verify: bad header ... at file ... offset 1048576, length 1048576 fio: ... error=Illegal byte sequence Our Analysis and Hypothesis The phenomenon of "Direct I/O succeeding while Buffered I/O fails" suggests the problem may be related to the cache synchronization mechanism at the end of the I/O process: Our UserProcessParallelTask_Impl function correctly handles READ and WRITE commands. When cp or fio (buffered) runs, the WRITE commands are successfully written to the LSI 3108 controller's onboard DRAM cache, and success is reported up the stack. At the end of the operation, to ensure data is flushed to disk, the macOS file system issues an fsync, which is ultimately translated into a SYNCHRONIZE CACHE SCSI command (Opcode 0x35 or 0x91) and sent to our UserProcessParallelTask_Impl. We hypothesize that our code may not be correctly identifying or handling this SYNCHRONIZE CACHE opcode. It might be reporting "success" up the stack without actually commanding the hardware to flush its cache to the physical disk. The OS receives this "success" status and assumes the operation is safely complete. In reality, however, the last batch of data remains only in the controller's volatile DRAM cache and is eventually lost. This results in an incomplete or incorrect file tail, and while the file size may be correct, the data checksum will inevitably fail. Summary Our DEXT driver performs correctly when handling Direct I/O but consistently fails with data corruption when handling Buffered I/O for files larger than 128MB. We can reliably reproduce this issue using fio with the direct=1 option removed. The root cause is very likely the improper handling of the SYNCHRONIZE CACHE command within our UserProcessParallelTask. P.S. This issue did not exist in the original KEXT version of the driver. We would appreciate any advice or guidance on this issue. Thank you.
13
0
441
1w
RFID read
Hi! Following this ticket: https://developer.apple.com/forums/thread/808764?page=1#868010022 Is there any way to use the hardware RFID reading capabilities of an iPhone to read ISO15693 RF tags silently, and without a UI pop-up? Perhaps using other native iOS libraries than the NFC library? If not, is there a way for a business to request this feature be allowed in internally used apps only?
2
0
77
1w
Neither macOS 14.7 "Standard" 'AppleUserHIDEventDriver' Matching Driver Nor Custom HIDDriverKit Driver 'IOUserHIDEventService::dispatchDigitizerTouchEvent' API Work for a HID-standard Digitizer Touch Pad Device
I have been working on a multi-platform multi-touch HID-standard digitizer clickpad device. The device uses Bluetooth Low Energy (BLE) as its connectivity transport and advertises HID over GATT. To date, I have the device working successfully on Windows 11 as a multi-touch, gesture-capable click pad with no custom driver or app on Windows. However, I have been having difficulty getting macOS to recognize and react to it as a HID-standard multi-touch click pad digitizer with either the standard Apple HID driver (AppleUserHIDEventDriver) or with a custom-coded driver extension (DEXT) modeled, based on the DTS stylus example and looking at the IOHIDFamily open source driver(s). The trackpad works with full-gesture support on Windows 11 and the descriptors seem to be compliant with the R23 Accessory Guidelines document, §15. With the standard, matching Apple AppleUserHIDEventDriver HID driver, when enumerating using stock-standard HID mouse descriptors, the device works fine on macOS 14.7 "Sonoma" as a relative pointer device with scroll wheel capability (two finger swipe generates a HID scroll report) and a single button. With the standard, matching Apple AppleUserHIDEventDriver HID driver, when enumerating using stock-standard HID digitizer click/touch pad descriptors (those same descriptors used successfully on Windows 11), the device does nothing. No button, no cursor, no gestures, nothing. Looking at ioreg -filtb, all of the key/value pairs for the driver match look correct. Because, even with the Apple open source IOHIDFamily drivers noted above, we could get little visibility into what might be going wrong, I wrote a custom DriverKit/HIDDriverKit driver extension (DEXT) (as noted above, based on the DTS HID stylus example and the open source IOHIDEventDriver. With that custom driver, I can get a single button click from the click pad to work by dispatching button events to dispatchRelativePointerEvent; however, when parsing, processing, and dispatching HID digitizer touch finger (that is, transducer) events via IOUserHIDEventService::dispatchDigitizerTouchEvent, nothing happens. If I log with: % sudo log stream --info --debug --predicate '(subsystem == "com.apple.iohid")' either using the standard AppleUserHIDEventDriver driver or our custom driver, we can see that our input events are tickling the IOHIDNXEventTranslatorSessionFilter HID event filter, so we know HID events are getting from the device into the macOS HID stack. This was further confirmed with the DTS Bluetooth PacketLogger app. Based on these events flowing in and hitting IOHIDNXEventTranslatorSessionFilter, using the standard AppleUserHIDEventDriver driver or our custom driver, clicks or click pad activity will either wake the display or system from sleep and activity will keep the display or system from going to sleep. In short, whether with the stock driver or our custom driver, HID input reports come in over Bluetooth and get processed successfully; however, nothing happens—no pointer movement or gesture recognition. STEPS TO REPRODUCE For the standard AppleUserHIDEventDriver: Pair the device with macOS 14.7 "Sonoma" using the Bluetooth menu. Confirm that it is paired / bonded / connected in the Bluetooth menu. Attempt to click or move one or more fingers on the touchpad surface. Nothing happens. For the our custom driver: Pair the device with macOS 14.7 "Sonoma" using the Bluetooth menu. Confirm that it is paired / bonded / connected in the Bluetooth menu. Attempt to click or move one or more fingers on the touchpad surface. Clicks are correctly registered. With transducer movement, regardless of the number of fingers, nothing happens.
4
0
750
1w
How to sign a DEXT
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
1
1
168
6d