The DDK documentation for NdisIMInitializeDeviceInstanceEx is a bit odd in a few places. To clarify it a bit, I’m going to take you through how NDIS Intermediate (IM) driver binding works in the case of a 1:1 filter IM, such as the Passthru sample.
This all started as I was going through an IM driver doing some code review. I noticed a lot of string manipulation code. That always makes me suspicious, since it is a very common source of bugs. This code seemed to be doing the same thing that the DDK passthru sample does – read names from the UpperBindings entry in the registry and use that value to initialize virtual adapters representing the underlying physical adapters. I had never really given this much thought, so I decided to investigate the details.
This string manipulation happens in the course of the ProtocolBindAdapter callback, of course. Names are used in a few places in this code path.
Some ancient history, subject to the limits of my foggy memory: Back in the pre-PnP days, protocols went looking for adapters to bind to based on configuration information stored in the registry. The whole bindings process in the NT4 days was a mess – go look at an interpreted INF for an NT4 net component to see how ugly it got. (There was even a little sub-industry that developed around writing obfuscated net component installation INFs because they were so hard to write. Fortunately, INFs are a lot easier now. Well, at least they’re shorter.) Anyway, those legacy protocols used to figure out which adapters they wanted to bind to and call NdisOpenAdapter on them, using the instance name.
ProtocolBindAdapter is now basically the only thing that calls NdisOpenAdapter in a PnP protocol. And, conveniently, it has the adapter’s instance name passed in as a parameter, which the protocol then passes directly to NdisOpenAdapter. Easy enough.
The other place that strings are used is in the call to NdisIMInitializeDeviceInstance(Ex). Looking at the DDK doc for the function (linked above), the first oddball thing that pops out is the fact that DeviceContext isn’t hyperlinked to its description. This is apparently because there’s an extra apostrophe before the D in Devicecontext. Looking around, there are a couple of more places that the extra apostrophe shows up on this page. Makes you wonder if the documentation got garbled somehow.
The function is prototyped as:
NDIS_STATUS
NdisIMInitializeDeviceInstanceEx(
IN NDIS_HANDLE DriverHandle,
IN PNDIS_STRING DriverInstance,
IN NDIS_HANDLE DeviceContext OPTIONAL
);
That second argument asks for a string representing the “driver instance”. That is a pretty uncommon term in the NDIS world; it turns out that it should probably be “device instance” or “adapter instance”, either of which would make much more sense in this context. Particularly given that the name of the function has the words “DeviceInstance” in it.
That still doesn’t tell us what the argument actually *does*, though. The documentation is unhelpful:
DriverInstance
Pointer to an NDIS_STRING type that describes a caller-initialized counted string, in the system-default character set, naming the registry key in which the driver stores information about its virtual NIC and, possibly, binding-specific information. For Windows 2000 and later drivers, this string contains Unicode characters. That is, for Windows 2000 and later, NDIS defines the NDIS_STRING type as a UNICODE_STRING type.
Pet peeve: I wish the NDIS docs would quit putting the UNICODE_STRING disclaimer on every string argument in every function. I get it by now.
Anyway, the description references the name of a registry key where we store binding-specific information. But, looking at PassThru, that’s not remotely what this argument is used for. It is not the name of a registry key – it’s the name of a device instance that’s written into UpperBindings by the class installer. Looking at it in the registry (HKLM\System\CCS\Services\Parameters\Adapters\{adapter guid}), it’s just the name of a device instance, it’s a REG_SZ, and the value is a GUID that identifies a particular instance of an adapter exported by the IM driver itself.
PassThru says:
//
// Read the "UpperBindings" reserved key that contains a list
// of device names representing our miniport instances corresponding
// to this lower binding. Since this is a 1:1 IM driver, this key
// contains exactly one name.
//
// If we want to implement a N:1 mux driver (N adapter instances
// over a single lower binding), then UpperBindings will be a
// MULTI_SZ containing a list of device names - we would loop through
// this list, calling NdisIMInitializeDeviceInstanceEx once for
// each name in it.
There are a couple of problems here. First, UpperBindings (as created by the class installer) is not a REG_MULTI_SZ; it’s a REG_SZ – disassemble the class installer and check for yourself, or if you’re smarter/lazier than that, search google and see what Elyias Yakub from Microsoft has to say on the point. Besides, it wouldn’t make sense – PassThru is a 1:1 filter IM, so it can’t export more than one adapter per underlying nic at a time or it wouldn’t be 1:1 any more.
The answer is this: The UpperBindings key contains the name of the virtual device instance that we should create to represent the underlying miniport’s device. It needs to match the name of the root-enumerated virtual device instance that PnP (may have) started (check HKLM\System\CCS\Enum\Root\[driver] and the Net class GUID under HKLM\System\CCS\Class\{guid} to see where this comes from). This is the step that creates the “XYZ Ethernet Adapter – Passthru Miniport” device that shows up in Device Manager.
If you don’t specify the name of a device that is already started (or that will be started soon by PnP), NDIS will silently fail to complete the device instance initialization, since the device you’re telling NDIS to initialize never actually shows up. That is exactly why you have to get the name of the device instance to start from UpperBindings. That is where the class installer connects the dots between the underlying miniport’s device and the virtual miniport device the IM should create.
So, to recap, here’s what IM binding looks like:
- The underlying miniport starts via PnP, which causes NDIS to look for bound protocols and call the relevant ProtocolBindAdapter handlers. The protocol in question is the IM driver (PassThru in this case).
- The IM’s ProtocolBindAdapter gets the underlying adapter’s name passed in as a parameter. It calls NdisOpenAdapter to complete the underlying binding.
- It then creates a virtual miniport device instance to represent the underlying device interface, using NdisIMInitializeDeviceInterface(Ex). As a part of that call, it identifies *which* of its root-enumerated virtual devices to start using the (single) value from UpperBindings.
This is obviously recursive, too – my IM test box has three IMs running at the moment, and you can easily follow the chain using !ndiskd.miniports in the kernel debugger.
One parting shot: the documentation on the return value is also wrong, as a result of all of this. The problem NDIS will reject your call for is NOT based on DriverHandle being initialized already (which is actually a requirement for the call to succeed, as far as I can tell); it’s based on having already initialized the device instance (called DrivderInstance in the docs).
I hope this clears some things up about this mysterious call.