The importance of including a unique iSerialNumber in your USB MIDI Devices
Uniquely identifying connected devices is something all operating systems must do in order to function. But persistently identifying them, across moves, reboots, and more, can be more challenging. This post covers why this topic is so important to Windows MIDI Services and what manufacturers and customers can expect to see in Windows MIDI Services.
Please note that the scope of this post is just USB MIDI devices. Other devices, like storage, have different requirements and different approaches to identification.
TLDR; Please provide a valid and unique iSerialNumber
string descriptor for your USB devices. If you cannot provide that value, do not provide any string descriptor for that (set the iSerialNumber
field value to 0x00
so it does not point to any string descriptor). If you want to learn more, please read on.
Note: When I mention the
iSerialNumber
field in the rest of this post, I’m generally referring to the string descriptor theiSerialNumber
field points to.
How Windows identifies USB devices
Have you ever noticed that when you unplug a device from one USB port, and then plug it into a different USB port, you’ll sometimes see it pop up as a new device, and maybe even get a driver installed?
That can happen when a USB device doesn’t include the iSerialNumber
field, and you plug it into a port which shows up as a different parent device, impacting the calculated unique id for that USB device. When a device doesn’t include an iSerialNumber
, operating systems use various techniques to uniquely identify the device, as best as they can.
Although implementations vary, this process isn’t unique to Windows. macOS, Linux, and other operating systems have similar algorithms to try to smartly identify a device in the absence of a serial number. They also have similar limitations.
Here are some low-level details about how devices are uniquely identified in Windows. This is quoted from our MS Learn docs (links below):
… For BusQueryInstanceID, a bus driver should supply a string that contains the instance ID for the device. Setup and bus drivers use the instance ID, with other information, to differentiate between two identical devices on the computer. The instance ID is either unique across the whole computer or just unique on the device’s parent bus.
If an instance ID is only unique on the bus, the bus driver specifies that string for
BusQueryInstanceID
but also specifies aUniqueID
value of FALSE in response to anIRP_MN_QUERY_CAPABILITIES
request for the device. IfUniqueID
is FALSE, the PnP manager enhances the instance ID by adding information about the device’s parent and thus makes the ID unique on the computer. In this case the bus driver should not take extra steps to make its devices’ instance IDs globally unique; just return the appropriate capabilities information and the operating system takes care of it.If a bus driver can supply a globally unique ID for each child device, such as a serial number, the bus driver specifies those strings for
BusQueryInstanceID
and specifies aUniqueID
value of TRUE in response to anIRP_MN_QUERY_CAPABILITIES
request for each device. …
So you can see in the above description, that if an iSerialNumber
is not supplied, Windows will use the device’s parent information as part of the differentiator to uniquely identify the device. If a device is moved to a new parent (plugged into a different port, plugged into a USB hub, etc.), that id changes and the device appears as a new device. Any link to its past is effectively lost.
Just as a reminder, storage and some other device classes have additional mechanisms for unique identification (a storage device can, well, store a supplied id). We’re talking about MIDI devices here.
References
- https://learn.microsoft.com/windows-hardware/drivers/kernel/irp-mn-query-id
- https://learn.microsoft.com/windows-hardware/drivers/install/device-instance-ids
How to check your own devices
As a developer, or a curious customer, you can check connected devices to see what they report using a number of free tools.
Plug & Play utility
First is the command-line tool pnputil
, which is included with Windows. If you are on recent Windows 11 releases, there’s a /properties
flag which will display additional information about devices, including the full Id of the device.
To list all connected USB devices, and all their properties, here’s the command line
>pnputil /enum-devices /properties /connected /bus USB
Here’s some pnputil information from the Arturia Keystep 32 I have here.
DEVPKEY_Device_Capabilities [UINT32]:
0x00000094 (148)
DEVPKEY_Device_InstanceId [String]:
USB\VID_1C75&PID_0288\00000000001A
DEVPKEY_Device_BusReportedDeviceDesc [String]:
Arturia KeyStep 32
DEVPKEY_NAME [String]:
USB Composite Device
And here’s a Modal Skulpt Synth. I don’t have more than one Skulpt synth handy to test, but it seems likely that they’ve hard-coded the iSerialNumber to just “ffff”, which is not helpful.
DEVPKEY_Device_Capabilities [UINT32]:
0x000000B4 (180)
DEVPKEY_Device_InstanceId [String]:
USB\VID_04D8&PID_EEFE\ffff
DEVPKEY_Device_FriendlyName [String]:
Skulpt Synth
DEVPKEY_NAME [String]:
Skulpt Synth
In this, we can see the Device Capabilities flag shows that the device has declared CM_DEVCAP_REMOVABLE, CM_DEVCAP_SILENTINSTALL, CM_DEVCAP_SURPRISEREMOVALOK, and importantly, we’ve set CM_DEVCAP_UNIQUEID because an iSerialNumber
was provided. The actual values can be found in cfgmgr32.h:
#define CM_DEVCAP_LOCKSUPPORTED (0x00000001)
#define CM_DEVCAP_EJECTSUPPORTED (0x00000002)
#define CM_DEVCAP_REMOVABLE (0x00000004)
#define CM_DEVCAP_DOCKDEVICE (0x00000008)
#define CM_DEVCAP_UNIQUEID (0x00000010)
#define CM_DEVCAP_SILENTINSTALL (0x00000020)
#define CM_DEVCAP_RAWDEVICEOK (0x00000040)
#define CM_DEVCAP_SURPRISEREMOVALOK (0x00000080)
#define CM_DEVCAP_HARDWAREDISABLED (0x00000100)
#define CM_DEVCAP_NONDYNAMIC (0x00000200)
If a device is going to declare that it has a unique ID by providing data in the iSerialNumber
string, it needs to provide an iSerialNumber
that is unique amongst other instances of the same device. Again, I can’t verify that "ffff"
is or is not the same across all the devices, but it looks pretty sus. Modal, if you’re reading this, and I got it wrong, please accept my apologies. You know I love your synths. 🙂
Quite a bit more information is reported, but I won’t paste it all in here. Try out pnputil
, it comes with every recent version of Windows.
USB viewer
PnP Util provides a view of what Windows sees for a device. Another tool, USB Viewer, shows more detail about what the device reports, and how the descriptors are structured. It’s free with the Windows SDK, or available on GitHub. You can find information about it here.
If you don’t have the SDK, or don’t want to use the version on GitHub, or simply want something with more reporting, there’s an enhanced version of the USB Treeview app created by a third-party. This is not an official endorsement, but this is the version I personally use. It’s available here.
Here’s a segment of the USB Tree Viewer output showing the Modal Skulpt
And here’s a device (MOTU Express 128 MIDI Interface) which does not implement iSerialNumber
. Note that it has a 0x00
index for that field, as is proper.
Finally, here’s a good implementation of iSerialNumber
, from the PreSonus ATOM.
You can use either of these tools to check to see what your device is reporting to Windows, and how Windows is interpreting what it sees.
Windows MIDI Services Features that work best with unique ids
Now we know how devices are identified, and how to verify that information, but how does this impact Windows MIDI Services?
The first defined and approved transport for MIDI 2.0 is USB. There’s a new device class specification, and Windows also has a new USB MIDI Class 2 driver to support it. There are also other transports in development, with Network MIDI 2.0 the most likely one to hit next. That transport also includes a way to uniquely identify devices, as does the MIDI 2.0 protocol itself. But those are in-protocol mechanisms which, although we will take advantage of them in Windows MIDI Services and use them in much the same way for MIDI, are not at the same level as Kernel-level PnP information from a driver.
The new Windows MIDI Services has a number of planned features which work best when we can uniquely identify a MIDI Device, no matter which USB port it’s connected on the PC. Not all of these features will be available on day 1, but we will be rolling them out over time as we’re sure the experience is what you want. Here are two of the more important ones:
Renaming MIDI Devices / UMP Endpoints
One of the most requested features for Windows MIDI Services is the ability to rename devices / UMP Endpoints. For this to be meaningful, we need to be able to uniquely identify the MIDI device you want to rename.
Renaming a device isn’t a ton of effort for a user, but it would be super confusing if they had two of the same synth, and one was set up with bass patches, and the other with pads, and Windows got confused about which one was which, and applied the wrong names, after they were unplugged and brought to a performance.
User-supplied metadata
We’re looking at ways to provide MIDI 2.0 profiles and properties for devices which don’t natively support those features. That would enable you to, for example, indicate that the synthesizer you have fits the organ profile and can be discovered as such, or that that Piano module you have can conform to the Piano profile.
There can be quite a bit of work involved in supplying this metadata, so we want to make it worth the customer’s time, and not have any confusion over which connected device the metadata applies to.
Some scenarios
There are some scenarios where calculated vs provided unique identifiers really come into play. We will do our best to optimize for a good user experience here, but valid iSerialNumber
values will really help everyone.
MIDI Device connected to PC and never moved or unplugged
The generated identifiers persist across reboots, so there are no immediate issues here, until devices are moved around, put on a hub, etc. We don’t want customers to be afraid to unplug and rearrange their studio.
If the device implements iSerialNumber
properly, then no problem. If it does not, we generate an identifier, which is likely to change especially if you plug it into a different port, or into a hub.
Setting up that live performance
This is a more common problem. Let’s say you have a couple controllers and synthesizers you bring to your performance. If you don’t plug those devices into the same ports they were in when you practiced at home, especially if you have multiples of any one device, there’s potential for the OS to get them mixed up. Without an iSerialNumber
, there’s no guarantee that the generated identifier will persist or will be applied to the same device.
Many club performers get little to no time to set up their equipment. They need to rush up on stage while the last performer is wrapping up so there’s no gap between the previous performance and their performance. It’s not like the roadies get there the day before and get everything set up and configured properly in advance. They shouldn’t need to struggle with recreating their performance setup, in the dark, while everyone is staring at them. This is just stress a live performer doesn’t need when setting up. And as a device manufacturer, you want to encourage your customers to buy more than one of your MIDI device, right? 🙂
Some options for iSerialNumber
The USB iSerialNumber
field is a pointer to a text record, just like iManufacturer
and other USB strings. There are practical limits on how long the string can be. The entire Windows Device Instance Id, which includes other information in addition to the serial, needs to be MAX_DEVICE_ID_LEN
characters or less — currently 200 characters per cfgmgr32.h. This means you should keep your number to something in the range of 32 alphanumeric digits. It can be larger (quite a bit, as long as you check that the Device Instance Id is still valid), but 32 digits should be plenty for this use.
#define MAX_DEVICE_ID_LEN 200
#define MAX_DEVNODE_ID_LEN MAX_DEVICE_ID_LEN
MIDI 2.0 allows for
ProductInstanceId
values, the in-protocoliSerialNumber
equivalent here, to be up to 255 characters in length. But the practical limit here is much lower if you want to support Windows devices. We recommend following the same rules for both iSerialNumber and theProductInstanceId
, and keeping them in sync when you provide both.
When I recently met with manufacturers, one revelation they had was that the iSerialNumber
doesn’t need to match the serial number on the box; it just needs to be a unique id. Understandably, manufacturers were concerned that the serial number matching the box would add a costly step to the manufacturing process. When I pointed out that we just need something that will uniquely identify a device within the domain of devices in a customer’s setup, the problem became much simpler to solve.
There’s no prescription here, as each device manufacturer has their own unique circumstances. However, here are some ideas for how to generate this number.
Value | Approach |
---|---|
USB controller-provided Id | Some embedded USB / Serial converter ICs also include support for iSerialNumber , and a small amount of memory to persist that value in |
Processor Unique Id | Some processors have unique Ids which can be read by code on the device. Simply report this number. |
MAC Address | If the device has a network port, the MAC address (with special characters removed), or a hash of it, will work as a unique Id here |
GUID/UUID | There are established algorithms for generating GUID/UUID values. In some cases, this is part of the libraries included with microcontrollers. Strip out the special characters, and you end up with a 32 character unique Id that has a very low chance of colliding with any other in the world. |
Random Number | Random number generators are included with almost all microcontroller libraries. Generating something reasonable like a 10 digit number would be unique enough for use here. |
User-supplied | If your device has a user interface for entering data (a web server or touch-screen display, for examples), you may also consider allowing the customer to provide/change the serial number themselves |
When we were discussing random number generators, one developer started going through how common collisions are with UUIDs. That’s mostly a non-issue here. The number doesn’t need to be unique in the world, just unique within the domain of the devices he user has connected to their computer. If you use a sufficiently large domain of numbers to draw from, you’ll be fine.
When creating the id, we recommend require removing any special characters, and leave it to just letters and numbers. Specifically, character values less than or equal to 0x20
(space), greater than 0x7F
, or equal to 0x2C
(comma) are not allowed unless you really want to bugcheck the PC. You can find more information in the IRP_MN_QUERY_ID kernel driver docs.
What happens when you can’t support a serial number?
There are cases when a device cannot support an iSerialNumber
because it has no available persistent storage for this. That is far less common these days, especially with devices which can support MIDI 2.0, but it does happen. In addition, the body of devices already available without an iSerialNumber
need to continue to function in Windows without a degraded experience.
Excellent user experience is top priority for Windows MIDI Services.
At a minimum, everything will continue using the algorithms the Windows USB stack uses today. In the tools, we may warn the user that the device does not support a serial number so they can consider how much time/effort they want to invest in customization. We’re optimizing for the user experience here, and investing time in changing names, adding metadata, etc. only to have it disassociated from the device, or applied to another instance of the device, would not be good there.
In addition, in the absence of iSerialNumber
, we are working on identifying MIDI devices by other properties reported by the driver. For example, the device name as reported by the hardware (although device naming is an entirely different and convoluted discussion). This can work if you only own one of those devices, but does not work well when you have more than one of the devices connected at a time, or more than one of the device with other unique values (like patch sets on a synth or sound module, or firmware-stored CC mapping on controllers).
Our request for device manufacturers and makers
Including and reporting iSerialNumber
on your USB devices will improve the user experience across all operating systems, not just Windows, so please considering this as part of your device firmware and/or provisioning.
Did I say please? PLEASE properly implement iSerialNumber
. Your customers will love you for it, even if they don’t quite realize why 🙂
Manufacturers may start with 0000 as a SN for each product. The Serial# can still cause lots of conflicts. Perhaps define a SYSEX message for GET/SET_PERMANENT_SERIALNUMBER or GET/SET_PERMANENT_DEVICE_ID in the MID 2.0 SPEC that the device uses to store it in it’s NVRAM.
The serial number only needs to be unique for the specific type of device from that manufacturer. We differentiate first by VID/PID in the case of USB.
I agree that being able to remotely set the serial or even the name would be helpful, using property exchange or a new stream message. Those are proposals I mentioned to the working group that I may make for a future revision.
Pete
It seems we are a step closer today with the updated UMP specification, but not fully there.
7.1.5 Product Instance Id Notification Message: Devices should declare a Product Instance Id. Product Instance Id should be, where possible, the same as the Serial Number of the Device and should be a unique number per Manufacturer/Family/Model.
Now
[1] its a "should", Not all (probably very few) manufacturer may be capable/willing to do this at the manufacturing...
Yep. ProductInstanceId was added for these reasons, but especially for non-USB transports. It's useful for Network, for example. USB has additional layers of consideration.
The reason it's optional in UMP still (although I have argued for it to be required for the upcoming Network protocol) is because not all devices can supply it. Personally, I think any device which can handle MIDI 2.0 can also supply a unique Id, but I am not a device manufacturer.
Note...
Bug
(replying to June 16 comment, but WP won't let me nest that deep)
[1] Fair enough. I know some companies are going to change this practice, though, in as many devices as they can.
[2] MIDI 2.0 has no concept of ports. In MIDI 1.0, a port is just a remapped cable number on an endpoint. That abstraction goes away with MIDI 2.0 where cable numbers are conceptually the same as the group for this use. Still...
[1] I think any device which can handle MIDI 2.0 can also supply a unique Id,
Many (specially small) devices hard code their USB descriptors and flash the device with that image and the serial-number field may not be easily (if at all) modifiable from code.
[2] USB device ends up with a unique Id anyway.
As long as the endpoint's (ports) ID does not change when another device gets plugged in/out, then...