/* * 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 #include #include #include #include #include #include #include #include #include #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; }