Add support for per-process timers.

This commit is contained in:
Jonas 'Sortie' Termansen 2013-10-13 23:56:58 +02:00
parent 12a8bb91aa
commit df5deac29b
27 changed files with 1473 additions and 127 deletions

View File

@ -350,6 +350,11 @@ sys/socket/socketpair.o \
system.o \
tfork.o \
time.o \
time/timer_create.o \
time/timer_delete.o \
time/timer_getoverrun.o \
time/timer_gettime.o \
time/timer_settime.o \
tmpfile.o \
tmpnam.o \
truncateat.o \

View File

@ -51,7 +51,7 @@ __BEGIN_DECLS
@include(ssize_t.h)
@include(suseconds_t.h)
@include(time_t.h)
/* TODO: timer_t */
@include(timer_t.h)
/* TODO: trace*_t */
@include(uid_t.h)
@include(useconds_t.h)

View File

@ -54,25 +54,18 @@ struct tm
__END_DECLS
#include <sortix/timespec.h>
#include <sortix/itimerspec.h>
__BEGIN_DECLS
/* TODO: This itimer stuff is replaced with another interface IIRC. */
struct itimerspec
{
struct timespec it_interval;
struct timespec it_value;
};
@include(NULL.h)
#define CLOCKS_PER_SEC ((clock_t) 1000000)
__END_DECLS
#include <sortix/clock.h>
#include <sortix/time.h>
__BEGIN_DECLS
#define TIMER_ABSTIME (1<<0)
/* getdate_err is omitted, use strptime */
char* asctime(const struct tm*);
@ -101,7 +94,7 @@ size_t strftime_l(char* __restrict, size_t, const char* __restrict,
char* strptime(const char* __restrict, const char* __restrict,
struct tm* __restrict);
time_t time(time_t*);
int timer_create(clockid_t, struct sigevent* __restrict, time_t* __restrict);
int timer_create(clockid_t, struct sigevent* __restrict, timer_t* __restrict);
int timer_delete(timer_t);
int timer_getoverrun(timer_t);
int timer_gettime(timer_t, struct itimerspec*);

View File

@ -0,0 +1,37 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
time/timer_create.cpp
Creates a per-process timer.
*******************************************************************************/
#include <sys/syscall.h>
#include <signal.h>
#include <time.h>
DEFN_SYSCALL3(int, sys_timer_create, SYSCALL_TIMER_CREATE, clockid_t,
struct sigevent*, timer_t*);
extern "C" int timer_create(clockid_t clockid, struct sigevent* restrict evp,
timer_t* restrict timerid)
{
return sys_timer_create(clockid, evp, timerid);
}

View File

@ -0,0 +1,34 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
time/timer_delete.cpp
Delete a per-process timer.
*******************************************************************************/
#include <sys/syscall.h>
#include <time.h>
DEFN_SYSCALL1(int, sys_timer_delete, SYSCALL_TIMER_DELETE, timer_t);
extern "C" int timer_delete(timer_t timerid)
{
return sys_timer_delete(timerid);
}

View File

@ -0,0 +1,34 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
time/timer_getoverrun.cpp
Retrieve number of missed events on a timer.
*******************************************************************************/
#include <sys/syscall.h>
#include <time.h>
DEFN_SYSCALL1(int, sys_timer_getoverrun, SYSCALL_TIMER_GETOVERRUN, timer_t);
extern "C" int timer_getoverrun(timer_t timerid)
{
return sys_timer_getoverrun(timerid);
}

View File

@ -0,0 +1,35 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
time/timer_gettime.cpp
Get time remaining on a timer.
*******************************************************************************/
#include <sys/syscall.h>
#include <time.h>
DEFN_SYSCALL2(int, sys_timer_gettime, SYSCALL_TIMER_GETTIME, timer_t,
struct itimerspec*);
extern "C" int timer_gettime(timer_t timerid, struct itimerspec* value)
{
return sys_timer_gettime(timerid, value);
}

View File

@ -0,0 +1,37 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
time/timer_settime.cpp
Set time remaining on a timer.
*******************************************************************************/
#include <sys/syscall.h>
#include <time.h>
DEFN_SYSCALL4(int, sys_timer_settime, SYSCALL_TIMER_SETTIME, timer_t, int,
const struct itimerspec*, struct itimerspec*);
extern "C" int timer_settime(timer_t timerid, int flags,
const struct itimerspec* restrict value,
struct itimerspec* restrict ovalue)
{
return sys_timer_settime(timerid, flags, value, ovalue);
}

View File

@ -54,6 +54,7 @@ ifdef X86FAMILY
$(CPU)/thread.o \
$(CPU)/scheduler.o \
$(CPU)/process.o \
x86-family/time.o \
x86-family/msr.o \
x86-family/float.o \
x86-family/x86-family.o
@ -74,6 +75,7 @@ addralloc.o \
ata.o \
bga.o \
calltrace.o \
clock.o \
com.o \
copy.o \
$(CPU)/calltrace.o \
@ -123,7 +125,9 @@ textbuffer.o \
textterminal.o \
thread.o \
time.o \
timer.o \
uart.o \
user-timer.o \
utf8.o \
vga.o \
vgatextbuffer.o \

374
sortix/clock.cpp Normal file
View File

@ -0,0 +1,374 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
clock.cpp
Clock and timer facility.
*******************************************************************************/
#include <assert.h>
#include <timespec.h>
#include <sortix/kernel/platform.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/timer.h>
#include <sortix/kernel/worker.h>
namespace Sortix {
Clock::Clock()
{
delay_timer = NULL;
absolute_timer = NULL;
current_time = timespec_nul();
resolution = timespec_nul();
clock_mutex = KTHREAD_MUTEX_INITIALIZER;
clock_callable_from_interrupt = false;
we_disabled_interrupts = false;
}
Clock::~Clock()
{
// TODO: The best solution would probably be to cancel everything that is
// waiting on us, but that is a bit dangerous since things have to be
// notified carefully that they should not use stale pointers to this
// clock. This is a bunch of work and since the clock is being
// destroyed, you could argue that you shouldn't be using a clock
// whose lifetime you don't control. Therefore assume that all users
// of the clock has stopped using it.
assert(!absolute_timer && !delay_timer);
}
// This clock and timer facility is designed to work even from interrupt
// handlers. For instance, this is needed by the uptime clock that is
// incremented every timer interrupt. If we don't need interrupt handler safety,
// we simply fall back on regular mutual exclusion.
void Clock::SetCallableFromInterrupts(bool callable_from_interrupts)
{
clock_callable_from_interrupt = callable_from_interrupts;
}
void Clock::LockClock()
{
if ( clock_callable_from_interrupt )
{
if ( (we_disabled_interrupts = Interrupt::IsEnabled()) )
Interrupt::Disable();
}
else
kthread_mutex_lock(&clock_mutex);
}
void Clock::UnlockClock()
{
if ( clock_callable_from_interrupt )
{
if ( we_disabled_interrupts )
Interrupt::Enable();
}
else
kthread_mutex_unlock(&clock_mutex);
}
void Clock::Set(struct timespec* now, struct timespec* res)
{
LockClock();
if ( now )
current_time = *now;
if ( res )
resolution = *res;
TriggerAbsolute();
UnlockClock();
}
void Clock::Get(struct timespec* now, struct timespec* res)
{
LockClock();
if ( now )
*now = current_time;
if ( res )
*res = resolution;
UnlockClock();
}
// We maintain two queues of timers; one for timers that sleep for a duration
// and one that that sleeps until a certain point in time. This lets us deal
// nicely with non-monotonic clocks and simplifies the code. The absolute timers
// queue is simply sorted after their wake-up time, while the delay timers queue
// is sorted after their delays, where each node stores the delay between it and
// its previous node (if any, otherwise just the actual time left of the timer).
// This data structure allows constant time detection of whether a timer should
// be fired and the double-linked queue allow constant-time cancellation - this
// is at the expense of linear time insertion, but it is kinda okay since timers
// that are soon will always be at the start (and hence quick to insert), while
// timers in the far future will be last and the calling thread probably
// wouldn't mind a little delay.
// TODO: If locking the clock means disabling interrupts, and a large numbers of
// timers are attached to this clock, then inserting a timer becomes
// expensive as the CPU locks up for a moment. Perhaps this is not as bad
// as it theoretically could be?
void Clock::RegisterAbsolute(Timer* timer) // Lock acquired.
{
assert(!(timer->flags & TIMER_ACTIVE));
timer->flags |= TIMER_ACTIVE;
Timer* before = NULL;
for ( Timer* iter = absolute_timer; iter; iter = before->next_timer )
if ( timespec_lt(timer->value.it_value, iter->value.it_value) )
break;
else
before = iter;
timer->prev_timer = before;
timer->next_timer = before ? before->next_timer : NULL;
if ( timer->next_timer ) timer->next_timer->prev_timer = timer;
(before ? before->next_timer : absolute_timer) = timer;
}
void Clock::RegisterDelay(Timer* timer) // Lock acquired.
{
assert(!(timer->flags & TIMER_ACTIVE));
timer->flags |= TIMER_ACTIVE;
Timer* before = NULL;
struct timespec before_time = timespec_nul();
for ( Timer* iter = delay_timer; iter; iter = before->next_timer )
if ( timespec_lt(timer->value.it_value, iter->value.it_value) )
break;
else
before = iter,
before_time = timespec_add(before_time, iter->value.it_value);
timer->value.it_value = timespec_sub(timer->value.it_value, before_time);
timer->prev_timer = before;
timer->next_timer = before ? before->next_timer : NULL;
if ( timer->next_timer ) timer->next_timer->prev_timer = timer;
(before ? before->next_timer : delay_timer) = timer;
if ( timer->next_timer )
timer->next_timer->value.it_value =
timespec_sub(timer->next_timer->value.it_value, timer->value.it_value);
}
void Clock::Register(Timer* timer)
{
if ( timer->flags & TIMER_ABSOLUTE )
RegisterAbsolute(timer);
else
RegisterDelay(timer);
}
void Clock::UnlinkAbsolute(Timer* timer) // Lock acquired.
{
(timer->prev_timer ? timer->prev_timer->next_timer : absolute_timer) = timer->next_timer;
if ( timer->next_timer ) timer->next_timer->prev_timer = timer->prev_timer;
timer->prev_timer = timer->next_timer = NULL;
timer->flags &= ~TIMER_ACTIVE;
}
void Clock::UnlinkDelay(Timer* timer) // Lock acquired.
{
(timer->prev_timer ? timer->prev_timer->next_timer : delay_timer) = timer->next_timer;
if ( timer->next_timer ) timer->next_timer->prev_timer = timer->prev_timer;
if ( timer->next_timer ) timer->next_timer->value.it_value = timespec_add(timer->next_timer->value.it_value, timer->value.it_value);
timer->prev_timer = timer->next_timer = NULL;
timer->flags &= ~TIMER_ACTIVE;
}
void Clock::Unlink(Timer* timer) // Lock acquired.
{
if ( timer->flags & TIMER_ACTIVE )
{
if ( timer->flags & TIMER_ABSOLUTE )
UnlinkAbsolute(timer);
else
UnlinkDelay(timer);
}
}
void Clock::Cancel(Timer* timer)
{
LockClock();
Unlink(timer);
while ( timer->flags & TIMER_FIRING )
{
UnlockClock();
// TODO: This busy-loop is rather inefficient. We could set up some
// condition variable and wait on it. However, if the lock is
// turning interrupts off, then there is no mutex we can use.
kthread_yield();
LockClock();
}
UnlockClock();
}
void Clock::Advance(struct timespec duration)
{
LockClock();
current_time = timespec_add(current_time, duration);
TriggerDelay(duration);
TriggerAbsolute();
UnlockClock();
}
// Fire timers that wait for a certain amount of time.
void Clock::TriggerDelay(struct timespec unaccounted) // Lock acquired.
{
while ( Timer* timer = delay_timer )
{
if ( timespec_lt(unaccounted, timer->value.it_value) )
{
timer->value.it_value = timespec_sub(timer->value.it_value, unaccounted);
break;
}
unaccounted = timespec_sub(unaccounted, timer->value.it_value);
timer->value.it_value = timespec_nul();
if ( (delay_timer = delay_timer->next_timer) )
delay_timer->prev_timer = NULL;
FireTimer(timer);
}
}
// Fire timers that wait until a certain point in time.
void Clock::TriggerAbsolute() // Lock acquired.
{
while ( Timer* timer = absolute_timer )
{
if ( timespec_lt(timer->value.it_value, current_time) )
break;
if ( (absolute_timer = absolute_timer->next_timer) )
absolute_timer->prev_timer = NULL;
FireTimer(timer);
}
}
static void Clock__DoFireTimer(Timer* timer)
{
timer->callback(timer->clock, timer, timer->user);
}
static void Clock__FireTimer(void* timer_ptr)
{
Timer* timer = (Timer*) timer_ptr;
assert(timer->clock);
// Combine all the additionally pending events into a single one and notify
// the caller of all the events that he missed because we couldn't call him
// fast enough.
timer->clock->LockClock();
timer->num_overrun_events = timer->num_firings_scheduled;
timer->num_firings_scheduled = 0;
timer->clock->UnlockClock();
Clock__DoFireTimer(timer);
// If additional events happened during the time of the event handler, we'll
// have to handle them because the firing bit is set. We'll schedule another
// worker thread job and resume there, so this worker thread can continue to
// do other important stuff.
timer->clock->LockClock();
if ( timer->num_firings_scheduled )
Worker::Schedule(Clock__FireTimer, timer_ptr);
// If this was the last event, we'll clear the firing bit and the advance
// thread now has the responsibility of creating worker thread jobs.
else
timer->flags &= ~TIMER_FIRING;
timer->clock->UnlockClock();
}
static void Clock__FireTimer_InterruptWorker(void* timer_ptr_ptr, size_t)
{
void* timer_ptr = *((void**) timer_ptr_ptr);
Clock__FireTimer(timer_ptr);
}
void Clock::FireTimer(Timer* timer)
{
timer->flags &= ~TIMER_ACTIVE;
// If the CPU is currently interrupted, we call the timer callback directly
// only if it is known to work when the interrupts are disabled on this CPU.
// Otherwise, we forward the timer pointer to a special interrupt-safe
// worker thread that'll run the callback normally.
if ( !Interrupt::IsEnabled() )
{
if ( timer->flags & TIMER_FUNC_INTERRUPT_HANDLER )
Clock__DoFireTimer(timer);
else if ( timer->flags & TIMER_FIRING )
timer->num_firings_scheduled++;
else
{
timer->flags |= TIMER_FIRING;
Interrupt::ScheduleWork(Clock__FireTimer_InterruptWorker, &timer,
sizeof(timer));
}
}
// Normally, we will run the timer callback in a worker thread, but as an
// optimization, if the callback is known to be short and simple and safely
// handles this situation, we'll simply call it from the current thread.
else
{
if ( timer->flags & TIMER_FUNC_ADVANCE_THREAD )
Clock__DoFireTimer(timer);
else if ( timer->flags & TIMER_FIRING )
timer->num_firings_scheduled++;
else
{
timer->flags |= TIMER_FIRING;
Worker::Schedule(Clock__FireTimer, timer);
}
}
// Rearm the timer only if it is periodic.
if ( timespec_le(timer->value.it_interval, timespec_nul()) )
return;
// TODO: If the period is too short (such a single nanosecond) on a delay
// timer, then it will try to spend each nanosecond avanced carefully
// and reliably schedule a shitload of firings. Not only that, but it
// will also loop this function many million timers per tick!
// TODO: Throtte the timer if firing while the callback is still running!
// TODO: Doesn't reload properly for absolute timers!
if ( timer->flags & TIMER_ABSOLUTE )
timer->value.it_value = timespec_add(timer->value.it_value, timer->value.it_interval);
else
timer->value.it_value = timer->value.it_interval;
Register(timer);
}
} // namespace Sortix

View File

@ -54,7 +54,7 @@ typedef long int __ssize_t;
#else
typedef int __ssize_t;
#endif
typedef void* __timer_t;
typedef __uintptr_t __timer_t;
typedef __SIZE_TYPE__ __socklen_t;
#if defined(SORTIX_KERNEL) || defined(LIBC_LIBRARY)

View File

@ -0,0 +1,40 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/itimerspec.h
Declaration of the itimerspec structure.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_ITIMERSPEC_H
#define INCLUDE_SORTIX_ITIMERSPEC_H
#include <features.h>
__BEGIN_DECLS
struct itimerspec
{
struct timespec it_interval;
struct timespec it_value;
};
__END_DECLS
#endif

View File

@ -0,0 +1,80 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/kernel/clock.h
A virtual clock that can be measured and waited upon.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_KERNEL_CLOCK_H
#define INCLUDE_SORTIX_KERNEL_CLOCK_H
#include <sys/types.h>
#include <sortix/timespec.h>
#include <sortix/kernel/kthread.h>
namespace Sortix {
class Clock;
class Timer;
class Clock
{
public:
Clock();
~Clock();
public:
Timer* delay_timer;
Timer* absolute_timer;
struct timespec current_time;
struct timespec resolution;
kthread_mutex_t clock_mutex;
bool clock_callable_from_interrupt;
bool we_disabled_interrupts;
public:
void SetCallableFromInterrupts(bool callable_from_interrupts);
void Set(struct timespec* now, struct timespec* res);
void Get(struct timespec* now, struct timespec* res);
void Advance(struct timespec duration);
void Register(Timer* timer);
void Unlink(Timer* timer);
void Cancel(Timer* timer);
void LockClock();
void UnlockClock();
public: // These should only be called if the clock is locked.
void RegisterAbsolute(Timer* timer);
void RegisterDelay(Timer* timer);
void UnlinkAbsolute(Timer* timer);
void UnlinkDelay(Timer* timer);
private: // These should only be called if the clock is locked.
void FireTimer(Timer* timer);
void TriggerDelay(struct timespec unaccounted);
void TriggerAbsolute();
};
} // namespace Sortix
#endif

View File

@ -33,13 +33,18 @@
#include <sortix/timespec.h>
namespace Sortix {
class Clock;
} // namespace Sortix
namespace Sortix {
namespace Time {
void Init();
struct timespec Get(clockid_t clock, struct timespec* res = NULL);
bool TryGet(clockid_t clock, struct timespec* time, struct timespec* res = NULL);
void Start();
void OnTick(struct timespec tick_period);
struct timespec Get(clockid_t clock);
Clock* GetClock(clockid_t clock);
} // namespace Time
} // namespace Sortix

View File

@ -0,0 +1,79 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/kernel/timer.h
A virtual timer that triggers an action in a worker thread when triggered.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_KERNEL_TIMER_H
#define INCLUDE_SORTIX_KERNEL_TIMER_H
#include <sortix/timespec.h>
#include <sortix/itimerspec.h>
#include <sortix/kernel/kthread.h>
namespace Sortix {
class Clock;
class Timer;
const int TIMER_ABSOLUTE = 1 << 0;
const int TIMER_ACTIVE = 1 << 1;
const int TIMER_FIRING = 1 << 2;
const int TIMER_FUNC_INTERRUPT_HANDLER = 1 << 3;
const int TIMER_FUNC_ADVANCE_THREAD = 1 << 4;
class Timer
{
public:
Timer();
~Timer();
public:
struct itimerspec value;
Clock* clock;
Timer* prev_timer;
Timer* next_timer;
void (*callback)(Clock* clock, Timer* timer, void* user);
void* user;
size_t num_firings_scheduled;
size_t num_overrun_events;
int flags;
private:
void Fire();
void GetInternal(struct itimerspec* current);
public:
void Attach(Clock* the_clock);
void Detach();
bool IsAttached() const { return clock; }
void Cancel();
Clock* GetClock() const { return clock; }
void Get(struct itimerspec* current);
void Set(struct itimerspec* value, struct itimerspec* ovalue, int flags,
void (*callback)(Clock*, Timer*, void*), void* user);
};
} // namespace Sortix
#endif

View File

@ -0,0 +1,50 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/kernel/user-timer.h
Timer facility provided to user-space.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_KERNEL_USER_TIMER_H
#define INCLUDE_SORTIX_KERNEL_USER_TIMER_H
#include <sortix/sigevent.h>
#include <sortix/kernel/timer.h>
namespace Sortix {
class Process;
struct UserTimer
{
Timer timer;
struct sigevent event;
Process* process;
timer_t timerid;
public:
static void Init();
};
} // namespace Sortix
#endif

View File

@ -0,0 +1,54 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/sigevent.h
Declares the sigevent structure.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_SIGEVENT_H
#define INCLUDE_SORTIX_SIGEVENT_H
#include <features.h>
__BEGIN_DECLS
#define SIGEV_NONE 0
#define SIGEV_SIGNAL 1
#define SIGEV_THREAD 2
union sigval
{
int sival_int;
void* sival_ptr;
};
struct sigevent
{
int sigev_notify;
int sigev_signo;
union sigval sigev_value;
void (*sigev_notify_function)(union sigval);
/*pthread_attr_t* sigev_notify_attributes;*/
void* sigev_notify_attributes;
};
__END_DECLS
#endif

View File

@ -118,6 +118,11 @@
#define SYSCALL_WRITEV 94
#define SYSCALL_PREADV 95
#define SYSCALL_PWRITEV 96
#define SYSCALL_MAX_NUM 97 /* index of highest constant + 1 */
#define SYSCALL_TIMER_CREATE 97
#define SYSCALL_TIMER_DELETE 98
#define SYSCALL_TIMER_GETOVERRUN 99
#define SYSCALL_TIMER_GETTIME 100
#define SYSCALL_TIMER_SETTIME 101
#define SYSCALL_MAX_NUM 102 /* index of highest constant + 1 */
#endif

View File

@ -0,0 +1,36 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
sortix/time.h
Declarations for the kernel time interfaces.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_TIME_H
#define INCLUDE_SORTIX_TIME_H
#include <features.h>
__BEGIN_DECLS
#define TIMER_ABSTIME (1<<0)
__END_DECLS
#endif

View File

@ -47,6 +47,7 @@
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/fcache.h>
#include <sortix/kernel/string.h>
#include <sortix/kernel/user-timer.h>
#include <sortix/fcntl.h>
#include <sortix/stat.h>
@ -247,6 +248,9 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo)
// Stage 2. Transition to Multithreaded Environment
//
// Initialize the clocks.
Time::Init();
// Initialize the process system.
Process::Init();
@ -296,7 +300,7 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo)
Float::Init();
// The time driver will run the scheduler on the next timer interrupt.
Time::Init();
Time::Start();
// Become the system idle thread.
SystemIdleThread(NULL);
@ -422,6 +426,9 @@ static void BootThread(void* /*user*/)
// Initialize poll system call.
Poll::Init();
// Initialize per-process timers.
UserTimer::Init();
// Initialize the kernel information query syscall.
Info::Init();

