460 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			460 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * Copyright (c) 2006-2023, RT-Thread Development Team
 | |
|  *
 | |
|  * SPDX-License-Identifier: Apache-2.0
 | |
|  *
 | |
|  * Change Logs:
 | |
|  * Date           Author       Notes
 | |
|  * 2012-09-30     Bernard      first version.
 | |
|  * 2016-10-31     armink       fix some resume push and pop thread bugs
 | |
|  * 2023-09-15     xqyjlj       perf rt_hw_interrupt_disable/enable
 | |
|  * 2024-01-25     Shell        porting to susp_list API
 | |
|  */
 | |
| 
 | |
| #include <rthw.h>
 | |
| #include <rtdevice.h>
 | |
| 
 | |
| #define DATAQUEUE_MAGIC  0xbead0e0e
 | |
| 
 | |
| struct rt_data_item
 | |
| {
 | |
|     const void *data_ptr;
 | |
|     rt_size_t data_size;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will initialize the data queue. Calling this function will
 | |
|  *           initialize the data queue control block and set the notification callback function.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  *
 | |
|  * @param    size is the maximum number of data in the data queue.
 | |
|  *
 | |
|  * @param    lwm is low water mark.
 | |
|  *           When the number of data in the data queue is less than this value, this function will
 | |
|  *           wake up the thread waiting for write data.
 | |
|  *
 | |
|  * @param    evt_notify is the notification callback function.
 | |
|  *
 | |
|  * @return   Return the operation status. When the return value is RT_EOK, the initialization is successful.
 | |
|  *           When the return value is -RT_ENOMEM, it means insufficient memory allocation failed.
 | |
|  */
 | |
| rt_err_t
 | |
| rt_data_queue_init(struct rt_data_queue *queue,
 | |
|                    rt_uint16_t size,
 | |
|                    rt_uint16_t lwm,
 | |
|                    void (*evt_notify)(struct rt_data_queue *queue, rt_uint32_t event))
 | |
| {
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(size > 0);
 | |
| 
 | |
|     queue->evt_notify = evt_notify;
 | |
| 
 | |
|     queue->magic = DATAQUEUE_MAGIC;
 | |
|     queue->size = size;
 | |
|     queue->lwm = lwm;
 | |
| 
 | |
|     queue->get_index = 0;
 | |
|     queue->put_index = 0;
 | |
|     queue->is_empty = 1;
 | |
|     queue->is_full = 0;
 | |
| 
 | |
|     rt_spin_lock_init(&(queue->spinlock));
 | |
| 
 | |
|     rt_list_init(&(queue->suspended_push_list));
 | |
|     rt_list_init(&(queue->suspended_pop_list));
 | |
| 
 | |
|     queue->queue = (struct rt_data_item *)rt_malloc(sizeof(struct rt_data_item) * size);
 | |
|     if (queue->queue == RT_NULL)
 | |
|     {
 | |
|         return -RT_ENOMEM;
 | |
|     }
 | |
| 
 | |
|     return RT_EOK;
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_init);
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will write data to the data queue. If the data queue is full,
 | |
|  *           the thread will suspend for the specified amount of time.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  * .
 | |
|  * @param    data_ptr is the buffer pointer of the data to be written.
 | |
|  *
 | |
|  * @param    size is the size in bytes of the data to be written.
 | |
|  *
 | |
|  * @param    timeout is the waiting time.
 | |
|  *
 | |
|  * @return   Return the operation status. When the return value is RT_EOK, the operation is successful.
 | |
|  *           When the return value is -RT_ETIMEOUT, it means the specified time out.
 | |
|  */
 | |
| rt_err_t rt_data_queue_push(struct rt_data_queue *queue,
 | |
|                             const void *data_ptr,
 | |
|                             rt_size_t data_size,
 | |
|                             rt_int32_t timeout)
 | |
| {
 | |
|     rt_base_t level;
 | |
|     rt_thread_t thread;
 | |
|     rt_err_t    result;
 | |
| 
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(queue->magic == DATAQUEUE_MAGIC);
 | |
| 
 | |
|     /* current context checking */
 | |
|     RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);
 | |
| 
 | |
|     result = RT_EOK;
 | |
|     thread = rt_thread_self();
 | |
| 
 | |
|     level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
|     while (queue->is_full)
 | |
|     {
 | |
|         /* queue is full */
 | |
|         if (timeout == 0)
 | |
|         {
 | |
|             result = -RT_ETIMEOUT;
 | |
| 
 | |
|             goto __exit;
 | |
|         }
 | |
| 
 | |
|         /* reset thread error number */
 | |
|         thread->error = RT_EOK;
 | |
| 
 | |
|         /* suspend thread on the push list */
 | |
|         result = rt_thread_suspend_to_list(thread, &queue->suspended_push_list,
 | |
|                                            RT_IPC_FLAG_FIFO, RT_UNINTERRUPTIBLE);
 | |
|         if (result == RT_EOK)
 | |
|         {
 | |
|             /* start timer */
 | |
|             if (timeout > 0)
 | |
|             {
 | |
|                 /* reset the timeout of thread timer and start it */
 | |
|                 rt_timer_control(&(thread->thread_timer),
 | |
|                                 RT_TIMER_CTRL_SET_TIME,
 | |
|                                 &timeout);
 | |
|                 rt_timer_start(&(thread->thread_timer));
 | |
|             }
 | |
| 
 | |
|             /* enable interrupt */
 | |
|             rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|             /* do schedule */
 | |
|             rt_schedule();
 | |
| 
 | |
|             /* thread is waked up */
 | |
|             level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
| 
 | |
|             /* error may be modified by waker, so take the lock before accessing it */
 | |
|             result = thread->error;
 | |
|         }
 | |
|         if (result != RT_EOK) goto __exit;
 | |
|     }
 | |
| 
 | |
|     queue->queue[queue->put_index].data_ptr  = data_ptr;
 | |
|     queue->queue[queue->put_index].data_size = data_size;
 | |
|     queue->put_index += 1;
 | |
|     if (queue->put_index == queue->size)
 | |
|     {
 | |
|         queue->put_index = 0;
 | |
|     }
 | |
|     queue->is_empty = 0;
 | |
|     if (queue->put_index == queue->get_index)
 | |
|     {
 | |
|         queue->is_full = 1;
 | |
|     }
 | |
| 
 | |
|     /* there is at least one thread in suspended list */
 | |
|     if (rt_susp_list_dequeue(&queue->suspended_push_list,
 | |
|                              RT_THREAD_RESUME_RES_THR_ERR))
 | |
|     {
 | |
|         /* unlock and perform a schedule */
 | |
|         rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|         /* perform a schedule */
 | |
|         rt_schedule();
 | |
| 
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
| __exit:
 | |
|     rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
|     if ((result == RT_EOK) && queue->evt_notify != RT_NULL)
 | |
|     {
 | |
|         queue->evt_notify(queue, RT_DATAQUEUE_EVENT_PUSH);
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_push);
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will pop data from the data queue. If the data queue is empty,the thread
 | |
|  *           will suspend for the specified amount of time.
 | |
|  *
 | |
|  * @note     When the number of data in the data queue is less than lwm(low water mark), will
 | |
|  *           wake up the thread waiting for write data.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  *
 | |
|  * @param    data_ptr is the buffer pointer of the data to be fetched.
 | |
|  *
 | |
|  * @param    size is the size in bytes of the data to be fetched.
 | |
|  *
 | |
|  * @param    timeout is the waiting time.
 | |
|  *
 | |
|  * @return   Return the operation status. When the return value is RT_EOK, the operation is successful.
 | |
|  *           When the return value is -RT_ETIMEOUT, it means the specified time out.
 | |
|  */
 | |
| rt_err_t rt_data_queue_pop(struct rt_data_queue *queue,
 | |
|                            const void **data_ptr,
 | |
|                            rt_size_t *size,
 | |
|                            rt_int32_t timeout)
 | |
| {
 | |
|     rt_base_t level;
 | |
|     rt_thread_t thread;
 | |
|     rt_err_t    result;
 | |
| 
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(queue->magic == DATAQUEUE_MAGIC);
 | |
|     RT_ASSERT(data_ptr != RT_NULL);
 | |
|     RT_ASSERT(size != RT_NULL);
 | |
| 
 | |
|     /* current context checking */
 | |
|     RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);
 | |
| 
 | |
|     result = RT_EOK;
 | |
|     thread = rt_thread_self();
 | |
| 
 | |
|     level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
|     while (queue->is_empty)
 | |
|     {
 | |
|         /* queue is empty */
 | |
|         if (timeout == 0)
 | |
|         {
 | |
|             result = -RT_ETIMEOUT;
 | |
|             goto __exit;
 | |
|         }
 | |
| 
 | |
|         /* reset thread error number */
 | |
|         thread->error = RT_EOK;
 | |
| 
 | |
|         /* suspend thread on the pop list */
 | |
|         result = rt_thread_suspend_to_list(thread, &queue->suspended_pop_list,
 | |
|                                            RT_IPC_FLAG_FIFO, RT_UNINTERRUPTIBLE);
 | |
|         if (result == RT_EOK)
 | |
|         {
 | |
|             /* start timer */
 | |
|             if (timeout > 0)
 | |
|             {
 | |
|                 /* reset the timeout of thread timer and start it */
 | |
|                 rt_timer_control(&(thread->thread_timer),
 | |
|                                 RT_TIMER_CTRL_SET_TIME,
 | |
|                                 &timeout);
 | |
|                 rt_timer_start(&(thread->thread_timer));
 | |
|             }
 | |
| 
 | |
|             /* enable interrupt */
 | |
|             rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|             /* do schedule */
 | |
|             rt_schedule();
 | |
| 
 | |
|             /* thread is waked up */
 | |
|             level  = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
|             result = thread->error;
 | |
|             if (result != RT_EOK)
 | |
|                 goto __exit;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     *data_ptr = queue->queue[queue->get_index].data_ptr;
 | |
|     *size     = queue->queue[queue->get_index].data_size;
 | |
|     queue->get_index += 1;
 | |
|     if (queue->get_index == queue->size)
 | |
|     {
 | |
|         queue->get_index = 0;
 | |
|     }
 | |
|     queue->is_full = 0;
 | |
|     if (queue->put_index == queue->get_index)
 | |
|     {
 | |
|         queue->is_empty = 1;
 | |
|     }
 | |
| 
 | |
|     if (rt_data_queue_len(queue) <= queue->lwm)
 | |
|     {
 | |
|         /* there is at least one thread in suspended list */
 | |
|         if (rt_susp_list_dequeue(&queue->suspended_push_list,
 | |
|                                        RT_THREAD_RESUME_RES_THR_ERR))
 | |
|         {
 | |
|             /* unlock and perform a schedule */
 | |
|             rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|             /* perform a schedule */
 | |
|             rt_schedule();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
|         }
 | |
| 
 | |
|         if (queue->evt_notify != RT_NULL)
 | |
|             queue->evt_notify(queue, RT_DATAQUEUE_EVENT_LWM);
 | |
| 
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
| __exit:
 | |
|     rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
|     if ((result == RT_EOK) && (queue->evt_notify != RT_NULL))
 | |
|     {
 | |
|         queue->evt_notify(queue, RT_DATAQUEUE_EVENT_POP);
 | |
|     }
 | |
| 
 | |
|     return result;
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_pop);
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will fetch but retaining data in the data queue.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  *
 | |
|  * @param    data_ptr is the buffer pointer of the data to be fetched.
 | |
|  *
 | |
|  * @param    size is the size in bytes of the data to be fetched.
 | |
|  *
 | |
|  * @return   Return the operation status. When the return value is RT_EOK, the operation is successful.
 | |
|  *           When the return value is -RT_EEMPTY, it means the data queue is empty.
 | |
|  */
 | |
| rt_err_t rt_data_queue_peek(struct rt_data_queue *queue,
 | |
|                             const void **data_ptr,
 | |
|                             rt_size_t *size)
 | |
| {
 | |
|     rt_base_t level;
 | |
| 
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(queue->magic == DATAQUEUE_MAGIC);
 | |
| 
 | |
|     if (queue->is_empty)
 | |
|     {
 | |
|         return -RT_EEMPTY;
 | |
|     }
 | |
| 
 | |
|     level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
| 
 | |
|     *data_ptr = queue->queue[queue->get_index].data_ptr;
 | |
|     *size     = queue->queue[queue->get_index].data_size;
 | |
| 
 | |
|     rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|     return RT_EOK;
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_peek);
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will reset the data queue.
 | |
|  *
 | |
|  * @note     Calling this function will wake up all threads on the data queue
 | |
|  *           that are hanging and waiting.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  */
 | |
| void rt_data_queue_reset(struct rt_data_queue *queue)
 | |
| {
 | |
|     rt_base_t level;
 | |
| 
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(queue->magic == DATAQUEUE_MAGIC);
 | |
| 
 | |
|     level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
| 
 | |
|     queue->get_index = 0;
 | |
|     queue->put_index = 0;
 | |
|     queue->is_empty = 1;
 | |
|     queue->is_full = 0;
 | |
| 
 | |
|     rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|     rt_enter_critical();
 | |
|     /* wakeup all suspend threads */
 | |
| 
 | |
|     /* resume on pop list */
 | |
|     rt_susp_list_resume_all_irq(&queue->suspended_pop_list, RT_ERROR,
 | |
|                                 &(queue->spinlock));
 | |
| 
 | |
|     /* resume on push list */
 | |
|     rt_susp_list_resume_all_irq(&queue->suspended_push_list, RT_ERROR,
 | |
|                                 &(queue->spinlock));
 | |
| 
 | |
|     rt_exit_critical();
 | |
| 
 | |
|     rt_schedule();
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_reset);
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will deinit the data queue.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  *
 | |
|  * @return   Return the operation status. When the return value is RT_EOK, the operation is successful.
 | |
|  */
 | |
| rt_err_t rt_data_queue_deinit(struct rt_data_queue *queue)
 | |
| {
 | |
|     rt_base_t level;
 | |
| 
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(queue->magic == DATAQUEUE_MAGIC);
 | |
| 
 | |
|     /* wakeup all suspend threads */
 | |
|     rt_data_queue_reset(queue);
 | |
| 
 | |
|     level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
|     queue->magic = 0;
 | |
|     rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|     rt_free(queue->queue);
 | |
| 
 | |
|     return RT_EOK;
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_deinit);
 | |
| 
 | |
| /**
 | |
|  * @brief    This function will get the number of data in the data queue.
 | |
|  *
 | |
|  * @param    queue is a pointer to the data queue object.
 | |
|  *
 | |
|  * @return   Return the number of data in the data queue.
 | |
|  */
 | |
| rt_uint16_t rt_data_queue_len(struct rt_data_queue *queue)
 | |
| {
 | |
|     rt_base_t level;
 | |
|     rt_int16_t len;
 | |
| 
 | |
|     RT_ASSERT(queue != RT_NULL);
 | |
|     RT_ASSERT(queue->magic == DATAQUEUE_MAGIC);
 | |
| 
 | |
|     if (queue->is_empty)
 | |
|     {
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     level = rt_spin_lock_irqsave(&(queue->spinlock));
 | |
| 
 | |
|     if (queue->put_index > queue->get_index)
 | |
|     {
 | |
|         len = queue->put_index - queue->get_index;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         len = queue->size + queue->put_index - queue->get_index;
 | |
|     }
 | |
| 
 | |
|     rt_spin_unlock_irqrestore(&(queue->spinlock), level);
 | |
| 
 | |
|     return len;
 | |
| }
 | |
| RTM_EXPORT(rt_data_queue_len);
 | |
| 
 |