149 lines
4.3 KiB
C
149 lines
4.3 KiB
C
|
/*
|
||
|
* Thread queue with water mark
|
||
|
*
|
||
|
* COPYRIGHT (C) 2014, Shanghai Real-Thread Technology Co., Ltd
|
||
|
*
|
||
|
* This file is part of RT-Thread (http://www.rt-thread.org)
|
||
|
*
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License along
|
||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||
|
*
|
||
|
* Change Logs:
|
||
|
* Date Author Notes
|
||
|
* 2014-04-16 Grissiom first version
|
||
|
*/
|
||
|
|
||
|
struct rt_watermark_queue
|
||
|
{
|
||
|
/* Current water level. */
|
||
|
unsigned int level;
|
||
|
unsigned int high_mark;
|
||
|
unsigned int low_mark;
|
||
|
rt_list_t suspended_threads;
|
||
|
};
|
||
|
|
||
|
/** Init the struct rt_watermark_queue.
|
||
|
*/
|
||
|
void rt_wm_que_init(struct rt_watermark_queue *wg,
|
||
|
unsigned int low, unsigned int high);
|
||
|
void rt_wm_que_set_mark(struct rt_watermark_queue *wg,
|
||
|
unsigned int low, unsigned int high);
|
||
|
void rt_wm_que_dump(struct rt_watermark_queue *wg);
|
||
|
|
||
|
/* Water marks are often used in performance critical places. Benchmark shows
|
||
|
* inlining functions will have 10% performance gain in some situation(for
|
||
|
* example, VBus). So keep the inc/dec compact and inline. */
|
||
|
|
||
|
/** Increase the water level.
|
||
|
*
|
||
|
* It should be called in the thread that want to raise the water level. If the
|
||
|
* current level is above the high mark, the thread will be suspended up to
|
||
|
* @timeout ticks.
|
||
|
*
|
||
|
* @return RT_EOK if water level increased successfully. -RT_EFULL on @timeout
|
||
|
* is zero and the level is above water mark. -RT_ETIMEOUT if timeout occurred.
|
||
|
*/
|
||
|
rt_inline rt_err_t rt_wm_que_inc(struct rt_watermark_queue *wg,
|
||
|
int timeout)
|
||
|
{
|
||
|
rt_base_t ilvl;
|
||
|
|
||
|
/* Assert as early as possible. */
|
||
|
if (timeout != 0)
|
||
|
{
|
||
|
RT_DEBUG_IN_THREAD_CONTEXT;
|
||
|
}
|
||
|
|
||
|
ilvl = rt_hw_interrupt_disable();
|
||
|
|
||
|
while (wg->level > wg->high_mark)
|
||
|
{
|
||
|
rt_thread_t thread;
|
||
|
|
||
|
if (timeout == 0)
|
||
|
{
|
||
|
rt_hw_interrupt_enable(ilvl);
|
||
|
return -RT_EFULL;
|
||
|
}
|
||
|
|
||
|
thread = rt_thread_self();
|
||
|
thread->error = RT_EOK;
|
||
|
rt_thread_suspend(thread);
|
||
|
rt_list_insert_after(&wg->suspended_threads, &thread->tlist);
|
||
|
if (timeout > 0)
|
||
|
{
|
||
|
rt_timer_control(&(thread->thread_timer),
|
||
|
RT_TIMER_CTRL_SET_TIME,
|
||
|
&timeout);
|
||
|
rt_timer_start(&(thread->thread_timer));
|
||
|
}
|
||
|
rt_hw_interrupt_enable(ilvl);
|
||
|
rt_schedule();
|
||
|
if (thread->error != RT_EOK)
|
||
|
return thread->error;
|
||
|
|
||
|
ilvl = rt_hw_interrupt_disable();
|
||
|
}
|
||
|
|
||
|
wg->level++;
|
||
|
|
||
|
if (wg->level == 0)
|
||
|
{
|
||
|
wg->level = ~0;
|
||
|
}
|
||
|
|
||
|
rt_hw_interrupt_enable(ilvl);
|
||
|
|
||
|
return RT_EOK;
|
||
|
}
|
||
|
|
||
|
/** Decrease the water level.
|
||
|
*
|
||
|
* It should be called by the consumer that drain the water out. If the water
|
||
|
* level reached low mark, all the thread suspended in this queue will be waken
|
||
|
* up. It's safe to call this function in interrupt context.
|
||
|
*/
|
||
|
rt_inline void rt_wm_que_dec(struct rt_watermark_queue *wg)
|
||
|
{
|
||
|
int need_sched = 0;
|
||
|
rt_base_t ilvl;
|
||
|
|
||
|
if (wg->level == 0)
|
||
|
return;
|
||
|
|
||
|
ilvl = rt_hw_interrupt_disable();
|
||
|
wg->level--;
|
||
|
if (wg->level == wg->low_mark)
|
||
|
{
|
||
|
/* There should be spaces between the low mark and high mark, so it's
|
||
|
* safe to resume all the threads. */
|
||
|
while (!rt_list_isempty(&wg->suspended_threads))
|
||
|
{
|
||
|
rt_thread_t thread;
|
||
|
|
||
|
thread = rt_list_entry(wg->suspended_threads.next,
|
||
|
struct rt_thread,
|
||
|
tlist);
|
||
|
rt_thread_resume(thread);
|
||
|
need_sched = 1;
|
||
|
}
|
||
|
}
|
||
|
rt_hw_interrupt_enable(ilvl);
|
||
|
|
||
|
if (need_sched)
|
||
|
rt_schedule();
|
||
|
}
|