@@ -62,6 +62,11 @@ func parseDeviceID(deviceID string, details *PortDetails) {
6262//sys setupDiOpenDevRegKey(set devicesSet, devInfo *devInfoData, scope dicsScope, hwProfile uint32, keyType uint32, samDesired regsam) (hkey syscall.Handle, err error) = setupapi.SetupDiOpenDevRegKey
6363//sys setupDiGetDeviceRegistryProperty(set devicesSet, devInfo *devInfoData, property deviceProperty, propertyType *uint32, outValue *byte, bufSize uint32, reqSize *uint32) (res bool) = setupapi.SetupDiGetDeviceRegistryPropertyW
6464
65+ //sys cmGetParent(outParentDev *devInstance, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Parent
66+ //sys cmGetDeviceIDSize(outLen *uint32, dev devInstance, flags uint32) (cmErr cmError) = cfgmgr32.CM_Get_Device_ID_Size
67+ //sys cmGetDeviceID(dev devInstance, buffer unsafe.Pointer, bufferSize uint32, flags uint32) (err cmError) = cfgmgr32.CM_Get_Device_IDW
68+ //sys cmMapCrToWin32Err(cmErr cmError, defaultErr uint32) (err uint32) = cfgmgr32.CM_MapCrToWin32Err
69+
6570// Device registry property codes
6671// (Codes marked as read-only (R) may only be used for
6772// SetupDiGetDeviceRegistryProperty)
@@ -194,14 +199,46 @@ func (set devicesSet) destroy() {
194199 setupDiDestroyDeviceInfoList (set )
195200}
196201
202+ type cmError uint32
203+
197204// https://msdn.microsoft.com/en-us/library/windows/hardware/ff552344(v=vs.85).aspx
198205type devInfoData struct {
199206 size uint32
200207 guid guid
201- devInst uint32
208+ devInst devInstance
202209 reserved uintptr
203210}
204211
212+ type devInstance uint32
213+
214+ func cmConvertError (cmErr cmError ) error {
215+ if cmErr == 0 {
216+ return nil
217+ }
218+ winErr := cmMapCrToWin32Err (cmErr , 0 )
219+ return fmt .Errorf ("error %d" , winErr )
220+ }
221+
222+ func (dev devInstance ) getParent () (devInstance , error ) {
223+ var res devInstance
224+ errN := cmGetParent (& res , dev , 0 )
225+ return res , cmConvertError (errN )
226+ }
227+
228+ func (dev devInstance ) GetDeviceID () (string , error ) {
229+ var size uint32
230+ cmErr := cmGetDeviceIDSize (& size , dev , 0 )
231+ if err := cmConvertError (cmErr ); err != nil {
232+ return "" , err
233+ }
234+ buff := make ([]uint16 , size )
235+ cmErr = cmGetDeviceID (dev , unsafe .Pointer (& buff [0 ]), uint32 (len (buff )), 0 )
236+ if err := cmConvertError (cmErr ); err != nil {
237+ return "" , err
238+ }
239+ return windows .UTF16ToString (buff [:]), nil
240+ }
241+
205242type deviceInfo struct {
206243 set devicesSet
207244 data devInfoData
@@ -291,6 +328,20 @@ func retrievePortDetailsFromDevInfo(device *deviceInfo, details *PortDetails) er
291328 }
292329 parseDeviceID (deviceID , details )
293330
331+ // On composite USB devices the serial number is usually reported on the parent
332+ // device, so let's navigate up one level and see if we can get this information
333+ if details .IsUSB && details .SerialNumber == "" {
334+ if parentInfo , err := device .data .devInst .getParent (); err == nil {
335+ if parentDeviceID , err := parentInfo .GetDeviceID (); err == nil {
336+ d := & PortDetails {}
337+ parseDeviceID (parentDeviceID , d )
338+ if details .VID == d .VID && details .PID == d .PID {
339+ details .SerialNumber = d .SerialNumber
340+ }
341+ }
342+ }
343+ }
344+
294345 /* spdrpDeviceDesc returns a generic name, e.g.: "CDC-ACM", which will be the same for 2 identical devices attached
295346 while spdrpFriendlyName returns a specific name, e.g.: "CDC-ACM (COM44)",
296347 the result of spdrpFriendlyName is therefore unique and suitable as an alternative string to for a port choice */
0 commit comments