UnnamedOS
pit.c
1 /*
2  * Programmable Interval Timer (IRQ0) - used for multitasking and system clock
3  *
4  * http://lowlevel.eu/wiki/PIT
5  * http://wiki.osdev.org/PIT
6  * http://stackoverflow.com/questions/7083482/how-to-prevent-gcc-from-optimizing-out-a-busy-wait-loop
7  * http://stackoverflow.com/questions/2219829/how-to-prevent-gcc-optimizing-some-statements-in-c
8  */
9 
10 #include <common.h>
11 #include <hardware/pit.h>
12 #include <tasks/schedule.h>
13 
14 #define PIT_CHANNEL(c) (0x40 + (c))
15 #define PIT_INIT 0x43
16 #define PIT_FREQ 1193182
17 #define MODE_RATE 0x02
18 #define MS_TO_TICKS(ms, freq) ((freq) * (ms) / 1000) // freq = ticks/1s = ticks/1000ms, so ticks = freq*1000ms
19 
20 typedef struct {
21  uint8_t fmt : 1; // counter format (binary/bcd)
22  uint8_t mode : 3; // usually 2 (rate generator)
23  uint8_t byte : 2; // which byte of the counter register to write
24  uint8_t chan : 2; // channel to be changed
25 } __attribute__((packed)) pit_init_t;
26 
27 static uint32_t freq = 0; // assume undefined frequency in the beginning (actually, this is 18.2065Hz)
28 static uint32_t seconds = 0, minutes = 0, hours = 0, ticks = 0;
29 
30 uint8_t pit_init_channel(uint8_t channel, uint8_t mode, uint32_t freq) {
31  if (freq < 19 || freq > PIT_FREQ / 2)
32  return 0;
33  // With every PIT_FREQ tick the counter is decremented, when the counter reaches 0, on channel 0 an IRQ0 is fired,
34  // on channel 2 the speaker is turned on. So our frequency is calculated as freq = PIT_FREQ / counter. We want to
35  uint16_t counter = PIT_FREQ / freq; // tell the PIT the counter it needs to decrement, so we rearrange the equation.
36  // (If freq < 19, counter > 65535 (uint16_t max) would generate an overflow, so 19 Hz is the lowest freq possible.
37  // If freq > 596591, counter <= 1, but the values 1 and 0 are not allowed: 1 because only the transition from 2 to 1
38  // fires an IRQ0 and that's not possible if we set counter to 1 initially. 0 because we can't divide through 0.)
39  // Our PIT is going to be a binary rate generator, we want to write to channel 0 (the freely usable one) or we init
40  // channel 2 (which controls the speaker) as a binary square wave generator. Then we will write the counter's LSB
41  pit_init_t init = {.fmt = 0, .mode = mode, .byte = 3, .chan = channel}; // first and next its MSB.
42  outb(PIT_INIT, *(uint8_t*) &init);
43  outb(PIT_CHANNEL(channel), counter & 0xFF);
44  outb(PIT_CHANNEL(channel), counter >> 8);
45  return 1;
46 }
47 
48 static cpu_state_t* pit_handle_interrupt(cpu_state_t* cpu) {
49  ticks++;
50  if (ticks % freq == 0) { // every "freq" ticks a second is gone (because 1Hz = 1/s)
51  seconds++;
52  if (seconds % 60 == 0) {
53  seconds = 0;
54  minutes++;
55  if (minutes % 60 == 0) {
56  minutes = 0;
57  hours++;
58  }
59  }
60  }
61  cpu = schedule(cpu); // returns a new stack pointer if a task change is due
62  return cpu;
63 }
64 
65 void pit_init(uint32_t new_freq) {
66  print("PIT init ... ");
67  isr_register_handler(ISR_IRQ(0), pit_handle_interrupt);
68  if (pit_init_channel(0, MODE_RATE, new_freq)) {
69  freq = new_freq;
70  println("%2aok%a. Frequency=%dHz.", freq);
71  } else
72  println("%4afail%a. Frequency must be > 18Hz and < 0.59MHz.");
73 }
74 
75 void pit_dump_time() {
76  print("%02d:%02d:%02d", hours, minutes, seconds);
77 }
78 
79 // we disable GCC's optimization here to prevent the idling loop from being optimized away
80 __attribute__((optimize("O0"))) void pit_sleep(uint32_t ms) {
81  uint32_t wait_until = ticks + MS_TO_TICKS(ms, freq); // If wait_until has overflowed, we
82  while (wait_until < ticks); // wait until ticks overflows, then it approaches
83  while (ticks < wait_until); // wait_until from the "bottom" as expected.
84  // (I used != before but when using timeouts and not an idling loop, the
85  // wait_until moment can easily be missed when doing "heavier" tasks like I/O
86  // polling. > and < are more robust in that regard.)
87  // Alternative implementation with timeouts from below:
88  // pit_timeout_t timeout = pit_make_timeout(ms); // This implementation works,
89  // while (!pit_timed_out(&timeout)); // but is slower and not as straightforward.
90 }
91 
92 pit_timeout_t pit_make_timeout(uint32_t ms) { // timeout constructor
93  pit_timeout_t timeout = { // avoid ticks + INT_MAX, this overflows to ticks + 0,
94  .wait_until = ticks + MS_TO_TICKS(ms, freq), // waiting NOT AT ALL.
95  .state = PIT_WAITING_UNTIL_OVERFLOW // We start by waiting until ticks overflows.
96  };
97  return timeout;
98 }
99 
100 int8_t pit_timed_out(pit_timeout_t* timeout) {
101  switch (timeout->state) { // simple state machine mimicking pit_sleep's
102  case PIT_WAITING_UNTIL_OVERFLOW: // behaviour in a more "asynchronous" style
103  if (!(timeout->wait_until < ticks)) // like pit_sleep's 1st while
104  timeout->state = PIT_WAITING_UNTIL_TIMEOUT;
105  return 0;
106  case PIT_WAITING_UNTIL_TIMEOUT:
107  if (!(ticks < timeout->wait_until)) { // like pit_sleep's 2nd while
108  timeout->state = PIT_TIMED_OUT;
109  return 1;
110  }
111  return 0;
112  case PIT_TIMED_OUT:
113  return 1;
114  default:
115  return -1;
116  }
117 }
cpu_state_t * schedule(cpu_state_t *cpu)
Returns the next task to run.
Definition: schedule.c:26
The CPU&#39;s state when an interrupt occurs.
Definition: isr.h:58
#define ISR_IRQ(irq)
the interrupt vector for an IRQ
Definition: isr.h:13
void isr_register_handler(size_t intr, isr_handler_t handler)
Registers a handler to call whenever a given interrupt is fired.
Definition: isr.c:66
Definition: pit.c:20