AmiTimeKeeper/time.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;
}