AmiTimeKeeper/timer.c

367 lines
8.8 KiB
C

/*-
* Copyright (c) 2017-2021 Carsten Sonne Larsen <cs@innolan.net>
* All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*
*/
#include "config.h"
#include "ptz.h"
#include "timer.h"
#include "mem.h"
#include "tz.h"
#include <exec/interrupts.h>
#include <clib/alib_protos.h>
#include <assert.h>
#include "logmod.h"
#define MODULENAME "Timer"
#define TIMER_ERROR 10
#define TIMER_OK 0
#define TIMER_INTERRUPT_ON 1
#define TIMER_INTERRUPT_OFF 2
#define TIMER_INTERRUPT_STOPPED 3
struct TimerInfo
{
bool InterruptTimer;
bool Trigger;
bool Restart;
long TimerFlag;
struct timeval Interval;
long Counter;
struct MsgPort *TimerPort;
struct Interrupt *TimerInterrupt;
struct timerequest *TimerIO;
struct Task *InterruptTask;
ULONG InterruptSigBit;
};
struct Device *TimerBase = NULL;
static struct timerequest *TimerIO = NULL;
// Only one interrupt timer is allowed
struct TimerInfo *InterruptInfo;
/*
* Set up pointer for timer functions.
*/
struct Device *OpenTimerBase(void)
{
LONG error;
TimerIO = AllocStructSafe(struct timerequest);
if (TimerIO == NULL)
{
return NULL;
}
error = OpenDevice((STRPTR)TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0);
if (error != 0)
{
FreeMemSafe(TimerIO);
TimerIO = NULL;
return NULL;
}
TimerBase = TimerIO->tr_node.io_Device;
return TimerBase;
}
void CloseTimerBase(void)
{
if (TimerBase != NULL && TimerIO != NULL)
{
CloseDevice((struct IORequest *)TimerIO);
}
if (TimerIO != NULL)
{
FreeMemSafe(TimerIO);
}
}
void SetTime(const struct timeval *tv)
{
assert(tv != NULL);
// Notice: Method is not thread safe.
TimerIO->tr_node.io_Command = TR_SETSYSTIME;
TimerIO->tr_time.tv_secs = (long)tv->tv_secs;
TimerIO->tr_time.tv_micro = tv->tv_micro;
DoIO((struct IORequest *)TimerIO);
}
static void TimerInterruptCode(void)
{
struct TimerInfo *info;
struct timerequest *tr;
info = InterruptInfo;
tr = (struct timerequest *)GetMsg(info->TimerPort);
if ((tr) && (info->TimerFlag == TIMER_INTERRUPT_ON))
{
if (info->Restart)
{
info->Counter = 0;
info->Trigger = false;
info->Restart = false;
tr->tr_time.tv_secs = 1;
tr->tr_time.tv_micro = 0;
}
else if (info->Trigger)
{
Signal(info->InterruptTask, info->InterruptSigBit);
info->Counter = 0;
info->Trigger = false;
tr->tr_time.tv_secs = 1;
tr->tr_time.tv_micro = 0;
}
else
{
info->Counter++;
if (info->Counter == info->Interval.tv_secs)
{
tr->tr_time.tv_secs = 0;
tr->tr_time.tv_micro = info->Interval.tv_micro;
info->Trigger = true;
}
else
{
tr->tr_time.tv_secs = 1;
tr->tr_time.tv_micro = 0;
}
}
tr->tr_node.io_Command = TR_ADDREQUEST;
BeginIO((struct IORequest *)tr);
}
else
{
info->TimerFlag = TIMER_INTERRUPT_STOPPED;
}
}
void SetInterruptTimerInterval(struct TimerInfo *info, struct timeval *tv)
{
info->Interval.tv_secs = tv->tv_secs;
info->Interval.tv_micro = tv->tv_micro;
if (info->Interval.tv_micro == 0)
{
info->Interval.tv_micro = 2;
}
info->Restart = true;
}
void StartInterruptTimer(struct TimerInfo *info)
{
assert(info != NULL);
if (!info->InterruptTimer)
{
return;
}
info->TimerFlag = TIMER_INTERRUPT_ON;
info->Trigger = false;
info->Restart = false;
info->TimerIO->tr_node.io_Command = TR_ADDREQUEST;
info->TimerIO->tr_time.tv_secs = 1;
info->TimerIO->tr_time.tv_micro = 0;
BeginIO((struct IORequest *)info->TimerIO);
}
/*
* Create a Timer device software interrupt as described on wiki.amigaos.net:
* https://wiki.amigaos.net/wiki/Exec_Interrupts#Software_Interrupts
* and Amiga Developer Docs 2.1, lib_examples/timersoftint.c
*/
struct TimerInfo *CreateInterruptTimer(struct Task *task, short sigBit)
{
LONG error;
struct TimerInfo *info = AllocStructSafe(struct TimerInfo);
if (info == NULL)
return NULL;
info->InterruptTimer = true;
info->InterruptTask = task;
info->InterruptSigBit = 1 << sigBit;
info->TimerPort = AllocStructSafe(struct MsgPort);
if (info->TimerPort == NULL)
{
DeleteTimer(info);
return NULL;
}
info->TimerInterrupt = AllocStructSafe(struct Interrupt);
if (info->TimerInterrupt == NULL)
{
DeleteTimer(info);
return NULL;
}
NewList(&(info->TimerPort->mp_MsgList));
info->TimerPort->mp_Node.ln_Type = NT_MSGPORT;
info->TimerPort->mp_Flags = PA_SOFTINT;
info->TimerPort->mp_SigTask = (struct Task *)info->TimerInterrupt;
info->TimerInterrupt->is_Code = TimerInterruptCode;
info->TimerInterrupt->is_Data = info;
info->TimerInterrupt->is_Node.ln_Pri = 0;
info->TimerIO = (struct timerequest *)CreateExtIO(info->TimerPort, sizeof(struct timerequest));
if (info->TimerIO == NULL)
{
DeleteTimer(info);
return NULL;
}
error = OpenDevice(
(STRPTR)TIMERNAME, UNIT_MICROHZ,
(struct IORequest *)info->TimerIO, 0);
if (error != 0)
{
DeleteTimer(info);
return NULL;
}
// Only one interrupt timer is supported
InterruptInfo = info;
return info;
}
/*
* Open a timer device with UNIT_MICROHZ.
*/
struct TimerInfo *CreateTimer(void)
{
LONG error;
static const char *name = APP_SHORT_NAME " Timer Message Port";
struct TimerInfo *info = AllocStructSafe(struct TimerInfo);
if (info == NULL)
return NULL;
info->TimerPort = CreateMsgPort();
if (info->TimerPort == NULL)
{
DeleteTimer(info);
return NULL;
}
info->TimerPort->mp_Node.ln_Name = (char *)name;
info->TimerPort->mp_Node.ln_Pri = 0;
info->TimerIO = (struct timerequest *)CreateExtIO(info->TimerPort, sizeof(struct timerequest));
if (info->TimerIO == NULL)
{
DeleteTimer(info);
return NULL;
}
error = OpenDevice(
(STRPTR)TIMERNAME, UNIT_MICROHZ,
(struct IORequest *)info->TimerIO, 0);
if (error != 0)
{
DeleteTimer(info);
return NULL;
}
return info;
}
void DeleteTimer(struct TimerInfo *info)
{
if (info == NULL)
{
return;
}
if (info->TimerIO != NULL)
{
if (info->TimerInterrupt)
{
info->TimerFlag = TIMER_INTERRUPT_OFF;
while (info->TimerFlag != TIMER_INTERRUPT_STOPPED)
{
Delay(10);
}
}
else
{
if (!(CheckIO((struct IORequest *)info->TimerIO)))
{
AbortIO((struct IORequest *)info->TimerIO);
}
WaitIO((struct IORequest *)info->TimerIO);
}
CloseDevice((struct IORequest *)info->TimerIO);
DeleteExtIO((struct IORequest *)info->TimerIO);
info->TimerIO = NULL;
}
if (info->TimerPort != NULL)
{
if (info->InterruptTimer)
{
FreeMemSafe(info->TimerPort);
}
else
{
DeleteMsgPort(info->TimerPort);
}
}
if (info->TimerInterrupt != NULL)
{
FreeMemSafe(info->TimerInterrupt);
}
FreeMemSafe(info);
}
static int seed = 123456789;
void SeedRandom(void)
{
struct timeval tv;
GetLocalTimeOfDay(&tv);
seed = tv.tv_micro;
}
int RandomFast(void)
{
seed = (1103515245 * seed + 12345) % 0x7fffffff;
return seed;
}