View File

@ -123,6 +123,7 @@ namespace Sortix
threadlock = KTHREAD_MUTEX_INITIALIZER;
ptrlock = KTHREAD_MUTEX_INITIALIZER;
idlock = KTHREAD_MUTEX_INITIALIZER;
user_timers_lock = KTHREAD_MUTEX_INITIALIZER;
mmapfrom = 0x80000000UL;
exitstatus = -1;
pid = AllocatePID();
@ -234,6 +235,16 @@ namespace Sortix
// This can't be called if the process is still alive.
assert(!firstthread);
// Disarm and detach all the timers in the process.
for ( timer_t i = 0; i < PROCESS_TIMER_NUM_MAX; i++ )
{
if ( user_timers[i].timer.IsAttached() )
{
user_timers[i].timer.Cancel();
user_timers[i].timer.Detach();
}
}
// We need to temporarily reload the correct addrese space of the dying
// process such that we can unmap and free its memory.
addr_t prevaddrspace = curthread->SwitchAddressSpace(addrspace);

View File

@ -25,10 +25,18 @@
#ifndef SORTIX_PROCESS_H
#define SORTIX_PROCESS_H
#include "cpu.h"
#include <sortix/fork.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#include <sortix/fork.h>
#include <sortix/kernel/time.h>
#include <sortix/kernel/timer.h>
#include <sortix/kernel/user-timer.h>
#include "cpu.h"
#define PROCESS_TIMER_NUM_MAX 32
namespace Sortix
{
@ -38,6 +46,7 @@ namespace Sortix
class DescriptorTable;
class MountTable;
struct ProcessSegment;
struct ProcessTimer;
struct ioctx_struct;
typedef struct ioctx_struct ioctx_t;
@ -133,6 +142,10 @@ namespace Sortix
public:
ProcessSegment* segments;
public:
kthread_mutex_t user_timers_lock;
UserTimer user_timers[PROCESS_TIMER_NUM_MAX];
public:
int Execute(const char* programname, const uint8_t* program,
size_t programsize, int argc, const char* const* argv,

View File

@ -1,6 +1,6 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013.
This file is part of Sortix.

View File

@ -29,13 +29,12 @@
#include <timespec.h>
#include <sortix/clock.h>
#include <sortix/timespec.h>
#include <sortix/kernel/platform.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/time.h>
#include <sortix/kernel/scheduler.h>
@ -46,130 +45,51 @@
#include "serialterminal.h"
#endif
#include "cpu.h"
#if !defined(PLATFORM_X86_FAMILY)
#error Provide a time implementation for this CPU.
#endif
namespace Sortix {
namespace Time {
struct timespec since_boot;
struct timespec since_boot_period;
uint16_t since_boot_divisor;
void CPUInit();
static uint16_t DivisorOfFrequency(long frequency)
{
// The value we send to the PIT is the value to divide it's input clock
// (1193180 Hz) by, to get our required frequency. Note that the divisor
// must be small enough to fit into 16 bits.
return 1193180 / frequency;
}
Clock* realtime_clock;
Clock* uptime_clock;
static long FrequencyOfDivisor(uint16_t divisor)
{
return 1193180 / divisor;
}
static long RealFrequencyOfFrequency(long frequency)
{
return FrequencyOfDivisor(DivisorOfFrequency(frequency));
}
static struct timespec PeriodOfFrequency(long frequency)
{
long period_ns = 1000000000L / frequency;
return timespec_make(0, period_ns);
}
static void RequestIRQ0(uint16_t divisor)
{
CPU::OutPortB(0x43, 0x36);
CPU::OutPortB(0x40, divisor >> 0 & 0xFF);
CPU::OutPortB(0x40, divisor >> 8 & 0xFF);
}
static void InitializeTimer(long frequency)
{
long real_frequence = RealFrequencyOfFrequency(frequency);
since_boot_divisor = DivisorOfFrequency(frequency);
since_boot = timespec_nul();
since_boot_period = PeriodOfFrequency(real_frequence);
RequestIRQ0(since_boot_divisor);
}
static void OnIRQ0(CPU::InterruptRegisters* regs, void* /*user*/)
{
since_boot = timespec_add(since_boot, since_boot_period);
Scheduler::Switch(regs);
// TODO: There is a horrible bug that causes Sortix to only receive
// one IRQ0 on my laptop, but it works in virtual machines. But
// re-requesting an addtional time seems to work. Hacky and ugly.
static bool did_ugly_irq0_hack = false;
if ( !did_ugly_irq0_hack )
{
RequestIRQ0(since_boot_divisor);
did_ugly_irq0_hack = true;
}
}
bool TryGet(clockid_t clock, struct timespec* time, struct timespec* res)
Clock* GetClock(clockid_t clock)
{
switch ( clock )
{
case CLOCK_REALTIME:
return errno = ENOTSUP, false;
case CLOCK_MONOTONIC:
case CLOCK_BOOT:
case CLOCK_INIT:
// TODO: Is is possible to implement the below as atomic operations?
Interrupt::Disable();
if ( time )
*time = since_boot;
if ( res )
*res = since_boot_period;
Interrupt::Enable();
return true;
default:
return errno = EINVAL, false;
case CLOCK_REALTIME: return realtime_clock;
case CLOCK_BOOT: return uptime_clock;
case CLOCK_INIT: return uptime_clock;
case CLOCK_MONOTONIC: return uptime_clock;
default: return errno = ENOTSUP, (Clock*) NULL;
}
}
struct timespec Get(clockid_t clock, struct timespec* res)
struct timespec Get(clockid_t clockid)
{
struct timespec ret;
if ( !TryGet(clock, &ret, res) )
return timespec_nul();
return ret;
Clock* clock = GetClock(clockid);
if ( clock )
{
struct timespec now;
clock->Get(&now, NULL);
return now;
}
return timespec_nul();
}
uintmax_t MicrosecondsOfTimespec(struct timespec time)
void OnTick(struct timespec tick_period)
{
uintmax_t seconds = time.tv_sec;
uintmax_t nano_seconds = time.tv_nsec;
return seconds * 1000000 + nano_seconds / 1000;
}
// TODO: This system call is for compatibility. Provide user-space with better
// facilities such as sys_clock_gettime.
static int sys_uptime(uintmax_t* usecssinceboot)
{
struct timespec now;
if ( !TryGet(CLOCK_BOOT, &now, NULL) )
return -1;
uintmax_t ret = MicrosecondsOfTimespec(now);
if ( !CopyToUser(usecssinceboot, &ret, sizeof(ret)) )
return -1;
return 0;
realtime_clock->Advance(tick_period);
uptime_clock->Advance(tick_period);
}
void Init()
{
Interrupt::RegisterHandler(Interrupt::IRQ0, &OnIRQ0, NULL);
Syscall::Register(SYSCALL_UPTIME, (void*) sys_uptime);
InitializeTimer(100/*Hz*/);
if ( !(realtime_clock = new Clock()) )
Panic("Unable to allocate realtime clock");
if ( !(uptime_clock = new Clock()) )
Panic("Unable to allocate uptime clock");
CPUInit();
}
} // namespace Time

134
sortix/timer.cpp Normal file
View File

@ -0,0 +1,134 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
timer.cpp
Clock and timer facility.
*******************************************************************************/
#include <assert.h>
#include <timespec.h>
#include <sortix/kernel/platform.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/timer.h>
namespace Sortix {
// The caller should protect the timer using a lock if multiple threads can
// access it.
Timer::Timer()
{
value = { timespec_nul(), timespec_nul() };
clock = NULL;
prev_timer = NULL;
next_timer = NULL;
callback = NULL;
user = NULL;
num_firings_scheduled = 0;
num_overrun_events = 0;
flags = 0;
}
Timer::~Timer()
{
// TODO: Is this the right thing?
// The user of this object should cancel all pending triggers before calling
// the destructor. We could try to cancel the timer, but if we fail the race
// the handler function will be called, and that function may access the
// timer object. We'd then have to wait for the function to finish and then
// continue the destuction. This is a bit complex and fragile, so we'll just
// make it the rule that all outstanding requests must be cancelled before
// our destruction. The caller should know better than we how to cancel.
assert(!(flags & TIMER_ACTIVE));
}
void Timer::Attach(Clock* the_clock)
{
assert(!clock);
assert(the_clock);
clock = the_clock;
}
void Timer::Detach()
{
assert(!(flags & TIMER_ACTIVE));
assert(clock);
clock = NULL;
}
void Timer::Cancel()
{
if ( clock )
clock->Cancel(this);
}
void Timer::GetInternal(struct itimerspec* current)
{
if ( !(this->flags & TIMER_ACTIVE ) )
current->it_value = timespec_nul(),
current->it_interval = timespec_nul();
else if ( flags & TIMER_ABSOLUTE )
current->it_value = timespec_sub(value.it_value, clock->current_time),
current->it_interval = value.it_interval;
else
*current = value;
}
void Timer::Get(struct itimerspec* current)
{
assert(clock);
clock->LockClock();
GetInternal(current);
clock->UnlockClock();
}
void Timer::Set(struct itimerspec* value, struct itimerspec* ovalue, int flags,
void (*callback)(Clock*, Timer*, void*), void* user)
{
assert(clock);
clock->LockClock();
// Dequeue this timer if it is already armed.
if ( flags & TIMER_ACTIVE )
clock->Unlink(this);
// Let the caller know how much time was left on the timer.
if ( ovalue )
GetInternal(ovalue);
// Arm the timer if a value was specified.
if ( timespec_lt(timespec_nul(), value->it_value) )
{
this->value = *value;
this->flags = flags;
this->callback = callback;
this->user = user;
clock->Register(this);
}
clock->UnlockClock();
}
} // namespace Sortix

237
sortix/user-timer.cpp Normal file
View File

@ -0,0 +1,237 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
user-timer.cpp
Timers that send signals when triggered.
*******************************************************************************/
#include <sys/types.h>
#include <errno.h>
#include <timespec.h>
#include <sortix/clock.h>
#include <sortix/signal.h>
#include <sortix/sigevent.h>
#include <sortix/time.h>
#include <sortix/kernel/platform.h>
#include <sortix/kernel/copy.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/time.h>
#include <sortix/kernel/syscall.h>
#include <sortix/kernel/user-timer.h>
#include "process.h"
// TODO: Memset all user timers in process constructor.
namespace Sortix {
// TODO: We also need to fetch the pthread attr if there is one.
static bool FetchSigevent(struct sigevent* dst, const struct sigevent* src)
{
if ( src )
return CopyFromUser(dst, src, sizeof(struct sigevent));
dst->sigev_notify = SIGEV_SIGNAL;
dst->sigev_signo = SIGALRM;
// TODO: "and the sigev_value member having the value of the timer ID."
// - Does POSIX want the caller to be psychic and should we write back the
// final default sigevent?
return true;
}
static UserTimer* LookupUserTimer(Process* process, timer_t timerid)
{
if ( PROCESS_TIMER_NUM_MAX <= timerid )
return errno = EINVAL, (UserTimer*) NULL;
UserTimer* user_timer = &process->user_timers[timerid];
if ( !user_timer->timer.IsAttached() )
return errno = EINVAL, (UserTimer*) NULL;
return user_timer;
}
static Timer* LookupTimer(Process* process, timer_t timerid)
{
UserTimer* user_timer = LookupUserTimer(process, timerid);
return user_timer ? &user_timer->timer : (Timer*) NULL;
}
static int sys_timer_create(clockid_t clockid, struct sigevent* sigevp,
timer_t* timerid_ptr)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->user_timers_lock);
Clock* clock = Time::GetClock(clockid);
if ( !clock )
return -1;
struct sigevent sigev;
if ( !FetchSigevent(&sigev, sigevp) )
return -1;
// Allocate a timer for this request.
timer_t timerid;
for ( timerid = 0; timerid < PROCESS_TIMER_NUM_MAX; timerid++ )
{
Timer* timer = &process->user_timers[timerid].timer;
if ( timer->IsAttached() )
continue;
timer->Attach(clock);
break;
}
if ( PROCESS_TIMER_NUM_MAX <= timerid )
return -1;
if ( !CopyToUser(timerid_ptr, &timerid, sizeof(timerid)) )
{
process->user_timers[timerid].timer.Detach();
return -1;
}
process->user_timers[timerid].process = process;
process->user_timers[timerid].event = sigev;
process->user_timers[timerid].timerid = timerid;
return 0;
}
static int sys_timer_delete(timer_t timerid)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->user_timers_lock);
Timer* timer = LookupTimer(process, timerid);
if ( !timer )
return -1;
timer->Cancel();
timer->Detach();
return 0;
}
static int sys_timer_getoverrun(timer_t timerid)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->user_timers_lock);
Timer* timer = LookupTimer(process, timerid);
if ( !timer )
return -1;
// TODO: This is not fully kept track of yet.
return 0;
}
static int sys_timer_gettime(timer_t timerid, struct itimerspec* user_value)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->user_timers_lock);
Timer* timer = LookupTimer(process, timerid);
if ( !timer )
return -1;
struct itimerspec value;
timer->Get(&value);
return CopyToUser(user_value, &value, sizeof(value)) ? 0 : -1;
}
static void timer_callback(Clock* /*clock*/, Timer* timer, void* user)
{
UserTimer* user_timer = (UserTimer*) user;
Process* process = user_timer->process;
ScopedLock lock(&process->user_timers_lock);
size_t current_overrun = timer->num_overrun_events;
// TODO: This delivery facility is insufficient! sigevent is much more
// powerful than sending a simple old-school signal.
// TODO: If the last signal from last time is still being processed, we need
// to handle the sum of overrun. I'm not sure how to handle overrun
// properly, so we'll just pretend to user-space it never happens when
// it does and we do some of the bookkeeping.
(void) current_overrun;
process->DeliverSignal(user_timer->event.sigev_signo);
}
static int sys_timer_settime(timer_t timerid, int flags,
const struct itimerspec* user_value,
struct itimerspec* user_ovalue)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->user_timers_lock);
UserTimer* user_timer = LookupUserTimer(process, timerid);
if ( !user_timer )
return -1;
Timer* timer = &user_timer->timer;
struct itimerspec value, ovalue;
if ( !CopyFromUser(&value, user_value, sizeof(value)) )
return -1;
if ( timespec_lt(value.it_value, timespec_nul()) ||
timespec_lt(value.it_interval, timespec_nul()) ||
(flags & ~(TIMER_ABSTIME)) != 0 )
return errno = EINVAL, -1;
int timer_flags = 0;
if ( flags & TIMER_ABSTIME )
timer_flags |= TIMER_ABSOLUTE;
timer->Set(&value, &ovalue, timer_flags, timer_callback, user_timer);
// Let the caller know how much time was left on the timer.
if ( user_ovalue && !CopyToUser(user_ovalue, &ovalue, sizeof(ovalue)) )
return -1;
return 0;
}
static int sys_uptime(uintmax_t* usecssinceboot)
{
struct timespec now;
Clock* clock = Time::GetClock(CLOCK_BOOT);
clock->Get(&now, NULL);
uintmax_t seconds = now.tv_sec;
uintmax_t nano_seconds = now.tv_nsec;
uintmax_t ret = seconds * 1000000 + nano_seconds / 1000;
return CopyToUser(usecssinceboot, &ret, sizeof(ret)) ? 0 : -1;
}
void UserTimer::Init()
{
Syscall::Register(SYSCALL_TIMER_CREATE, (void*) sys_timer_create);
Syscall::Register(SYSCALL_TIMER_DELETE, (void*) sys_timer_delete);
Syscall::Register(SYSCALL_TIMER_GETOVERRUN, (void*) sys_timer_getoverrun);
Syscall::Register(SYSCALL_TIMER_GETTIME, (void*) sys_timer_gettime);
Syscall::Register(SYSCALL_TIMER_SETTIME, (void*) sys_timer_settime);
Syscall::Register(SYSCALL_UPTIME, (void*) sys_uptime);
}
} // namespace Sortix

