Skip to content

Commit 872e355

Browse files
teburdcarlescufi
authored andcommitted
kernel: Option to assert on spin lock time
Spin locks held for any lengthy duration prevent interrupts and in a real time system where interrupts drive tasks this can be problematic. Add an option to assert if a spin lock is held for a duration longer than the configurable number of microseconds. Signed-off-by: Tom Burdick <[email protected]>
1 parent 4321fef commit 872e355

File tree

6 files changed

+65
-3
lines changed

6 files changed

+65
-3
lines changed

drivers/timer/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ config SYSTEM_TIMER_HAS_DISABLE_SUPPORT
5454
This option should be selected by drivers implementing support for
5555
sys_clock_disable() API.
5656

57+
config SYSTEM_CLOCK_LOCK_FREE_COUNT
58+
bool
59+
help
60+
This option should be selected by drivers implementing a lock free
61+
cycle count accessor. This is needed for instrumenting spin lock
62+
hold times.
63+
5764
source "drivers/timer/Kconfig.altera_avalon"
5865
source "drivers/timer/Kconfig.apic"
5966
source "drivers/timer/Kconfig.arcv2"

include/zephyr/spinlock.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include <zephyr/sys/atomic.h>
1616
#include <zephyr/sys/__assert.h>
17+
#include <zephyr/sys/time_units.h>
1718
#include <stdbool.h>
1819
#include <zephyr/arch/cpu.h>
1920

@@ -49,7 +50,12 @@ struct k_spinlock {
4950
* ID in the bottom two bits.
5051
*/
5152
uintptr_t thread_cpu;
52-
#endif
53+
#ifdef CONFIG_SPIN_LOCK_TIME_LIMIT
54+
/* Stores the time (in cycles) when a lock was taken
55+
*/
56+
uint32_t lock_time;
57+
#endif /* CONFIG_SPIN_LOCK_TIME_LIMIT */
58+
#endif /* CONFIG_SPIN_VALIDATE */
5359

5460
#if defined(CONFIG_CPLUSPLUS) && !defined(CONFIG_SMP) && \
5561
!defined(CONFIG_SPIN_VALIDATE)
@@ -152,7 +158,10 @@ static ALWAYS_INLINE k_spinlock_key_t k_spin_lock(struct k_spinlock *l)
152158

153159
#ifdef CONFIG_SPIN_VALIDATE
154160
z_spin_lock_set_owner(l);
155-
#endif
161+
#if defined(CONFIG_SPIN_LOCK_TIME_LIMIT) && (CONFIG_SPIN_LOCK_TIME_LIMIT != 0)
162+
l->lock_time = sys_clock_cycle_get_32();
163+
#endif /* CONFIG_SPIN_LOCK_TIME_LIMIT */
164+
#endif/* CONFIG_SPIN_VALIDATE */
156165
return k;
157166
}
158167

@@ -183,7 +192,15 @@ static ALWAYS_INLINE void k_spin_unlock(struct k_spinlock *l,
183192
ARG_UNUSED(l);
184193
#ifdef CONFIG_SPIN_VALIDATE
185194
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
186-
#endif
195+
196+
#if defined(CONFIG_SPIN_LOCK_TIME_LIMIT) && (CONFIG_SPIN_LOCK_TIME_LIMIT != 0)
197+
uint32_t delta = sys_clock_cycle_get_32() - l->lock_time;
198+
199+
__ASSERT(delta < CONFIG_SPIN_LOCK_TIME_LIMIT,
200+
"Spin lock %p held %u cycles, longer than limit of %u cycles",
201+
l, delta, CONFIG_SPIN_LOCK_TIME_LIMIT);
202+
#endif /* CONFIG_SPIN_LOCK_TIME_LIMIT */
203+
#endif /* CONFIG_SPIN_VALIDATE */
187204

188205
#ifdef CONFIG_SMP
189206
/* Strictly we don't need atomic_clear() here (which is an

include/zephyr/sys/time_units.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define ZEPHYR_INCLUDE_TIME_UNITS_H_
99

1010
#include <zephyr/toolchain.h>
11+
#include <zephyr/sys/util.h>
1112

1213
#ifdef __cplusplus
1314
extern "C" {

subsys/debug/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ config SPIN_VALIDATE
261261
enabled. It adds a relatively hefty overhead (about 3k or so) to
262262
kernel code size, don't use on platforms known to be small.
263263

264+
config SPIN_LOCK_TIME_LIMIT
265+
int "Spin lock holding time limit in cycles"
266+
default 0
267+
depends on SPIN_VALIDATE
268+
depends on SYSTEM_CLOCK_LOCK_FREE_COUNT
269+
help
270+
Assert at the time of unlocking the number of system clock cycles
271+
the lock has been held is less than the configured value. Requires
272+
the timer driver sys_clock_get_cycles_32() be lock free.
273+
264274
endif # ASSERT
265275

266276
config FORCE_NO_ASSERT

tests/kernel/spinlock/prj.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
CONFIG_ZTEST=y
22
CONFIG_SPIN_VALIDATE=y
3+
CONFIG_SPIN_LOCK_TIME_LIMIT=1000
34
CONFIG_ZTEST_NEW_API=y

tests/kernel/spinlock/src/spinlock_error_case.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
6+
#include "zephyr/ztest_test_new.h"
67
#include <zephyr/kernel.h>
78
#include <zephyr/ztest.h>
89
#include <zephyr/spinlock.h>
@@ -119,3 +120,28 @@ ZTEST(spinlock, test_spinlock_release_error)
119120

120121
ztest_test_fail();
121122
}
123+
124+
/**
125+
* @brief Test unlocking spinlock held over the time limit
126+
*
127+
* @details Validate unlocking spinlock held past the time limit will trigger
128+
* assertion.
129+
*
130+
* @ingroup kernel_spinlock_tests
131+
*
132+
* @see k_spin_unlock()
133+
*/
134+
ZTEST(spinlock, test_spinlock_lock_time_limit)
135+
{
136+
Z_TEST_SKIP_IFNDEF(CONFIG_SPIN_LOCK_TIME_LIMIT);
137+
138+
key = k_spin_lock(&lock);
139+
140+
for (volatile int i = 0; i < 100000; i++) {
141+
}
142+
143+
set_assert_valid(true);
144+
k_spin_unlock(&lock, key);
145+
146+
ztest_test_fail();
147+
}

0 commit comments

Comments
 (0)