Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ LIBDRAGON_OBJS += \
$(BUILD_DIR)/vaddr64.o \
$(BUILD_DIR)/mi_memset.o \
$(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/async.o \
$(BUILD_DIR)/backtrace.o \
$(BUILD_DIR)/symtable.o \
$(BUILD_DIR)/dir.o \
Expand Down
56 changes: 56 additions & 0 deletions include/async.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#ifndef __LIBDRAGON_ASYNC_H
#define __LIBDRAGON_ASYNC_H

#include <async_.h>
#include <stdint.h>
#include <stdbool.h>


/**
* @defgroup async Async routines
* @ingroup libdragon
* @brief Interface to the timer module in the MIPS r4300 processor.
*
* Stackless coroutine implementation based on async.h Any async task
* scheduled via `async_schedule` is executed until completion. The system runs
* with interrupts disabled and they are acknowledged in a tight loop without
* any context switch. This trades some CPU overhead for added latency. Probably
* the most critical one is audio latency as it will can create an audible click.
* To combat this, one can simply insert async_yield througout long running
* tasks.
*
* @{
*/

/**
* @brief Async context for interrupt handlers.
*/
typedef struct {
async_state;
} async_interrupt_ctx_t;

/**
* @brief Initialize async routines. This basically disables interrupts system-wide
* so the interrupts are acknowledged in a tight loop rather than going through
* a full context switch.
*/
void async_init(void);

/**
* @brief This is a basic async scheduler. Runs the given task till completion
* with the given initial state.
*
* @param task Task to schedule
* @param initial_state Initial struct with async_state to keep track of task
* local variables.
*/
void async_schedule(async (*task)(void *st), void *initial_state);

/**
* @brief Terminate the async system
*/
void async_close(void);

#endif

/** @} */ /* async */
163 changes: 163 additions & 0 deletions include/async_.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#pragma once
#ifndef ASYNC_H
#define ASYNC_H

/*
* Copyright 2021 Sandro Magi
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* Pulled from: https://github.com/naasking/async.h
*
* Author: Sandro Magi <naasking@gmail.com>
*/

/**
* @file async.h
* Stackless Async Subroutines
*
* Taking inspiration from protothreads and async/await as found in C#, Rust and JS,
* this is an async/await implementation for C based on Duff's device.
*
* Features:
*
* 1. Subroutines can have persistent state that isn't just static state, because
* each async subroutine accepts its own struct it uses as a parameter, and
* the async state is stored there.
* 2. Because of the more flexible state handling, async subroutines can be nested
* in tree-like fashion which permits fork/join concurrency patterns.
*
* Caveats:
*
* 1. Due to compile-time bug, MSVC requires changing:
* Project Properties > Configuration Properties > C/C++ > General > Debug Information Format
* from "Program Database for Edit And Continue" to "Program Database".
* 2. As with protothreads, you have to be very careful with switch statements within an async
* subroutine. Rule of thumb: just place all switch statements in their own function.
* 3. As with protothreads, you can't make blocking system calls and preserve the async semantics.
* These must be changed into non-blocking calls that test a condition.
*/

#include <limits.h>

/**
* The async computation status
*/
typedef enum ASYNC_EVT
{
ASYNC_INIT = 0,
ASYNC_CONT = ASYNC_INIT,
ASYNC_DONE = 1
} async;

/**
* Declare the async state
*/
#define async_state unsigned _async_k

/**
* Core async structure, optional to use.
*/
struct async
{
async_state;
};

/**
* Mark the start of an async subroutine
*
* @param k The async state
*/
#define async_begin(k) \
unsigned *_async_k = &(k)->_async_k; \
switch (*_async_k) \
{ \
default:

/**
* Mark the end of a async subroutine
*/
#define async_end \
*_async_k = ASYNC_DONE; \
case ASYNC_DONE: \
return ASYNC_DONE; \
}

/**
* Wait until the condition succeeds
* @param cond The condition that must be satisfied before execution can proceed
*/
#define await(cond) await_while(!(cond))

/**
* Wait while the condition succeeds
* @param cond The condition that must fail before execution can proceed
*/
#define await_while(cond) \
*_async_k = __LINE__; \
case __LINE__: \
if (cond) \
return ASYNC_CONT

/**
* Yield execution
*/
#define async_yield \
*_async_k = __LINE__; \
return ASYNC_CONT; \
case __LINE__:

/**
* Exit the current async subroutine
*/
#define async_exit \
*_async_k = ASYNC_DONE; \
return ASYNC_DONE

/**
* Initialize a new async computation
* @param state The async procedure state to initialize
*/
#define async_new(state) (state)->_async_k = ASYNC_INIT

/**
* Check if async subroutine is done
* @param state The async procedure state to check
*/
#define async_done(state) (state)->_async_k == ASYNC_DONE

/**
* Resume a running async computation and check for completion
*
* Calling the function itself will return true if the async call is complete,
* or false if it's still in progress.
* @param f The async procedure
* @param state The async procedure state
*/
#define async_call(f, state) (async_done(state) || (f)(state))

#endif
1 change: 1 addition & 0 deletions include/libdragon.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,6 @@
#include "a3d.h"
#include "profile.h"
#include "coroutine.h"
#include "async.h"

#endif
2 changes: 2 additions & 0 deletions include/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ void stop_timer(timer_link_t *timer);
*/
void delete_timer(timer_link_t *timer);

bool async_timer_expired(timer_link_t *timer);

#ifdef __cplusplus
}
#endif
Expand Down
46 changes: 46 additions & 0 deletions src/async.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "async.h"
#include "async_.h"
#include "mi.h"
#include "n64sys.h"
#include "interrupt.h"
#include "cop0.h"
#include "debug.h"


extern void __TI_handler(void);

extern int __interrupt_depth;

void async_init(void)
{
disable_interrupts();
}

void async_close(void)
{
enable_interrupts();
}

bool async_poll_timer(uint32_t cause) {
assertf(get_interrupts_state() == INTERRUPTS_DISABLED, "async routines not initialized");
if (cause & C0_INTERRUPT_TIMER)
{
// Acknowledge timer interrupt by writing to C0_COMPARE.
// This is necessary to clear the pending bit in CAUSE.
uint32_t compare = C0_COMPARE();
C0_WRITE_COMPARE(compare);
__TI_handler();
return true;
}

return false;
}

void async_schedule(async (*task)(void *st), void *initial_state)
{
assertf(get_interrupts_state() == INTERRUPTS_DISABLED, "async routines not initialized");
do {
uint32_t cause = C0_CAUSE();
async_poll_timer(cause);
} while (task && task(initial_state) != ASYNC_DONE);
}
11 changes: 11 additions & 0 deletions src/async_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
*
*/
#ifndef LIBDRAGON_ASYNC_INTERNAL_H
#define LIBDRAGON_ASYNC_INTERNAL_H

#include <stdint.h>

bool async_poll_timer(uint32_t cause);

#endif
9 changes: 9 additions & 0 deletions src/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,12 @@ long long timer_ticks(void)
assertf(timer_init_refcount > 0, "timer module not initialized");
return get_ticks();
}

bool async_timer_expired(timer_link_t *timer)
{
int ovfl = timer->ovfl;
// It is safe to reset this here, async_schedule executes all "int" handlers
// before invoking the async flows.
timer->ovfl = 0;
return ovfl > 0;
}
Loading