528 lines
14 KiB
C
528 lines
14 KiB
C
/*-
|
|
* Copyright (c) 2017-2019 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 "time.h"
|
|
#include "mem.h"
|
|
|
|
#include <exec/interrupts.h>
|
|
#include <clib/alib_protos.h>
|
|
|
|
/*
|
|
* 2922 is the number of days between 1.1.1970 and 1.1.1978
|
|
* (2 leap years and 6 normal)
|
|
* 2922 * 24 * 60 * 60 = 252460800
|
|
*/
|
|
#define AMIGA_OFFSET 252460800
|
|
#define SECONDSPERDAY 86400
|
|
#define TIMER_ERROR 10
|
|
#define TIMER_OK 0
|
|
|
|
struct TimerInfo
|
|
{
|
|
bool InterruptTimer;
|
|
bool TimerStarted;
|
|
bool TimerActive;
|
|
bool TimerExited;
|
|
long 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;
|
|
static long utcOffset = 0;
|
|
|
|
// Only one interrupt timer is allowed
|
|
struct TimerInfo *InterruptInfo;
|
|
|
|
/*
|
|
* Set up pointer for timer functions.
|
|
*/
|
|
struct Device *OpenTimerBase(void)
|
|
{
|
|
LONG error;
|
|
|
|
TimerIO = (struct timerequest *)AllocMemSafe(sizeof(struct timerequest));
|
|
if (TimerIO == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
error = OpenDevice((STRPTR)TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0);
|
|
if (error != 0)
|
|
{
|
|
FreeMemSafe(TimerIO, __FUNCTION__);
|
|
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, __FUNCTION__);
|
|
}
|
|
}
|
|
|
|
void TimerInterruptCode(void)
|
|
{
|
|
struct TimerInfo *info = InterruptInfo;
|
|
struct timerequest *tr = (struct timerequest *)GetMsg(info->TimerPort);
|
|
|
|
if (!info->TimerActive)
|
|
{
|
|
info->TimerExited = true;
|
|
return;
|
|
}
|
|
|
|
info->Counter++;
|
|
Signal(info->InterruptTask, info->InterruptSigBit);
|
|
|
|
tr->tr_node.io_Command = TR_ADDREQUEST;
|
|
tr->tr_time.tv_secs = Globals->Settings->Interval / 1000;
|
|
tr->tr_time.tv_micro = Globals->Settings->Interval % 1000;
|
|
|
|
BeginIO((struct IORequest *)tr);
|
|
}
|
|
|
|
void StartInterruptTimer(struct TimerInfo *info)
|
|
{
|
|
info->TimerStarted = true;
|
|
info->TimerActive = true;
|
|
info->TimerIO->tr_node.io_Command = TR_ADDREQUEST;
|
|
info->TimerIO->tr_time.tv_secs = Globals->Settings->Interval / 1000;
|
|
info->TimerIO->tr_time.tv_micro = Globals->Settings->Interval % 1000;
|
|
BeginIO((struct IORequest *)info->TimerIO);
|
|
}
|
|
|
|
void StopInterruptTimer(struct TimerInfo *info)
|
|
{
|
|
info->TimerActive = false;
|
|
|
|
AbortIO((struct IORequest *)info->TimerIO);
|
|
if (info->TimerStarted)
|
|
{
|
|
WaitIO((struct IORequest *)info->TimerIO);
|
|
}
|
|
|
|
while (!info->TimerExited)
|
|
{
|
|
Delay(10);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a Timer device software interrupt as descibed 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 = (struct TimerInfo *)AllocMemSafe(sizeof(struct TimerInfo));
|
|
if (info == NULL)
|
|
return NULL;
|
|
|
|
info->InterruptTimer = true;
|
|
info->InterruptTask = task;
|
|
info->InterruptSigBit = 1 << sigBit;
|
|
|
|
info->TimerPort = (struct MsgPort *)AllocMemSafe(sizeof(struct MsgPort));
|
|
if (info->TimerPort == NULL)
|
|
{
|
|
DeleteTimer(info);
|
|
return NULL;
|
|
}
|
|
|
|
info->TimerInterrupt = (struct Interrupt *)AllocMemSafe(sizeof(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 = (struct TimerInfo *)AllocMemSafe(sizeof(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)
|
|
{
|
|
AbortIO((struct IORequest *)info->TimerIO);
|
|
if (info->TimerStarted)
|
|
{
|
|
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, __FUNCTION__);
|
|
}
|
|
else
|
|
{
|
|
DeleteMsgPort(info->TimerPort);
|
|
}
|
|
}
|
|
|
|
if (info->TimerInterrupt != NULL)
|
|
{
|
|
FreeMemSafe(info->TimerInterrupt, __FUNCTION__);
|
|
}
|
|
|
|
FreeMemSafe(info, __FUNCTION__);
|
|
}
|
|
|
|
void LogTzInfo()
|
|
{
|
|
char text[SETTINGMESSAGELEN];
|
|
|
|
if (Globals->Timezone == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SNPrintf(text, SETTINGMESSAGELEN,
|
|
"Standard timezone is UTC%s%ld:%02ld:%02ld (%s)\n",
|
|
Globals->Timezone->std.offset.sign < 0 ? "-" : "+",
|
|
(long)Globals->Timezone->std.offset.hours,
|
|
(long)Globals->Timezone->std.offset.minutes,
|
|
(long)Globals->Timezone->std.offset.seconds,
|
|
Globals->Timezone->std.abbreviation);
|
|
LogTrace(text);
|
|
|
|
if (Globals->Timezone->dst.abbreviation != NULL)
|
|
{
|
|
SNPrintf(text, SETTINGMESSAGELEN,
|
|
"DST timezone is UTC%s%ld:%02ld:%02ld (%s)\n",
|
|
Globals->Timezone->dst.offset.sign < 0 ? "-" : "+",
|
|
(long)Globals->Timezone->dst.offset.hours,
|
|
(long)Globals->Timezone->dst.offset.minutes,
|
|
(long)Globals->Timezone->dst.offset.seconds,
|
|
Globals->Timezone->dst.abbreviation);
|
|
LogTrace(text);
|
|
}
|
|
}
|
|
|
|
void InitUtcOffset(void)
|
|
{
|
|
char timeZone[10];
|
|
int tz;
|
|
|
|
tz = InitTimezone();
|
|
switch (tz)
|
|
{
|
|
case 1:
|
|
LogTrace("No TZ or TZONE environment variable found");
|
|
break;
|
|
case 2:
|
|
LogTrace("Unknown TZ or TZONE environment variable found");
|
|
break;
|
|
case 3:
|
|
LogTrace("Found valid TZONE environment variable");
|
|
break;
|
|
case 4:
|
|
LogTrace("Found valid TZ environment variable");
|
|
break;
|
|
default:
|
|
LogTrace("Unknown TZ or TZONE code");
|
|
break;
|
|
}
|
|
|
|
if (tz == 3 || tz == 4)
|
|
{
|
|
struct timeval tv;
|
|
GetSysTime(&tv);
|
|
SetTimezone(&tv);
|
|
LogTzInfo();
|
|
utcOffset = -1L * Globals->Timezone->current.offset.time + AMIGA_OFFSET;
|
|
GetTimeZoneText(timeZone, true);
|
|
LogWarning("Local timezone is %s (%s)", timeZone,
|
|
Globals->Timezone->current.abbreviation);
|
|
return;
|
|
}
|
|
|
|
utcOffset = Globals->Locale->loc_GMTOffset * 60 + AMIGA_OFFSET;
|
|
GetTimeZoneText(timeZone, true);
|
|
LogWarning("Local timezone is %s", timeZone);
|
|
|
|
InitZoneShift();
|
|
}
|
|
|
|
void Amiga2DateStamp(time_t *time, struct DateStamp *date)
|
|
{
|
|
long t, d, m, q;
|
|
|
|
t = *time;
|
|
d = t / SECONDSPERDAY;
|
|
m = (t - SECONDSPERDAY * d) / 60;
|
|
q = (t - SECONDSPERDAY * d - m * 60) * 60;
|
|
|
|
date->ds_Days = d;
|
|
date->ds_Minute = m;
|
|
date->ds_Tick = q;
|
|
}
|
|
|
|
void InitZoneShift(void)
|
|
{
|
|
struct DateStamp ds;
|
|
char timeString[10], dateString[10], dayString[10];
|
|
struct PosixTransitionTime *transition;
|
|
long gmtoffset = utcOffset - AMIGA_OFFSET;
|
|
struct timeval tv;
|
|
time_t next;
|
|
ULONG now;
|
|
|
|
if (Globals->Timezone == NULL || Globals->Timezone->start.type == 0)
|
|
{
|
|
Globals->NextTransition = NULL;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
for (i = 0; i < 138; i++)
|
|
{
|
|
transition = &Globals->Timezone->transitions[i];
|
|
next = transition->time;
|
|
Amiga2DateStamp(&next, &ds);
|
|
DateTimeString(&ds, timeString, dateString, dayString);
|
|
LogWarning("XChanging (%ld) to %s time on %s %s at %s %s",
|
|
next,
|
|
transition->isdst ? "DST" : "standard",
|
|
dayString, dateString, timeString,
|
|
transition->isdst
|
|
? Globals->Timezone->std.abbreviation
|
|
: Globals->Timezone->dst.abbreviation);
|
|
}
|
|
*/
|
|
|
|
GetSysTime(&tv);
|
|
now = (ULONG)(tv.tv_secs + gmtoffset);
|
|
transition = FindNextTransition(now);
|
|
|
|
if (transition == NULL)
|
|
{
|
|
Globals->NextTransition = NULL;
|
|
return;
|
|
}
|
|
|
|
Globals->NextTransition = transition;
|
|
next = transition->time;
|
|
|
|
Amiga2DateStamp(&next, &ds);
|
|
DateTimeString(&ds, timeString, dateString, dayString);
|
|
LogWarning("Changing to %s on %s %s at %s %s",
|
|
transition->isdst ? "DST" : "standard time",
|
|
dayString, dateString, timeString,
|
|
transition->isdst
|
|
? Globals->Timezone->std.abbreviation
|
|
: Globals->Timezone->dst.abbreviation);
|
|
}
|
|
|
|
void GetTimeZoneText(char *text, bool includeUtc)
|
|
{
|
|
long gmtoffset = (utcOffset - AMIGA_OFFSET) / 60L;
|
|
SNPrintf(text, 10, "%s%s%ld:%02ld",
|
|
includeUtc ? "UTC" : "", (gmtoffset < 0L) ? "+" : "",
|
|
(long)(gmtoffset / -60L), (long)(gmtoffset % -60L));
|
|
}
|
|
|
|
void Unix2Amiga(struct timeval *unix, struct timeval *tv)
|
|
{
|
|
tv->tv_secs = unix->tv_secs + AMIGA_OFFSET;
|
|
tv->tv_micro = unix->tv_micro;
|
|
}
|
|
|
|
void Utc2Local(struct timeval *utc, struct timeval *tv)
|
|
{
|
|
int gmtoffset = Globals->Locale->loc_GMTOffset * 60;
|
|
tv->tv_secs = utc->tv_secs - gmtoffset;
|
|
tv->tv_micro = utc->tv_micro;
|
|
}
|
|
|
|
void GetTimeOfDay(struct timeval *tv)
|
|
{
|
|
GetSysTime(tv);
|
|
tv->tv_secs = tv->tv_secs + utcOffset;
|
|
}
|
|
|
|
void GetLocalTimeOfDay(struct timeval *tv)
|
|
{
|
|
GetSysTime(tv);
|
|
}
|
|
|
|
void SetTimeOfDay(const struct TimerInfo *info, const struct timeval *tv)
|
|
{
|
|
info->TimerIO->tr_node.io_Command = TR_SETSYSTIME;
|
|
info->TimerIO->tr_time.tv_secs = (long)tv->tv_secs - utcOffset;
|
|
info->TimerIO->tr_time.tv_micro = tv->tv_micro;
|
|
DoIO((struct IORequest *)info->TimerIO);
|
|
}
|
|
|
|
void SaveTimeOfDay(const struct timeval *tv)
|
|
{
|
|
WriteBattClock((long)tv->tv_secs - utcOffset);
|
|
}
|
|
|
|
void SystemTimeString(char *time, char *date, char *day, char *zone)
|
|
{
|
|
struct DateTime dt;
|
|
|
|
DateStamp(&dt.dat_Stamp);
|
|
|
|
dt.dat_Format = FORMAT_DOS;
|
|
dt.dat_Flags = 0;
|
|
dt.dat_StrDay = (void *)day;
|
|
dt.dat_StrDate = (void *)date;
|
|
dt.dat_StrTime = (void *)time;
|
|
|
|
DateToStr(&dt);
|
|
|
|
GetTimeZoneText(zone, false);
|
|
}
|
|
|
|
void DateTimeString(struct DateStamp *ds, char *time, char *date, char *day)
|
|
{
|
|
struct DateTime dt;
|
|
|
|
dt.dat_Stamp = *ds;
|
|
dt.dat_Format = FORMAT_DOS;
|
|
dt.dat_Flags = 0;
|
|
dt.dat_StrDay = (void *)day;
|
|
dt.dat_StrDate = (void *)date;
|
|
dt.dat_StrTime = (void *)time;
|
|
|
|
DateToStr(&dt);
|
|
}
|
|
|
|
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;
|
|
}
|