1
0
mirror of https://github.com/deadw00d/AROS.git synced 2025-12-09 16:00:35 +00:00
Files
AROS-v0/rom/timer/lowlevel.c

430 lines
13 KiB
C

/*
Copyright (C) 1995-2018, The AROS Development Team. All rights reserved.
Desc: Common IORequest processing routines
*/
#include <aros/debug.h>
#include <aros/symbolsets.h>
#include <devices/newstyle.h>
#include <exec/errors.h>
#include <exec/initializers.h>
#include <hardware/intbits.h>
#include <proto/exec.h>
#include <proto/execlock.h>
#include <proto/timer.h>
#include "timer_intern.h"
#include "timer_macros.h"
/****************************************************************************************/
#define NEWSTYLE_DEVICE 1
#define ioStd(x) ((struct IOStdReq *)x)
/****************************************************************************************/
#if NEWSTYLE_DEVICE
static const UWORD SupportedCommands[] =
{
TR_GETSYSTIME,
TR_SETSYSTIME,
TR_ADDREQUEST,
NSCMD_DEVICEQUERY,
0
};
#endif
static void addToWaitList(struct MinList *list, struct timerequest *iotr, struct ExecBase *SysBase)
{
/* We are disabled, so we should take as little time as possible. */
struct timerequest *tr;
BOOL added = FALSE;
ForeachNode(list, tr)
{
/* If the time in the new request is less than the next request */
if (CMPTIME3232(&tr->tr_time, &iotr->tr_time) < 0)
{
/* Add the node before the next request */
Insert((struct List *)list, &iotr->tr_node.io_Message.mn_Node, tr->tr_node.io_Message.mn_Node.ln_Pred);
added = TRUE;
break;
}
}
/*
* This will catch the case of either an empty list, or request is
* for after all other requests
*/
if(!added)
ADDTAIL(list, iotr);
#if PRINT_LIST
bug("Current list contents:\n");
ForeachNode(list, tr)
{
bug("%u.%u\n", tr->tr_time.tv_secs, tr->tr_time.tv_micro);
}
#endif
}
BOOL common_BeginIO(struct timerequest *timereq, struct TimerBase *TimerBase)
{
ULONG unitNum = (IPTR)timereq->tr_node.io_Unit;
BOOL replyit = FALSE;
BOOL addedhead = FALSE;
timereq->tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
timereq->tr_node.io_Error = 0;
switch(timereq->tr_node.io_Command)
{
#if NEWSTYLE_DEVICE
case NSCMD_DEVICEQUERY:
/*
* CHECKME: In timer.device this is maybe a bit problematic, as the
* timerequest structure does not have io_Data and io_Length members
*/
if (timereq->tr_node.io_Message.mn_Length < sizeof(struct IOStdReq))
{
timereq->tr_node.io_Error = IOERR_BADLENGTH;
}
else if(ioStd(timereq)->io_Length < ((LONG)OFFSET(NSDeviceQueryResult, SupportedCommands)) + sizeof(UWORD *))
{
timereq->tr_node.io_Error = IOERR_BADLENGTH;
}
else
{
struct NSDeviceQueryResult *d = (struct NSDeviceQueryResult *)ioStd(timereq)->io_Data;
d->DevQueryFormat = 0;
d->SizeAvailable = sizeof(struct NSDeviceQueryResult);
d->DeviceType = NSDEVTYPE_TIMER;
d->DeviceSubType = 0;
d->SupportedCommands = (UWORD *)SupportedCommands;
ioStd(timereq)->io_Actual = sizeof(struct NSDeviceQueryResult);
}
break;
#endif
case TR_GETSYSTIME:
{
struct timeval tv;
GetSysTime(&tv);
timereq->tr_time.tv_secs = tv.tv_secs;
timereq->tr_time.tv_micro = tv.tv_micro;
if (!(timereq->tr_node.io_Flags & IOF_QUICK))
ReplyMsg(&timereq->tr_node.io_Message);
replyit = FALSE; /* Because replyit will clear the timeval */
break;
}
case TR_SETSYSTIME:
Disable();
/* Set current time value */
TimerBase->tb_CurrentTime.tv_secs = timereq->tr_time.tv_secs;
TimerBase->tb_CurrentTime.tv_micro = timereq->tr_time.tv_micro;
/* Update hardware */
EClockSet(TimerBase);
Enable();
replyit = TRUE;
break;
case TR_ADDREQUEST:
switch(unitNum)
{
case UNIT_WAITUNTIL:
Disable();
/* Query the hardware first */
EClockUpdate(TimerBase);
if (CMPTIMEXX32(&TimerBase->tb_CurrentTime, &timereq->tr_time) <= 0)
{
timereq->tr_time.tv_secs = timereq->tr_time.tv_micro = 0;
timereq->tr_node.io_Error = 0;
replyit = TRUE;
}
else
{
#if defined(__AROSEXEC_SMP__)
struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
if (ExecLockBase) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
#endif
/* Ok, we add this to the list */
addToWaitList(&TimerBase->tb_Lists[TL_WAITVBL], timereq, SysBase);
timereq->tr_node.io_Flags &= ~IOF_QUICK;
/*
* If our request was added to the head of the list, we may need to
* readjust our hardware interrupt (reset elapsed time).
* This routine returns TRUE in order to indicate this.
*/
if (TimerBase->tb_Lists[TL_WAITVBL].mlh_Head == (struct MinNode *)timereq)
addedhead = TRUE;
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase) ReleaseLock(TimerBase->tb_ListLock, 0);
#endif
replyit = FALSE;
}
Enable();
break;
case UNIT_VBLANK:
case UNIT_MICROHZ:
{
#if defined(__AROSEXEC_SMP__)
struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
#endif
Disable();
/* Query the hardware first */
EClockUpdate(TimerBase);
/*
* Adjust the time request to be relative to the
* elapsed time counter that we keep.
*/
ADDTIME(&timereq->tr_time, &TimerBase->tb_Elapsed);
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
#endif
/* Slot it into the list. Use unit number as index. */
addToWaitList(&TimerBase->tb_Lists[unitNum], timereq, SysBase);
timereq->tr_node.io_Flags &= ~IOF_QUICK;
/* Indicate if HW need to be reprogrammed */
if (TimerBase->tb_Lists[unitNum].mlh_Head == (struct MinNode *)timereq)
addedhead = TRUE;
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase) ReleaseLock(TimerBase->tb_ListLock, 0);
#endif
Enable();
replyit = FALSE;
break;
}
case UNIT_ECLOCK:
case UNIT_WAITECLOCK:
/* TODO: implement these (backport from m68k-Amiga) */
default:
replyit = FALSE;
timereq->tr_node.io_Error = IOERR_NOCMD;
break;
} /* switch(unitNum) */
break;
case CMD_CLEAR:
case CMD_FLUSH:
case CMD_INVALID:
case CMD_READ:
case CMD_RESET:
case CMD_START:
case CMD_STOP:
case CMD_UPDATE:
case CMD_WRITE:
default:
replyit = TRUE;
timereq->tr_node.io_Error = IOERR_NOCMD;
break;
} /* switch(command) */
if (replyit)
{
timereq->tr_time.tv_secs = 0;
timereq->tr_time.tv_micro = 0;
if (!(timereq->tr_node.io_Flags & IOF_QUICK))
ReplyMsg(&timereq->tr_node.io_Message);
}
return addedhead;
}
void TimerProcessMicroHZ(struct TimerBase *TimerBase, struct ExecBase *SysBase, BOOL locked)
{
#if defined(__AROSEXEC_SMP__)
struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
#endif
struct MinList *unit = &TimerBase->tb_Lists[TL_MICROHZ];
struct timerequest *tr, *next;
/*
* Go through the list and return requests that have completed.
* A completed request is one whose time is less than that of the elapsed time.
*/
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase && !locked) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
#endif
ForeachNodeSafe(unit, tr, next)
{
if (CMPTIMEXX32(&TimerBase->tb_Elapsed, &tr->tr_time) <= 0)
{
/* This request has finished */
REMOVE(tr);
#ifdef USE_VBLANK_EMU
if (tr == &TimerBase->tb_vblank_timerequest)
{
struct IntVector *iv = &SysBase->IntVects[INTB_VERTB];
/* VBlank Emu */
if (iv->iv_Code)
{
AROS_INTC2(iv->iv_Code, iv->iv_Data, INTF_VERTB);
}
/*
* Process VBlank unit.
* The philosophy behind is that only software which needs to measure
* exact intervals uses MICROHZ unit. Others use VBLANK one. As a result,
* VBLANK queue is generally more populated than MICROHZ one.
* VBLANK queue is checked more rarely than MICROHZ, this helps to decrease
* CPU usage.
*/
TimerProcessVBlank(TimerBase, SysBase, TRUE);
/*
* Automatically requeue/reactivate request.
* Feature: get value every time from SysBase. This means
* that the user can change our VBlank rate in runtime by modifying
* this field.
*/
tr->tr_time.tv_secs = 0;
tr->tr_time.tv_micro = 1000000 / SysBase->VBlankFrequency;
ADDTIME(&tr->tr_time, &TimerBase->tb_Elapsed);
addToWaitList(unit, tr, SysBase);
continue;
}
#endif
D(bug("[Timer] Replying msg 0x%p\n", tr));
tr->tr_time.tv_secs = 0;
tr->tr_time.tv_micro = 0;
tr->tr_node.io_Error = 0;
ReplyMsg(&tr->tr_node.io_Message);
}
else
{
/*
The first request hasn't finished, as all requests are in
order, we don't bother searching through the remaining
*/
break;
}
}
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase && !locked) ReleaseLock(TimerBase->tb_ListLock, 0);
#endif
}
void TimerProcessVBlank(struct TimerBase *TimerBase, struct ExecBase *SysBase, BOOL locked)
{
#if defined(__AROSEXEC_SMP__)
struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
#endif
/*
* VBlank handler is the same as above, with two differences:
* 1. We don't check for VBlank emulation request.
* 2. VBlank unit consists of two list, not one. The second list
* is UNIT_WAITUNTIL queue.
* We could use subroutines and save some space, but we prefer speed here.
*/
struct timerequest *tr, *next;
/*
* Go through the "wait for x seconds" list and return requests
* that have completed. A completed request is one whose time
* is less than that of the elapsed time.
*/
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase && !locked) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
#endif
ForeachNodeSafe(&TimerBase->tb_Lists[TL_VBLANK], tr, next)
{
if (CMPTIMEXX32(&TimerBase->tb_Elapsed, &tr->tr_time) <= 0)
{
/* This request has finished */
REMOVE(tr);
tr->tr_time.tv_secs = tr->tr_time.tv_micro = 0;
tr->tr_node.io_Error = 0;
ReplyMsg(&tr->tr_node.io_Message);
}
else
break;
}
/*
* The other this is the "wait until a specified time". Here a request
* is complete if the time we are waiting for is before the current time.
*/
ForeachNodeSafe(&TimerBase->tb_Lists[TL_WAITVBL], tr, next)
{
if (CMPTIMEXX32(&TimerBase->tb_CurrentTime, &tr->tr_time) <= 0)
{
/* This request has finished */
REMOVE(tr);
tr->tr_time.tv_secs = tr->tr_time.tv_micro = 0;
tr->tr_node.io_Error = 0;
ReplyMsg(&tr->tr_node.io_Message);
}
else
break;
}
#if defined(__AROSEXEC_SMP__)
if (ExecLockBase && !locked) ReleaseLock(TimerBase->tb_ListLock, 0);
#endif
}
/****************************************************************************************/
static int Timer_Open(struct TimerBase *LIBBASE, struct timerequest *tr, ULONG unitNum, ULONG flags)
{
/*
* Normally, we should check the length of the message and other
* such things, however the RKM documents an example where the
* length of the timerrequest isn't set, so we must not check
* this.
* This fixes bug SF# 741580
*/
if (unitNum > UNIT_WAITECLOCK)
tr->tr_node.io_Error = IOERR_OPENFAIL;
else
{
tr->tr_node.io_Error = 0;
tr->tr_node.io_Unit = (NULL + unitNum);
tr->tr_node.io_Device = &LIBBASE->tb_Device;
}
return TRUE;
}
ADD2OPENDEV(Timer_Open, 0);