View Javadoc
1   /*
2    * Copyright 2021-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.unix.openbsd;
6   
7   import java.util.ArrayList;
8   import java.util.Collections;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  
13  import oshi.annotation.concurrent.Immutable;
14  import oshi.hardware.UsbDevice;
15  import oshi.hardware.common.AbstractUsbDevice;
16  import oshi.util.ExecutingCommand;
17  
18  /**
19   * OpenBsd Usb Device
20   */
21  @Immutable
22  public class OpenBsdUsbDevice extends AbstractUsbDevice {
23  
24      public OpenBsdUsbDevice(String name, String vendor, String vendorId, String productId, String serialNumber,
25              String uniqueDeviceId, List<UsbDevice> connectedDevices) {
26          super(name, vendor, vendorId, productId, serialNumber, uniqueDeviceId, connectedDevices);
27      }
28  
29      /**
30       * Instantiates a list of {@link oshi.hardware.UsbDevice} objects, representing devices connected via a usb port
31       * (including internal devices).
32       * <p>
33       * If the value of {@code tree} is true, the top level devices returned from this method are the USB Controllers;
34       * connected hubs and devices in its device tree share that controller's bandwidth. If the value of {@code tree} is
35       * false, USB devices (not controllers) are listed in a single flat list.
36       *
37       * @param tree If true, returns a list of controllers, which requires recursive iteration of connected devices. If
38       *             false, returns a flat list of devices excluding controllers.
39       * @return a list of {@link oshi.hardware.UsbDevice} objects.
40       */
41      public static List<UsbDevice> getUsbDevices(boolean tree) {
42          List<UsbDevice> devices = getUsbDevices();
43          if (tree) {
44              return devices;
45          }
46          List<UsbDevice> deviceList = new ArrayList<>();
47          // Top level is controllers; they won't be added to the list, but all
48          // their connected devices will be
49          for (UsbDevice device : devices) {
50              deviceList.add(new OpenBsdUsbDevice(device.getName(), device.getVendor(), device.getVendorId(),
51                      device.getProductId(), device.getSerialNumber(), device.getUniqueDeviceId(),
52                      Collections.emptyList()));
53              addDevicesToList(deviceList, device.getConnectedDevices());
54          }
55          return deviceList;
56      }
57  
58      private static List<UsbDevice> getUsbDevices() {
59          // Maps to store information using node # as the key
60          // Node is controller+addr (+port+addr etc.)
61          Map<String, String> nameMap = new HashMap<>();
62          Map<String, String> vendorMap = new HashMap<>();
63          Map<String, String> vendorIdMap = new HashMap<>();
64          Map<String, String> productIdMap = new HashMap<>();
65          Map<String, String> serialMap = new HashMap<>();
66          Map<String, List<String>> hubMap = new HashMap<>();
67  
68          List<String> rootHubs = new ArrayList<>();
69          // For each item enumerated, store information in the maps
70          String key = "";
71          // Addresses repeat for each controller:
72          // prepend the controller /dev/usb* for the key
73          String parent = "";
74          // Enumerate all devices and build information maps.
75          // This will build the entire device tree in hubMap
76          for (String line : ExecutingCommand.runNative("usbdevs -v")) {
77              if (line.startsWith("Controller ")) {
78                  parent = line.substring(11);
79              } else if (line.startsWith("addr ")) {
80                  // addr 01: 8086:0000 Intel, EHCI root hub
81                  if (line.indexOf(':') == 7 && line.indexOf(',') >= 18) {
82                      key = parent + line.substring(0, 7);
83                      String[] split = line.substring(8).trim().split(",");
84                      if (split.length > 1) {
85                          // 0 = vid:pid vendor
86                          String vendorStr = split[0].trim();
87                          int idx1 = vendorStr.indexOf(':');
88                          int idx2 = vendorStr.indexOf(' ');
89                          if (idx1 >= 0 && idx2 >= 0) {
90                              vendorIdMap.put(key, vendorStr.substring(0, idx1));
91                              productIdMap.put(key, vendorStr.substring(idx1 + 1, idx2));
92                              vendorMap.put(key, vendorStr.substring(idx2 + 1));
93                          }
94                          // 1 = product
95                          nameMap.put(key, split[1].trim());
96                          // Add this key to the parent's hubmap list
97                          hubMap.computeIfAbsent(parent, x -> new ArrayList<>()).add(key);
98                          // For the first addr in a controller, make it the parent
99                          if (!parent.contains("addr")) {
100                             parent = key;
101                             rootHubs.add(parent);
102                         }
103                     }
104                 }
105             } else if (!key.isEmpty()) {
106                 // Continuing to read for the previous key
107                 // CSV is speed, power, config, rev, optional iSerial
108                 // Since all we need is the serial...
109                 int idx = line.indexOf("iSerial ");
110                 if (idx >= 0) {
111                     serialMap.put(key, line.substring(idx + 8).trim());
112                 }
113                 key = "";
114             }
115         }
116 
117         // Build tree and return
118         List<UsbDevice> controllerDevices = new ArrayList<>();
119         for (String devusb : rootHubs) {
120             controllerDevices.add(getDeviceAndChildren(devusb, "0000", "0000", nameMap, vendorMap, vendorIdMap,
121                     productIdMap, serialMap, hubMap));
122         }
123         return controllerDevices;
124     }
125 
126     private static void addDevicesToList(List<UsbDevice> deviceList, List<UsbDevice> list) {
127         for (UsbDevice device : list) {
128             deviceList.add(device);
129             addDevicesToList(deviceList, device.getConnectedDevices());
130         }
131     }
132 
133     /**
134      * Recursively creates OpenBsdUsbDevices by fetching information from maps to populate fields
135      *
136      * @param devPath      The device node path.
137      * @param vid          The default (parent) vendor ID
138      * @param pid          The default (parent) product ID
139      * @param nameMap      the map of names
140      * @param vendorMap    the map of vendors
141      * @param vendorIdMap  the map of vendorIds
142      * @param productIdMap the map of productIds
143      * @param serialMap    the map of serial numbers
144      * @param hubMap       the map of hubs
145      * @return A SolarisUsbDevice corresponding to this device
146      */
147     private static OpenBsdUsbDevice getDeviceAndChildren(String devPath, String vid, String pid,
148             Map<String, String> nameMap, Map<String, String> vendorMap, Map<String, String> vendorIdMap,
149             Map<String, String> productIdMap, Map<String, String> serialMap, Map<String, List<String>> hubMap) {
150         String vendorId = vendorIdMap.getOrDefault(devPath, vid);
151         String productId = productIdMap.getOrDefault(devPath, pid);
152         List<String> childPaths = hubMap.getOrDefault(devPath, new ArrayList<>());
153         List<UsbDevice> usbDevices = new ArrayList<>();
154         for (String path : childPaths) {
155             usbDevices.add(getDeviceAndChildren(path, vendorId, productId, nameMap, vendorMap, vendorIdMap,
156                     productIdMap, serialMap, hubMap));
157         }
158         Collections.sort(usbDevices);
159         return new OpenBsdUsbDevice(nameMap.getOrDefault(devPath, vendorId + ":" + productId),
160                 vendorMap.getOrDefault(devPath, ""), vendorId, productId, serialMap.getOrDefault(devPath, ""), devPath,
161                 usbDevices);
162     }
163 }