linux/drivers/usb/typec/port-mapper.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * USB Type-C Connector Class Port Mapping Utility
   4 *
   5 * Copyright (C) 2021, Intel Corporation
   6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
   7 */
   8
   9#include <linux/acpi.h>
  10#include <linux/usb.h>
  11#include <linux/usb/typec.h>
  12
  13#include "class.h"
  14
  15struct port_node {
  16        struct list_head list;
  17        struct device *dev;
  18        void *pld;
  19};
  20
  21static int acpi_pld_match(const struct acpi_pld_info *pld1,
  22                          const struct acpi_pld_info *pld2)
  23{
  24        if (!pld1 || !pld2)
  25                return 0;
  26
  27        /*
  28         * To speed things up, first checking only the group_position. It seems
  29         * to often have the first unique value in the _PLD.
  30         */
  31        if (pld1->group_position == pld2->group_position)
  32                return !memcmp(pld1, pld2, sizeof(struct acpi_pld_info));
  33
  34        return 0;
  35}
  36
  37static void *get_pld(struct device *dev)
  38{
  39#ifdef CONFIG_ACPI
  40        struct acpi_pld_info *pld;
  41        acpi_status status;
  42
  43        if (!has_acpi_companion(dev))
  44                return NULL;
  45
  46        status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld);
  47        if (ACPI_FAILURE(status))
  48                return NULL;
  49
  50        return pld;
  51#else
  52        return NULL;
  53#endif
  54}
  55
  56static void free_pld(void *pld)
  57{
  58#ifdef CONFIG_ACPI
  59        ACPI_FREE(pld);
  60#endif
  61}
  62
  63static int __link_port(struct typec_port *con, struct port_node *node)
  64{
  65        int ret;
  66
  67        ret = sysfs_create_link(&node->dev->kobj, &con->dev.kobj, "connector");
  68        if (ret)
  69                return ret;
  70
  71        ret = sysfs_create_link(&con->dev.kobj, &node->dev->kobj,
  72                                dev_name(node->dev));
  73        if (ret) {
  74                sysfs_remove_link(&node->dev->kobj, "connector");
  75                return ret;
  76        }
  77
  78        list_add_tail(&node->list, &con->port_list);
  79
  80        return 0;
  81}
  82
  83static int link_port(struct typec_port *con, struct port_node *node)
  84{
  85        int ret;
  86
  87        mutex_lock(&con->port_list_lock);
  88        ret = __link_port(con, node);
  89        mutex_unlock(&con->port_list_lock);
  90
  91        return ret;
  92}
  93
  94static void __unlink_port(struct typec_port *con, struct port_node *node)
  95{
  96        sysfs_remove_link(&con->dev.kobj, dev_name(node->dev));
  97        sysfs_remove_link(&node->dev->kobj, "connector");
  98        list_del(&node->list);
  99}
 100
 101static void unlink_port(struct typec_port *con, struct port_node *node)
 102{
 103        mutex_lock(&con->port_list_lock);
 104        __unlink_port(con, node);
 105        mutex_unlock(&con->port_list_lock);
 106}
 107
 108static struct port_node *create_port_node(struct device *port)
 109{
 110        struct port_node *node;
 111
 112        node = kzalloc(sizeof(*node), GFP_KERNEL);
 113        if (!node)
 114                return ERR_PTR(-ENOMEM);
 115
 116        node->dev = get_device(port);
 117        node->pld = get_pld(port);
 118
 119        return node;
 120}
 121
 122static void remove_port_node(struct port_node *node)
 123{
 124        put_device(node->dev);
 125        free_pld(node->pld);
 126        kfree(node);
 127}
 128
 129static int connector_match(struct device *dev, const void *data)
 130{
 131        const struct port_node *node = data;
 132
 133        if (!is_typec_port(dev))
 134                return 0;
 135
 136        return acpi_pld_match(to_typec_port(dev)->pld, node->pld);
 137}
 138
 139static struct device *find_connector(struct port_node *node)
 140{
 141        if (!node->pld)
 142                return NULL;
 143
 144        return class_find_device(&typec_class, NULL, node, connector_match);
 145}
 146
 147/**
 148 * typec_link_port - Link a port to its connector
 149 * @port: The port device
 150 *
 151 * Find the connector of @port and create symlink named "connector" for it.
 152 * Returns 0 on success, or errno in case of a failure.
 153 *
 154 * NOTE. The function increments the reference count of @port on success.
 155 */
 156int typec_link_port(struct device *port)
 157{
 158        struct device *connector;
 159        struct port_node *node;
 160        int ret;
 161
 162        node = create_port_node(port);
 163        if (IS_ERR(node))
 164                return PTR_ERR(node);
 165
 166        connector = find_connector(node);
 167        if (!connector) {
 168                ret = 0;
 169                goto remove_node;
 170        }
 171
 172        ret = link_port(to_typec_port(connector), node);
 173        if (ret)
 174                goto put_connector;
 175
 176        return 0;
 177
 178put_connector:
 179        put_device(connector);
 180remove_node:
 181        remove_port_node(node);
 182
 183        return ret;
 184}
 185EXPORT_SYMBOL_GPL(typec_link_port);
 186
 187static int port_match_and_unlink(struct device *connector, void *port)
 188{
 189        struct port_node *node;
 190        struct port_node *tmp;
 191        int ret = 0;
 192
 193        if (!is_typec_port(connector))
 194                return 0;
 195
 196        mutex_lock(&to_typec_port(connector)->port_list_lock);
 197        list_for_each_entry_safe(node, tmp, &to_typec_port(connector)->port_list, list) {
 198                ret = node->dev == port;
 199                if (ret) {
 200                        unlink_port(to_typec_port(connector), node);
 201                        remove_port_node(node);
 202                        put_device(connector);
 203                        break;
 204                }
 205        }
 206        mutex_unlock(&to_typec_port(connector)->port_list_lock);
 207
 208        return ret;
 209}
 210
 211/**
 212 * typec_unlink_port - Unlink port from its connector
 213 * @port: The port device
 214 *
 215 * Removes the symlink "connector" and decrements the reference count of @port.
 216 */
 217void typec_unlink_port(struct device *port)
 218{
 219        class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
 220}
 221EXPORT_SYMBOL_GPL(typec_unlink_port);
 222
 223static int each_port(struct device *port, void *connector)
 224{
 225        struct port_node *node;
 226        int ret;
 227
 228        node = create_port_node(port);
 229        if (IS_ERR(node))
 230                return PTR_ERR(node);
 231
 232        if (!connector_match(connector, node)) {
 233                remove_port_node(node);
 234                return 0;
 235        }
 236
 237        ret = link_port(to_typec_port(connector), node);
 238        if (ret) {
 239                remove_port_node(node->pld);
 240                return ret;
 241        }
 242
 243        get_device(connector);
 244
 245        return 0;
 246}
 247
 248int typec_link_ports(struct typec_port *con)
 249{
 250        int ret = 0;
 251
 252        con->pld = get_pld(&con->dev);
 253        if (!con->pld)
 254                return 0;
 255
 256        ret = usb_for_each_port(&con->dev, each_port);
 257        if (ret)
 258                typec_unlink_ports(con);
 259
 260        return ret;
 261}
 262
 263void typec_unlink_ports(struct typec_port *con)
 264{
 265        struct port_node *node;
 266        struct port_node *tmp;
 267
 268        mutex_lock(&con->port_list_lock);
 269
 270        list_for_each_entry_safe(node, tmp, &con->port_list, list) {
 271                __unlink_port(con, node);
 272                remove_port_node(node);
 273                put_device(&con->dev);
 274        }
 275
 276        mutex_unlock(&con->port_list_lock);
 277
 278        free_pld(con->pld);
 279}
 280