amiga-ntimed/time_amiga.c

284 lines
7.0 KiB
C

/*
* Copyright (c) 2015 Carsten Larsen
* 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 <stdio.h>
#include <string.h>
#include <math.h>
#include <exec/types.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <clib/alib_protos.h>
#include <proto/dos.h>
#include <devices/timer.h>
#ifdef Debug
#undef Debug
#endif
#include "atimed.h"
#include "ntimed.h"
int amiga_get_time(struct timeval *tv);
int amiga_set_time(struct timeval *tv);
struct timerequest* create_timer();
void delete_timer(struct timerequest*);
void adjust_timeval(struct timeval *tv, double offset);
// Ntimed internals
static double adj_offset = 0;
static double adj_duration = 0;
static double adj_freq = 0;
static uintptr_t ticker;
static struct todolist *kt_tdl;
#ifndef AMIGA
struct timeval {
ULONG tv_secs;
ULONG tv_micro;
};
#endif
/**********************************************************************/
static void
amiga_setfreq(struct ocx *ocx, double frequency)
{
assert(isfinite(frequency));
// frequency offset (scaled ppm)
long int freq = (long)floor(frequency * (65536 * 1e6));
Put(ocx, OCX_TRACE, "KERNPLL %.6e\n", frequency);
}
static enum todo_e __match_proto__(todo_f)
amiga_ticker(struct ocx *ocx, struct todolist *tdl, void *priv)
{
(void)ocx;
AN(tdl);
AZ(priv);
amiga_setfreq(ocx, adj_freq);
ticker = 0;
return (TODO_OK);
}
static void __match_proto__(tb_adjust_f)
amiga_adjust(struct ocx *ocx, double offset, double duration, double frequency)
{
double freq;
(void)ocx;
assert(duration >= 0.0);
if (ticker)
TODO_Cancel(kt_tdl, &ticker);
adj_offset = offset;
adj_duration = floor(duration);
if (adj_offset > 0.0 && adj_duration == 0.0)
adj_duration = 1.0;
adj_freq = frequency;
freq = adj_freq;
if (adj_duration > 0.0)
freq += adj_offset / adj_duration;
amiga_setfreq(ocx, freq);
if (adj_duration > 0.0)
ticker = TODO_ScheduleRel(kt_tdl, amiga_ticker, NULL,
adj_duration, 0.0, "KT_TICK");
}
/**********************************************************************/
static void __match_proto__(tb_step_f)
amiga_step(struct ocx *ocx, double offset)
{
double d;
struct timeval tv;
Put(ocx, OCX_TRACE, "KERNTIME_STEP %.3e\n", offset);
AZ(amiga_get_time(&tv));
adjust_timeval(&tv, offset);
AZ(amiga_set_time(&tv));
TB_generation++;
}
/**********************************************************************/
static struct timestamp * __match_proto__(tb_now_f)
amiga_now(struct timestamp *storage)
{
struct timeval tv;
AZ(amiga_get_time(&tv));
return (TS_Nanosec(storage, tv.tv_secs, tv.tv_micro * 1000LL));
}
/**********************************************************************/
static int __match_proto__(tb_sleep_f)
amiga_sleep(double dur)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
adjust_timeval(&tv, dur);
struct timerequest *request = create_timer();
if (request == NULL)
return 1;
request->tr_node.io_Command = TR_ADDREQUEST;
request->tr_time = tv;
DoIO((struct IORequest*)request);
delete_timer(request);
return 0;
}
/**********************************************************************/
void Time_Amiga(struct todolist *tdl)
{
AN(tdl);
TB_Step = amiga_step;
TB_Adjust = amiga_adjust;
TB_Sleep = amiga_sleep;
TB_Now = amiga_now;
kt_tdl = tdl;
}
void Time_Amiga_Passive(void)
{
TB_Sleep = amiga_sleep;
TB_Now = amiga_now;
}
/**********************************************************************/
struct timerequest* create_timer()
{
struct MsgPort *port = CreatePort(0, 0);
if (port == NULL)
return NULL;
struct timerequest *request = (struct timerequest*)CreateExtIO(port, sizeof(struct timerequest));
if (request == NULL)
{
DeletePort(port);
return NULL;
}
LONG error = OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest*)request, 0L);
if (error != 0)
{
delete_timer(request);
return NULL;
}
return request;
}
void delete_timer(struct timerequest *request)
{
if (request == NULL)
return;
struct MsgPort *port = request->tr_node.io_Message.mn_ReplyPort;
if (port != 0)
DeletePort(port);
CloseDevice((struct IORequest*)request);
DeleteExtIO((struct IORequest*)request);
}
/**********************************************************************/
// 2922 is the number of days between 1.1.1970 and 1.1.1978 (2 leap years and 6 normal)
const long amigaoffset = 2922 * 24 * 60 * 60; // 252460800
const long cetoffset = 60 * 60;
int amiga_get_time(struct timeval *tv)
{
struct timerequest *request = create_timer();
if (request == NULL)
return 1;
request->tr_node.io_Command = TR_GETSYSTIME;
DoIO((struct IORequest*)request);
*tv = request->tr_time;
tv->tv_secs = tv->tv_secs + (amigaoffset - cetoffset);
delete_timer(request);
return 0;
}
int amiga_set_time(struct timeval *tv)
{
struct timerequest *request = create_timer();
if (request == NULL)
return 1;
request->tr_node.io_Command = TR_SETSYSTIME;
request->tr_time = *tv;
request->tr_time.tv_secs -= (amigaoffset - cetoffset);
DoIO((struct IORequest*)request);
delete_timer(request);
return 0;
}
/**********************************************************************/
void adjust_timeval(struct timeval *tv, double offset)
{
double d = floor(offset);
offset -= d;
long sec = tv->tv_secs + (long)d;
long micro = tv->tv_micro + (long)floor(offset * 1e6);
if (micro < 0) {
sec -= 1;
micro += 1000000;
} else if (micro >= 1000000) {
sec += 1;
micro -= 1000000;
}
tv->tv_secs = sec;
tv->tv_micro = micro;
}