diff --git a/README.md b/README.md index f4432d2..74bea79 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Minimal operating systems to learn low level programming. 1. [disk load](bios_disk_load.S) 1. [disk load 2](bios_disk_load2.S) 1. [detect memory](bios_detect_memory.S) + 1. [tick count](bios_tick_count.S) 1. [Initial state](initial_state.S) 1. [reboot](reboot.S) 1. [Not testable in userland](not-testable-in-userland.md) @@ -40,6 +41,7 @@ Minimal operating systems to learn low level programming. 1. [in keyboard](in_keyboard.S) 1. [in RTC](in_rtc.S) 1. [in PIT](in_pit.S) + 1. [PIT once](pit_once.S) 1. [in beep](in_beep.S) 1. [in beep_illinois](in_beep_illinois.S) 1. [in mouse (TODO)](in_mouse.S) @@ -48,7 +50,6 @@ Minimal operating systems to learn low level programming. 1. [IDT](idt.S) 1. [IDT 1](idt1.S) 1. [IDT zero divide](idt_zero_divide.S) - 1. [Maskable](maskable.S) 1. IDT PIT 1. Segmentation fault handler: memory bound, ring, RWX violations 1. [Paging](paging.S) diff --git a/TODO.md b/TODO.md index b841237..604467e 100644 --- a/TODO.md +++ b/TODO.md @@ -72,7 +72,7 @@ - http://stackoverflow.com/questions/14299893/any-tutorials-on-how-to-set-up-a-ldt?rq=1 -- WRMSR https://en.wikipedia.org/wiki/Model-specific_register http://x86.renejeschke.de/html/file_module_x86_id_326.html +- WRMSR https://en.wikipedia.org/wiki/Model-specific_register http://x86.renejeschke.de/html/file_module_x86_id_326.html - BIOS diff --git a/bios_tick_count.S b/bios_tick_count.S new file mode 100644 index 0000000..a906510 --- /dev/null +++ b/bios_tick_count.S @@ -0,0 +1,20 @@ +/* +# BIOS tick count + +Method mentioned at: http://stackoverflow.com/a/9973442/895245 + +TODO Not working on QEMU 2.3.0: the value does not get updated! + +But if I run qemu many times in a sequence, it does seem to get incremented... +*/ + +#include "common.h" +BEGIN +start: + mov 0x046C, %ax + #cmp %ax, %bx + #je start + PRINT_WORD_HEX + PRINT_NEWLINE + #mov %ax, %bx +jmp start diff --git a/common.h b/common.h index bff646c..12fc7d3 100644 --- a/common.h +++ b/common.h @@ -145,18 +145,12 @@ Sample usage: Stage 2 code here. */ .macro STAGE2 - mov $2, %ah - /* - TODO get working with linker script. - Above my paygrade for now, so I just load a bunch of sectors instead. - */ - /* mov __stage2_size, %al;\ */ - mov $9, %al - mov $0x80, %dl - mov $0, %ch - mov $0, %dh - mov $2, %cl + /* Defined in the linker script. */ + mov $__stage2_nsectors, %al + mov $0x02, %ah mov $1f, %bx + mov $0x0002, %cx + mov $0x0080, %dx int $0x13 jmp 1f .section .stage2 @@ -645,11 +639,40 @@ end: #define PORT_PIC_MASTER_CMD $0x20 #define PORT_PIC_MASTER_DATA $0x21 +#define PORT_PIT_CHANNEL0 $0x40 +#define PORT_PIT_MODE $0x43 #define PORT_PIC_SLAVE_CMD $0xA0 #define PORT_PIC_SLAVE_DATA $0xA1 -/* PIC. */ +/* PIC */ #define PIC_CMD_RESET $0x20 #define PIC_ICR_ADDRESS $0xFEE00300 +/* EOI End Of Interrupt: PIC it will not fire again unless we reset it. */ +.macro PIC_EOI + OUTB PIC_CMD_RESET, PORT_PIC_MASTER_CMD +.endm + +/* PIT */ + +/* +Set the minimum possible PIT frequency. +This is a human friendly frequency: you can see individual events, +but you don't have to wait much for each one. +*/ +.macro PIT_SET_MIN_FREQ + mov $0xFF, %al + out %al, PORT_PIT_CHANNEL0 + out %al, PORT_PIT_CHANNEL0 +.endm + +/* IVT */ + +#define IVT_PIT 8 + +/* Setup interrupt handler 8: this is where the PIC maps IRQ 0 to. */ +.macro IVT_PIT_SETUP + movw $handler, IVT_PIT * 4 + mov %cs, IVT_PIT * 4 + 2 +.endm diff --git a/in_pit.S b/in_pit.S index 2d89c57..c98d7d7 100644 --- a/in_pit.S +++ b/in_pit.S @@ -8,7 +8,7 @@ Expected output: "a\n" is printed infinitely many times. Generates periodic interrupts (or sound!) with a given frequency to IRQ0, which on real mode maps to interrupt 8 by default. -Major applicaiton: interrupt the running process +Major application: interrupt the running process to allow the OS to schedule processes. Read this *now*: http://wiki.osdev.org/PIT @@ -36,18 +36,34 @@ https://en.wikipedia.org/wiki/Frequency_divider 1193181 has 2 occurrences on Linux 4.2. +The frequency comes from historical reasons to reuse television hardware. + +## Maskable interrupts + +## sti + +http://wiki.osdev.org/Non_Maskable_Interrupt + +Interrupts generated by the CPU from 0 - 31 are not maskable: they generate interrupts even with `cli`. + +Those from the PIC are maskable however. + +QEMU generates many maskable interrupts by default. TODO: where do they come from? What are they excatly? + +If we don't set a handler, we usually triple-fault and the machine restarts, thus going into a restart loop. + ## Bibliography - https://en.wikipedia.org/wiki/Intel_8253 That is the circuit ID for the PIT. - http://kernelx.weebly.com/programmable-interval-timer.html + +TODO learn to turn off the PIT after some iterations */ #include "common.h" BEGIN - /* Setup interrupt handler 8: this is where the PIC maps IRQ 0 to. */ - movw $handler, 8 * 4 - mov %cs, 8 * 4 + 2 + IVT_PIT_SETUP /* Define the properties of the wave: @@ -57,7 +73,7 @@ BEGIN - operating mode: rate generator - BCD/binary: binary */ - OUTB $0b00110100, $0x43 + OUTB $0b00110100, PORT_PIT_MODE /* Set frequency of Channel 0. @@ -75,10 +91,9 @@ BEGIN */ /* Instead, let's just use the smallest frequency possible. */ - mov $0xFF, %al - out %al, $0x40 - out %al, $0x40 + PIT_SET_MIN_FREQ + /* If we comment out sti, nothing happens, because the PIT interrupts are maskable. */ sti jmp . @@ -88,8 +103,6 @@ handler: PUTC $'a PUTC $'\n /* EOI: it will not fire again unless we reset it. */ - OUTB PIC_CMD_RESET, PORT_PIC_MASTER_CMD + PIC_EOI sti iret - - /* TODO turn off. */ diff --git a/in_rtc.S b/in_rtc.S index fa96400..1dfd922 100644 --- a/in_rtc.S +++ b/in_rtc.S @@ -17,22 +17,20 @@ Kenrel 4.2 usage: https://github.com/torvalds/linux/blob/v4.2/arch/x86/kernel/rt ## Milliseconds -Not possible. Must use the PIT. +Not possible. Consider the PIT, or the HPET. ## Time zone QEMU uses UTC. */ -#include "common.h" -BEGIN - -/* -TODO what do those numbers mean? Where is this all documented? -*/ +/* TODO what do those numbers mean? Where is this all documented? */ .equ RTCaddress, 0x70 .equ RTCdata, 0x71 +#include "common.h" +BEGIN + update_in_progress: mov $10, %al out %al, $RTCaddress diff --git a/linker.ld b/linker.ld index b40f824..99c9af9 100644 --- a/linker.ld +++ b/linker.ld @@ -8,6 +8,8 @@ SECTIONS . = 0x7c00; .text : { + __start = .; + /* We are going to stuff everything into a text segment for now, including data. @@ -31,18 +33,20 @@ SECTIONS *(.stage2) /* - TODO get this working. - - Number of sectors in stage 2. Used by the `int 13`. + Number of sectors in stage 2. Used by the `int 13` to load it from disk. The value gets put into memory as the very last thing in the `.stage` section if it exists. We must put it *before* the final `. = ALIGN(512)`, or else it would fall out of the loaded memory. + + This must be absolute, or else it would get converted + to the actual address relative to this section (7c00 + ...) + and linking would fail with "Relocation truncated to fit" + because we are trying to put that into al for the int 13. */ - __stage2_size = .; - /*BYTE((ALIGN(.) / 512) - 1);*/ + __stage2_nsectors = ABSOLUTE((. - __start) / 512); /* Ensure that the generated image is a multiple of 512 bytes long. */ . = ALIGN(512); diff --git a/maskable.S b/maskable.S deleted file mode 100644 index 80d69e8..0000000 --- a/maskable.S +++ /dev/null @@ -1,27 +0,0 @@ -/* -# Maskable interrupts - -# sti - -http://wiki.osdev.org/Non_Maskable_Interrupt - -Interrupts generated by the CPU from 0 - 31 are not maskable: they generate interrupts even with `cli`. - -Those from the PIC are maskable however. - -QEMU generates many maskable interrupts by default. TODO: where do they come from? What are they excatly? - -If we don't set a handler, we usually triple-fault and the machine restarts, thus going into a restart loop. -*/ - -#include "common.h" - -BEGIN - STAGE2 - CLEAR - PROTECTED_MODE - #sti - VGA_PRINT_STRING $message - jmp . -message: - .asciz "hello" diff --git a/pit_once.S b/pit_once.S new file mode 100644 index 0000000..429f537 --- /dev/null +++ b/pit_once.S @@ -0,0 +1,30 @@ +/* +# PIT once + +Make the PIT generate a single interrupt instead of a frequency. + +Expected output: "a". + +TODO how does this mode work exactly? +I think it counts down from the value in PORT_PIT_CHANNEL0, +and fires when it reaches 0 the first time. +*/ + +#include "common.h" +BEGIN + IVT_PIT_SETUP + /* Operating mode: 0 rate generator. */ + OUTB $0b00110000, PORT_PIT_MODE + /* TODO: Set the counter to 1. */ + mov $0x01, %al + out %al, PORT_PIT_CHANNEL0 + mov $0x00, %al + out %al, PORT_PIT_CHANNEL0 + sti + jmp . +handler: + cli + PUTC $'a + PIC_EOI + sti + iret diff --git a/smp.S b/smp.S index 99ba87c..8eebb89 100644 --- a/smp.S +++ b/smp.S @@ -3,7 +3,7 @@ Expected output: "SMP started" -TODO get working: +TODO get working + answer all of: - http://stackoverflow.com/questions/16364817/assessing-the-apic-and-creating-ipis-in-x86-assembly Closest quesiton so far! Almost there! @@ -24,11 +24,15 @@ TODO get working: - https://github.com/cirosantilli/oszur11-operating-system-examples/tree/1af6451852887fac3d7206d4d09714c181c81d1e/Chapter_07_Threads */ +/* Must be a multiple of 0x1000. */ +.equ STARTUP_CODE_ADDRESS, 0x1000 +.equ SPINLOCK_ADDRESS, 0x2000 + #include "common.h" BEGIN CLEAR /* - TODO do we need 32-bit mode? The APIC register + TODO do we need 32-bit mode? I think yes because the APIC register FEE00300 is too high for 16-bit? */ PROTECTED_MODE @@ -42,11 +46,11 @@ BEGIN cld mov $init_len, %ecx mov $init, %esi - mov $0x1000, %edi + mov $STARTUP_CODE_ADDRESS, %edi rep movsb /* Setup the value that threads will modify for us. */ - movb $0, 0x2000 + movb $0, SPINLOCK_ADDRESS /* Move data into the lower ICR register: @@ -68,26 +72,24 @@ BEGIN mov %eax, (%esi) /* 10-millisecond delay loop. - TODO + TODO I think I'm going to do this with the PIT. */ /* Load ICR encoding for broadcast SIPI IP to all APs. The low byte of this is the vector which encodes the staring address for the processors! This address is multiplied by 0x1000: processors start at CS = vector * 0x100 and IP = 0. */ - /*TODO*/ - mov $0x000C4601, %eax + mov $0x000C4600 + STARTUP_CODE_ADDRESS / 0x1000, %eax /* Broadcast SIPI IPI to all APs. */ mov %eax, (%esi) /* 200-microsecond delay loop. */ /* TODO */ /* Broadcast second SIPI IPI to all APs */ mov %eax, (%esi) - /* Wait for the timer interrupt until the timer expires. */ - /* Busy loop until another thread changes this memory. */ + /* TODO improve this spinlock. */ not_started: - cmpb $1, 0x2000 + cmpb $1, SPINLOCK_ADDRESS jne not_started VGA_PRINT_STRING $message @@ -98,7 +100,9 @@ message: .asciz "SMP started" .code16 init: - movb $1, 0x2000 + xor %ax, %ax + mov %ax, %ds + movb $1, SPINLOCK_ADDRESS /* TODO mandatory? */ wbinvd hlt diff --git a/smp.md b/smp.md index 8b49c59..591f8e2 100644 --- a/smp.md +++ b/smp.md @@ -2,6 +2,12 @@ # Symmetric multiprocessing +At fist, a single processor starts. + +To start the others, the first processor must tell the APIC to send a few special interrupts to the other processors. + +One of those interrupts says where the there processors start running their first instruction from. + - Intel docs: Volume 3, Chapter 8 "Multiple processor Management". Does not seem standardized across to AMD.