Post

Replies

Boosts

Views

Activity

# IOUserSCSIParallelInterfaceController: `UserGetDataBuffer` returns a zero-filled buffer for some writes
I'm writing a software IOUserSCSIParallelInterfaceController in DriverKit — there's no DMA hardware behind it (it forwards commands elsewhere), so for each command I read the payload on the CPU by calling UserGetDataBuffer() from within my task-processing method and then CreateMapping() on the returned IOBufferMemoryDescriptor. This works almost all the time, but I've got an intermittent case I can't explain: for some WRITE tasks the buffer I get back is entirely zero-filled, even though it's a genuine write that should carry data. In those cases the SCSIUserParallelTask.fTransferDirection I'm handed is kSCSIDataTransfer_FromInitiatorToTarget, so the task itself looks like a perfectly normal write to me. It seems to happen when the same task object gets reused — a read on that task, then a write. I got stuck, so I disassembled IOSCSIParallelFamily to try to understand where the buffer comes from. In UserGetDataBuffer_Impl it looks like it allocates a fresh IOBufferMemoryDescriptor, zero-fills it, and only copies the client data in when the transfer direction is "from initiator to target". Roughly what I think I'm seeing: ; allocate a fresh IOBufferMemoryDescriptor, then bzero it bl GetDataTransferDirection ; SCSIParallelTask -> SCSITask (+0x100), then ldrb w0, [x0, #0x5b] cmp w0, #1 ; kSCSIDataTransfer_FromInitiatorToTarget ? b.ne Lskip ; if not a write, leave the buffer zeroed ... client->readBytes(0, bounce, len) ; copy client -> bounce Lskip: ... return bounce ; hand back the (possibly still-zeroed) buffer The direction it tests there is read from the SCSITask itself (the byte at SCSITask+0x5b, via GetDataTransferDirection), not from the fTransferDirection field I get in the task struct. And in the failing cases that byte still seems to hold the previous direction for that task (a read, 0x02), so the cmp w0, #1 doesn't match and the copy is skipped — which would explain the zero buffer. I could easily be misreading the disassembly, so I'd really appreciate a sanity check on the intended contract: Is UserGetDataBuffer the right way for a software (no-DMA) controller to get at write payload on the CPU at all? The docs frame it as the exception and otherwise point at fBufferIOVMAddr, but that reads like an IOVM/physical segment I don't think I can map for CPU access — is there a CPU-accessible path I'm missing? Is it my responsibility (or the layer above me) to make sure the task's data-transfer direction is established before UserGetDataBuffer runs? Is there something I should be doing at task setup / UserMapHBAData / completion so this direction isn't stale when a task object is recycled? Or is the SCSITask direction meant to always agree with fTransferDirection by the time my task-processing method runs, and a mismatch means I've done something wrong on my end? Any pointers on the intended behavior here would be a big help — thanks.
1
0
25
27m
Reboot-free upgrade of an always-matched DriverKit dext
I have a DriverKit dext that implements a virtual SCSI HBA (no physical hardware). Because a bare IOUserSCSIParallelInterfaceController has no provider to match, the bundle ships two IOKitPersonalities: Bootstrap — IOClass IOUserService, IOProviderClass IOUserResources, IOResourceMatch IOKit. In Start() it does SetProperties({NvmeOfSCSIHBA: true}) + RegisterService(), publishing itself as a nub. Controller — IOClass IOUserSCSIParallelInterfaceController, IOProviderClass IOUserService, IOPropertyMatch {NvmeOfSCSIHBA: true} — it matches the bootstrap nub. This loads and runs correctly. The problem is upgrades. Activating a higher CFBundleVersion via OSSystemExtensionRequest.activationRequest (in-place replace) always defers the old version's termination to reboot. The new version reaches [activated enabled] but never starts; the old process keeps running until reboot. From sysextd/kernelmanagerd: kernelmanagerd Dext … v15 … is being replaced and cannot be terminated right away sysextd delegate returns Error Domain=OSSystemExtensionErrorDomain Code=101 "…is being replaced", assumes responsibility for old version …, keeping old version 15 sysextd turning the responsibility for termination of …, version 15 over to delegate (with uninstallation at the next reboot) sysextd a category delegate declined to terminate extension with identifier: … sysextd v15 terminating_for_uninstall → terminating_for_upgrade_via_delegate Key observation: this defers even on a fresh boot where the dext was never opened — no app/daemon ever opened the IOUserClient, no I/O, nothing attached beyond the controller↔nub match. So it does not appear to be a "client still holds it open" / busy-state situation; the driver_extension category delegate declines the moment it's a replacement. What I've tried: In-place activationRequest (replace): always defers to reboot (above). deactivationRequest (standalone): the request hangs — no delegate callback at all (waited ~13 min), even with no client open. Disconnecting all clients first (graceful Stop() that cancels its dispatch queues and completes async) does not change the replace deferral. My understanding from the docs/forums is that the normal reboot-free replace relies on the backing device being disconnected/reconnected to quiesce the old dext (thread 677040). My controller matches a persistent IOUserResources-backed nub that never detaches, so there's no equivalent quiesce point. Questions: For a dext whose only provider is a self-published IOUserResources nub (no detachable hardware), is reboot-free replacement structurally impossible — i.e. is the Code=101 "is being replaced" defer inherent to this matching pattern? Is the supported way to live-upgrade such a dext to deactivationRequest → (on .completed) → activationRequest rather than an in-place replace? If so, what makes a deactivationRequest complete in-session vs. defer to reboot for an IOUserResources-matched dext — and what would cause it to hang with no delegate callback? (Daemon's IOUserClient is closed; the controller's Stop() cancels its queues and completes.) Should the dext itself proactively tear down the published nub (e.g. terminate the bootstrap IOService) before/at upgrade so the controller detaches — or does that just re-match the still-staged old personalities and relaunch the old version? Is there a recommended pattern for a virtual (hardwareless) DriverKit HBA that needs in-field, reboot-free version updates, or is reboot genuinely required for this class of dext? Environment: macOS 27 (Tahoe)
1
0
165
6d
Reboot-free upgrade of an always-matched DriverKit dext
I have a DriverKit dext that implements a virtual SCSI HBA (no physical hardware). Because a bare IOUserSCSIParallelInterfaceController has no provider to match, the bundle ships two IOKitPersonalities: Bootstrap — IOClass IOUserService, IOProviderClass IOUserResources, IOResourceMatch IOKit. In Start() it does SetProperties({NvmeOfSCSIHBA: true}) + RegisterService(), publishing itself as a nub. Controller — IOClass IOUserSCSIParallelInterfaceController, IOProviderClass IOUserService, IOPropertyMatch {NvmeOfSCSIHBA: true} — it matches the bootstrap nub. This loads and runs correctly. The problem is upgrades. Activating a higher CFBundleVersion via OSSystemExtensionRequest.activationRequest (in-place replace) always defers the old version's termination to reboot. The new version reaches [activated enabled] but never starts; the old process keeps running until reboot. From sysextd/kernelmanagerd: kernelmanagerd Dext … v15 … is being replaced and cannot be terminated right away sysextd delegate returns Error Domain=OSSystemExtensionErrorDomain Code=101 "…is being replaced", assumes responsibility for old version …, keeping old version 15 sysextd turning the responsibility for termination of …, version 15 over to delegate (with uninstallation at the next reboot) sysextd a category delegate declined to terminate extension with identifier: … sysextd v15 terminating_for_uninstall → terminating_for_upgrade_via_delegate Key observation: this defers even on a fresh boot where the dext was never opened — no app/daemon ever opened the IOUserClient, no I/O, nothing attached beyond the controller↔nub match. So it does not appear to be a "client still holds it open" / busy-state situation; the driver_extension category delegate declines the moment it's a replacement. What I've tried: In-place activationRequest (replace): always defers to reboot (above). deactivationRequest (standalone): the request hangs — no delegate callback at all (waited ~13 min), even with no client open. Disconnecting all clients first (graceful Stop() that cancels its dispatch queues and completes async) does not change the replace deferral. My understanding from the docs/forums is that the normal reboot-free replace relies on the backing device being disconnected/reconnected to quiesce the old dext (thread 677040). My controller matches a persistent IOUserResources-backed nub that never detaches, so there's no equivalent quiesce point. Questions: For a dext whose only provider is a self-published IOUserResources nub (no detachable hardware), is reboot-free replacement structurally impossible — i.e. is the Code=101 "is being replaced" defer inherent to this matching pattern? Is the supported way to live-upgrade such a dext to deactivationRequest → (on .completed) → activationRequest rather than an in-place replace? If so, what makes a deactivationRequest complete in-session vs. defer to reboot for an IOUserResources-matched dext — and what would cause it to hang with no delegate callback? (Daemon's IOUserClient is closed; the controller's Stop() cancels its queues and completes.) Should the dext itself proactively tear down the published nub (e.g. terminate the bootstrap IOService) before/at upgrade so the controller detaches — or does that just re-match the still-staged old personalities and relaunch the old version? Is there a recommended pattern for a virtual (hardwareless) DriverKit HBA that needs in-field, reboot-free version updates, or is reboot genuinely required for this class of dext? Environment: macOS 27 (Tahoe)
0
0
149
1w
# IOUserSCSIParallelInterfaceController: `UserGetDataBuffer` returns a zero-filled buffer for some writes
I'm writing a software IOUserSCSIParallelInterfaceController in DriverKit — there's no DMA hardware behind it (it forwards commands elsewhere), so for each command I read the payload on the CPU by calling UserGetDataBuffer() from within my task-processing method and then CreateMapping() on the returned IOBufferMemoryDescriptor. This works almost all the time, but I've got an intermittent case I can't explain: for some WRITE tasks the buffer I get back is entirely zero-filled, even though it's a genuine write that should carry data. In those cases the SCSIUserParallelTask.fTransferDirection I'm handed is kSCSIDataTransfer_FromInitiatorToTarget, so the task itself looks like a perfectly normal write to me. It seems to happen when the same task object gets reused — a read on that task, then a write. I got stuck, so I disassembled IOSCSIParallelFamily to try to understand where the buffer comes from. In UserGetDataBuffer_Impl it looks like it allocates a fresh IOBufferMemoryDescriptor, zero-fills it, and only copies the client data in when the transfer direction is "from initiator to target". Roughly what I think I'm seeing: ; allocate a fresh IOBufferMemoryDescriptor, then bzero it bl GetDataTransferDirection ; SCSIParallelTask -> SCSITask (+0x100), then ldrb w0, [x0, #0x5b] cmp w0, #1 ; kSCSIDataTransfer_FromInitiatorToTarget ? b.ne Lskip ; if not a write, leave the buffer zeroed ... client->readBytes(0, bounce, len) ; copy client -> bounce Lskip: ... return bounce ; hand back the (possibly still-zeroed) buffer The direction it tests there is read from the SCSITask itself (the byte at SCSITask+0x5b, via GetDataTransferDirection), not from the fTransferDirection field I get in the task struct. And in the failing cases that byte still seems to hold the previous direction for that task (a read, 0x02), so the cmp w0, #1 doesn't match and the copy is skipped — which would explain the zero buffer. I could easily be misreading the disassembly, so I'd really appreciate a sanity check on the intended contract: Is UserGetDataBuffer the right way for a software (no-DMA) controller to get at write payload on the CPU at all? The docs frame it as the exception and otherwise point at fBufferIOVMAddr, but that reads like an IOVM/physical segment I don't think I can map for CPU access — is there a CPU-accessible path I'm missing? Is it my responsibility (or the layer above me) to make sure the task's data-transfer direction is established before UserGetDataBuffer runs? Is there something I should be doing at task setup / UserMapHBAData / completion so this direction isn't stale when a task object is recycled? Or is the SCSITask direction meant to always agree with fTransferDirection by the time my task-processing method runs, and a mismatch means I've done something wrong on my end? Any pointers on the intended behavior here would be a big help — thanks.
Replies
1
Boosts
0
Views
25
Activity
27m
Reboot-free upgrade of an always-matched DriverKit dext
I have a DriverKit dext that implements a virtual SCSI HBA (no physical hardware). Because a bare IOUserSCSIParallelInterfaceController has no provider to match, the bundle ships two IOKitPersonalities: Bootstrap — IOClass IOUserService, IOProviderClass IOUserResources, IOResourceMatch IOKit. In Start() it does SetProperties({NvmeOfSCSIHBA: true}) + RegisterService(), publishing itself as a nub. Controller — IOClass IOUserSCSIParallelInterfaceController, IOProviderClass IOUserService, IOPropertyMatch {NvmeOfSCSIHBA: true} — it matches the bootstrap nub. This loads and runs correctly. The problem is upgrades. Activating a higher CFBundleVersion via OSSystemExtensionRequest.activationRequest (in-place replace) always defers the old version's termination to reboot. The new version reaches [activated enabled] but never starts; the old process keeps running until reboot. From sysextd/kernelmanagerd: kernelmanagerd Dext … v15 … is being replaced and cannot be terminated right away sysextd delegate returns Error Domain=OSSystemExtensionErrorDomain Code=101 "…is being replaced", assumes responsibility for old version …, keeping old version 15 sysextd turning the responsibility for termination of …, version 15 over to delegate (with uninstallation at the next reboot) sysextd a category delegate declined to terminate extension with identifier: … sysextd v15 terminating_for_uninstall → terminating_for_upgrade_via_delegate Key observation: this defers even on a fresh boot where the dext was never opened — no app/daemon ever opened the IOUserClient, no I/O, nothing attached beyond the controller↔nub match. So it does not appear to be a "client still holds it open" / busy-state situation; the driver_extension category delegate declines the moment it's a replacement. What I've tried: In-place activationRequest (replace): always defers to reboot (above). deactivationRequest (standalone): the request hangs — no delegate callback at all (waited ~13 min), even with no client open. Disconnecting all clients first (graceful Stop() that cancels its dispatch queues and completes async) does not change the replace deferral. My understanding from the docs/forums is that the normal reboot-free replace relies on the backing device being disconnected/reconnected to quiesce the old dext (thread 677040). My controller matches a persistent IOUserResources-backed nub that never detaches, so there's no equivalent quiesce point. Questions: For a dext whose only provider is a self-published IOUserResources nub (no detachable hardware), is reboot-free replacement structurally impossible — i.e. is the Code=101 "is being replaced" defer inherent to this matching pattern? Is the supported way to live-upgrade such a dext to deactivationRequest → (on .completed) → activationRequest rather than an in-place replace? If so, what makes a deactivationRequest complete in-session vs. defer to reboot for an IOUserResources-matched dext — and what would cause it to hang with no delegate callback? (Daemon's IOUserClient is closed; the controller's Stop() cancels its queues and completes.) Should the dext itself proactively tear down the published nub (e.g. terminate the bootstrap IOService) before/at upgrade so the controller detaches — or does that just re-match the still-staged old personalities and relaunch the old version? Is there a recommended pattern for a virtual (hardwareless) DriverKit HBA that needs in-field, reboot-free version updates, or is reboot genuinely required for this class of dext? Environment: macOS 27 (Tahoe)
Replies
1
Boosts
0
Views
165
Activity
6d
Reboot-free upgrade of an always-matched DriverKit dext
I have a DriverKit dext that implements a virtual SCSI HBA (no physical hardware). Because a bare IOUserSCSIParallelInterfaceController has no provider to match, the bundle ships two IOKitPersonalities: Bootstrap — IOClass IOUserService, IOProviderClass IOUserResources, IOResourceMatch IOKit. In Start() it does SetProperties({NvmeOfSCSIHBA: true}) + RegisterService(), publishing itself as a nub. Controller — IOClass IOUserSCSIParallelInterfaceController, IOProviderClass IOUserService, IOPropertyMatch {NvmeOfSCSIHBA: true} — it matches the bootstrap nub. This loads and runs correctly. The problem is upgrades. Activating a higher CFBundleVersion via OSSystemExtensionRequest.activationRequest (in-place replace) always defers the old version's termination to reboot. The new version reaches [activated enabled] but never starts; the old process keeps running until reboot. From sysextd/kernelmanagerd: kernelmanagerd Dext … v15 … is being replaced and cannot be terminated right away sysextd delegate returns Error Domain=OSSystemExtensionErrorDomain Code=101 "…is being replaced", assumes responsibility for old version …, keeping old version 15 sysextd turning the responsibility for termination of …, version 15 over to delegate (with uninstallation at the next reboot) sysextd a category delegate declined to terminate extension with identifier: … sysextd v15 terminating_for_uninstall → terminating_for_upgrade_via_delegate Key observation: this defers even on a fresh boot where the dext was never opened — no app/daemon ever opened the IOUserClient, no I/O, nothing attached beyond the controller↔nub match. So it does not appear to be a "client still holds it open" / busy-state situation; the driver_extension category delegate declines the moment it's a replacement. What I've tried: In-place activationRequest (replace): always defers to reboot (above). deactivationRequest (standalone): the request hangs — no delegate callback at all (waited ~13 min), even with no client open. Disconnecting all clients first (graceful Stop() that cancels its dispatch queues and completes async) does not change the replace deferral. My understanding from the docs/forums is that the normal reboot-free replace relies on the backing device being disconnected/reconnected to quiesce the old dext (thread 677040). My controller matches a persistent IOUserResources-backed nub that never detaches, so there's no equivalent quiesce point. Questions: For a dext whose only provider is a self-published IOUserResources nub (no detachable hardware), is reboot-free replacement structurally impossible — i.e. is the Code=101 "is being replaced" defer inherent to this matching pattern? Is the supported way to live-upgrade such a dext to deactivationRequest → (on .completed) → activationRequest rather than an in-place replace? If so, what makes a deactivationRequest complete in-session vs. defer to reboot for an IOUserResources-matched dext — and what would cause it to hang with no delegate callback? (Daemon's IOUserClient is closed; the controller's Stop() cancels its queues and completes.) Should the dext itself proactively tear down the published nub (e.g. terminate the bootstrap IOService) before/at upgrade so the controller detaches — or does that just re-match the still-staged old personalities and relaunch the old version? Is there a recommended pattern for a virtual (hardwareless) DriverKit HBA that needs in-field, reboot-free version updates, or is reboot genuinely required for this class of dext? Environment: macOS 27 (Tahoe)
Replies
0
Boosts
0
Views
149
Activity
1w