1 /*
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #ifndef _PICO_UTIL_QUEUE_H
8 #define _PICO_UTIL_QUEUE_H
9 
10 #include "pico.h"
11 #include "hardware/sync.h"
12 
13 // PICO_CONFIG: PICO_QUEUE_MAX_LEVEL, Maintain a field for the highest level that has been reached by a queue, type=bool, default=0, advanced=true, group=queue
14 #ifndef PICO_QUEUE_MAX_LEVEL
15 #define PICO_QUEUE_MAX_LEVEL 0
16 #endif
17 
18 /** \file queue.h
19  * \defgroup queue queue
20  * \brief Multi-core and IRQ safe queue implementation
21  *
22  * Note that this queue stores values of a specified size, and pushed values are copied into the queue
23  * \ingroup pico_util
24  */
25 
26 #ifdef __cplusplus
27 extern "C" {
28 #endif
29 
30 #include "pico/lock_core.h"
31 
32 typedef struct {
33     lock_core_t core;
34     uint8_t *data;
35     uint16_t wptr;
36     uint16_t rptr;
37     uint16_t element_size;
38     uint16_t element_count;
39 #if PICO_QUEUE_MAX_LEVEL
40     uint16_t max_level;
41 #endif
42 } queue_t;
43 
44 /*! \brief Initialise a queue with a specific spinlock for concurrency protection
45  *  \ingroup queue
46  *
47  * \param q Pointer to a queue_t structure, used as a handle
48  * \param element_size Size of each value in the queue
49  * \param element_count Maximum number of entries in the queue
50  * \param spinlock_num The spin ID used to protect the queue
51  */
52 void queue_init_with_spinlock(queue_t *q, uint element_size, uint element_count, uint spinlock_num);
53 
54 /*! \brief Initialise a queue, allocating a (possibly shared) spinlock
55  *  \ingroup queue
56  *
57  * \param q Pointer to a queue_t structure, used as a handle
58  * \param element_size Size of each value in the queue
59  * \param element_count Maximum number of entries in the queue
60  */
queue_init(queue_t * q,uint element_size,uint element_count)61 static inline void queue_init(queue_t *q, uint element_size, uint element_count) {
62     queue_init_with_spinlock(q, element_size, element_count, next_striped_spin_lock_num());
63 }
64 
65 /*! \brief Destroy the specified queue.
66  *  \ingroup queue
67  *
68  * \param q Pointer to a queue_t structure, used as a handle
69  *
70  * Does not deallocate the queue_t structure itself.
71  */
72 void queue_free(queue_t *q);
73 
74 /*! \brief Unsafe check of level of the specified queue.
75  *  \ingroup queue
76  *
77  * \param q Pointer to a queue_t structure, used as a handle
78  * \return Number of entries in the queue
79  *
80  * This does not use the spinlock, so may return incorrect results if the
81  * spin lock is not externally locked
82  */
queue_get_level_unsafe(queue_t * q)83 static inline uint queue_get_level_unsafe(queue_t *q) {
84     int32_t rc = (int32_t)q->wptr - (int32_t)q->rptr;
85     if (rc < 0) {
86         rc += q->element_count + 1;
87     }
88     return (uint)rc;
89 }
90 
91 /*! \brief Check of level of the specified queue.
92  *  \ingroup queue
93  *
94  * \param q Pointer to a queue_t structure, used as a handle
95  * \return Number of entries in the queue
96  */
queue_get_level(queue_t * q)97 static inline uint queue_get_level(queue_t *q) {
98     uint32_t save = spin_lock_blocking(q->core.spin_lock);
99     uint level = queue_get_level_unsafe(q);
100     spin_unlock(q->core.spin_lock, save);
101     return level;
102 }
103 
104 #if PICO_QUEUE_MAX_LEVEL
105 /*! \brief Returns the highest level reached by the specified queue since it was created
106  *         or since the max level was reset
107  *  \ingroup queue
108  *
109  * \param q Pointer to a queue_t structure, used as a handle
110  * \return Maximum level of the queue
111  */
queue_get_max_level(queue_t * q)112 static inline uint queue_get_max_level(queue_t *q) {
113     return q->max_level;
114 }
115 #endif
116 
117 #if PICO_QUEUE_MAX_LEVEL
118 /*! \brief Reset the highest level reached of the specified queue.
119  *  \ingroup queue
120  *
121  * \param q Pointer to a queue_t structure, used as a handle
122  */
queue_reset_max_level(queue_t * q)123 static inline void queue_reset_max_level(queue_t *q) {
124     uint32_t save = spin_lock_blocking(q->core.spin_lock);
125     q->max_level = queue_get_level_unsafe(q);
126     spin_unlock(q->core.spin_lock, save);
127 }
128 #endif
129 
130 /*! \brief Check if queue is empty
131  *  \ingroup queue
132  *
133  * \param q Pointer to a queue_t structure, used as a handle
134  * \return true if queue is empty, false otherwise
135  *
136  * This function is interrupt and multicore safe.
137  */
queue_is_empty(queue_t * q)138 static inline bool queue_is_empty(queue_t *q) {
139     return queue_get_level(q) == 0;
140 }
141 
142 /*! \brief Check if queue is full
143  *  \ingroup queue
144  *
145  * \param q Pointer to a queue_t structure, used as a handle
146  * \return true if queue is full, false otherwise
147  *
148  * This function is interrupt and multicore safe.
149  */
queue_is_full(queue_t * q)150 static inline bool queue_is_full(queue_t *q) {
151     return queue_get_level(q) == q->element_count;
152 }
153 
154 // nonblocking queue access functions:
155 
156 /*! \brief Non-blocking add value queue if not full
157  *  \ingroup queue
158  *
159  * \param q Pointer to a queue_t structure, used as a handle
160  * \param data Pointer to value to be copied into the queue
161  * \return true if the value was added
162  *
163  * If the queue is full this function will return immediately with false, otherwise
164  * the data is copied into a new value added to the queue, and this function will return true.
165  */
166 bool queue_try_add(queue_t *q, const void *data);
167 
168 /*! \brief Non-blocking removal of entry from the queue if non empty
169  *  \ingroup queue
170  *
171  * \param q Pointer to a queue_t structure, used as a handle
172  * \param data Pointer to the location to receive the removed value, or NULL if the data isn't required
173  * \return true if a value was removed
174  *
175  * If the queue is not empty function will copy the removed value into the location provided and return
176  * immediately with true, otherwise the function will return immediately with false.
177  */
178 bool queue_try_remove(queue_t *q, void *data);
179 
180 /*! \brief Non-blocking peek at the next item to be removed from the queue
181  *  \ingroup queue
182  *
183  * \param q Pointer to a queue_t structure, used as a handle
184  * \param data Pointer to the location to receive the peeked value, or NULL if the data isn't required
185  * \return true if there was a value to peek
186  *
187  * If the queue is not empty this function will return immediately with true with the peeked entry
188  * copied into the location specified by the data parameter, otherwise the function will return false.
189  */
190 bool queue_try_peek(queue_t *q, void *data);
191 
192 // blocking queue access functions:
193 
194 /*! \brief Blocking add of value to queue
195  *  \ingroup queue
196  *
197  * \param q Pointer to a queue_t structure, used as a handle
198  * \param data Pointer to value to be copied into the queue
199  *
200  * If the queue is full this function will block, until a removal happens on the queue
201  */
202 void queue_add_blocking(queue_t *q, const void *data);
203 
204 /*! \brief Blocking remove entry from queue
205  *  \ingroup queue
206  *
207  * \param q Pointer to a queue_t structure, used as a handle
208  * \param data Pointer to the location to receive the removed value, or NULL if the data isn't required
209  *
210  * If the queue is empty this function will block until a value is added.
211  */
212 void queue_remove_blocking(queue_t *q, void *data);
213 
214 /*! \brief Blocking peek at next value to be removed from queue
215  *  \ingroup queue
216  *
217  * \param q Pointer to a queue_t structure, used as a handle
218  * \param data Pointer to the location to receive the peeked value, or NULL if the data isn't required
219  *
220  * If the queue is empty function will block until a value is added
221  */
222 void queue_peek_blocking(queue_t *q, void *data);
223 
224 #ifdef __cplusplus
225 }
226 #endif
227 #endif
228