Files
banan-os/kernel/arch/i686/boot.S
Bananymous 9eb3834ae5 Kernel: Add syscall-less clock_gettime
If the processor has invariant TSC it can be used to measure time. We
keep track of the last nanosecond and TSC values and offset them based
on the current TSC. This allows getting current time in userspace.

The implementation maps a single RO page to every processes' address
space. The page contains the TSC info which gets updated every 100 ms.
If the processor does not have invariant TSC, this page will not
indicate the capability for TSC based timing.

There was the problem about how does a processor know which cpu it is
running without doing syscall. TSC counters may or may not be
synchronized between cores, so we need a separate TSC info for each
processor. I ended up adding sequence of bytes 0..255 at the start of
the shared page. When a scheduler gets a new thread, it updates the
threads gs/fs segment to point to the byte corresponding to the current
cpu.

This TSC based timing is also used in kernel. With 64 bit HPET this
probably does not bring much of a benefit, but on PIT or 32 bit HPET
this removes the need to aquire a spinlock to get the current time.

This change does force the userspace to not use gs/fs themselves and
they are both now reserved. Other one is used for TLS (this can be
technically used if user does not call libc code) and the other for
the current processor index (cannot be used as kernel unconditionally
resets it after each load balance).

I was looking at how many times timer's current time was polled
(userspace and kernel combined). When idling in window manager, it was
around 8k times/s. When running doom it peaked at over 1 million times
per second when loading and settled at ~30k times/s.
2026-01-08 17:13:59 +02:00

349 lines
5.3 KiB
ArmAsm

