usb: typec: Port mapping utility
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>
Wed, 7 Apr 2021 06:55:52 +0000 (09:55 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 9 Apr 2021 14:00:00 +0000 (16:00 +0200)
Adding functions that can be used to link/unlink ports -
USB ports, TBT3/USB4 ports, DisplayPorts and so on - to
the USB Type-C connectors they are attached to inside a
system. The symlink that is created for the port device is
named "connector".

Initially only ACPI is supported. ACPI port object shares
the _PLD (Physical Location of Device) with the USB Type-C
connector that it's attached to.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://lore.kernel.org/r/20210407065555.88110-2-heikki.krogerus@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/typec/Makefile
drivers/usb/typec/class.c
drivers/usb/typec/class.h
drivers/usb/typec/port-mapper.c [new file with mode: 0644]
include/linux/usb/typec.h

index 1fb8b66..a0adb89 100644 (file)
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TYPEC)            += typec.o
-typec-y                                := class.o mux.o bus.o
+typec-y                                := class.o mux.o bus.o port-mapper.o
 obj-$(CONFIG_TYPEC)            += altmodes/
 obj-$(CONFIG_TYPEC_TCPM)       += tcpm/
 obj-$(CONFIG_TYPEC_UCSI)       += ucsi/
index d3e1002..ff199e2 100644 (file)
@@ -18,7 +18,7 @@
 
 static DEFINE_IDA(typec_index_ida);
 
-static struct class typec_class = {
+struct class typec_class = {
        .name = "typec",
        .owner = THIS_MODULE,
 };
@@ -1601,6 +1601,7 @@ static void typec_release(struct device *dev)
        ida_destroy(&port->mode_ids);
        typec_switch_put(port->sw);
        typec_mux_put(port->mux);
+       free_pld(port->pld);
        kfree(port->cap);
        kfree(port);
 }
@@ -1983,6 +1984,8 @@ struct typec_port *typec_register_port(struct device *parent,
 
        ida_init(&port->mode_ids);
        mutex_init(&port->port_type_lock);
+       mutex_init(&port->port_list_lock);
+       INIT_LIST_HEAD(&port->port_list);
 
        port->id = id;
        port->ops = cap->ops;
@@ -2024,6 +2027,8 @@ struct typec_port *typec_register_port(struct device *parent,
                return ERR_PTR(ret);
        }
 
+       port->pld = get_pld(&port->dev);
+
        return port;
 }
 EXPORT_SYMBOL_GPL(typec_register_port);
index d414be5..52294f7 100644 (file)
@@ -54,6 +54,11 @@ struct typec_port {
 
        const struct typec_capability   *cap;
        const struct typec_operations   *ops;
+
+       struct list_head                port_list;
+       struct mutex                    port_list_lock; /* Port list lock */
+
+       void                            *pld;
 };
 
 #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
@@ -72,5 +77,9 @@ extern const struct device_type typec_port_dev_type;
 #define is_typec_port(dev) ((dev)->type == &typec_port_dev_type)
 
 extern struct class typec_mux_class;
+extern struct class typec_class;
+
+void *get_pld(struct device *dev);
+void free_pld(void *pld);
 
 #endif /* __USB_TYPEC_CLASS__ */
diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c
new file mode 100644 (file)
index 0000000..5bee7a9
--- /dev/null
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Type-C Connector Class Port Mapping Utility
+ *
+ * Copyright (C) 2021, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/usb.h>
+#include <linux/usb/typec.h>
+
+#include "class.h"
+
+struct port_node {
+       struct list_head list;
+       struct device *dev;
+       void *pld;
+};
+
+static int acpi_pld_match(const struct acpi_pld_info *pld1,
+                         const struct acpi_pld_info *pld2)
+{
+       if (!pld1 || !pld2)
+               return 0;
+
+       /*
+        * To speed things up, first checking only the group_position. It seems
+        * to often have the first unique value in the _PLD.
+        */
+       if (pld1->group_position == pld2->group_position)
+               return !memcmp(pld1, pld2, sizeof(struct acpi_pld_info));
+
+       return 0;
+}
+
+void *get_pld(struct device *dev)
+{
+#ifdef CONFIG_ACPI
+       struct acpi_pld_info *pld;
+       acpi_status status;
+
+       if (!has_acpi_companion(dev))
+               return NULL;
+
+       status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld);
+       if (ACPI_FAILURE(status))
+               return NULL;
+
+       return pld;
+#else
+       return NULL;
+#endif
+}
+
+void free_pld(void *pld)
+{
+#ifdef CONFIG_ACPI
+       ACPI_FREE(pld);
+#endif
+}
+
+static int __link_port(struct typec_port *con, struct port_node *node)
+{
+       int ret;
+
+       ret = sysfs_create_link(&node->dev->kobj, &con->dev.kobj, "connector");
+       if (ret)
+               return ret;
+
+       ret = sysfs_create_link(&con->dev.kobj, &node->dev->kobj,
+                               dev_name(node->dev));
+       if (ret) {
+               sysfs_remove_link(&node->dev->kobj, "connector");
+               return ret;
+       }
+
+       list_add_tail(&node->list, &con->port_list);
+
+       return 0;
+}
+
+static int link_port(struct typec_port *con, struct port_node *node)
+{
+       int ret;
+
+       mutex_lock(&con->port_list_lock);
+       ret = __link_port(con, node);
+       mutex_unlock(&con->port_list_lock);
+
+       return ret;
+}
+
+static void __unlink_port(struct typec_port *con, struct port_node *node)
+{
+       sysfs_remove_link(&con->dev.kobj, dev_name(node->dev));
+       sysfs_remove_link(&node->dev->kobj, "connector");
+       list_del(&node->list);
+}
+
+static void unlink_port(struct typec_port *con, struct port_node *node)
+{
+       mutex_lock(&con->port_list_lock);
+       __unlink_port(con, node);
+       mutex_unlock(&con->port_list_lock);
+}
+
+static struct port_node *create_port_node(struct device *port)
+{
+       struct port_node *node;
+
+       node = kzalloc(sizeof(*node), GFP_KERNEL);
+       if (!node)
+               return ERR_PTR(-ENOMEM);
+
+       node->dev = get_device(port);
+       node->pld = get_pld(port);
+
+       return node;
+}
+
+static void remove_port_node(struct port_node *node)
+{
+       put_device(node->dev);
+       free_pld(node->pld);
+       kfree(node);
+}
+
+static int connector_match(struct device *dev, const void *data)
+{
+       const struct port_node *node = data;
+
+       if (!is_typec_port(dev))
+               return 0;
+
+       return acpi_pld_match(to_typec_port(dev)->pld, node->pld);
+}
+
+static struct device *find_connector(struct port_node *node)
+{
+       if (!node->pld)
+               return NULL;
+
+       return class_find_device(&typec_class, NULL, node, connector_match);
+}
+
+/**
+ * typec_link_port - Link a port to its connector
+ * @port: The port device
+ *
+ * Find the connector of @port and create symlink named "connector" for it.
+ * Returns 0 on success, or errno in case of a failure.
+ *
+ * NOTE. The function increments the reference count of @port on success.
+ */
+int typec_link_port(struct device *port)
+{
+       struct device *connector;
+       struct port_node *node;
+       int ret = 0;
+
+       node = create_port_node(port);
+       if (IS_ERR(node))
+               return PTR_ERR(node);
+
+       connector = find_connector(node);
+       if (!connector)
+               goto remove_node;
+
+       ret = link_port(to_typec_port(connector), node);
+       if (ret)
+               goto put_connector;
+
+       return 0;
+
+put_connector:
+       put_device(connector);
+remove_node:
+       remove_port_node(node);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(typec_link_port);
+
+static int port_match_and_unlink(struct device *connector, void *port)
+{
+       struct port_node *node;
+       struct port_node *tmp;
+       int ret = 0;
+
+       if (!is_typec_port(connector))
+               return 0;
+
+       mutex_lock(&to_typec_port(connector)->port_list_lock);
+       list_for_each_entry_safe(node, tmp, &to_typec_port(connector)->port_list, list) {
+               ret = node->dev == port;
+               if (ret) {
+                       unlink_port(to_typec_port(connector), node);
+                       remove_port_node(node);
+                       put_device(connector);
+                       break;
+               }
+       }
+       mutex_unlock(&to_typec_port(connector)->port_list_lock);
+
+       return ret;
+}
+
+/**
+ * typec_unlink_port - Unlink port from its connector
+ * @port: The port device
+ *
+ * Removes the symlink "connector" and decrements the reference count of @port.
+ */
+void typec_unlink_port(struct device *port)
+{
+       class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
+}
+EXPORT_SYMBOL_GPL(typec_unlink_port);
index 91b4303..e271472 100644 (file)
@@ -298,4 +298,17 @@ int typec_find_port_data_role(const char *name);
 void typec_partner_set_svdm_version(struct typec_partner *partner,
                                    enum usb_pd_svdm_ver svdm_version);
 int typec_get_negotiated_svdm_version(struct typec_port *port);
+
+#if IS_REACHABLE(CONFIG_TYPEC)
+int typec_link_port(struct device *port);
+void typec_unlink_port(struct device *port);
+#else
+static inline int typec_link_port(struct device *port)
+{
+       return 0;
+}
+
+static inline void typec_unlink_port(struct device *port) { }
+#endif
+
 #endif /* __LINUX_USB_TYPEC_H */