summaryrefslogtreecommitdiffstats
path: root/firmware/target/hosted/sdl/kernel-sdl.c
blob: 4fb1aede0a7e5f3f2d07af5003ca23ad2130e9fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Felix Arends
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <SDL.h>
#include <SDL_thread.h>
#include <inttypes.h>
#include "system-sdl.h"
#include "thread-sdl.h"
#include "kernel.h"
#include "thread.h"
#include "panic.h"
#include "debug.h"

static SDL_TimerID tick_timer_id;
long start_tick;

#ifndef HAVE_SDL_THREADS
/* for the wait_for_interrupt function */
static bool do_exit;
static SDL_cond *wfi_cond;
static SDL_mutex *wfi_mutex;
#else
#define do_exit false
#endif
/* Condition to signal that "interrupts" may proceed */
static SDL_cond *sim_thread_cond;
/* Mutex to serialize changing levels and exclude other threads while
 * inside a handler */
static SDL_mutex *sim_irq_mtx;
/* Level: 0 = enabled, not 0 = disabled */
static int volatile interrupt_level = HIGHEST_IRQ_LEVEL;
/* How many handers waiting? Not strictly needed because CondSignal is a
 * noop if no threads were waiting but it filters-out calls to functions
 * with higher overhead and provides info when debugging. */
static int handlers_pending = 0;
/* 1 = executing a handler; prevents CondSignal calls in set_irq_level
 * while in a handler */
static int status_reg = 0;

/* Nescessary logic:
 * 1) All threads must pass unblocked
 * 2) Current handler must always pass unblocked
 * 3) Threads must be excluded when irq routine is running
 * 4) No more than one handler routine should execute at a time
 */
int set_irq_level(int level)
{
    SDL_LockMutex(sim_irq_mtx);

    int oldlevel = interrupt_level;

    if (status_reg == 0 && level == 0 && oldlevel != 0)
    {
        /* Not in a handler and "interrupts" are going from disabled to
         * enabled; signal any pending handlers still waiting */
        if (handlers_pending > 0)
            SDL_CondSignal(sim_thread_cond);
    }

    interrupt_level = level; /* save new level */

    SDL_UnlockMutex(sim_irq_mtx);
    return oldlevel;
}

void sim_enter_irq_handler(void)
{
    SDL_LockMutex(sim_irq_mtx);
    handlers_pending++;

    /* Check each time before proceeding: disabled->enabled->...->disabled
     * is possible on an app thread before a handler thread is ever granted
     * the mutex; a handler can also leave "interrupts" disabled during
     * its execution */
    while (interrupt_level != 0)
        SDL_CondWait(sim_thread_cond, sim_irq_mtx);

    status_reg = 1;
}

void sim_exit_irq_handler(void)
{
    /* If any others are waiting, give the signal */
    if (--handlers_pending > 0)
        SDL_CondSignal(sim_thread_cond);

    status_reg = 0;
    SDL_UnlockMutex(sim_irq_mtx);
#ifndef HAVE_SDL_THREADS
    SDL_CondSignal(wfi_cond);
#endif
}

static bool sim_kernel_init(void)
{
    sim_irq_mtx = SDL_CreateMutex();
    if (sim_irq_mtx == NULL)
    {
        panicf("Cannot create sim_handler_mtx\n");
        return false;
    }

    sim_thread_cond = SDL_CreateCond();
    if (sim_thread_cond == NULL)
    {
        panicf("Cannot create sim_thread_cond\n");
        return false;
    }
#ifndef HAVE_SDL_THREADS
    wfi_cond = SDL_CreateCond();
    if (wfi_cond == NULL)
    {
        panicf("Cannot create wfi\n");
        return false;
    }
    wfi_mutex = SDL_CreateMutex();
    if (wfi_mutex == NULL)
    {
        panicf("Cannot create wfi mutex\n");
        return false;
    }
#endif
    return true;
}

void sim_kernel_shutdown(void)
{
    SDL_RemoveTimer(tick_timer_id);
#ifndef HAVE_SDL_THREADS
    do_exit = true;
    SDL_CondSignal(wfi_cond);
#endif
    disable_irq(); 
    SDL_DestroyMutex(sim_irq_mtx);
    SDL_DestroyCond(sim_thread_cond); 
}

Uint32 tick_timer(Uint32 interval, void *param)
{
    long new_tick;

    (void) interval;
    (void) param;
    
    new_tick = (SDL_GetTicks() - start_tick) / (1000/HZ);
        
    while(new_tick != current_tick && !do_exit)
    {
        sim_enter_irq_handler();

        /* Run through the list of tick tasks - increments tick
         * on each iteration. */
        call_tick_tasks();

        sim_exit_irq_handler();
    }
    
    return do_exit ? 0 : interval;
}

void tick_start(unsigned int interval_in_ms)
{
    if (!sim_kernel_init())
    {
        panicf("Could not initialize kernel!");
        exit(-1);
    }

    if (tick_timer_id != NULL)
    {
        SDL_RemoveTimer(tick_timer_id);
        tick_timer_id = NULL;
    }
    else
    {
        start_tick = SDL_GetTicks();
    }

    tick_timer_id = SDL_AddTimer(interval_in_ms, tick_timer, NULL);
#ifndef HAVE_SDL_THREADS
    SDL_LockMutex(wfi_mutex);
#endif
}

#ifndef HAVE_SDL_THREADS
static void check_exit(void)
{
    if (UNLIKELY(do_exit))
    {
        SDL_DestroyCond(wfi_cond);
        SDL_UnlockMutex(wfi_mutex);
        SDL_DestroyMutex(wfi_mutex);
        sim_do_exit();
    }
}

void wait_for_interrupt(void)
{
    /* the exit may come at any time, during the CondWait or before,
     * so check it twice */
    check_exit();
    SDL_CondWait(wfi_cond, wfi_mutex);
    check_exit();
}
#endif