GNU Linux-libre 5.16.19-gnu
[releases.git] / drivers / usb / typec / port-mapper.c
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
15 struct port_node {
16         struct list_head list;
17         struct device *dev;
18         void *pld;
19 };
20
21 static 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
37 static 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
56 static void free_pld(void *pld)
57 {
58 #ifdef CONFIG_ACPI
59         ACPI_FREE(pld);
60 #endif
61 }
62
63 static 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
83 static 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
94 static 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
101 static 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
108 static 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
122 static void remove_port_node(struct port_node *node)
123 {
124         put_device(node->dev);
125         free_pld(node->pld);
126         kfree(node);
127 }
128
129 static 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
139 static 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  */
156 int 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
178 put_connector:
179         put_device(connector);
180 remove_node:
181         remove_port_node(node);
182
183         return ret;
184 }
185 EXPORT_SYMBOL_GPL(typec_link_port);
186
187 static 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  */
217 void typec_unlink_port(struct device *port)
218 {
219         class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
220 }
221 EXPORT_SYMBOL_GPL(typec_unlink_port);
222
223 static 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
248 int 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
263 void 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 }