In Windows Vista, the WPD team shipped an MTP driver with support for two transports: USB and TCP/IP (typically WiFi). This expanded support also introduced an interesting problem: to Windows, the same device connected twice looks like two different devices. This is because device identity in Windows is based on information derived from the underlying transport (or bus). Why is this a problem? Consider the case of an MTP camera. Windows has a photo import application that remembers which photos have already been imported. If the user leaves photos on the camera storage after import, the next import will skip those and only pick up the new ones. However, the photo import application stores this information about imported photos based on the device identity. If the device identity changes when it connects to the same PC using a different transport, those old photos will be imported again because the OS presents this as a different device to the application. Similar problems can occur with music players or mobile phones, where two different synchronization partnerships would be created for the same device.
One way to address this would be to have the device provide its own identity that could be retrieved and examined by each application. This approach can work, but it requires that applications that rely on device identity be modified to use this new identity instead. It also wouldn’t account for future scenarios where multiple transports might be active at the same time.
There have been proprietary solutions, where a custom WPD driver was written to communicate with the device over any of its transports via a multi-transport-aware service. But to make this work, the hardware vendor was required to install and run a Windows service and a kernel mode bus enumerator that communicate with each other to load and unload drivers based on device presence.
The Composite Driver Solution
With Windows 7, there is now built-in support for devices with multiple transports. This support is currently limited to WPD devices, but the groundwork has been laid for other device classes in future OS releases. Now, when a multi-transport device is connected with a Windows 7 PC, it has a single identity. This is even true when multiple transports are simultaneously active, which with the added support for MTP over Bluetooth, will be a common occurrence, such as when a device already connected over Bluetooth is plugged into a USB port for charging.
The Windows 7 solution is a combination of the approaches mentioned above. The following diagram shows the general relationship between the components involved:
Each transport will continue to have a driver loaded that knows how to talk to the device over that transport. But this “transport” driver won’t be seen by applications. Instead, the transport driver will inform a system service (the Composite Bus Enumerator) that a transport is available. The Composite Bus Enumerator will keep track of the transports announced for any one device, and will cause a single Composite Driver to be loaded for that device. Only the Composite Driver will be seen by applications, and it will be the single point of contact with the device. This will be true whether the device has one transport active, or many, either sequentially, or simultaneously. When the last active transport is disconnected, the Composite Bus Enumerator will remove the Composite Driver.
The Composite Driver has three primary functions. One, it chooses the best underlying transport when an application opens a handle to the device (typically via IPortableDevice::Open). Two, it acts as a pass-through for WPD requests and responses between the application and the transport drivers. And three, it acts as a proxy for registering and unregistering PnP interfaces on behalf of the underlying transport drivers. This latter functionality is how the transport drivers remain hidden from applications: since no PnP interfaces are registered by the transport driver, there is no (easy) way for an application to obtain a symbolic link directly to the transport. This latter functionality was also the most complex aspect of the implementation, where multiple transport drivers interacting with the Composite Driver to dynamically register and unregister interfaces must be made to look like a single underlying device.
How does the Composite Driver determine the “best” transport when multiple transports are simultaneously active? In Windows 7, it’s a simple comparison of reported theoretical maximum bandwidths reported by the transport driver to the Composite Driver: USB is faster than IP is faster than Bluetooth. While this is generally true, it is also theoretically possible for USB and IP to actually be slower than Bluetooth depending on conditions at the time. All are asynchronous protocols that rely on some form of bandwidth sharing.
What’s the difference between a transport driver and a regular driver? Not much. A transport driver has the added responsibility of communicating transport status with the Composite Bus Enumerator and the Composite Driver, but it is otherwise just a normal WPD driver. For instance, the Windows 7 MTP driver fulfills either duty depending on the capability of the underlying device. Much of the complexity is encapsulated in the WPD Class Extension (a helper object that manages the communication with the Composite Bus Enumerator and the Composite Driver).
Multi-Transport Capable Devices
What makes a device a multi-transport device? Simple: it just supplies a device property known as the Functional Unique ID (FUID). This is a 128-bit GUID (Globally Unique ID). For MTP devices, this property is defined by the new MTP Device Services Extension (see the MTP Device Services Extension Specification). This does not mean that the device must support services. It is perfectly okay for a device to indicate support for this extension, but only implement the parts that it needs (in this case, the “Functional ID” 0xD301 device property code).
The FUID must be unique across all devices of the same manufacturer, model and version. If two devices of the same model also had the same FUID, they would be treated as different transports of the same device. To avoid this, and to further simplify things for the device manufacturer, if the MTP driver finds an empty value for the FUID property on the device, it will automatically provision the device with a FUID generated at runtime. The only responsibility of the device manufacturer is to ensure that the FUID value is persisted across connections and available over all transports.
Writing your own Transport Driver
To help simplify the explanation so far, I’ve been describing how this works for an MTP device, where the system-provided MTP Class driver does most of the bookkeeping. However, this is supported for 3rd-party WPD drivers as well. Because most of the complexity is hidden from the driver by the WPD Class Extension, the driver just has to make a few simple adjustments to become multi-transport capable. This is covered in more detail in the documentation for the WPD Multi-Transport Sample driver, but the key changes are as follows:
In single-transport WPD drivers, the default I/O queue is sequential (a UMDF term for a queue that delivers one request at a time). The default I/O queue for a multi-transport driver must be parallel (a UMDF term for asynchronous delivery of I/O requests). WPD requests received in this queue shall be forwarded to the sequential WPD queue. All other requests are to be forwarded to a queue obtained from the WPD Class Extension.
On driver initialization (in the OnPrepareHardware callback), the driver must provision the device with a FUID (if necessary), and it must use the WPD Class Extension to inform the Composite Bus Enumerator of the Manufacturer, Model, Version and FUID values for the device.
Application Development Considerations
While applications are now largely isolated from the complexities of multi-transport devices, there are some things to be aware of, and things an application can do, to provide a better experience. When one transport goes away and another arrives for the same device, the Composite Driver may remain loaded during the transition, but it will not automatically “fail over” to the new transport. There are several reasons why this was not feasible, with limitations of the current MTP protocol being chief among them. The handle associated with the departed transport will no longer function (attempts to use it will result in an ERROR_DEVICE_REMOVED response). If other transports for the device are active, the Composite Driver will re-advertise the PnP interfaces available for the new best transport. But this does not lend itself to a seamless transition if the application treats it as a device departure followed by a device arrival.
The application can mitigate transport departures in a couple ways:
The application can close and reopen its handle at some scenario-specific interval (e.g., between object transfers when synchronizing with the device, or between user interactions with the device). This happens to be the way both Windows Media Player and the Windows Shell already behaved. This approach has the added benefit of guaranteeing that the fastest underlying transport will be used when multiple transports are simultaneously active.
The application can recognize that an ERROR_DEVICE_REMOVED failure might be transient and that it should try reopening the handle once before giving up on the device. Because the device has the same identity regardless of transport, the same symbolic link is used (and reused) across all transports.
This posting is provided "AS IS" with no warranties and confers no rights.