.set PG_PRESENT, 1<<0
.set PG_READ_WRITE, 1<<1
.set PG_PAGE_SIZE, 1<<7
.set FB_WIDTH, 800
.set FB_HEIGHT, 600
.set FB_BPP, 32
#define KERNEL_OFFSET 0xC0000000
#define V2P(vaddr) ((vaddr) - KERNEL_OFFSET)
.code32
# multiboot2 header
.section .multiboot, "aw"
.align 8
multiboot_start:
.long 0x1BADB002
.long (1 << 2) # page align modules
.long -(0x1BADB002 + (1 << 2))
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long FB_WIDTH
.long FB_HEIGHT
.long FB_BPP
multiboot_end:
.align 8
multiboot2_start:
.long 0xE85250D6
.long 0
.long multiboot2_end - multiboot2_start
.long -(0xE85250D6 + (multiboot2_end - multiboot2_start))
# framebuffer tag
.align 8
.short 5
.short 0
.long 20
.long FB_WIDTH
.long FB_HEIGHT
.long FB_BPP
# legacy start
.align 8
.short 3
.short 0
.long 12
.long V2P(_start)
# page align modules
.align 8
.short 6
.short 0
.long 8
.align 8
.short 0
.short 0
.long 8
multiboot2_end:
.section .bananboot, "aw"
.align 8
bananboot_start:
.long 0xBABAB007
.long -(0xBABAB007 + FB_WIDTH + FB_HEIGHT + FB_BPP)
.long FB_WIDTH
.long FB_HEIGHT
.long FB_BPP
bananboot_end:
.section .bss, "aw", @nobits
.global g_boot_stack_top
g_boot_stack_bottom:
.skip 4096 * 4
g_boot_stack_top:
.global g_kernel_cmdline
g_kernel_cmdline:
.skip 4096
bootloader_magic:
.skip 8
bootloader_info:
.skip 8
.section .data
# Map first GiB to 0x00000000 and 0xC0000000
.align 32
boot_pdpt:
.long V2P(boot_pd) + (PG_PRESENT)
.long 0
.quad 0
.quad 0
.long V2P(boot_pd) + (PG_PRESENT)
.long 0
.align 4096
boot_pd:
.set i, 0
.rept 512
.long V2P(boot_pts) + i + (PG_READ_WRITE | PG_PRESENT)
.long 0
.set i, i + 0x1000
.endr
boot_pts:
.set i, 0
.rept 512
.rept 512
.long i + (PG_READ_WRITE | PG_PRESENT)
.long 0
.set i, i + 0x1000
.endr
.endr
boot_gdt:
.quad 0x0000000000000000 # null descriptor
.quad 0x00CF9A000000FFFF # kernel code
.quad 0x00CF92000000FFFF # kernel data
boot_gdtr:
.short . - boot_gdt - 1
.long V2P(boot_gdt)
.global g_ap_startup_done
g_ap_startup_done:
.byte 0
.global g_ap_running_count
g_ap_running_count:
.byte 0
.global g_ap_stack_loaded
g_ap_stack_loaded:
.byte 0
.section .text
has_cpuid:
pushfl
pushfl
xorl $0x00200000, (%esp)
popfl
pushfl
popl %eax
xorl (%esp), %eax
popfl
testl $0x00200000, %eax
ret
has_pae:
movl $0, %eax
cpuid
testl $(1 << 6), %edx
ret
has_sse:
movl $1, %eax
cpuid
testl $(1 << 25), %edx
ret
check_requirements:
call has_cpuid
jz .exit
call has_pae
jz .exit
call has_sse
jz .exit
ret
.exit:
jmp system_halt
enable_sse:
movl %cr0, %eax
andw $0xFFFB, %ax
orw $0x0002, %ax
movl %eax, %cr0
movl %cr4, %eax
orw $0x0600, %ax
movl %eax, %cr4
ret
enable_tsc:
# allow userspace to use RDTSC
movl %cr4, %ecx
andl $0xFFFFFFFB, %ecx
movl %ecx, %cr4
ret
initialize_paging:
# enable PAE
movl %cr4, %ecx
orl $(1 << 5), %ecx
movl %ecx, %cr4
# load page tables
movl $V2P(boot_pdpt), %ecx
movl %ecx, %cr3
# enable paging
movl %cr0, %ecx
orl $(1 << 31), %ecx
movl %ecx, %cr0
ret
.global _start
.type _start, @function
_start:
cli; cld
# save bootloader magic and info
movl %eax, V2P(bootloader_magic)
movl %ebx, V2P(bootloader_info)
# load boot stack
movl $V2P(g_boot_stack_top), %esp
# load boot GDT
lgdt V2P(boot_gdtr)
ljmpl $0x08, $V2P(gdt_flush)
gdt_flush:
# set correct segment registers
movw $0x10, %ax
movw %ax, %ds
movw %ax, %ss
movw %ax, %es
# do processor initialization
call check_requirements
call enable_sse
call enable_tsc
call initialize_paging
# load higher half stack pointer
movl $g_boot_stack_top, %esp
# jump to higher half
leal higher_half, %ecx
jmp *%ecx
higher_half:
# call global constuctors
call _init
movl $g_init_array_start, %ebx
jmp 2f
1: call *(%ebx)
addl $4, %ebx
2: cmpl $g_init_array_end, %ebx
jne 1b
# call to the kernel itself (clear ebp for stacktrace)
xorl %ebp, %ebp
subl $8, %esp
pushl bootloader_info
pushl bootloader_magic
call kernel_main
addl $16, %esp
# call global destructors
call _fini
system_halt:
xchgw %bx, %bx
cli
1: hlt
jmp 1b
#define AP_V2P(vaddr) ((vaddr) - ap_trampoline + 0xF000)
.section .ap_init, "ax"
.code16
.global ap_trampoline
ap_trampoline:
jmp 1f
.align 8
ap_stack_ptr:
.skip 4
ap_stack_loaded:
.skip 1
1: cli; cld
ljmpl $0x00, $AP_V2P(ap_cs_clear)
ap_cs_clear:
# load ap gdt and enter protected mode
lgdt AP_V2P(ap_gdtr)
movl %cr0, %eax
orb $1, %al
movl %eax, %cr0
ljmpl $0x08, $AP_V2P(ap_protected_mode)
.code32
ap_protected_mode:
movw $0x10, %ax
movw %ax, %ds
movw %ax, %ss
movw %ax, %es
movl AP_V2P(ap_stack_ptr), %esp
movb $1, AP_V2P(ap_stack_loaded)
leal V2P(enable_sse), %ecx; call *%ecx
leal V2P(enable_tsc), %ecx; call *%ecx
leal V2P(initialize_paging), %ecx; call *%ecx
# load boot gdt and enter long mode
lgdt V2P(boot_gdtr)
ljmpl $0x08, $AP_V2P(ap_flush_gdt)
ap_flush_gdt:
# move stack pointer to higher half
movl %esp, %esp
addl $KERNEL_OFFSET, %esp
# jump to higher half
leal ap_higher_half, %ecx
jmp *%ecx
ap_higher_half:
# clear rbp for stacktrace
xorl %ebp, %ebp
1: pause
cmpb $0, g_ap_startup_done
jz 1b
lock incb g_ap_running_count
call ap_main
jmp system_halt
ap_gdt:
.quad 0x0000000000000000 # null descriptor
.quad 0x00CF9A000000FFFF # 32 bit code
.quad 0x00CF92000000FFFF # 32 bit data
ap_gdtr:
.short . - ap_gdt - 1
.long ap_gdt