xref: /arm-trusted-firmware/lib/locks/bakery/bakery_lock_normal.c (revision 91f16700b400a8c0651d24a598fc48ee2997a0d7)
1*91f16700Schasinglulu /*
2*91f16700Schasinglulu  * Copyright (c) 2015-2020, Arm Limited and Contributors. All rights reserved.
3*91f16700Schasinglulu  * Copyright (c) 2020, NVIDIA Corporation. All rights reserved.
4*91f16700Schasinglulu  *
5*91f16700Schasinglulu  * SPDX-License-Identifier: BSD-3-Clause
6*91f16700Schasinglulu  */
7*91f16700Schasinglulu 
8*91f16700Schasinglulu #include <assert.h>
9*91f16700Schasinglulu #include <string.h>
10*91f16700Schasinglulu 
11*91f16700Schasinglulu #include <arch_helpers.h>
12*91f16700Schasinglulu #include <lib/bakery_lock.h>
13*91f16700Schasinglulu #include <lib/el3_runtime/cpu_data.h>
14*91f16700Schasinglulu #include <lib/utils_def.h>
15*91f16700Schasinglulu #include <plat/common/platform.h>
16*91f16700Schasinglulu 
17*91f16700Schasinglulu /*
18*91f16700Schasinglulu  * Functions in this file implement Bakery Algorithm for mutual exclusion with the
19*91f16700Schasinglulu  * bakery lock data structures in cacheable and Normal memory.
20*91f16700Schasinglulu  *
21*91f16700Schasinglulu  * ARM architecture offers a family of exclusive access instructions to
22*91f16700Schasinglulu  * efficiently implement mutual exclusion with hardware support. However, as
23*91f16700Schasinglulu  * well as depending on external hardware, these instructions have defined
24*91f16700Schasinglulu  * behavior only on certain memory types (cacheable and Normal memory in
25*91f16700Schasinglulu  * particular; see ARMv8 Architecture Reference Manual section B2.10). Use cases
26*91f16700Schasinglulu  * in trusted firmware are such that mutual exclusion implementation cannot
27*91f16700Schasinglulu  * expect that accesses to the lock have the specific type required by the
28*91f16700Schasinglulu  * architecture for these primitives to function (for example, not all
29*91f16700Schasinglulu  * contenders may have address translation enabled).
30*91f16700Schasinglulu  *
31*91f16700Schasinglulu  * This implementation does not use mutual exclusion primitives. It expects
32*91f16700Schasinglulu  * memory regions where the locks reside to be cacheable and Normal.
33*91f16700Schasinglulu  *
34*91f16700Schasinglulu  * Note that the ARM architecture guarantees single-copy atomicity for aligned
35*91f16700Schasinglulu  * accesses regardless of status of address translation.
36*91f16700Schasinglulu  */
37*91f16700Schasinglulu 
38*91f16700Schasinglulu #ifdef PLAT_PERCPU_BAKERY_LOCK_SIZE
39*91f16700Schasinglulu /*
40*91f16700Schasinglulu  * Verify that the platform defined value for the per-cpu space for bakery locks is
41*91f16700Schasinglulu  * a multiple of the cache line size, to prevent multiple CPUs writing to the same
42*91f16700Schasinglulu  * bakery lock cache line
43*91f16700Schasinglulu  *
44*91f16700Schasinglulu  * Using this value, if provided, rather than the linker generated value results in
45*91f16700Schasinglulu  * more efficient code
46*91f16700Schasinglulu  */
47*91f16700Schasinglulu CASSERT((PLAT_PERCPU_BAKERY_LOCK_SIZE & (CACHE_WRITEBACK_GRANULE - 1)) == 0,
48*91f16700Schasinglulu 	PLAT_PERCPU_BAKERY_LOCK_SIZE_not_cacheline_multiple);
49*91f16700Schasinglulu #define PERCPU_BAKERY_LOCK_SIZE (PLAT_PERCPU_BAKERY_LOCK_SIZE)
50*91f16700Schasinglulu #else
51*91f16700Schasinglulu /*
52*91f16700Schasinglulu  * Use the linker defined symbol which has evaluated the size reqiurement.
53*91f16700Schasinglulu  * This is not as efficient as using a platform defined constant
54*91f16700Schasinglulu  */
55*91f16700Schasinglulu IMPORT_SYM(uintptr_t, __PERCPU_BAKERY_LOCK_START__, BAKERY_LOCK_START);
56*91f16700Schasinglulu IMPORT_SYM(uintptr_t, __PERCPU_BAKERY_LOCK_END__, BAKERY_LOCK_END);
57*91f16700Schasinglulu #define PERCPU_BAKERY_LOCK_SIZE (BAKERY_LOCK_END - BAKERY_LOCK_START)
58*91f16700Schasinglulu #endif
59*91f16700Schasinglulu 
60*91f16700Schasinglulu static inline bakery_lock_t *get_bakery_info(unsigned int cpu_ix,
61*91f16700Schasinglulu 					     bakery_lock_t *lock)
62*91f16700Schasinglulu {
63*91f16700Schasinglulu 	return (bakery_info_t *)((uintptr_t)lock +
64*91f16700Schasinglulu 				cpu_ix * PERCPU_BAKERY_LOCK_SIZE);
65*91f16700Schasinglulu }
66*91f16700Schasinglulu 
67*91f16700Schasinglulu static inline void write_cache_op(uintptr_t addr, bool cached)
68*91f16700Schasinglulu {
69*91f16700Schasinglulu 	if (cached)
70*91f16700Schasinglulu 		dccvac(addr);
71*91f16700Schasinglulu 	else
72*91f16700Schasinglulu 		dcivac(addr);
73*91f16700Schasinglulu 
74*91f16700Schasinglulu 	dsbish();
75*91f16700Schasinglulu }
76*91f16700Schasinglulu 
77*91f16700Schasinglulu static inline void read_cache_op(uintptr_t addr, bool cached)
78*91f16700Schasinglulu {
79*91f16700Schasinglulu 	if (cached)
80*91f16700Schasinglulu 		dccivac(addr);
81*91f16700Schasinglulu 
82*91f16700Schasinglulu 	dmbish();
83*91f16700Schasinglulu }
84*91f16700Schasinglulu 
85*91f16700Schasinglulu /* Helper function to check if the lock is acquired */
86*91f16700Schasinglulu static inline __unused bool is_lock_acquired(const bakery_info_t *my_bakery_info,
87*91f16700Schasinglulu 				    bool is_cached)
88*91f16700Schasinglulu {
89*91f16700Schasinglulu 	/*
90*91f16700Schasinglulu 	 * Even though lock data is updated only by the owning cpu and
91*91f16700Schasinglulu 	 * appropriate cache maintenance operations are performed,
92*91f16700Schasinglulu 	 * if the previous update was done when the cpu was not participating
93*91f16700Schasinglulu 	 * in coherency, then there is a chance that cache maintenance
94*91f16700Schasinglulu 	 * operations were not propagated to all the caches in the system.
95*91f16700Schasinglulu 	 * Hence do a `read_cache_op()` prior to read.
96*91f16700Schasinglulu 	 */
97*91f16700Schasinglulu 	read_cache_op((uintptr_t)my_bakery_info, is_cached);
98*91f16700Schasinglulu 	return bakery_ticket_number(my_bakery_info->lock_data) != 0U;
99*91f16700Schasinglulu }
100*91f16700Schasinglulu 
101*91f16700Schasinglulu static unsigned int bakery_get_ticket(bakery_lock_t *lock,
102*91f16700Schasinglulu 				      unsigned int me, bool is_cached)
103*91f16700Schasinglulu {
104*91f16700Schasinglulu 	unsigned int my_ticket, their_ticket;
105*91f16700Schasinglulu 	unsigned int they;
106*91f16700Schasinglulu 	bakery_info_t *my_bakery_info, *their_bakery_info;
107*91f16700Schasinglulu 
108*91f16700Schasinglulu 	/*
109*91f16700Schasinglulu 	 * Obtain a reference to the bakery information for this cpu and ensure
110*91f16700Schasinglulu 	 * it is not NULL.
111*91f16700Schasinglulu 	 */
112*91f16700Schasinglulu 	my_bakery_info = get_bakery_info(me, lock);
113*91f16700Schasinglulu 	assert(my_bakery_info != NULL);
114*91f16700Schasinglulu 
115*91f16700Schasinglulu 	/* Prevent recursive acquisition.*/
116*91f16700Schasinglulu 	assert(!is_lock_acquired(my_bakery_info, is_cached));
117*91f16700Schasinglulu 
118*91f16700Schasinglulu 	/*
119*91f16700Schasinglulu 	 * Tell other contenders that we are through the bakery doorway i.e.
120*91f16700Schasinglulu 	 * going to allocate a ticket for this cpu.
121*91f16700Schasinglulu 	 */
122*91f16700Schasinglulu 	my_ticket = 0U;
123*91f16700Schasinglulu 	my_bakery_info->lock_data = make_bakery_data(CHOOSING_TICKET, my_ticket);
124*91f16700Schasinglulu 
125*91f16700Schasinglulu 	write_cache_op((uintptr_t)my_bakery_info, is_cached);
126*91f16700Schasinglulu 
127*91f16700Schasinglulu 	/*
128*91f16700Schasinglulu 	 * Iterate through the bakery information of each contender to allocate
129*91f16700Schasinglulu 	 * the highest ticket number for this cpu.
130*91f16700Schasinglulu 	 */
131*91f16700Schasinglulu 	for (they = 0U; they < BAKERY_LOCK_MAX_CPUS; they++) {
132*91f16700Schasinglulu 		if (me == they)
133*91f16700Schasinglulu 			continue;
134*91f16700Schasinglulu 
135*91f16700Schasinglulu 		/*
136*91f16700Schasinglulu 		 * Get a reference to the other contender's bakery info and
137*91f16700Schasinglulu 		 * ensure that a stale copy is not read.
138*91f16700Schasinglulu 		 */
139*91f16700Schasinglulu 		their_bakery_info = get_bakery_info(they, lock);
140*91f16700Schasinglulu 		assert(their_bakery_info != NULL);
141*91f16700Schasinglulu 
142*91f16700Schasinglulu 		read_cache_op((uintptr_t)their_bakery_info, is_cached);
143*91f16700Schasinglulu 
144*91f16700Schasinglulu 		/*
145*91f16700Schasinglulu 		 * Update this cpu's ticket number if a higher ticket number is
146*91f16700Schasinglulu 		 * seen
147*91f16700Schasinglulu 		 */
148*91f16700Schasinglulu 		their_ticket = bakery_ticket_number(their_bakery_info->lock_data);
149*91f16700Schasinglulu 		if (their_ticket > my_ticket)
150*91f16700Schasinglulu 			my_ticket = their_ticket;
151*91f16700Schasinglulu 	}
152*91f16700Schasinglulu 
153*91f16700Schasinglulu 	/*
154*91f16700Schasinglulu 	 * Compute ticket; then signal to other contenders waiting for us to
155*91f16700Schasinglulu 	 * finish calculating our ticket value that we're done
156*91f16700Schasinglulu 	 */
157*91f16700Schasinglulu 	++my_ticket;
158*91f16700Schasinglulu 	my_bakery_info->lock_data = make_bakery_data(CHOSEN_TICKET, my_ticket);
159*91f16700Schasinglulu 
160*91f16700Schasinglulu 	write_cache_op((uintptr_t)my_bakery_info, is_cached);
161*91f16700Schasinglulu 
162*91f16700Schasinglulu 	return my_ticket;
163*91f16700Schasinglulu }
164*91f16700Schasinglulu 
165*91f16700Schasinglulu void bakery_lock_get(bakery_lock_t *lock)
166*91f16700Schasinglulu {
167*91f16700Schasinglulu 	unsigned int they, me;
168*91f16700Schasinglulu 	unsigned int my_ticket, my_prio, their_ticket;
169*91f16700Schasinglulu 	bakery_info_t *their_bakery_info;
170*91f16700Schasinglulu 	unsigned int their_bakery_data;
171*91f16700Schasinglulu 	bool is_cached;
172*91f16700Schasinglulu 
173*91f16700Schasinglulu 	me = plat_my_core_pos();
174*91f16700Schasinglulu 	is_cached = is_dcache_enabled();
175*91f16700Schasinglulu 
176*91f16700Schasinglulu 	/* Get a ticket */
177*91f16700Schasinglulu 	my_ticket = bakery_get_ticket(lock, me, is_cached);
178*91f16700Schasinglulu 
179*91f16700Schasinglulu 	/*
180*91f16700Schasinglulu 	 * Now that we got our ticket, compute our priority value, then compare
181*91f16700Schasinglulu 	 * with that of others, and proceed to acquire the lock
182*91f16700Schasinglulu 	 */
183*91f16700Schasinglulu 	my_prio = bakery_get_priority(my_ticket, me);
184*91f16700Schasinglulu 	for (they = 0U; they < BAKERY_LOCK_MAX_CPUS; they++) {
185*91f16700Schasinglulu 		if (me == they)
186*91f16700Schasinglulu 			continue;
187*91f16700Schasinglulu 
188*91f16700Schasinglulu 		/*
189*91f16700Schasinglulu 		 * Get a reference to the other contender's bakery info and
190*91f16700Schasinglulu 		 * ensure that a stale copy is not read.
191*91f16700Schasinglulu 		 */
192*91f16700Schasinglulu 		their_bakery_info = get_bakery_info(they, lock);
193*91f16700Schasinglulu 		assert(their_bakery_info != NULL);
194*91f16700Schasinglulu 
195*91f16700Schasinglulu 		/* Wait for the contender to get their ticket */
196*91f16700Schasinglulu 		do {
197*91f16700Schasinglulu 			read_cache_op((uintptr_t)their_bakery_info, is_cached);
198*91f16700Schasinglulu 			their_bakery_data = their_bakery_info->lock_data;
199*91f16700Schasinglulu 		} while (bakery_is_choosing(their_bakery_data));
200*91f16700Schasinglulu 
201*91f16700Schasinglulu 		/*
202*91f16700Schasinglulu 		 * If the other party is a contender, they'll have non-zero
203*91f16700Schasinglulu 		 * (valid) ticket value. If they do, compare priorities
204*91f16700Schasinglulu 		 */
205*91f16700Schasinglulu 		their_ticket = bakery_ticket_number(their_bakery_data);
206*91f16700Schasinglulu 		if (their_ticket && (bakery_get_priority(their_ticket, they) < my_prio)) {
207*91f16700Schasinglulu 			/*
208*91f16700Schasinglulu 			 * They have higher priority (lower value). Wait for
209*91f16700Schasinglulu 			 * their ticket value to change (either release the lock
210*91f16700Schasinglulu 			 * to have it dropped to 0; or drop and probably content
211*91f16700Schasinglulu 			 * again for the same lock to have an even higher value)
212*91f16700Schasinglulu 			 */
213*91f16700Schasinglulu 			do {
214*91f16700Schasinglulu 				wfe();
215*91f16700Schasinglulu 				read_cache_op((uintptr_t)their_bakery_info, is_cached);
216*91f16700Schasinglulu 			} while (their_ticket
217*91f16700Schasinglulu 				== bakery_ticket_number(their_bakery_info->lock_data));
218*91f16700Schasinglulu 		}
219*91f16700Schasinglulu 	}
220*91f16700Schasinglulu 
221*91f16700Schasinglulu 	/*
222*91f16700Schasinglulu 	 * Lock acquired. Ensure that any reads and writes from a shared
223*91f16700Schasinglulu 	 * resource in the critical section read/write values after the lock is
224*91f16700Schasinglulu 	 * acquired.
225*91f16700Schasinglulu 	 */
226*91f16700Schasinglulu 	dmbish();
227*91f16700Schasinglulu }
228*91f16700Schasinglulu 
229*91f16700Schasinglulu void bakery_lock_release(bakery_lock_t *lock)
230*91f16700Schasinglulu {
231*91f16700Schasinglulu 	bakery_info_t *my_bakery_info;
232*91f16700Schasinglulu 	bool is_cached = is_dcache_enabled();
233*91f16700Schasinglulu 
234*91f16700Schasinglulu 	my_bakery_info = get_bakery_info(plat_my_core_pos(), lock);
235*91f16700Schasinglulu 
236*91f16700Schasinglulu 	assert(is_lock_acquired(my_bakery_info, is_cached));
237*91f16700Schasinglulu 
238*91f16700Schasinglulu 	/*
239*91f16700Schasinglulu 	 * Ensure that other observers see any stores in the critical section
240*91f16700Schasinglulu 	 * before releasing the lock. Also ensure all loads in the critical
241*91f16700Schasinglulu 	 * section are complete before releasing the lock. Release the lock by
242*91f16700Schasinglulu 	 * resetting ticket. Then signal other waiting contenders.
243*91f16700Schasinglulu 	 */
244*91f16700Schasinglulu 	dmbish();
245*91f16700Schasinglulu 	my_bakery_info->lock_data = 0U;
246*91f16700Schasinglulu 	write_cache_op((uintptr_t)my_bakery_info, is_cached);
247*91f16700Schasinglulu 
248*91f16700Schasinglulu 	/* This sev is ordered by the dsbish in write_cahce_op */
249*91f16700Schasinglulu 	sev();
250*91f16700Schasinglulu }
251