From a762a629aa49a046453de7fd2d65680e73c9123a Mon Sep 17 00:00:00 2001 From: Andrew Hannam Date: Sat, 17 Nov 2012 01:42:12 +1000 Subject: GEVENT, GTIMER & GINPUT subsystems GEVENT - for passing event structures from Sources to Listeners GTIMER - thread context based once-off and periodic timers. GINPUT - extensible, multiple device-type, input sub-system. gevent & gtimer are code complete, ginput is definition complete but not code complete. --- src/gevent.c | 216 ++++++++++++++++++++++++++++++++++++++++++ src/ginput.c | 42 +++++++++ src/gtimer.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 560 insertions(+) create mode 100644 src/gevent.c create mode 100644 src/ginput.c create mode 100644 src/gtimer.c (limited to 'src') diff --git a/src/gevent.c b/src/gevent.c new file mode 100644 index 00000000..bfda6d53 --- /dev/null +++ b/src/gevent.c @@ -0,0 +1,216 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file src/gevent.c + * @brief GEVENT Driver code. + * + * @addtogroup GEVENT + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gevent.h" + +#ifndef _GEVENT_C +#define _GEVENT_C + +#if GFX_USE_GEVENT || defined(__DOXYGEN__) + +#if GEVENT_ASSERT_NO_RESOURCE + #define GEVENT_ASSERT(x) assert(x) +#else + #define GEVENT_ASSERT(x) +#endif + +// This mutex protects access to our tables +static MUTEX_DECL(geventMutex); + +// Our table of listener/source pairs +static GSourceListener Assignments[MAX_SOURCE_LISTENERS]; + +// Loop through the assignment table deleting this listener/source pair. +// Null is treated as a wildcard. +static void deleteAssignments(GListener *pl, GSourceHandle gsh) { + GSourceListener *psl; + + for(psl = Assignments; psl < Assignments+MAX_SOURCE_LISTENERS; psl++) { + if ((!pl || psl->pListener == pl) && (!gsh || psl->pSource == gsh)) { + if (chSemGetCounterI(&psl->pListener->waitqueue) < 0) { + chBSemWait(&psl->pListener->eventlock); // Obtain the buffer lock + psl->pListener->event.type = GEVENT_EXIT; // Set up the EXIT event + chSemSignal(&psl->pListener->waitqueue); // Wake up the listener + chBSemSignal(&psl->pListener->eventlock); // Release the buffer lock + } + psl->pListener = 0; + } + } +} + +/* Create a Listener. + * If insufficient resources are available it will either assert or return NULL + * depending on the value of GEVENT_ASSERT_NO_RESOURCE. + */ +void geventListenerInit(GListener *pl) { + chSemInit(&pl->waitqueue, 0); // Next wait'er will block + chBSemInit(&pl->eventlock, FALSE); // Only one thread at a time looking at the event buffer + pl->event.type = GEVENT_NULL; // Always safety +} + +/* Attach a source to a listener. + * Flags are interpreted by the source when generating events for each listener. + * If this source is already assigned to the listener it will update the flags. + * If insufficient resources are available it will either assert or return FALSE + * depending on the value of GEVENT_ASSERT_NO_RESOURCE. + */ +bool_t geventAttachSource(GListener *pl, GSourceHandle gsh, unsigned flags) { + GSourceListener *psl, *pslfree; + + // Safety first + if (!pl || !gsh) { + GEVENT_ASSERT(FALSE); + return FALSE; + } + + chMtxLock(&geventMutex); + + // Check if this pair is already in the table (scan for a free slot at the same time) + pslfree = 0; + for(psl = Assignments; psl < Assignments+MAX_SOURCE_LISTENERS; psl++) { + + if (pl == psl->pListener && gsh == psl->pSource) { + // Just update the flags + chBSemWait(&pl->eventlock); // Safety first - just in case a source is using it + psl->listenflags = flags; + chBSemSignal(&pl->eventlock); // Release this lock + chMtxUnlock(); + return TRUE; + } + if (!pslfree && !psl->pListener) + pslfree = psl; + } + + // A free slot was found - allocate it + if (pslfree) { + pslfree->pListener = pl; + pslfree->pSource = gsh; + pslfree->listenflags = flags; + pslfree->srcflags = 0; + } + chMtxUnlock(); + GEVENT_ASSERT(pslfree != 0); + return pslfree != 0; +} + +/* Detach a source from a listener + * If gsh is NULL detach all sources from this listener and if there is still + * a thread waiting for events on this listener, it is sent the exit event. + */ +void geventDetachSource(GListener *pl, GSourceHandle gsh) { + if (pl && gsh) { + chMtxLock(&geventMutex); + deleteAssignments(pl, gsh); + if (!gsh && chSemGetCounterI(&pl->waitqueue) < 0) { + chBSemWait(&pl->eventlock); // Obtain the buffer lock + pl->event.type = GEVENT_EXIT; // Set up the EXIT event + chSemSignal(&pl->waitqueue); // Wake up the listener + chBSemSignal(&pl->eventlock); // Release the buffer lock + } + chMtxUnlock(); + } +} + +/* Wait for an event on a listener from an assigned source. + * The type of the event should be checked (pevent->type) and then pevent should be typecast to the + * actual event type if it needs to be processed. + * timeout specifies the time to wait in system ticks. + * TIME_INFINITE means no timeout - wait forever for an event. + * TIME_IMMEDIATE means return immediately + * Returns NULL on timeout. + * Note: The GEvent buffer is staticly allocated within the GListener so the event does not + * need to be dynamicly freed however it will get overwritten by the next call to + * this routine. + */ +GEvent *geventEventWait(GListener *pl, systime_t timeout) { + return chSemWaitTimeout(&pl->waitqueue, timeout) == RDY_OK ? &pl->event : 0; +} + +/* Called by a source with a possible event to get a listener record. + * 'lastlr' should be NULL on the first call and thereafter the result of the previous call. + * It will return NULL when there are no more listeners for this source. + */ +GSourceListener *geventGetSourceListener(GSourceHandle gsh, GSourceListener *lastlr) { + GSourceListener *psl; + + // Safety first + if (!gsh) + return 0; + + chMtxLock(&geventMutex); + + // Unlock the last listener event buffer + if (lastlr) + chBSemSignal(&lastlr->pListener->eventlock); + + // Loop through the table looking for attachments to this source + for(psl = lastlr ? (lastlr+1) : Assignments; psl < Assignments+MAX_SOURCE_LISTENERS; psl++) { + if (gsh == psl->pSource) { + chBSemWait(&psl->pListener->eventlock); // Obtain a lock on the listener event buffer + chMtxUnlock(); + return psl; + } + } + chMtxUnlock(); + return 0; +} + +/* Get the event buffer from the GSourceListener. + * Returns NULL if the listener is not currently listening. + * A NULL return allows the source to record (perhaps in glr->scrflags) that the listener has missed events. + * This can then be notified as part of the next event for the listener. + * The buffer can only be accessed untill the next call to geventGetSourceListener or geventSendEvent + */ +GEvent *geventGetEventBuffer(GSourceListener *psl) { + // We already know we have the event lock + return chSemGetCounterI(&psl->pListener->waitqueue) < 0 ? &psl->pListener->event : 0; +} + +/* Called by a source to indicate the listener's event buffer has been filled. + * After calling this function the source must not reference in fields in the GSourceListener or the event buffer. + */ +void geventSendEvent(GSourceListener *psl) { + chMtxLock(&geventMutex); + // Wake up the listener + if (chSemGetCounterI(&psl->pListener->waitqueue) < 0) + chSemSignal(&psl->pListener->waitqueue); + chMtxUnlock(); +} + +/* Detach any listener that has this source attached */ +void geventDetachSourceListeners(GSourceHandle gsh) { + chMtxLock(&geventMutex); + deleteAssignments(0, gsh); + chMtxUnlock(); +} + +#endif /* GFX_USE_GEVENT */ + +#endif /* _GEVENT_C */ +/** @} */ diff --git a/src/ginput.c b/src/ginput.c new file mode 100644 index 00000000..d12bef2b --- /dev/null +++ b/src/ginput.c @@ -0,0 +1,42 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file src/ginput.c + * @brief GINPUT Driver code. + * + * @addtogroup GINPUT + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "ginput.h" + +#ifndef _GINPUT_C +#define _GINPUT_C + +#if GFX_USE_GINPUT || defined(__DOXYGEN__) + +#error "GINPUT: Not Implemented Yet" + +#endif /* GFX_USE_GINPUT */ + +#endif /* _GINPUT_C */ +/** @} */ diff --git a/src/gtimer.c b/src/gtimer.c new file mode 100644 index 00000000..84518a23 --- /dev/null +++ b/src/gtimer.c @@ -0,0 +1,302 @@ +/* + ChibiOS/GFX - Copyright (C) 2012 + Joel Bodenmann aka Tectu + + This file is part of ChibiOS/GFX. + + ChibiOS/GFX 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 3 of the License, or + (at your option) any later version. + + ChibiOS/GFX 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, see . +*/ + +/** + * @file src/gtimer.c + * @brief GTIMER Driver code. + * + * @addtogroup GTIMER + * @{ + */ +#include "ch.h" +#include "hal.h" +#include "gtimer.h" + +#ifndef _GTIMER_C +#define _GTIMER_C + +#if GFX_USE_GTIMER || defined(__DOXYGEN__) + +#define GTIMER_FLG_PERIODIC 0x0001 +#define GTIMER_FLG_INFINITE 0x0002 +#define GTIMER_FLG_JABBED 0x0004 +#define GTIMER_FLG_SCHEDULED 0x0008 + +#define TimeIsWithin(time, start, end) (end > start ? (time >= start && time <= end) : (time >= start || time <= end)) + +// This mutex protects access to our tables +static MUTEX_DECL(mutex); +static Thread *pThread = 0; +static GTimer *pTimerHead = 0; +static systime_t lastTime = 0; +static SEMAPHORE_DECL(waitsem, 0); +static WORKING_AREA(waTimerThread, GTIMER_THREAD_STACK_SIZE); + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static msg_t GTimerThreadHandler(void *arg) { + (void)arg; + GTimer *pt; + systime_t tm; + systime_t nxtTimeout; + systime_t tmptime; + GTimerFunction fn; + void *param; + + #if CH_USE_REGISTRY + chRegSetThreadName("GTimer"); + #endif + + nxtTimeout = TIME_INFINITE; + while(1) { + /* Wait for work to do. */ + chSemWaitTimeout(&waitsem, nxtTimeout); + + restartTimerChecks: + + // Our reference time + tm = chTimeNow(); + nxtTimeout = TIME_INFINITE; + + /* We need to obtain the mutex */ + chMtxLock(&mutex); + + if (pTimerHead) { + pt = pTimerHead; + do { + // Do we have something to do for this timer? + if ((pt->flags & GTIMER_FLG_JABBED) || (!(pt->flags & GTIMER_FLG_INFINITE) && TimeIsWithin(pt->when, lastTime, tm))) { + + // Is this timer periodic? + if ((pt->flags & GTIMER_FLG_PERIODIC) && pt->period != TIME_IMMEDIATE) { + // Yes - Update ready for the next period + if (!(pt->flags & GTIMER_FLG_INFINITE)) { + do { + pt->when += pt->period; // We may have skipped a period + } while (TimeIsWithin(pt->when, lastTime, tm)); + } + + // We are definitely no longer jabbed + pt->flags &= ~GTIMER_FLG_JABBED; + + } else { + // No - get us off the timers list + if (pt->next == pt->prev) + pTimerHead = 0; + else { + pt->next->prev = pt->prev; + pt->prev->next = pt->next; + if (pTimerHead == pt) + pTimerHead = pt->next; + } + pt->flags = 0; + } + + // Call the callback function + fn = pt->fn; + param = pt->param; + chMtxUnlock(); + fn(param); + + // We no longer hold the mutex, the callback function may have taken a while + // and our list may have been altered so start again! + goto restartTimerChecks; + } + + // Find when we next need to wake up + if (!(pt->flags & GTIMER_FLG_INFINITE)) { + tmptime = pt->when - tm; + if (tmptime < nxtTimeout) nxtTimeout = tmptime; + } + pt = pt->next; + } while(pt != pTimerHead); + } + + // Ready for the next loop + lastTime = tm; + chMtxUnlock(); + } + return 0; +} + +/** + * @brief Initialise a timer. + * + * @param[in] pt pointer to a GTimer structure + * + * @api + */ +void gtimerInit(GTimer *pt) { + pt->flags = 0; +} + +/** + * @brief Set a timer going or alter its properties if it is already going. + * + * @param[in] pt Pointer to a GTimer structure + * @param[in] fn The callback function + * @param[in] param The parameter to pass to the callback function + * @param[in] periodic Is the timer a periodic timer? FALSE is a once-only timer. + * @param[in] millisec The timer period. The following special values are allowed: + * TIME_IMMEDIATE causes the callback function to be called asap. + * A periodic timer with this value will fire once only. + * TIME_INFINITE never timeout (unless triggered by gtimerJab or gtimerJabI) + * + * @note If the timer is already active its properties are updated with the new parameters. + * The current period will be immediately canceled (without the callback function being + * called) and the timer will be restart with the new timer properties. + * @note The callback function should be careful not to over-run the thread stack. + * Define a new value for the macro GTIME_THREAD_STACK_SIZE if you want to + * change the default size. + * @note The callback function should return as quickly as possible as all + * timer callbacks are performed by a single thread. If a callback function + * takes too long it could affect the timer response for other timers. + * @note A timer callback function is not a replacement for a dedicated thread if the + * function wants to perform computationally expensive stuff. + * @note As the callback function is called on GTIMER's thread, the function must make sure it uses + * appropriate synchronisation controls such as semaphores or mutexes around any data + * structures it shares with other threads such as the main application thread. + * + * @api + */ +void gtimerStart(GTimer *pt, GTimerFunction fn, void *param, bool_t periodic, systime_t millisec) { + chMtxLock(&mutex); + + // Start our thread if not already going + if (!pThread) + pThread = chThdCreateStatic(waTimerThread, sizeof(waTimerThread), HIGHPRIO, GTimerThreadHandler, NULL); + + // Is this already scheduled? + if (pt->flags & GTIMER_FLG_SCHEDULED) { + // Cancel it! + if (pt->next == pt->prev) + pTimerHead = 0; + else { + pt->next->prev = pt->prev; + pt->prev->next = pt->next; + if (pTimerHead == pt) + pTimerHead = pt->next; + } + } + + // Set up the timer structure + pt->fn = fn; + pt->param = param; + pt->flags = GTIMER_FLG_SCHEDULED; + if (periodic) + pt->flags |= GTIMER_FLG_PERIODIC; + if (millisec != TIME_INFINITE) { + pt->period = MS2ST(millisec); + pt->when = chTimeNow() + pt->period; + } else + pt->flags |= GTIMER_FLG_INFINITE; + + // Just pop it on the end of the queue + if (pTimerHead) { + pt->next = pTimerHead; + pt->prev = pTimerHead->prev; + pt->prev->next = pt; + pt->next->prev = pt; + } else + pt->next = pt->prev = pTimerHead = pt; + + // Bump the thread + chSemSignal(&waitsem); + chMtxUnlock(); +} + +/** + * @brief Stop a timer (periodic or otherwise) + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * + * @api + */ +void gtimerStop(GTimer *pt) { + chMtxLock(&mutex); + if (pt->flags & GTIMER_FLG_SCHEDULED) { + // Cancel it! + if (pt->next == pt->prev) + pTimerHead = 0; + else { + pt->next->prev = pt->prev; + pt->prev->next = pt->next; + if (pTimerHead == pt) + pTimerHead = pt->next; + } + // Make sure we know the structure is dead! + pt->flags = 0; + } + chMtxUnlock(); +} + +/** + * @brief Jab a timer causing the current period to immediate expire + * @details The callback function will be called as soon as possible. + * + * @pre Use from a normal thread context. + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * @note Repeated Jabs before the callback function actually happens are ignored. + * + * @api + */ +void gtimerJab(GTimer *pt) { + chMtxLock(&mutex); + + // Jab it! + pt->flags |= GTIMER_FLG_JABBED; + + // Bump the thread + chSemSignal(&waitsem); + chMtxUnlock(); +} + +/** + * @brief Jab a timer causing the current period to immediate expire + * @details The callback function will be called as soon as possible. + * + * @pre Use from an interrupt routine context. + * + * @param[in] pt Pointer to a GTimer structure + * + * @note If the timer is not active this does nothing. + * @note Repeated Jabs before the callback function actually happens are ignored. + * + * @api + */ +void gtimerJabI(GTimer *pt) { + // Jab it! + pt->flags |= GTIMER_FLG_JABBED; + + // Bump the thread + chSemSignalI(&waitsem); +} + +#endif /* GFX_USE_GTIMER */ + +#endif /* _GTIMER_C */ +/** @} */ -- cgit v1.2.3