How to share HW resources with another driver not in the same PnP hierarchy
First, I have to say that I don't agree with this design pattern at all. I think it leads to too many problems and complications that are not worth the pain. The only reason I am writing this entry is that I have seen so many people get this wrong or not account for some details and I want to make sure that if such a driver is installed, the stability of the system is not compromised. The scenario I am covering is not the case where you are enumerating child devices and want to share resources assigned to the parent amongst the children. Rather, this is sharing HW resources assigned to your device (device A) with another device (device B). Device B can be a legacy NT4 style device or another PnP device stack, it doesn't matter.
There are 3 aspects to the issue that you must solve:
- Handing off the resources to device B. This means that device B will open a handle against device A and send an internal IOCTL (e.g. IOCTL_INTERNAL_GET_RESOURCES) which device A completes with the appropriate values. If the number of resources is fixed, this is easy. If the number of resources varies, this can get complicated.
- Coordinating access to the resources between device A and device B. If both drivers want to touch the resources at the same time, you now have to synchronize between 2 components and that can get very complicated, especially if there are callbacks involved.
- Only touching the resources when the device has power. This is easy for device A to do since it is the power policy owner for the device and sees pnp state changing and device power irps. This is not easy for device B to do though.
Aspects number 1 and 2 I will leave for you to solve. Number 3 is where things get interesting. There are scenarios where the device can lose power:
- PnP graceful remove (query remove, remove)
- PnP surprise removal
- PnP stop
- System power state change (e.g. a standby or hibernate)
The first 2 scenarios in green are easily solved. Device B registers for PnP notifications on the file handle that it opens and it will be told when a PnP remove or surprise removal occurs. If you are using KMDF, the WDFIOTARGET automatically does this for you. But there is a catch to the surprise removal notification...it is sent to device B after device A has already processed the PnP IRP. This means that there is a window where device B thinks it has valid resources, but in reality it does not. File handle notifications do not solve the last 2 scenarios though and the surprise removal case does not work well, so it would be nice if there was one solution for all 4 scenarios.
The solution is that device B must register 2 callbacks with device A, a power up and power down callback (they can be condensed to one callback with an additional passed in parameter indicating power state if you want). These callbacks should be set at the same time that the resources are acquired so that there is no window where the resources have been acquired, the device loses power and the callback has not yet been set notifying device B of the power state change. These callbacks can be passed from device B to device in any number of ways. Two traditional ways are
- To pass the callbacks in a buffer in an internal IOCTL (you can overload IOCTL_INTERNAL_GET_RESOURCES if you want)
- To send a IRP_MN_QUERY_INTERFACE with these callbacks as input values in the INTERFACE based structure as I describe in this post
Once the callbacks have been set, device A must call them at the right time. In a KMDF driver, it is a very simple implementation. You call device B's power up callback in EvtDeviceSelfManagedIoRestart and device B's power down routine in EvtDeviceSelfManagedIoSuspend. This covers all cases because KMDF treats PnP state changes as implicit power changes, powering down the device and using the same callbacks for all cases. This also covers the surprise removal window because device B is now notified at the beginning of device A's power down instead of after it. One final thing to consider is what device B thinks the initial power state of device A is in. Ideally, device B should assume device A is in low power and should not touch the retrieved resources until its power up callback has been invoked. This would mean that the initial call to device B's power up call might occur outside of EvtDeviceSelfManagedIoRestart, perhaps immediately after completing the IOCTL_INTERNAL_GET_RESOURCES request.
If you are writing a WDM driver, well, I will leave that implementation up to you ;).