122
sortix/x86-family/time.cpp Normal file
View File

@ -0,0 +1,122 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
x86-family/time.cpp
Retrieving the current time.
*******************************************************************************/
#include <sys/types.h>
#include <timespec.h>
#include <sortix/timespec.h>
#include <sortix/kernel/platform.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/scheduler.h>
#include <sortix/kernel/time.h>
#include "../cpu.h"
namespace Sortix {
namespace Time {
static uint16_t DivisorOfFrequency(long frequency)
{
// The value we send to the PIT is the value to divide it's input clock
// (1193180 Hz) by, to get our required frequency. Note that the divisor
// must be small enough to fit into 16 bits.
return 1193180 / frequency;
}
static long FrequencyOfDivisor(uint16_t divisor)
{
return 1193180 / divisor;
}
static long RealFrequencyOfFrequency(long frequency)
{
return FrequencyOfDivisor(DivisorOfFrequency(frequency));
}
static struct timespec PeriodOfFrequency(long frequency)
{
long period_ns = 1000000000L / frequency;
return timespec_make(0, period_ns);
}
static void RequestIRQ0(uint16_t divisor)
{
CPU::OutPortB(0x43, 0x36);
CPU::OutPortB(0x40, divisor >> 0 & 0xFF);
CPU::OutPortB(0x40, divisor >> 8 & 0xFF);
}
extern Clock* realtime_clock;
extern Clock* uptime_clock;
static struct timespec tick_period;
static long tick_frequency;
static uint16_t tick_divisor;
static void OnIRQ0(CPU::InterruptRegisters* regs, void* /*user*/)
{
OnTick(tick_period);
Scheduler::Switch(regs);
// TODO: There is a horrible bug that causes Sortix to only receive
// one IRQ0 on my laptop, but it works in virtual machines. But
// re-requesting an addtional time seems to work. Hacky and ugly.
// TODO: Confirm whether this still happens and whether it is trigged by
// another bug in my system.
static bool did_ugly_irq0_hack = false;
if ( !did_ugly_irq0_hack )
RequestIRQ0(tick_divisor),
did_ugly_irq0_hack = true;
}
void CPUInit()
{
// Estimate the rate that interrupts will be coming at.
long desired_frequency = 100/*Hz*/;
tick_frequency = RealFrequencyOfFrequency(desired_frequency);
tick_divisor = DivisorOfFrequency(tick_frequency);
tick_period = PeriodOfFrequency(tick_frequency);
// Initialize the clocks on this system.
realtime_clock->SetCallableFromInterrupts(true);
uptime_clock->SetCallableFromInterrupts(true);
struct timespec nul_time = timespec_nul();
realtime_clock->Set(&nul_time, &tick_period);
uptime_clock->Set(&nul_time, &tick_period);
}
void Start()
{
// Handle timer interrupts if they arrive.
Interrupt::RegisterHandler(Interrupt::IRQ0, &OnIRQ0, NULL);
// Request a timer interrupt now that we can handle them safely.
RequestIRQ0(tick_divisor);
}
} // namespace Time
} // namespace Sortix