1 /*
2  * Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #ifndef _HARDWARE_BOOT_LOCK_H
8 #define _HARDWARE_BOOT_LOCK_H
9 
10 #include "pico.h"
11 
12 // PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_HARDWARE_BOOT_LOCK, Enable/disable assertions in the hardware_boot_lock module, type=bool, default=0, group=hardware_boot_lock
13 #ifndef PARAM_ASSERTIONS_ENABLED_HARDWARE_BOOT_LOCK
14 #define PARAM_ASSERTIONS_ENABLED_HARDWARE_BOOT_LOCK 0
15 #endif
16 
17 #if NUM_BOOT_LOCKS > 0
18 #include "hardware/sync.h"
19 #include "hardware/structs/bootram.h"
20 
21 /** \brief A boot lock identifier
22  * \ingroup hardware_sync
23  */
24 typedef volatile uint32_t boot_lock_t;
25 
26 /*! \brief Get HW Bootlock instance from number
27  *  \ingroup hardware_sync
28  *
29  * \param lock_num Bootlock ID
30  * \return The bootlock instance
31  */
boot_lock_instance(uint lock_num)32 __force_inline static boot_lock_t *boot_lock_instance(uint lock_num) {
33     invalid_params_if(HARDWARE_BOOT_LOCK, lock_num >= NUM_BOOT_LOCKS);
34     return (boot_lock_t *) (BOOTRAM_BASE + BOOTRAM_BOOTLOCK0_OFFSET + lock_num * 4);
35 }
36 
37 /*! \brief Get HW Bootlock number from instance
38  *  \ingroup hardware_sync
39  *
40  * \param lock The Bootlock instance
41  * \return The Bootlock ID
42  */
boot_lock_get_num(boot_lock_t * lock)43 __force_inline static uint boot_lock_get_num(boot_lock_t *lock) {
44     invalid_params_if(HARDWARE_BOOT_LOCK, (uint) lock < BOOTRAM_BASE + BOOTRAM_BOOTLOCK0_OFFSET ||
45                             (uint) lock >= NUM_BOOT_LOCKS * sizeof(boot_lock_t) + BOOTRAM_BASE + BOOTRAM_BOOTLOCK0_OFFSET ||
46                             ((uint) lock - BOOTRAM_BASE + BOOTRAM_BOOTLOCK0_OFFSET) % sizeof(boot_lock_t) != 0);
47     return (uint) (lock - (boot_lock_t *) (BOOTRAM_BASE + BOOTRAM_BOOTLOCK0_OFFSET));
48 }
49 
50 /*! \brief Acquire a boot lock without disabling interrupts (hence unsafe)
51  *  \ingroup hardware_sync
52  *
53  * \param lock Bootlock instance
54  */
boot_lock_unsafe_blocking(boot_lock_t * lock)55 __force_inline static void boot_lock_unsafe_blocking(boot_lock_t *lock) {
56     // Note we don't do a wfe or anything, because by convention these boot_locks are VERY SHORT LIVED and NEVER BLOCK and run
57     // with INTERRUPTS disabled (to ensure that)... therefore nothing on our core could be blocking us, so we just need to wait on another core
58     // anyway which should be finished soon
59     while (__builtin_expect(!*lock, 0)) { // read from bootlock register (tries to acquire the lock)
60         tight_loop_contents();
61     }
62     __mem_fence_acquire();
63 }
64 
65 /*! \brief try to acquire a boot lock without disabling interrupts (hence unsafe)
66  *  \ingroup hardware_sync
67  *
68  * \param lock Bootlock instance
69  */
boot_try_lock_unsafe(boot_lock_t * lock)70 __force_inline static bool boot_try_lock_unsafe(boot_lock_t *lock) {
71     if (*lock) {
72         __mem_fence_acquire();
73         return true;
74     }
75     return false;
76 }
77 
78 /*! \brief Release a boot lock without re-enabling interrupts
79  *  \ingroup hardware_sync
80  *
81  * \param lock Bootlock instance
82  */
boot_unlock_unsafe(boot_lock_t * lock)83 __force_inline static void boot_unlock_unsafe(boot_lock_t *lock) {
84     __mem_fence_release();
85     *lock = 0; // write to bootlock register (release lock)
86 }
87 
88 /*! \brief Acquire a boot lock safely
89  *  \ingroup hardware_sync
90  *
91  * This function will disable interrupts prior to acquiring the bootlock
92  *
93  * \param lock Bootlock instance
94  * \return interrupt status to be used when unlocking, to restore to original state
95  */
boot_lock_blocking(boot_lock_t * lock)96 __force_inline static uint32_t boot_lock_blocking(boot_lock_t *lock) {
97     uint32_t save = save_and_disable_interrupts();
98     boot_lock_unsafe_blocking(lock);
99     return save;
100 }
101 
102 /*! \brief Check to see if a bootlock is currently acquired elsewhere.
103  *  \ingroup hardware_sync
104  *
105  * \param lock Bootlock instance
106  */
is_boot_locked(boot_lock_t * lock)107 inline static bool is_boot_locked(boot_lock_t *lock) {
108     check_hw_size(boot_lock_t, 4);
109     uint lock_num = boot_lock_get_num(lock);
110     return 0 != (*(io_ro_32 *) (BOOTRAM_BASE + BOOTRAM_BOOTLOCK_STAT_OFFSET) & (1u << lock_num));
111 }
112 
113 /*! \brief Release a boot lock safely
114  *  \ingroup hardware_sync
115  *
116  * This function will re-enable interrupts according to the parameters.
117  *
118  * \param lock Bootlock instance
119  * \param saved_irq Return value from the \ref boot_lock_blocking() function.
120  *
121  * \sa boot_lock_blocking()
122  */
boot_unlock(boot_lock_t * lock,uint32_t saved_irq)123 __force_inline static void boot_unlock(boot_lock_t *lock, uint32_t saved_irq) {
124     boot_unlock_unsafe(lock);
125     restore_interrupts_from_disabled(saved_irq);
126 }
127 
128 /*! \brief Initialise a boot lock
129  *  \ingroup hardware_sync
130  *
131  * The boot lock is initially unlocked
132  *
133  * \param lock_num The boot lock number
134  * \return The boot lock instance
135  */
136 boot_lock_t *boot_lock_init(uint lock_num);
137 
138 /*! \brief Release all boot locks
139  *  \ingroup hardware_sync
140  */
141 void boot_locks_reset(void);
142 
143 #endif
144 #endif