View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.unix.freebsd;
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.Locale;
12  import java.util.Map;
13  
14  import oshi.annotation.concurrent.Immutable;
15  import oshi.hardware.UsbDevice;
16  import oshi.hardware.common.AbstractUsbDevice;
17  import oshi.util.ExecutingCommand;
18  import oshi.util.ParseUtil;
19  
20  /**
21   * FreeBsd Usb Device
22   */
23  @Immutable
24  public class FreeBsdUsbDevice extends AbstractUsbDevice {
25  
26      public FreeBsdUsbDevice(String name, String vendor, String vendorId, String productId, String serialNumber,
27              String uniqueDeviceId, List<UsbDevice> connectedDevices) {
28          super(name, vendor, vendorId, productId, serialNumber, uniqueDeviceId, connectedDevices);
29      }
30  
31      /**
32       * Instantiates a list of {@link oshi.hardware.UsbDevice} objects, representing devices connected via a usb port
33       * (including internal devices).
34       * <p>
35       * If the value of {@code tree} is true, the top level devices returned from this method are the USB Controllers;
36       * connected hubs and devices in its device tree share that controller's bandwidth. If the value of {@code tree} is
37       * false, USB devices (not controllers) are listed in a single flat list.
38       *
39       * @param tree If true, returns a list of controllers, which requires recursive iteration of connected devices. If
40       *             false, returns a flat list of devices excluding controllers.
41       * @return a list of {@link oshi.hardware.UsbDevice} objects.
42       */
43      public static List<UsbDevice> getUsbDevices(boolean tree) {
44          List<UsbDevice> devices = getUsbDevices();
45          if (tree) {
46              return devices;
47          }
48          List<UsbDevice> deviceList = new ArrayList<>();
49          // Top level is controllers; they won't be added to the list, but all
50          // their connected devices will be
51          for (UsbDevice device : devices) {
52              deviceList.add(new FreeBsdUsbDevice(device.getName(), device.getVendor(), device.getVendorId(),
53                      device.getProductId(), device.getSerialNumber(), device.getUniqueDeviceId(),
54                      Collections.emptyList()));
55              addDevicesToList(deviceList, device.getConnectedDevices());
56          }
57          return deviceList;
58      }
59  
60      private static List<UsbDevice> getUsbDevices() {
61          // Maps to store information using node # as the key
62          Map<String, String> nameMap = new HashMap<>();
63          Map<String, String> vendorMap = new HashMap<>();
64          Map<String, String> vendorIdMap = new HashMap<>();
65          Map<String, String> productIdMap = new HashMap<>();
66          Map<String, String> serialMap = new HashMap<>();
67          Map<String, String> parentMap = new HashMap<>();
68          Map<String, List<String>> hubMap = new HashMap<>();
69  
70          // Enumerate all devices and build information maps. This will build the
71          // entire device tree; we will identify the controllers as the parents
72          // of the usbus entries and eventually only populate the returned
73          // results with those
74          List<String> devices = ExecutingCommand.runNative("lshal");
75          if (devices.isEmpty()) {
76              return Collections.emptyList();
77          }
78          // For each item enumerated, store information in the maps
79          String key = "";
80          List<String> usBuses = new ArrayList<>();
81          for (String line : devices) {
82              // udi = ... identifies start of a new tree
83              if (line.startsWith("udi =")) {
84                  // Remove indent for key
85                  key = ParseUtil.getSingleQuoteStringValue(line);
86              } else if (!key.isEmpty()) {
87                  // We are currently processing for node identified by key. Save
88                  // approrpriate variables to maps.
89                  line = line.trim();
90                  if (!line.isEmpty()) {
91                      if (line.startsWith("freebsd.driver =")
92                              && "usbus".equals(ParseUtil.getSingleQuoteStringValue(line))) {
93                          usBuses.add(key);
94                      } else if (line.contains(".parent =")) {
95                          String parent = ParseUtil.getSingleQuoteStringValue(line);
96                          // If this is interface of parent, skip
97                          if (key.replace(parent, "").startsWith("_if")) {
98                              continue;
99                          }
100                         // Store parent for later usbus-skipping
101                         parentMap.put(key, parent);
102                         // Add this key to the parent's hubmap list
103                         hubMap.computeIfAbsent(parent, x -> new ArrayList<>()).add(key);
104                     } else if (line.contains(".product =")) {
105                         nameMap.put(key, ParseUtil.getSingleQuoteStringValue(line));
106                     } else if (line.contains(".vendor =")) {
107                         vendorMap.put(key, ParseUtil.getSingleQuoteStringValue(line));
108                     } else if (line.contains(".serial =")) {
109                         String serial = ParseUtil.getSingleQuoteStringValue(line);
110                         serialMap.put(key,
111                                 serial.startsWith("0x") ? ParseUtil.hexStringToString(serial.replace("0x", ""))
112                                         : serial);
113                     } else if (line.contains(".vendor_id =")) {
114                         vendorIdMap.put(key, String.format(Locale.ROOT, "%04x", ParseUtil.getFirstIntValue(line)));
115                     } else if (line.contains(".product_id =")) {
116                         productIdMap.put(key, String.format(Locale.ROOT, "%04x", ParseUtil.getFirstIntValue(line)));
117                     }
118                 }
119             }
120         }
121 
122         // Build tree and return
123         List<UsbDevice> controllerDevices = new ArrayList<>();
124         for (String usbus : usBuses) {
125             // Skip the usbuses: make their parents the controllers and replace
126             // parents' children with the buses' children
127             String parent = parentMap.get(usbus);
128             hubMap.put(parent, hubMap.get(usbus));
129             controllerDevices.add(getDeviceAndChildren(parent, "0000", "0000", nameMap, vendorMap, vendorIdMap,
130                     productIdMap, serialMap, hubMap));
131         }
132         return controllerDevices;
133     }
134 
135     private static void addDevicesToList(List<UsbDevice> deviceList, List<UsbDevice> list) {
136         for (UsbDevice device : list) {
137             deviceList.add(device);
138             addDevicesToList(deviceList, device.getConnectedDevices());
139         }
140     }
141 
142     /**
143      * Recursively creates FreeBsdUsbDevices by fetching information from maps to populate fields
144      *
145      * @param devPath      The device node path.
146      * @param vid          The default (parent) vendor ID
147      * @param pid          The default (parent) product ID
148      * @param nameMap      the map of names
149      * @param vendorMap    the map of vendors
150      * @param vendorIdMap  the map of vendorIds
151      * @param productIdMap the map of productIds
152      * @param serialMap    the map of serial numbers
153      * @param hubMap       the map of hubs
154      * @return A SolarisUsbDevice corresponding to this device
155      */
156     private static FreeBsdUsbDevice getDeviceAndChildren(String devPath, String vid, String pid,
157             Map<String, String> nameMap, Map<String, String> vendorMap, Map<String, String> vendorIdMap,
158             Map<String, String> productIdMap, Map<String, String> serialMap, Map<String, List<String>> hubMap) {
159         String vendorId = vendorIdMap.getOrDefault(devPath, vid);
160         String productId = productIdMap.getOrDefault(devPath, pid);
161         List<String> childPaths = hubMap.getOrDefault(devPath, new ArrayList<>());
162         List<UsbDevice> usbDevices = new ArrayList<>();
163         for (String path : childPaths) {
164             usbDevices.add(getDeviceAndChildren(path, vendorId, productId, nameMap, vendorMap, vendorIdMap,
165                     productIdMap, serialMap, hubMap));
166         }
167         Collections.sort(usbDevices);
168         return new FreeBsdUsbDevice(nameMap.getOrDefault(devPath, vendorId + ":" + productId),
169                 vendorMap.getOrDefault(devPath, ""), vendorId, productId, serialMap.getOrDefault(devPath, ""), devPath,
170                 usbDevices);
171     }
172 }