completion_up.c 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /*
  2. * Copyright (c) 2006-2023, RT-Thread Development Team
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. *
  6. * Change Logs:
  7. * Date Author Notes
  8. * 2012-09-30 Bernard first version.
  9. * 2021-08-18 chenyingchun add comments
  10. * 2023-09-15 xqyjlj perf rt_hw_interrupt_disable/enable
  11. * 2024-01-25 Shell reduce resource usage in completion for better synchronization
  12. * and smaller footprint.
  13. */
  14. #define DBG_TAG "drivers.ipc"
  15. #define DBG_LVL DBG_INFO
  16. #include <rtdbg.h>
  17. #include <rthw.h>
  18. #include <rtdevice.h>
  19. /**
  20. * This is an implementation of completion core on UP system.
  21. * Noted that spinlock is (preempt_lock + irq_mask) on UP scheduler.
  22. */
  23. #define RT_COMPLETED 1
  24. #define RT_UNCOMPLETED 0
  25. #define RT_COMPLETION_FLAG(comp) ((comp)->susp_thread_n_flag & 1)
  26. #define RT_COMPLETION_THREAD(comp) ((rt_thread_t)((comp)->susp_thread_n_flag & ~1))
  27. #define RT_COMPLETION_NEW_STAT(thread, flag) (((flag) & 1) | (((rt_base_t)thread) & ~1))
  28. static struct rt_spinlock _completion_lock = RT_SPINLOCK_INIT;
  29. /**
  30. * @brief This function will initialize a completion object.
  31. *
  32. * @param completion is a pointer to a completion object.
  33. */
  34. void rt_completion_init(struct rt_completion *completion)
  35. {
  36. RT_ASSERT(completion != RT_NULL);
  37. completion->susp_thread_n_flag = RT_COMPLETION_NEW_STAT(RT_NULL, RT_UNCOMPLETED);
  38. }
  39. RTM_EXPORT(rt_completion_init);
  40. /**
  41. * @brief This function will wait for a completion, if the completion is unavailable, the thread shall wait for
  42. * the completion up to a specified time.
  43. *
  44. * @param completion is a pointer to a completion object.
  45. *
  46. * @param timeout is a timeout period (unit: OS ticks). If the completion is unavailable, the thread will wait for
  47. * the completion done up to the amount of time specified by the argument.
  48. * NOTE: Generally, we use the macro RT_WAITING_FOREVER to set this parameter, which means that when the
  49. * completion is unavailable, the thread will be waitting forever.
  50. * @param suspend_flag suspend flags. See rt_thread_suspend_with_flag()
  51. *
  52. * @return Return the operation status. ONLY when the return value is RT_EOK, the operation is successful.
  53. * If the return value is any other values, it means that the completion wait failed.
  54. *
  55. * @warning This function can ONLY be called in the thread context. It MUST NOT be called in interrupt context.
  56. */
  57. rt_err_t rt_completion_wait_flags(struct rt_completion *completion,
  58. rt_int32_t timeout, int suspend_flag)
  59. {
  60. rt_err_t result;
  61. rt_base_t level;
  62. rt_thread_t thread;
  63. RT_ASSERT(completion != RT_NULL);
  64. /* current context checking */
  65. RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);
  66. result = RT_EOK;
  67. thread = rt_thread_self();
  68. level = rt_spin_lock_irqsave(&_completion_lock);
  69. __try_again:
  70. if (RT_COMPLETION_FLAG(completion) != RT_COMPLETED)
  71. {
  72. /* only one thread can suspend on complete */
  73. RT_ASSERT(RT_COMPLETION_THREAD(completion) == RT_NULL);
  74. if (timeout == 0)
  75. {
  76. result = -RT_ETIMEOUT;
  77. goto __exit;
  78. }
  79. else
  80. {
  81. /* reset thread error number */
  82. thread->error = RT_EOK;
  83. /* suspend thread */
  84. result = rt_thread_suspend_with_flag(thread, suspend_flag);
  85. if (result == RT_EOK)
  86. {
  87. /* add to suspended thread */
  88. rt_base_t waiting_stat = RT_COMPLETION_NEW_STAT(thread, RT_UNCOMPLETED);
  89. completion->susp_thread_n_flag = waiting_stat;
  90. /* current context checking */
  91. RT_DEBUG_NOT_IN_INTERRUPT;
  92. /* start timer */
  93. if (timeout > 0)
  94. {
  95. /* reset the timeout of thread timer and start it */
  96. rt_timer_control(&(thread->thread_timer),
  97. RT_TIMER_CTRL_SET_TIME,
  98. &timeout);
  99. rt_timer_start(&(thread->thread_timer));
  100. }
  101. /* enable interrupt */
  102. rt_spin_unlock_irqrestore(&_completion_lock, level);
  103. /* do schedule */
  104. rt_schedule();
  105. level = rt_spin_lock_irqsave(&_completion_lock);
  106. if (completion->susp_thread_n_flag != waiting_stat)
  107. {
  108. /* completion may be completed after we suspend */
  109. timeout = 0;
  110. goto __try_again;
  111. }
  112. else
  113. {
  114. /* no changes, waiting failed */
  115. result = thread->error;
  116. result = result > 0 ? -result : result;
  117. RT_ASSERT(result != RT_EOK);
  118. }
  119. }
  120. }
  121. }
  122. /* clean completed flag & remove susp_thread on the case of waking by timeout */
  123. completion->susp_thread_n_flag = RT_COMPLETION_NEW_STAT(RT_NULL, RT_UNCOMPLETED);
  124. __exit:
  125. rt_spin_unlock_irqrestore(&_completion_lock, level);
  126. return result;
  127. }
  128. /**
  129. * @brief This is same as rt_completion_wait_flags(), except that this API is NOT
  130. * ISR-safe (you can NOT call completion_done() on isr routine).
  131. *
  132. * @param completion is a pointer to a completion object.
  133. * @param timeout is a timeout period (unit: OS ticks). If the completion is unavailable, the thread will wait for
  134. * the completion done up to the amount of time specified by the argument.
  135. * NOTE: Generally, we use the macro RT_WAITING_FOREVER to set this parameter, which means that when the
  136. * completion is unavailable, the thread will be waitting forever.
  137. * @param suspend_flag suspend flags. See rt_thread_suspend_with_flag()
  138. *
  139. * @return Return the operation status. ONLY when the return value is RT_EOK, the operation is successful.
  140. * If the return value is any other values, it means that the completion wait failed.
  141. *
  142. * @warning This function can ONLY be called in the thread context. It MUST NOT be called in interrupt context.
  143. */
  144. rt_err_t rt_completion_wait_flags_noisr(struct rt_completion *completion,
  145. rt_int32_t timeout, int suspend_flag)
  146. {
  147. return rt_completion_wait_flags(completion, timeout, suspend_flag);
  148. }
  149. /**
  150. * @brief This function indicates a completion has done and wakeup the thread
  151. * and update its errno. No update is applied if it's a negative value.
  152. *
  153. * @param completion is a pointer to a completion object.
  154. * @param thread_errno is the errno set to waking thread.
  155. * @return RT_EOK if wakeup succeed.
  156. * RT_EEMPTY if wakeup failure and the completion is set to completed.
  157. * RT_EBUSY if the completion is still in completed state
  158. */
  159. rt_err_t rt_completion_wakeup_by_errno(struct rt_completion *completion,
  160. rt_err_t thread_errno)
  161. {
  162. rt_base_t level;
  163. rt_err_t error;
  164. rt_thread_t suspend_thread;
  165. RT_ASSERT(completion != RT_NULL);
  166. level = rt_spin_lock_irqsave(&_completion_lock);
  167. if (RT_COMPLETION_FLAG(completion) == RT_COMPLETED)
  168. {
  169. rt_spin_unlock_irqrestore(&_completion_lock, level);
  170. return -RT_EBUSY;
  171. }
  172. suspend_thread = RT_COMPLETION_THREAD(completion);
  173. if (suspend_thread)
  174. {
  175. /* there is one thread in suspended list */
  176. if (thread_errno >= 0)
  177. {
  178. suspend_thread->error = thread_errno;
  179. }
  180. error = rt_thread_resume(suspend_thread);
  181. if (error)
  182. {
  183. LOG_D("%s: failed to resume thread with %d", __func__, error);
  184. error = -RT_EEMPTY;
  185. }
  186. }
  187. else
  188. {
  189. /* no thread waiting */
  190. error = -RT_EEMPTY;
  191. }
  192. completion->susp_thread_n_flag = RT_COMPLETION_NEW_STAT(RT_NULL, RT_COMPLETED);
  193. rt_spin_unlock_irqrestore(&_completion_lock, level);
  194. return error;
  195. }