284 lines
7.0 KiB
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;
|
|
}
|