usb: core: Set connect_type of ports based on DT node
authorStephen Boyd <swboyd@chromium.org>
Fri, 23 Feb 2024 00:58:21 +0000 (16:58 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 5 Mar 2024 13:28:46 +0000 (13:28 +0000)
When a USB hub is described in DT, such as any device that matches the
onboard-hub driver, the connect_type is set to "unknown" or
USB_PORT_CONNECT_TYPE_UNKNOWN. This makes any device plugged into that
USB port report their 'removable' device attribute as "unknown".
ChromeOS userspace would like to know if the USB device is actually
removable or not so that security policies can be applied. Improve the
connect_type attribute for ports, and in turn the removable attribute
for USB devices, by looking for child devices with a reg property or an
OF graph when the device is described in DT.

If the graph exists, endpoints that are connected to a remote node must
be something like a usb-{a,b,c}-connector compatible node, or an
intermediate node like a redriver, and not a hardwired USB device on the
board. Set the connect_type to USB_PORT_CONNECT_TYPE_HOT_PLUG in this
case because the device is going to be plugged in. Set the connect_type
to USB_PORT_CONNECT_TYPE_HARD_WIRED if there's a child node for the port
like 'device@2' for port2. Set the connect_type to USB_PORT_NOT_USED if
there isn't an endpoint or child node corresponding to the port number.

To make sure things don't change, only set the port to not used if
there are child nodes. This way an onboard hub connect_type doesn't
change until ports are added or child nodes are added to describe
hardwired devices. It's assumed that all ports or no ports will be
described for a device.

Cc: Matthias Kaehlcke <mka@chromium.org>
Cc: linux-usb@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: Pin-yen Lin <treapking@chromium.org>
Cc: maciek swiech <drmasquatch@google.com>
Signed-off-by: Stephen Boyd <swboyd@chromium.org>
Link: https://lore.kernel.org/r/20240223005823.3074029-3-swboyd@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/of.c
drivers/usb/core/port.c
include/linux/usb/of.h

index db4ccf9..f1a499e 100644 (file)
@@ -8,6 +8,7 @@
  */
 
 #include <linux/of.h>
+#include <linux/of_graph.h>
 #include <linux/usb/of.h>
 
 /**
@@ -75,6 +76,76 @@ bool usb_of_has_combined_node(struct usb_device *udev)
 }
 EXPORT_SYMBOL_GPL(usb_of_has_combined_node);
 
+static bool usb_of_has_devices_or_graph(const struct usb_device *hub)
+{
+       const struct device_node *np = hub->dev.of_node;
+       struct device_node *child;
+
+       if (of_graph_is_present(np))
+               return true;
+
+       for_each_child_of_node(np, child)
+               if (of_property_present(child, "reg"))
+                       return true;
+
+       return false;
+}
+
+/**
+ * usb_of_get_connect_type() - get a USB hub's port connect_type
+ * @hub: hub to which port is for @port1
+ * @port1: one-based index of port
+ *
+ * Get the connect_type of @port1 based on the device node for @hub. If the
+ * port is described in the OF graph, the connect_type is "hotplug". If the
+ * @hub has a child device has with a 'reg' property equal to @port1 the
+ * connect_type is "hard-wired". If there isn't an OF graph or child node at
+ * all then the connect_type is "unknown". Otherwise, the port is considered
+ * "unused" because it isn't described at all.
+ *
+ * Return: A connect_type for @port1 based on the device node for @hub.
+ */
+enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int port1)
+{
+       struct device_node *np, *child, *ep, *remote_np;
+       enum usb_port_connect_type connect_type;
+
+       /* Only set connect_type if binding has ports/hardwired devices. */
+       if (!usb_of_has_devices_or_graph(hub))
+               return USB_PORT_CONNECT_TYPE_UNKNOWN;
+
+       /* Assume port is unused if there's a graph or a child node. */
+       connect_type = USB_PORT_NOT_USED;
+
+       np = hub->dev.of_node;
+       /*
+        * Hotplug ports are connected to an available remote node, e.g.
+        * usb-a-connector compatible node, in the OF graph.
+        */
+       if (of_graph_is_present(np)) {
+               ep = of_graph_get_endpoint_by_regs(np, port1, -1);
+               if (ep) {
+                       remote_np = of_graph_get_remote_port_parent(ep);
+                       of_node_put(ep);
+                       if (of_device_is_available(remote_np))
+                               connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG;
+                       of_node_put(remote_np);
+               }
+       }
+
+       /*
+        * Hard-wired ports are child nodes with a reg property corresponding
+        * to the port number, i.e. a usb device.
+        */
+       child = usb_of_get_device_node(hub, port1);
+       if (of_device_is_available(child))
+               connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
+       of_node_put(child);
+
+       return connect_type;
+}
+EXPORT_SYMBOL_GPL(usb_of_get_connect_type);
+
 /**
  * usb_of_get_interface_node() - get a USB interface node
  * @udev: USB device of interface
index 84d3617..e1613b0 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/slab.h>
 #include <linux/pm_qos.h>
 #include <linux/component.h>
+#include <linux/usb/of.h>
 
 #include "hub.h"
 
@@ -708,6 +709,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
                return -ENOMEM;
        }
 
+       port_dev->connect_type = usb_of_get_connect_type(hdev, port1);
        hub->ports[port1 - 1] = port_dev;
        port_dev->portnum = port1;
        set_bit(port1, hub->power_bits);
index 98487fd..de42f14 100644 (file)
@@ -6,6 +6,7 @@
 #ifndef __LINUX_USB_OF_H
 #define __LINUX_USB_OF_H
 
+#include <linux/usb.h>
 #include <linux/usb/ch9.h>
 #include <linux/usb/otg.h>
 #include <linux/usb/phy.h>
@@ -17,6 +18,7 @@ enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0);
 bool of_usb_host_tpl_support(struct device_node *np);
 int of_usb_update_otg_caps(struct device_node *np,
                        struct usb_otg_caps *otg_caps);
+enum usb_port_connect_type usb_of_get_connect_type(struct usb_device *hub, int port1);
 struct device_node *usb_of_get_device_node(struct usb_device *hub, int port1);
 bool usb_of_has_combined_node(struct usb_device *udev);
 struct device_node *usb_of_get_interface_node(struct usb_device *udev,
@@ -37,6 +39,11 @@ static inline int of_usb_update_otg_caps(struct device_node *np,
 {
        return 0;
 }
+static inline enum usb_port_connect_type
+usb_of_get_connect_type(const struct usb_device *hub, int port1)
+{
+       return USB_PORT_CONNECT_TYPE_UNKNOWN;
+}
 static inline struct device_node *
 usb_of_get_device_node(struct usb_device *hub, int port1)
 {