xref: /illumos-gate/usr/src/uts/common/io/i2c/nexus/i2cnex_device.c (revision 32002227574cf0a435dc03de622191ca53724f0a)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2025 Oxide Computer Company
14  */
15 
16 /*
17  * Device-specific nexus functions.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/kmem.h>
22 #include <sys/ddi.h>
23 #include <sys/sunddi.h>
24 #include <sys/sunndi.h>
25 #include <sys/stddef.h>
26 #include <sys/fs/dv_node.h>
27 
28 #include "i2cnex.h"
29 
30 i2c_dev_t *
i2c_device_find_by_addr(i2c_txn_t * txn,i2c_port_t * port,const i2c_addr_t * addr)31 i2c_device_find_by_addr(i2c_txn_t *txn, i2c_port_t *port,
32     const i2c_addr_t *addr)
33 {
34 	i2c_dev_t d;
35 
36 	VERIFY(i2c_txn_held(txn));
37 	VERIFY3P(txn->txn_ctrl, ==, port->ip_nex->in_ctrl);
38 	(void) memset(&d, 0, sizeof (i2c_dev_t));
39 	d.id_addr = *addr;
40 
41 	return (avl_find(&port->ip_devices, &d, NULL));
42 }
43 
44 static void
i2c_device_free(i2c_dev_t * dev)45 i2c_device_free(i2c_dev_t *dev)
46 {
47 	VERIFY3P(dev->id_mux, ==, NULL);
48 	if (dev->id_nucompat > 0) {
49 		VERIFY3P(dev->id_ucompat, !=, NULL);
50 		for (uint_t i = 0; i < dev->id_nucompat; i++) {
51 			strfree(dev->id_ucompat[i]);
52 		}
53 		kmem_free(dev->id_ucompat, sizeof (char *) * dev->id_nucompat);
54 	}
55 	i2cnex_nex_free(dev->id_nex);
56 	list_destroy(&dev->id_clients);
57 	kmem_free(dev, sizeof (i2c_dev_t));
58 }
59 
60 static bool
i2c_device_parent_rm(i2c_port_t * port,void * arg)61 i2c_device_parent_rm(i2c_port_t *port, void *arg)
62 {
63 	VERIFY3U(port->ip_ndevs_ds, >, 0);
64 	port->ip_ndevs_ds--;
65 	return (true);
66 }
67 
68 static bool
i2c_device_parent_add(i2c_port_t * port,void * arg)69 i2c_device_parent_add(i2c_port_t *port, void *arg)
70 {
71 	port->ip_ndevs_ds++;
72 	return (true);
73 }
74 
75 void
i2c_device_fini(i2c_txn_t * txn,i2c_port_t * port,i2c_dev_t * dev)76 i2c_device_fini(i2c_txn_t *txn, i2c_port_t *port, i2c_dev_t *dev)
77 {
78 	VERIFY(i2c_txn_held(txn));
79 	VERIFY3P(txn->txn_ctrl, ==, port->ip_nex->in_ctrl);
80 	VERIFY3P(dev->id_nex->in_pnex, ==, port->ip_nex);
81 
82 	i2c_port_parent_iter(port, i2c_device_parent_rm, NULL);
83 	avl_remove(&port->ip_devices, dev);
84 	i2c_addr_free(port, &dev->id_addr);
85 	i2c_device_free(dev);
86 }
87 
88 /*
89  * Attempt to allocate the address specified and return the allocated device.
90  * The device will not be visible in the port until i2c_device_config() has been
91  * called on it.
92  */
93 i2c_dev_t *
i2c_device_init(i2c_txn_t * txn,i2c_port_t * port,const i2c_addr_t * addr,const char * name,char * const * compat,uint_t ncompat,i2c_error_t * err)94 i2c_device_init(i2c_txn_t *txn, i2c_port_t *port, const i2c_addr_t *addr,
95     const char *name, char *const *compat, uint_t ncompat, i2c_error_t *err)
96 {
97 	char ua[I2C_NAME_MAX];
98 	i2c_dev_t *dev;
99 	i2c_ctrl_t *ctrl = port->ip_nex->in_ctrl;
100 
101 	VERIFY(i2c_txn_held(txn));
102 	VERIFY3P(txn->txn_ctrl, ==, ctrl);
103 
104 	/*
105 	 * First attempt to grab the address. If we can't grab it, then that's
106 	 * that.
107 	 */
108 	if (!i2c_addr_alloc(port, addr, err)) {
109 		return (NULL);
110 	}
111 
112 	dev = kmem_zalloc(sizeof (i2c_dev_t), KM_SLEEP);
113 	list_create(&dev->id_clients, sizeof (i2c_client_t),
114 	    offsetof(i2c_client_t, icli_dev_link));
115 	dev->id_addr = *addr;
116 	if (ncompat > 0) {
117 		dev->id_nucompat = ncompat;
118 		dev->id_ucompat = kmem_alloc(sizeof (char *) * ncompat,
119 		    KM_SLEEP);
120 		for (uint_t i = 0; i < ncompat; i++) {
121 			dev->id_ucompat[i] = strdup(compat[i]);
122 		}
123 	}
124 
125 	(void) snprintf(ua, sizeof (ua), "%x,%x", addr->ia_type, addr->ia_addr);
126 	dev->id_nex = i2cnex_nex_alloc(I2C_NEXUS_T_DEV, port->ip_nex->in_dip,
127 	    port->ip_nex, name, ua, ctrl);
128 	if (dev->id_nex == NULL) {
129 		i2c_addr_free(port, &dev->id_addr);
130 		i2c_device_free(dev);
131 		return (NULL);
132 	}
133 	dev->id_nex->in_data.in_dev = dev;
134 
135 	/*
136 	 * Finish by adding it to the list such that it can be discovered by
137 	 * device configuration logic. Then walk our parents so they can update
138 	 * their device count metrics.
139 	 */
140 	avl_add(&port->ip_devices, dev);
141 	i2c_port_parent_iter(port, i2c_device_parent_add, NULL);
142 
143 	return (dev);
144 }
145 
146 /*
147  * We are going to attempt to unconfigure a node. This normally would be a
148  * straightforward request based on the unit address; however, our node in
149  * question may never have been attached and therefore may not have an address
150  * assigned. The NDI doesn't give us a good way to deal with this, therefore we
151  * need to look at the node state ourselves and figure out what to do.
152  *
153  * This seems unfortunate, especially as we have the dip at our disposal. We
154  * should make this the NDI's problem. It'd be nice if we had an
155  * ndi_devi_unconfig_dip() or similar.
156  */
157 bool
i2c_device_unconfig(i2c_port_t * port,i2c_dev_t * dev)158 i2c_device_unconfig(i2c_port_t *port, i2c_dev_t *dev)
159 {
160 	int ret;
161 
162 	VERIFY(DEVI_BUSY_OWNED(port->ip_nex->in_dip));
163 
164 	/*
165 	 * This node may already have been detached and torn down for some
166 	 * reason say due to a BUS_UNCONFIG_ALL of the port. If there is no dip,
167 	 * then we're done. This also means that this device should not be in
168 	 * the AVL tree.
169 	 */
170 	if (dev->id_nex->in_dip == NULL) {
171 		return (true);
172 	}
173 
174 	/*
175 	 * The fundamental problem here is that if we're not initialized, then
176 	 * we can't actually be found by ndi_devi_unconfig_one. This is an
177 	 * unfortuante NDI wart which means that we don't actually get uniform
178 	 * clean up and teardown that we need. So we have to basically cheat
179 	 * here. This should be solved with improvements to the NDI.
180 	 */
181 	if (i_ddi_node_state(dev->id_nex->in_dip) < DS_INITIALIZED) {
182 		i2c_nex_dev_cleanup(dev->id_nex);
183 		ret = ddi_remove_child(dev->id_nex->in_dip, 0);
184 		if (ret == NDI_SUCCESS) {
185 			dev->id_nex->in_dip = NULL;
186 		}
187 	} else {
188 		char ua[I2C_NAME_MAX * 4];
189 		(void) snprintf(ua, sizeof (ua), "%s@%s", dev->id_nex->in_name,
190 		    dev->id_nex->in_addr);
191 		(void) devfs_clean(port->ip_nex->in_dip, ua, DV_CLEAN_FORCE);
192 		ret = ndi_devi_unconfig_one(port->ip_nex->in_dip, ua, NULL,
193 		    NDI_DEVI_REMOVE | NDI_UNCONFIG);
194 	}
195 
196 	if (ret != NDI_SUCCESS) {
197 		return (false);
198 	}
199 
200 	return (true);
201 }
202 
203 bool
i2c_device_config(i2c_port_t * port,i2c_dev_t * dev)204 i2c_device_config(i2c_port_t *port, i2c_dev_t *dev)
205 {
206 	int ret;
207 	char ua[I2C_NAME_MAX * 4];
208 	dev_info_t *child;
209 
210 	VERIFY(DEVI_BUSY_OWNED(port->ip_nex->in_dip));
211 
212 	/*
213 	 * Ask the system to go ahead and configure this node. As long as a
214 	 * dev_info_t is successfully created, then we consider this a success.
215 	 * If it is created successfully, we will be given a reference count on
216 	 * the node that we need to decrement.
217 	 *
218 	 * It is possible that ndi_devi_config_one() will fail, but that's
219 	 * because if it fails to bind a driver to the node (which may not
220 	 * exist), then it will fail the call. Instead we use the existence of
221 	 * our dev_info_t in the nexus as the true sign of success.
222 	 */
223 
224 	(void) snprintf(ua, sizeof (ua), "%s@%s", dev->id_nex->in_name,
225 	    dev->id_nex->in_addr);
226 	ret = ndi_devi_config_one(port->ip_nex->in_dip, ua, &child, NDI_CONFIG |
227 	    NDI_ONLINE_ATTACH);
228 	if (ret == NDI_SUCCESS) {
229 		ndi_rele_devi(child);
230 	}
231 
232 	return (dev->id_nex->in_dip != NULL);
233 }
234