View Javadoc
1   /*
2    * Copyright 2016-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.linux;
6   
7   import static oshi.software.os.linux.LinuxOperatingSystem.HAS_UDEV;
8   
9   import java.util.ArrayList;
10  import java.util.Collections;
11  import java.util.HashMap;
12  import java.util.List;
13  import java.util.Map;
14  
15  import org.slf4j.Logger;
16  import org.slf4j.LoggerFactory;
17  
18  import com.sun.jna.platform.linux.Udev;
19  import com.sun.jna.platform.linux.Udev.UdevDevice;
20  import com.sun.jna.platform.linux.Udev.UdevEnumerate;
21  import com.sun.jna.platform.linux.Udev.UdevListEntry;
22  
23  import oshi.annotation.concurrent.Immutable;
24  import oshi.hardware.UsbDevice;
25  import oshi.hardware.common.AbstractUsbDevice;
26  
27  /**
28   * Linux Usb Device
29   */
30  @Immutable
31  public class LinuxUsbDevice extends AbstractUsbDevice {
32  
33      private static final Logger LOG = LoggerFactory.getLogger(LinuxUsbDevice.class);
34  
35      private static final String SUBSYSTEM_USB = "usb";
36      private static final String DEVTYPE_USB_DEVICE = "usb_device";
37      private static final String ATTR_PRODUCT = "product";
38      private static final String ATTR_MANUFACTURER = "manufacturer";
39      private static final String ATTR_VENDOR_ID = "idVendor";
40      private static final String ATTR_PRODUCT_ID = "idProduct";
41      private static final String ATTR_SERIAL = "serial";
42  
43      public LinuxUsbDevice(String name, String vendor, String vendorId, String productId, String serialNumber,
44              String uniqueDeviceId, List<UsbDevice> connectedDevices) {
45          super(name, vendor, vendorId, productId, serialNumber, uniqueDeviceId, connectedDevices);
46      }
47  
48      /**
49       * Instantiates a list of {@link oshi.hardware.UsbDevice} objects, representing devices connected via a usb port
50       * (including internal devices).
51       * <p>
52       * If the value of {@code tree} is true, the top level devices returned from this method are the USB Controllers;
53       * connected hubs and devices in its device tree share that controller's bandwidth. If the value of {@code tree} is
54       * false, USB devices (not controllers) are listed in a single flat list.
55       *
56       * @param tree If true, returns a list of controllers, which requires recursive iteration of connected devices. If
57       *             false, returns a flat list of devices excluding controllers.
58       * @return a list of {@link oshi.hardware.UsbDevice} objects.
59       */
60      public static List<UsbDevice> getUsbDevices(boolean tree) {
61          List<UsbDevice> devices = getUsbDevices();
62          if (tree) {
63              return devices;
64          }
65          List<UsbDevice> deviceList = new ArrayList<>();
66          // Top level is controllers; they won't be added to the list, but all
67          // their connected devices will be
68          for (UsbDevice device : devices) {
69              deviceList.add(new LinuxUsbDevice(device.getName(), device.getVendor(), device.getVendorId(),
70                      device.getProductId(), device.getSerialNumber(), device.getUniqueDeviceId(),
71                      Collections.emptyList()));
72              addDevicesToList(deviceList, device.getConnectedDevices());
73          }
74          return deviceList;
75      }
76  
77      private static List<UsbDevice> getUsbDevices() {
78          if (!HAS_UDEV) {
79              LOG.warn("USB Device information requires libudev, which is not present.");
80              return Collections.emptyList();
81          }
82          // Build a list of devices with no parent; these will be the roots
83          List<String> usbControllers = new ArrayList<>();
84  
85          // Maps to store information using device syspath as the key
86          Map<String, String> nameMap = new HashMap<>();
87          Map<String, String> vendorMap = new HashMap<>();
88          Map<String, String> vendorIdMap = new HashMap<>();
89          Map<String, String> productIdMap = new HashMap<>();
90          Map<String, String> serialMap = new HashMap<>();
91          Map<String, List<String>> hubMap = new HashMap<>();
92  
93          // Enumerate all usb devices and build information maps
94          Udev.UdevContext udev = Udev.INSTANCE.udev_new();
95          try {
96              UdevEnumerate enumerate = udev.enumerateNew();
97              try {
98                  enumerate.addMatchSubsystem(SUBSYSTEM_USB);
99                  enumerate.scanDevices();
100 
101                 // For each item enumerated, store information in the maps
102                 for (UdevListEntry entry = enumerate.getListEntry(); entry != null; entry = entry.getNext()) {
103                     String syspath = entry.getName();
104                     UdevDevice device = udev.deviceNewFromSyspath(syspath);
105                     if (device != null) {
106                         try {
107                             // Only include usb_device devtype, skipping usb_interface
108                             if (DEVTYPE_USB_DEVICE.equals(device.getDevtype())) {
109                                 String value = device.getSysattrValue(ATTR_PRODUCT);
110                                 if (value != null) {
111                                     nameMap.put(syspath, value);
112                                 }
113                                 value = device.getSysattrValue(ATTR_MANUFACTURER);
114                                 if (value != null) {
115                                     vendorMap.put(syspath, value);
116                                 }
117                                 value = device.getSysattrValue(ATTR_VENDOR_ID);
118                                 if (value != null) {
119                                     vendorIdMap.put(syspath, value);
120                                 }
121                                 value = device.getSysattrValue(ATTR_PRODUCT_ID);
122                                 if (value != null) {
123                                     productIdMap.put(syspath, value);
124                                 }
125                                 value = device.getSysattrValue(ATTR_SERIAL);
126                                 if (value != null) {
127                                     serialMap.put(syspath, value);
128                                 }
129 
130                                 UdevDevice parent = device.getParentWithSubsystemDevtype(SUBSYSTEM_USB,
131                                         DEVTYPE_USB_DEVICE);
132                                 if (parent == null) {
133                                     // This is a controller with no parent, add to list
134                                     usbControllers.add(syspath);
135                                 } else {
136                                     // Add child syspath to parent's path
137                                     String parentPath = parent.getSyspath();
138                                     hubMap.computeIfAbsent(parentPath, x -> new ArrayList<>()).add(syspath);
139                                 }
140                             }
141                         } finally {
142                             device.unref();
143                         }
144                     }
145                 }
146             } finally {
147                 enumerate.unref();
148             }
149         } finally {
150             udev.unref();
151         }
152 
153         // Build tree and return
154         List<UsbDevice> controllerDevices = new ArrayList<>();
155         for (String controller : usbControllers) {
156             controllerDevices.add(getDeviceAndChildren(controller, "0000", "0000", nameMap, vendorMap, vendorIdMap,
157                     productIdMap, serialMap, hubMap));
158         }
159         return controllerDevices;
160     }
161 
162     private static void addDevicesToList(List<UsbDevice> deviceList, List<UsbDevice> list) {
163         for (UsbDevice device : list) {
164             deviceList.add(device);
165             addDevicesToList(deviceList, device.getConnectedDevices());
166         }
167     }
168 
169     /**
170      * Recursively creates LinuxUsbDevices by fetching information from maps to populate fields
171      *
172      * @param devPath      The device node path.
173      * @param vid          The default (parent) vendor ID
174      * @param pid          The default (parent) product ID
175      * @param nameMap      the map of names
176      * @param vendorMap    the map of vendors
177      * @param vendorIdMap  the map of vendorIds
178      * @param productIdMap the map of productIds
179      * @param serialMap    the map of serial numbers
180      * @param hubMap       the map of hubs
181      * @return A LinuxUsbDevice corresponding to this device
182      */
183     private static LinuxUsbDevice getDeviceAndChildren(String devPath, String vid, String pid,
184             Map<String, String> nameMap, Map<String, String> vendorMap, Map<String, String> vendorIdMap,
185             Map<String, String> productIdMap, Map<String, String> serialMap, Map<String, List<String>> hubMap) {
186         String vendorId = vendorIdMap.getOrDefault(devPath, vid);
187         String productId = productIdMap.getOrDefault(devPath, pid);
188         List<String> childPaths = hubMap.getOrDefault(devPath, new ArrayList<>());
189         List<UsbDevice> usbDevices = new ArrayList<>();
190         for (String path : childPaths) {
191             usbDevices.add(getDeviceAndChildren(path, vendorId, productId, nameMap, vendorMap, vendorIdMap,
192                     productIdMap, serialMap, hubMap));
193         }
194         Collections.sort(usbDevices);
195         return new LinuxUsbDevice(nameMap.getOrDefault(devPath, vendorId + ":" + productId),
196                 vendorMap.getOrDefault(devPath, ""), vendorId, productId, serialMap.getOrDefault(devPath, ""), devPath,
197                 usbDevices);
198     }
199 }