From df5deac29bb0bb2734f54fdcbd37d14189d65d76 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sun, 13 Oct 2013 23:56:58 +0200 Subject: [PATCH] Add support for per-process timers. --- libc/Makefile | 5 + libc/include/sys/types.h | 2 +- libc/include/time.h | 13 +- libc/time/timer_create.cpp | 37 +++ libc/time/timer_delete.cpp | 34 ++ libc/time/timer_getoverrun.cpp | 34 ++ libc/time/timer_gettime.cpp | 35 ++ libc/time/timer_settime.cpp | 37 +++ sortix/Makefile | 4 + sortix/clock.cpp | 374 ++++++++++++++++++++++ sortix/include/sortix/__/types.h | 2 +- sortix/include/sortix/itimerspec.h | 40 +++ sortix/include/sortix/kernel/clock.h | 80 +++++ sortix/include/sortix/kernel/time.h | 11 +- sortix/include/sortix/kernel/timer.h | 79 +++++ sortix/include/sortix/kernel/user-timer.h | 50 +++ sortix/include/sortix/sigevent.h | 54 ++++ sortix/include/sortix/syscallnum.h | 7 +- sortix/include/sortix/time.h | 36 +++ sortix/kernel.cpp | 9 +- sortix/process.cpp | 11 + sortix/process.h | 17 +- sortix/thread.h | 2 +- sortix/time.cpp | 134 ++------ sortix/timer.cpp | 134 ++++++++ sortix/user-timer.cpp | 237 ++++++++++++++ sortix/x86-family/time.cpp | 122 +++++++ 27 files changed, 1473 insertions(+), 127 deletions(-) create mode 100644 libc/time/timer_create.cpp create mode 100644 libc/time/timer_delete.cpp create mode 100644 libc/time/timer_getoverrun.cpp create mode 100644 libc/time/timer_gettime.cpp create mode 100644 libc/time/timer_settime.cpp create mode 100644 sortix/clock.cpp create mode 100644 sortix/include/sortix/itimerspec.h create mode 100644 sortix/include/sortix/kernel/clock.h create mode 100644 sortix/include/sortix/kernel/timer.h create mode 100644 sortix/include/sortix/kernel/user-timer.h create mode 100644 sortix/include/sortix/sigevent.h create mode 100644 sortix/include/sortix/time.h create mode 100644 sortix/timer.cpp create mode 100644 sortix/user-timer.cpp create mode 100644 sortix/x86-family/time.cpp diff --git a/libc/Makefile b/libc/Makefile index 84f2e49c..ab6573e1 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -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 \ diff --git a/libc/include/sys/types.h b/libc/include/sys/types.h index 16983ea1..948189df 100644 --- a/libc/include/sys/types.h +++ b/libc/include/sys/types.h @@ -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) diff --git a/libc/include/time.h b/libc/include/time.h index 7c77158d..7abdedda 100644 --- a/libc/include/time.h +++ b/libc/include/time.h @@ -54,25 +54,18 @@ struct tm __END_DECLS #include +#include __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 +#include __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*); diff --git a/libc/time/timer_create.cpp b/libc/time/timer_create.cpp new file mode 100644 index 00000000..ca8f87ad --- /dev/null +++ b/libc/time/timer_create.cpp @@ -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 . + + time/timer_create.cpp + Creates a per-process timer. + +*******************************************************************************/ + +#include + +#include +#include + +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); +} diff --git a/libc/time/timer_delete.cpp b/libc/time/timer_delete.cpp new file mode 100644 index 00000000..6c675d28 --- /dev/null +++ b/libc/time/timer_delete.cpp @@ -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 . + + time/timer_delete.cpp + Delete a per-process timer. + +*******************************************************************************/ + +#include + +#include + +DEFN_SYSCALL1(int, sys_timer_delete, SYSCALL_TIMER_DELETE, timer_t); + +extern "C" int timer_delete(timer_t timerid) +{ + return sys_timer_delete(timerid); +} diff --git a/libc/time/timer_getoverrun.cpp b/libc/time/timer_getoverrun.cpp new file mode 100644 index 00000000..d030526d --- /dev/null +++ b/libc/time/timer_getoverrun.cpp @@ -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 . + + time/timer_getoverrun.cpp + Retrieve number of missed events on a timer. + +*******************************************************************************/ + +#include + +#include + +DEFN_SYSCALL1(int, sys_timer_getoverrun, SYSCALL_TIMER_GETOVERRUN, timer_t); + +extern "C" int timer_getoverrun(timer_t timerid) +{ + return sys_timer_getoverrun(timerid); +} diff --git a/libc/time/timer_gettime.cpp b/libc/time/timer_gettime.cpp new file mode 100644 index 00000000..144f22e4 --- /dev/null +++ b/libc/time/timer_gettime.cpp @@ -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 . + + time/timer_gettime.cpp + Get time remaining on a timer. + +*******************************************************************************/ + +#include + +#include + +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); +} diff --git a/libc/time/timer_settime.cpp b/libc/time/timer_settime.cpp new file mode 100644 index 00000000..2aec7db1 --- /dev/null +++ b/libc/time/timer_settime.cpp @@ -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 . + + time/timer_settime.cpp + Set time remaining on a timer. + +*******************************************************************************/ + +#include + +#include + +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); +} diff --git a/sortix/Makefile b/sortix/Makefile index aefa4801..5c829627 100644 --- a/sortix/Makefile +++ b/sortix/Makefile @@ -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 \ diff --git a/sortix/clock.cpp b/sortix/clock.cpp new file mode 100644 index 00000000..7c7ac4a7 --- /dev/null +++ b/sortix/clock.cpp @@ -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 . + + clock.cpp + Clock and timer facility. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +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 diff --git a/sortix/include/sortix/__/types.h b/sortix/include/sortix/__/types.h index c0d6981f..3a9433d1 100644 --- a/sortix/include/sortix/__/types.h +++ b/sortix/include/sortix/__/types.h @@ -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) diff --git a/sortix/include/sortix/itimerspec.h b/sortix/include/sortix/itimerspec.h new file mode 100644 index 00000000..02af6ef9 --- /dev/null +++ b/sortix/include/sortix/itimerspec.h @@ -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 . + + sortix/itimerspec.h + Declaration of the itimerspec structure. + +*******************************************************************************/ + +#ifndef INCLUDE_SORTIX_ITIMERSPEC_H +#define INCLUDE_SORTIX_ITIMERSPEC_H + +#include + +__BEGIN_DECLS + +struct itimerspec +{ + struct timespec it_interval; + struct timespec it_value; +}; + +__END_DECLS + +#endif diff --git a/sortix/include/sortix/kernel/clock.h b/sortix/include/sortix/kernel/clock.h new file mode 100644 index 00000000..f9f33170 --- /dev/null +++ b/sortix/include/sortix/kernel/clock.h @@ -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 . + + 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 + +#include + +#include + +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 diff --git a/sortix/include/sortix/kernel/time.h b/sortix/include/sortix/kernel/time.h index 963b2726..f16b71bf 100644 --- a/sortix/include/sortix/kernel/time.h +++ b/sortix/include/sortix/kernel/time.h @@ -33,13 +33,18 @@ #include +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 diff --git a/sortix/include/sortix/kernel/timer.h b/sortix/include/sortix/kernel/timer.h new file mode 100644 index 00000000..0798a33c --- /dev/null +++ b/sortix/include/sortix/kernel/timer.h @@ -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 . + + 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 +#include + +#include + +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 diff --git a/sortix/include/sortix/kernel/user-timer.h b/sortix/include/sortix/kernel/user-timer.h new file mode 100644 index 00000000..4fe5e1c0 --- /dev/null +++ b/sortix/include/sortix/kernel/user-timer.h @@ -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 . + + 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 + +#include + +namespace Sortix { + +class Process; + +struct UserTimer +{ + Timer timer; + struct sigevent event; + Process* process; + timer_t timerid; + +public: + static void Init(); + +}; + +} // namespace Sortix + +#endif diff --git a/sortix/include/sortix/sigevent.h b/sortix/include/sortix/sigevent.h new file mode 100644 index 00000000..5dd4c106 --- /dev/null +++ b/sortix/include/sortix/sigevent.h @@ -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 . + + sortix/sigevent.h + Declares the sigevent structure. + +*******************************************************************************/ + +#ifndef INCLUDE_SORTIX_SIGEVENT_H +#define INCLUDE_SORTIX_SIGEVENT_H + +#include + +__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 diff --git a/sortix/include/sortix/syscallnum.h b/sortix/include/sortix/syscallnum.h index 4e38b594..1b90bbe6 100644 --- a/sortix/include/sortix/syscallnum.h +++ b/sortix/include/sortix/syscallnum.h @@ -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 diff --git a/sortix/include/sortix/time.h b/sortix/include/sortix/time.h new file mode 100644 index 00000000..670cc071 --- /dev/null +++ b/sortix/include/sortix/time.h @@ -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 . + + sortix/time.h + Declarations for the kernel time interfaces. + +*******************************************************************************/ + +#ifndef INCLUDE_SORTIX_TIME_H +#define INCLUDE_SORTIX_TIME_H + +#include + +__BEGIN_DECLS + +#define TIMER_ABSTIME (1<<0) + +__END_DECLS + +#endif diff --git a/sortix/kernel.cpp b/sortix/kernel.cpp index 9c6a9be8..d8f43dcf 100644 --- a/sortix/kernel.cpp +++ b/sortix/kernel.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -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(); diff --git a/sortix/process.cpp b/sortix/process.cpp index 6805dcbf..16ee19b7 100644 --- a/sortix/process.cpp +++ b/sortix/process.cpp @@ -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); diff --git a/sortix/process.h b/sortix/process.h index e4381be4..49f7be67 100644 --- a/sortix/process.h +++ b/sortix/process.h @@ -25,10 +25,18 @@ #ifndef SORTIX_PROCESS_H #define SORTIX_PROCESS_H -#include "cpu.h" +#include + +#include #include #include -#include +#include +#include +#include + +#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, diff --git a/sortix/thread.h b/sortix/thread.h index 32d09b1e..04a87976 100644 --- a/sortix/thread.h +++ b/sortix/thread.h @@ -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. diff --git a/sortix/time.cpp b/sortix/time.cpp index 04761e36..50bb267c 100644 --- a/sortix/time.cpp +++ b/sortix/time.cpp @@ -29,13 +29,12 @@ #include #include -#include #include #include #include #include -#include +#include #include #include @@ -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 diff --git a/sortix/timer.cpp b/sortix/timer.cpp new file mode 100644 index 00000000..ff661789 --- /dev/null +++ b/sortix/timer.cpp @@ -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 . + + timer.cpp + Clock and timer facility. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +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 diff --git a/sortix/user-timer.cpp b/sortix/user-timer.cpp new file mode 100644 index 00000000..aca67ea5 --- /dev/null +++ b/sortix/user-timer.cpp @@ -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 . + + user-timer.cpp + Timers that send signals when triggered. + +*******************************************************************************/ + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 diff --git a/sortix/x86-family/time.cpp b/sortix/x86-family/time.cpp new file mode 100644 index 00000000..67e1d30d --- /dev/null +++ b/sortix/x86-family/time.cpp @@ -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 . + + x86-family/time.cpp + Retrieving the current time. + +*******************************************************************************/ + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#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