Files
MentOS/mentos/src/hardware/timer.c
T
2021-10-05 14:29:24 +02:00

747 lines
22 KiB
C

/// MentOS, The Mentoring Operating system project
/// @file timer.c
/// @brief Timer implementation.
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#include "hardware/timer.h"
#include "klib/irqflags.h"
#include "process/scheduler.h"
#include "hardware/pic8259.h"
#include "io/port_io.h"
#include "stdint.h"
#include "mem/kheap.h"
#include "misc/debug.h"
#include "process/wait.h"
#include "drivers/rtc.h"
#include "descriptor_tables/isr.h"
#include "devices/fpu.h"
#include "system/signal.h"
#include "assert.h"
#include "sys/errno.h"
/// Number of ticks per seconds.
#define TICKS_PER_SECOND 1193
/// @defgroup picregs Programmable Interval Timer Registers
/// @brief The list of registers used to set the PIT.
/// @{
/// Channel 0 data port (read/write).
#define PIT_DATAREG0 0x40u
/// Channel 1 data port (read/write).
#define PIT_DATAREG1 0x41u
/// Channel 2 data port (read/write).
#define PIT_DATAREG2 0x42u
/// Mode/Command register (write only, a read is ignored).
#define PIT_COMREG 0x43u
/// @}
/// @brief Frequency divider value (1.193182 MHz).
#define PIT_DIVISOR 1193182
/// @brief Command used to configure the PIT.
/// @details
/// Bits Usage
/// 6 and 7 [Select channel]
/// 0 0 = Channel 0
/// 0 1 = Channel 1
/// 1 0 = Channel 2
/// 1 1 = Read-back command (8254 only)
/// 4 and 5 [Access mode]
/// 0 0 = Latch count value command
/// 0 1 = Access mode: lobyte only
/// 1 0 = Access mode: hibyte only
/// 1 1 = Access mode: lobyte/hibyte
/// 1 to 3 [Operating mode]
/// 0 0 0 = Mode 0 (interrupt on terminal count)
/// 0 0 1 = Mode 1 (hardware re-triggerable one-shot)
/// 0 1 0 = Mode 2 (rate generator)
/// 0 1 1 = Mode 3 (square wave generator)
/// 1 0 0 = Mode 4 (software triggered strobe)
/// 1 0 1 = Mode 5 (hardware triggered strobe)
/// 1 1 0 = Mode 2 (rate generator, same as 010b)
/// 1 1 1 = Mode 3 (square wave generator, same as 011b)
/// 0 [BCD/Binary mode]
/// 0 = 16-bit binary
/// 1 = four-digit BCD
///
/// Examples:
/// 0x36 = 00|11|011|0
/// 0x34 = 00|11|010|0
#define PIT_CONFIGURATION 0x34u
/// Mask used to set the divisor.
#define PIT_MASK 0xFFu
/// The number of ticks since the system started its execution.
static __volatile__ unsigned long timer_ticks = 0;
void timer_phase(const uint32_t hz)
{
// Calculate our divisor.
unsigned int divisor = PIT_DIVISOR / hz;
// Set our command byte 0x36.
outportb(PIT_COMREG, PIT_CONFIGURATION);
// Set low byte of divisor.
outportb(PIT_DATAREG0, divisor & PIT_MASK);
// Set high byte of divisor.
outportb(PIT_DATAREG0, (divisor >> 8u) & PIT_MASK);
}
void timer_handler(pt_regs *reg)
{
// Save current process fpu state.
switch_fpu();
// Check if a second has passed.
++timer_ticks;
// Update all timers
run_timer_softirq();
// Perform the schedule.
scheduler_run(reg);
// Restore fpu state.
unswitch_fpu();
// The ack is sent to PIC only when all handlers terminated!
pic8259_send_eoi(IRQ_TIMER);
}
void timer_install()
{
dynamic_timers_install();
// Set the timer phase.
timer_phase(TICKS_PER_SECOND);
// Installs 'timer_handler' to IRQ0.
irq_install_handler(IRQ_TIMER, timer_handler, "timer");
// Enable the IRQ of the timer.
pic8259_irq_enable(IRQ_TIMER);
}
uint64_t timer_get_seconds()
{
return timer_ticks / TICKS_PER_SECOND;
}
unsigned long timer_get_ticks()
{
return timer_ticks;
}
//======================================================================================
// Dynamics timers
/// Contains timer for each CPU (for now only one)
static tvec_base_t cpu_base = { 0 };
/// Contains all process waiting for a sleep
static wait_queue_head_t sleep_queue;
/// @brief Initialize dynamic timer system
void dynamic_timers_install()
{
#ifndef ENABLE_REAL_TIMER_SYSTEM
list_head_init(&cpu_base.list);
#endif
// Initialize tvec_base structure
tvec_base_t *base = &cpu_base;
base->timer_ticks = 0;
for(int i = 0; i < TVR_SIZE; ++i)
list_head_init(base->tv1.vec + i);
for(int i = 0; i < TVN_SIZE; ++i) {
list_head_init(base->tv2.vec + i);
list_head_init(base->tv3.vec + i);
list_head_init(base->tv4.vec + i);
list_head_init(base->tv5.vec + i);
}
// Initialize sleeping process list
list_head_init(&sleep_queue.task_list);
spinlock_init(&sleep_queue.lock);
}
/// Prints used slots of timer vector
static void __print_tvec_slots(tvec_base_t *base, int tv_index) {
if (tv_index < 0 || tv_index > 5)
return;
// Write buffer
char result[TVN_SIZE + 1];
result[TVN_SIZE] = '\0';
struct timer_vec* tv = NULL;
switch(tv_index) {
// Root
case 1: {
pr_debug("base->tv1.vec:");
for(int i = 0; i < TVR_SIZE; ++i) {
// New line in order to not clutter the screen
int index = i % TVN_SIZE;
if (i != 0 && index == 0)
pr_debug("\n\t%s", result);
if (!list_head_empty(base->tv1.vec + i))
result[index] = '1';
else
result[index] = '0';
}
// The last line
pr_debug("\n\t%s\n", result);
return;
} break;
// Normal
case 2: tv = &base->tv2; break;
case 3: tv = &base->tv3; break;
case 4: tv = &base->tv4; break;
case 5: tv = &base->tv5; break;
}
for(int i = 0; i < TVN_SIZE; ++i) {
if (list_head_empty(tv->vec + i))
result[i] = '0';
else
result[i] = '1';
}
pr_debug("base->tv%d.vec:\n\t%s\n", tv_index, result);
}
/// Dump all timer vector in base
static inline void __dump_all_tvec_slots(tvec_base_t *base) {
__print_tvec_slots(base, 1);
__print_tvec_slots(base, 2);
__print_tvec_slots(base, 3);
__print_tvec_slots(base, 4);
__print_tvec_slots(base, 5);
}
/// Select correct timer vector and position inside of it for the input timer
/// index contains the position inside of the tv_index timer vector
static void __find_tvec(tvec_base_t *base, struct timer_list *timer, int* index, int* tv_index)
{
assert(index && "index is NULL");
assert(tv_index && "tv_index is NULL");
unsigned long expires = timer->expires;
unsigned long ticks = expires - base->timer_ticks;
unsigned long tv1_ticks = TIMER_TICKS(0);
unsigned long tv2_ticks = TIMER_TICKS(1);
unsigned long tv3_ticks = TIMER_TICKS(2);
unsigned long tv4_ticks = TIMER_TICKS(3);
// Can happen if you add a timer with expires == ticks, or in the past
if ((signed long)ticks < 0) {
*index = base->timer_ticks & TVR_MASK;
*tv_index = 1;
}
// tv1
else if (ticks < tv1_ticks) {
*index = expires & TVR_MASK;
*tv_index = 1;
}
// tv2
else if (ticks < tv2_ticks) {
*index = (expires >> TIMER_TICKS_BITS(0)) & TVN_MASK;
*tv_index = 2;
}
// tv3
else if (ticks < tv3_ticks) {
*index = (expires >> TIMER_TICKS_BITS(1)) & TVN_MASK;
*tv_index = 3;
}
// tv4
else if (ticks < tv4_ticks) {
*index = (expires >> TIMER_TICKS_BITS(2)) & TVN_MASK;
*tv_index = 4;
}
// tv5
else {
*index = (expires >> TIMER_TICKS_BITS(3)) & TVN_MASK;
*tv_index = 5;
}
}
/// Add timers into different lists based on their expire time
static void __add_timer_tvec_base(tvec_base_t *base, struct timer_list *timer) {
int index = 0, tv_index = 0;
__find_tvec(base, timer, &index, &tv_index);
struct list_head* vec;
switch(tv_index) {
case 1: vec = base->tv1.vec + index; break;
case 2: vec = base->tv2.vec + index; break;
case 3: vec = base->tv3.vec + index; break;
case 4: vec = base->tv4.vec + index; break;
case 5: vec = base->tv5.vec + index; break;
}
pr_debug("Adding timer at time_index: %d in tv%d\n", index, tv_index);
list_head_add_tail(&timer->entry, vec);
#ifdef ENABLE_REAL_TIMER_SYSTEM_DUMP
__dump_all_tvec_slots(base);
#endif
}
/// Remove timer from tvec_base
static void __rem_timer_tvec_base(tvec_base_t *base, struct timer_list *timer) {
int index = 0, tv_index = 0;
__find_tvec(base, timer, &index, &tv_index);
struct list_head* vec;
switch(tv_index) {
case 1: vec = base->tv1.vec + index; break;
case 2: vec = base->tv2.vec + index; break;
case 3: vec = base->tv3.vec + index; break;
case 4: vec = base->tv4.vec + index; break;
case 5: vec = base->tv5.vec + index; break;
}
pr_debug("Removing timer at time_index: %d in tv%d\n", index, tv_index);
list_head_del(&timer->entry);
#ifdef ENABLE_REAL_TIMER_SYSTEM_DUMP
__dump_all_tvec_slots(base);
#endif
}
/// Move all timers from tv up one level
static int cascate(tvec_base_t* base, timer_vec* tv, int time_index, int tv_index) {
if (!list_head_empty(tv->vec + time_index)) {
pr_debug("Relocating timers in tv%d.vec[%d]\n", tv_index, time_index);
// Reinsert all timers into base in the new correct list
struct list_head *it, *tmp;
list_for_each_safe (it, tmp, tv->vec + time_index) {
struct timer_list *timer = list_entry(it, struct timer_list, entry);
list_head_del(it);
__add_timer_tvec_base(base, timer);
}
}
return time_index;
}
void run_timer_softirq()
{
tvec_base_t *base = &cpu_base;
spinlock_lock(&base->lock);
#ifdef ENABLE_REAL_TIMER_SYSTEM
// While we are not up to date with current ticks
unsigned long current_ticks = timer_get_ticks();
while (base->timer_ticks <= current_ticks) {
// Index of the current timer to execute
int current_time_index = base->timer_ticks & TVR_MASK;
// If the index is zero then all lists in base->tv1 have been checked, so they are empty
if (!current_time_index) {
// Consider the first invocation of the cascade() function: it receives as arguments
// the address in base, the address of base->tv2, and the index of the list
// in base->tv2 including the timers that will decay in the next 256 ticks. This
// index is determined by looking at the proper bits of the base->timer_ticks value.
// cascade() moves all dynamic timers in the base->tv2 list into the
// proper lists of base->tv1; then, it returns a positive value, unless all base->tv2
// lists are now empty. If so, cascade() is invoked once more to replenish
// base->tv2 with the timers included in a list of base->tv3, and so on.
int tv2_index = (base->timer_ticks >> TIMER_TICKS_BITS(0)) & TVN_MASK;
int tv3_index = (base->timer_ticks >> TIMER_TICKS_BITS(1)) & TVN_MASK;
int tv4_index = (base->timer_ticks >> TIMER_TICKS_BITS(2)) & TVN_MASK;
int tv5_index = (base->timer_ticks >> TIMER_TICKS_BITS(3)) & TVN_MASK;
if (!cascate(base, &base->tv2, tv2_index, 2) &&
!cascate(base, &base->tv3, tv3_index, 3) &&
!cascate(base, &base->tv4, tv4_index, 4) &&
!cascate(base, &base->tv5, tv5_index, 5));
}
// If there are timers to execute in this instant
if (!list_head_empty(&base->tv1.vec[current_time_index])) {
pr_notice("Executing dynamic timers at %d ticks from start inside of tv1.vec[%d]\n",
base->timer_ticks, current_time_index);
// Trigger all timers
struct list_head *it, *tmp;
list_for_each_safe (it, tmp, &base->tv1.vec[current_time_index]) {
struct timer_list *timer = list_entry(it, struct timer_list, entry);
// Executes timer function
spinlock_unlock(&base->lock);
pr_notice("Executing dynamic timer function...\n");
timer->function(timer->data);
spinlock_lock(&base->lock);
// Removes timer from list
list_head_del(it);
kfree(timer);
}
}
// Advance timer check
++base->timer_ticks;
}
base->running_timer = NULL;
#else
struct list_head *it, *tmp;
list_for_each_safe (it, tmp, &base->list) {
struct timer_list *timer = list_entry(it, struct timer_list, entry);
if (timer->expires <= timer_get_ticks()) {
base->running_timer = timer;
timer->base = NULL;
// Executes timer function
spinlock_unlock(&base->lock);
pr_notice("Executing dynamic timer function...\n");
timer->function(timer->data);
spinlock_lock(&base->lock);
// Removes timer from list
pr_notice("Removing dynamic timer...\n");
list_head_del(it);
kfree(timer);
}
}
#endif
base->running_timer = NULL;
spinlock_unlock(&base->lock);
}
void init_timer(struct timer_list *timer)
{
timer->base = NULL;
list_head_init(&timer->entry);
spinlock_unlock(&timer->lock);
}
void add_timer(struct timer_list *timer)
{
tvec_base_t *base = &cpu_base;
timer->base = base;
#ifdef ENABLE_REAL_TIMER_SYSTEM
__add_timer_tvec_base(base, timer);
#else
list_head_add_tail(&timer->entry, &base->list);
#endif
}
void del_timer(struct timer_list *timer)
{
tvec_base_t *base = &cpu_base;
timer->base = NULL;
#ifdef ENABLE_REAL_TIMER_SYSTEM
__rem_timer_tvec_base(base, timer);
#else
list_head_del(&timer->entry);
#endif
}
//======================================================================================
// Sleep
/// @brief Debugging function.
/// @param data The data.
static inline void debug_timeout(unsigned long data)
{
pr_notice("Il timer è stato attivato con successo: %d, ticks: %d, seconds: %d\n",
data, timer_ticks, timer_get_seconds());
}
/// @brief Contains the entry of a wait queue and timespec which keeps trakc of
/// the remaining time.
typedef struct sleep_data_t {
/// POinter to the entry of a wait queue.
wait_queue_entry_t *entry;
/// Keeps track of the remaining time.
timespec *rem;
} sleep_data_t;
/// @brief Callback for when a sleep timer expires
/// @param data Custom data stored in the timer
void sleep_timeout(unsigned long data)
{
// NOTE: We could modify the sleep_on and make it return the wait_queue_entry_t
// and then store it in the dynamic timer data member instead of the task pid,
// this would remove the need to iterate the sleep queue list.
sleep_data_t *sleep_data = (sleep_data_t *)data;
wait_queue_entry_t *entry = sleep_data->entry;
task_struct *task = entry->task;
// Executed entry's wakeup test function
int res = entry->func(entry, 0, 0);
if (res == 1) {
// Removes entry from list and memory
remove_wait_queue(&sleep_queue, entry);
kfree(entry);
pr_debug("Process (pid: %d) restored from sleep\n", task->pid);
}
}
int sys_nanosleep(const timespec *req, timespec *rem)
{
// Probabilmente devi salvare rem da qualche parte, perche' dentro ci va
// messo quanto tempo mancava allo scadere del timer nel caso in cui il
// timer venga interrotto prima da un segnale.
pr_debug("sys_nanosleep([s:%d; ns:%d],...)\n", req->tv_sec, req->tv_nsec);
// Saves pid and rem timespec
sleep_data_t *data = kmalloc(sizeof(sleep_data_t));
data->rem = rem;
// Create a dinamic timer to wake up the process after some time
struct timer_list *sleep_timer = kmalloc(sizeof(struct timer_list));
init_timer(sleep_timer);
sleep_timer->expires = timer_get_ticks() + TICKS_PER_SECOND * req->tv_sec;
sleep_timer->function = &sleep_timeout;
sleep_timer->data = (unsigned long)data;
// Removes current process from runqueue and stores it in the waiting queue,
// this must be done at the end, because it changes the current active page
// and invalidates the req and rem pointers (?)
wait_queue_entry_t *entry = sleep_on(&sleep_queue);
data->entry = entry;
add_timer(sleep_timer);
return -1;
}
/// @brief Function executed when the real_timer of a process expires, sends SIGALRM to process.
/// @param pid PID of the process whos associated timer has expired
void alarm_timeout(unsigned long pid)
{
sys_kill(pid, SIGALRM);
struct task_struct *cur = scheduler_get_current_process();
cur->real_timer = NULL;
}
int sys_alarm(int seconds)
{
pr_debug("sys_alarm(seconds:%d)\n", seconds);
struct task_struct *current = scheduler_get_current_process();
struct timer_list *timer;
// If there is already a timer running
int result = 0;
if (current->real_timer != NULL) {
del_timer(current->real_timer);
result = (current->real_timer->expires - timer_get_ticks()) / TICKS_PER_SECOND;
timer = current->real_timer;
// Returns only the amount of seconds remaining
if (seconds == 0) {
kfree(current->real_timer);
current->real_timer = NULL;
return result;
}
} else {
if (seconds == 0)
return 0;
// Allocate new timer
timer = (struct timer_list *)kmalloc(sizeof(struct timer_list));
}
current->real_timer = timer;
init_timer(timer);
timer->expires = timer_get_ticks() + TICKS_PER_SECOND * seconds;
timer->function = &alarm_timeout;
timer->data = current->pid;
add_timer(timer);
return result;
}
static void calc_itimerval(unsigned long incr, unsigned long value, struct itimerval *result)
{
result->it_interval.tv_sec = incr / TICKS_PER_SECOND;
result->it_interval.tv_usec = incr / TICKS_PER_SECOND * 1000;
result->it_value.tv_sec = value / TICKS_PER_SECOND;
result->it_value.tv_usec = value / TICKS_PER_SECOND * 1000;
}
static void update_task_itimerval(int which, const struct itimerval *val)
{
unsigned long interval_ticks = val->it_interval.tv_sec * TICKS_PER_SECOND;
interval_ticks += val->it_interval.tv_usec * TICKS_PER_SECOND / 1000;
unsigned long value_ticks = val->it_value.tv_sec * TICKS_PER_SECOND;
value_ticks += val->it_value.tv_usec * TICKS_PER_SECOND / 1000;
struct task_struct *curr = scheduler_get_current_process();
switch (which) {
case ITIMER_REAL:
curr->it_real_incr = interval_ticks;
curr->it_real_value = value_ticks;
break;
case ITIMER_VIRTUAL:
curr->it_virt_incr = interval_ticks;
curr->it_virt_value = value_ticks;
break;
case ITIMER_PROF:
curr->it_prof_incr = interval_ticks;
curr->it_prof_value = value_ticks;
break;
}
}
int sys_getitimer(int which, struct itimerval *curr_value)
{
// Invalid time domain
if (which < 0 || which > 3)
return EINVAL;
struct task_struct *curr = scheduler_get_current_process();
switch (which) {
case ITIMER_REAL: {
// Extract remaining time in dynamic timer
unsigned long value = curr->real_timer->expires - timer_get_ticks();
curr->it_real_value = value;
calc_itimerval(curr->it_real_incr, curr->it_real_value, curr_value);
} break;
case ITIMER_VIRTUAL:
calc_itimerval(curr->it_virt_incr, curr->it_virt_value, curr_value);
break;
case ITIMER_PROF:
calc_itimerval(curr->it_prof_incr, curr->it_prof_value, curr_value);
break;
}
return 0;
}
// Real timer interval timemout
static void it_real_fn(unsigned long pid)
{
struct task_struct *cur = scheduler_get_running_process(pid);
sys_kill(pid, SIGALRM);
// If the real incr is not 0 then restart
if (cur->it_real_incr != 0) {
// Create new timer for process
struct timer_list *real_timer = (struct timer_list *)kmalloc(sizeof(struct timer_list));
cur->real_timer = real_timer;
init_timer(real_timer);
real_timer->expires = timer_get_ticks() + cur->it_real_incr;
real_timer->function = &it_real_fn;
real_timer->data = cur->pid;
add_timer(real_timer);
return;
}
// No more timer
cur->real_timer = NULL;
}
int sys_setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
{
// Invalid time domain
if (which < 0 || which > 3)
return EINVAL;
// Returns old timer interval
if (old_value != NULL)
sys_getitimer(which, old_value);
// Get ticks of interval
unsigned long interval_ticks = new_value->it_interval.tv_sec * TICKS_PER_SECOND;
interval_ticks += new_value->it_interval.tv_usec * TICKS_PER_SECOND / 1000;
// If interval is 0 removes timer
struct task_struct *cur = scheduler_get_current_process();
if (interval_ticks == 0) {
// Removes real_timer
if (which == ITIMER_REAL && cur->real_timer != NULL)
cur->real_timer = NULL;
update_task_itimerval(which, new_value);
return -1;
}
switch (which) {
// Uses Dynamic Timers
case ITIMER_REAL: {
// Remove real_timer if already in use
struct timer_list *timer = cur->real_timer;
if (timer != NULL) {
del_timer(timer); // Recycle memory
} else {
// Alloc new timer
timer = (struct timer_list *)kmalloc(sizeof(struct timer_list));
}
init_timer(timer);
timer->expires = timer_get_ticks() + interval_ticks;
timer->function = &it_real_fn;
timer->data = cur->pid;
add_timer(timer);
} break;
case ITIMER_VIRTUAL:
case ITIMER_PROF:
break;
}
update_task_itimerval(which, new_value);
return -1;
}
void update_process_profiling_timer(task_struct *proc)
{
// If the timer is active
if (proc->it_prof_incr != 0) {
proc->it_prof_value += proc->se.exec_runtime;
if (proc->it_prof_value >= proc->it_prof_incr) {
sys_kill(proc->pid, SIGPROF);
proc->it_prof_value = 0;
}
}
}