xref: /illumos-gate/usr/src/uts/common/io/i2c/nexus/i2cnex_txn.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  * This file implements the core controller locking logic and mechanisms. See
18  * the Locking section in the theory statement for more background and
19  * information.
20  */
21 
22 #include "i2cnex.h"
23 
24 void
i2c_txn_free(i2c_txn_t * txn)25 i2c_txn_free(i2c_txn_t *txn)
26 {
27 	if (txn == NULL) {
28 		return;
29 	}
30 
31 	VERIFY3U(txn->txn_state, ==, I2C_TXN_STATE_UNLOCKED);
32 	i2c_ctrl_t *ctrl = txn->txn_ctrl;
33 
34 	mutex_enter(&ctrl->ic_txn_lock);
35 	list_remove(&ctrl->ic_txns, txn);
36 	mutex_exit(&ctrl->ic_txn_lock);
37 
38 	cv_destroy(&txn->txn_cv);
39 	kmem_free(txn, sizeof (i2c_txn_t));
40 }
41 
42 i2c_txn_t *
i2c_txn_alloc(i2c_ctrl_t * ctrl,i2c_txn_tag_t tag,const void * debug)43 i2c_txn_alloc(i2c_ctrl_t *ctrl, i2c_txn_tag_t tag, const void *debug)
44 {
45 	i2c_txn_t *txn = kmem_zalloc(sizeof (i2c_txn_t), KM_SLEEP);
46 
47 	txn->txn_tag = tag;
48 	txn->txn_debug = debug;
49 	txn->txn_alloc_kthread = (uintptr_t)curthread;
50 	txn->txn_ctrl = ctrl;
51 	cv_init(&txn->txn_cv, NULL, CV_DRIVER, NULL);
52 
53 	mutex_enter(&ctrl->ic_txn_lock);
54 	list_insert_tail(&ctrl->ic_txns, txn);
55 	mutex_exit(&ctrl->ic_txn_lock);
56 
57 	return (txn);
58 }
59 
60 
61 bool
i2c_txn_held(i2c_txn_t * txn)62 i2c_txn_held(i2c_txn_t *txn)
63 {
64 	if (txn == NULL) {
65 		return (false);
66 	}
67 
68 	i2c_ctrl_t *ctrl = txn->txn_ctrl;
69 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
70 
71 	mutex_enter(&lock->cl_mutex);
72 	bool ret = lock->cl_owner == txn;
73 	mutex_exit(&lock->cl_mutex);
74 
75 	return (ret);
76 }
77 
78 static void
i2c_txn_lock_acquire(i2c_ctrl_lock_t * lock,i2c_txn_t * txn)79 i2c_txn_lock_acquire(i2c_ctrl_lock_t *lock, i2c_txn_t *txn)
80 {
81 	VERIFY3P(lock->cl_owner, ==, NULL);
82 	VERIFY3P(txn->txn_state, ==, I2C_TXN_STATE_UNLOCKED);
83 	VERIFY3U(list_link_active(&txn->txn_wait_link), ==, 0);
84 	VERIFY3P(&txn->txn_ctrl->ic_lock, ==, lock);
85 
86 	txn->txn_state = I2C_TXN_STATE_ACQUIRED;
87 	txn->txn_last_change = gethrtime();
88 	txn->txn_acq_kthread = (uintptr_t)curthread;
89 	txn->txn_acq_pid = curproc->p_pid;
90 
91 	lock->cl_owner = txn;
92 	lock->cl_nlocks++;
93 }
94 
95 static void
i2c_txn_lock_wakeup(i2c_ctrl_t * ctrl)96 i2c_txn_lock_wakeup(i2c_ctrl_t *ctrl)
97 {
98 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
99 
100 	VERIFY(MUTEX_HELD(&lock->cl_mutex));
101 	VERIFY3P(lock->cl_owner, ==, NULL);
102 
103 	/*
104 	 * If there is no one next in line, then we're done. Otherwise we need
105 	 * to reset that callers state to unlocked and then allow them to
106 	 * acquire it. After that we will need to signal them.
107 	 */
108 	i2c_txn_t *next = list_remove_head(&lock->cl_waiters);
109 	if (next == NULL)
110 		return;
111 
112 	/*
113 	 * As we're about to acquire this, we're going to note that we're no
114 	 * longer blocked.
115 	 */
116 	next->txn_state = I2C_TXN_STATE_UNLOCKED;
117 	next->txn_last_change = gethrtime();
118 	i2c_txn_lock_acquire(lock, next);
119 	cv_signal(&next->txn_cv);
120 }
121 
122 /*
123  * Our txn was interrupted due to a signal. However, other activity could have
124  * still been going on and we cannot guarantee that the caller didn't actually
125  * acquire this and then raced with receiving a signal compared to the
126  * cv_signal(). If they did acquire it, then we treat this as a case where we
127  * need to go ahead and wake up the next person.
128  */
129 static void
i2c_txn_lock_signal(i2c_ctrl_t * ctrl,i2c_txn_t * txn)130 i2c_txn_lock_signal(i2c_ctrl_t *ctrl, i2c_txn_t *txn)
131 {
132 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
133 
134 	/*
135 	 * We don't believe that we should be able to enter here with the lock
136 	 * unlocked. Either we were blocked waiting for this or the next person
137 	 * woke us up because we had acquired the controller. There should be no
138 	 * other state.
139 	 */
140 	VERIFY3U(txn->txn_state, !=, I2C_TXN_STATE_UNLOCKED);
141 	VERIFY(MUTEX_HELD(&lock->cl_mutex));
142 
143 	/*
144 	 * As we're changing the state of the this node, we're going to update
145 	 * the txn's change time now as well as setting the error that'll get
146 	 * propagated back.
147 	 */
148 	txn->txn_last_change = gethrtime();
149 	txn->txn_err = I2C_CORE_E_LOCK_WAIT_SIGNAL;
150 	lock->cl_nsig++;
151 
152 	if (txn->txn_state == I2C_TXN_STATE_BLOCKED) {
153 		VERIFY3U(lock->cl_owner, !=, txn);
154 		VERIFY3U(list_link_active(&txn->txn_wait_link), !=, 0);
155 		list_remove(&lock->cl_waiters, txn);
156 		txn->txn_state = I2C_TXN_STATE_UNLOCKED;
157 
158 		lock->cl_nsig_block++;
159 		return;
160 	}
161 
162 	/*
163 	 * This caller had actually managed to acquire this and now has to give
164 	 * it up. We treat this as an immediate unlock request.
165 	 */
166 	lock->cl_nsig_acq++;
167 	i2c_txn_ctrl_unlock(txn);
168 }
169 
170 void
i2c_txn_ctrl_unlock(i2c_txn_t * txn)171 i2c_txn_ctrl_unlock(i2c_txn_t *txn)
172 {
173 	i2c_ctrl_t *ctrl = txn->txn_ctrl;
174 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
175 
176 	mutex_enter(&lock->cl_mutex);
177 	VERIFY3P(lock->cl_owner, ==, txn);
178 	VERIFY3U(txn->txn_state, ==, I2C_TXN_STATE_ACQUIRED);
179 
180 	txn->txn_last_change = gethrtime();
181 	txn->txn_state = I2C_TXN_STATE_UNLOCKED;
182 
183 	/*
184 	 * Before we proceed to wake anyone up, check to see if we have anything
185 	 * in our stack. If so, then basically we're in the recursive nexus case
186 	 * and we need to resume them as the rightful owner without waking
187 	 * anyone up.
188 	 */
189 	lock->cl_owner = list_remove_head(&lock->cl_stack);
190 	if (lock->cl_owner == NULL) {
191 		i2c_txn_lock_wakeup(ctrl);
192 	}
193 	mutex_exit(&lock->cl_mutex);
194 }
195 
196 i2c_errno_t
i2c_txn_ctrl_lock(i2c_txn_t * txn,bool block)197 i2c_txn_ctrl_lock(i2c_txn_t *txn, bool block)
198 {
199 	i2c_ctrl_t *ctrl = txn->txn_ctrl;
200 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
201 	hrtime_t sleep_time;
202 	i2c_errno_t ret;
203 
204 	mutex_enter(&lock->cl_mutex);
205 	/*
206 	 * First check if this controller is currently held. If it is not, then
207 	 * this is easy, we get it right away. Note, this implies that there are
208 	 * no waiters as part of our invariants.
209 	 */
210 	VERIFY3P(lock->cl_owner, !=, txn);
211 	if (lock->cl_owner == NULL) {
212 		VERIFY3U(list_is_empty(&lock->cl_waiters), !=, 0);
213 		i2c_txn_lock_acquire(lock, txn);
214 		mutex_exit(&lock->cl_mutex);
215 		return (I2C_CORE_E_OK);
216 	}
217 
218 	/*
219 	 * Now, we must see if this is a nexus related operation and we're in
220 	 * the nexus thread. If so, lock inheritance is basically allowed. That
221 	 * means that we're allowed to basically "obtain" the lock. This should
222 	 * only really happen during a device's attach / detach operation.
223 	 */
224 	if (lock->cl_nexus_thr == (uintptr_t)curthread) {
225 		list_insert_head(&lock->cl_stack, lock->cl_owner);
226 		lock->cl_owner = NULL;
227 		i2c_txn_lock_acquire(lock, txn);
228 		lock->cl_nstack++;
229 		mutex_exit(&lock->cl_mutex);
230 		return (I2C_CORE_E_OK);
231 	}
232 
233 	if (!block) {
234 		mutex_exit(&lock->cl_mutex);
235 		return (I2C_CORE_E_LOCK_WOULD_BLOCK);
236 	}
237 
238 	VERIFY3U(txn->txn_state, ==, I2C_TXN_STATE_UNLOCKED);
239 	txn->txn_state = I2C_TXN_STATE_BLOCKED;
240 	sleep_time = gethrtime();
241 	list_insert_tail(&lock->cl_waiters, txn);
242 
243 	while (txn->txn_state == I2C_TXN_STATE_BLOCKED) {
244 		/*
245 		 * Block until we receive a signal. Signals to interrupt us
246 		 * trump any other action that could occur. It's important to
247 		 * note that we could raise with being signaled and handed the
248 		 * controller. In that case, we defer to the signal.
249 		 */
250 		if (cv_wait_sig(&txn->txn_cv, &lock->cl_mutex) == 0) {
251 			i2c_txn_lock_signal(ctrl, txn);
252 			break;
253 		}
254 	}
255 
256 	VERIFY3S(txn->txn_last_change, !=, sleep_time);
257 	VERIFY3U(list_link_active(&txn->txn_wait_link), ==, 0);
258 	if (txn->txn_state == I2C_TXN_STATE_UNLOCKED) {
259 		ret = txn->txn_err;
260 	} else {
261 		VERIFY3U(txn->txn_state, ==, I2C_TXN_STATE_ACQUIRED);
262 		VERIFY3P(lock->cl_owner, ==, txn);
263 		ret = I2C_CORE_E_OK;
264 	}
265 
266 	mutex_exit(&lock->cl_mutex);
267 	return (ret);
268 }
269 
270 void
i2c_txn_nexus_op_end(i2c_txn_t * txn)271 i2c_txn_nexus_op_end(i2c_txn_t *txn)
272 {
273 	i2c_ctrl_t *ctrl = txn->txn_ctrl;
274 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
275 
276 	mutex_enter(&lock->cl_mutex);
277 	VERIFY3P(lock->cl_owner, ==, txn);
278 	VERIFY3U(txn->txn_state, ==, I2C_TXN_STATE_ACQUIRED);
279 	VERIFY3U(lock->cl_nexus_thr, ==, curthread);
280 
281 	/*
282 	 * It is possible for us to have multiple layers of nexus stacking. For
283 	 * example, a user is doing a device create, which in turn calls a bus
284 	 * config operation. If this has happened, then we'll have a non-empty
285 	 * stack.
286 	 */
287 	if (list_is_empty(&lock->cl_stack) != 0) {
288 		lock->cl_nexus_thr = 0;
289 	}
290 	mutex_exit(&lock->cl_mutex);
291 }
292 
293 void
i2c_txn_nexus_op_begin(i2c_txn_t * txn)294 i2c_txn_nexus_op_begin(i2c_txn_t *txn)
295 {
296 	i2c_ctrl_t *ctrl = txn->txn_ctrl;
297 	i2c_ctrl_lock_t *lock = &ctrl->ic_lock;
298 
299 	mutex_enter(&lock->cl_mutex);
300 	VERIFY3P(lock->cl_owner, ==, txn);
301 	VERIFY3U(txn->txn_state, ==, I2C_TXN_STATE_ACQUIRED);
302 	VERIFY0(lock->cl_nexus_thr);
303 	if (lock->cl_nexus_thr != 0) {
304 		VERIFY3U(lock->cl_nexus_thr, ==, curthread);
305 	}
306 	lock->cl_nexus_thr = (uintptr_t)curthread;
307 	lock->cl_nnexus++;
308 	mutex_exit(&lock->cl_mutex);
309 }
310