SMP booting race and caching
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 3 Apr 2009 20:55:41 +0000 (13:55 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 3 Apr 2009 20:55:41 +0000 (13:55 -0700)
Reworked some timing and locking in smp_boot / smp_entry to avoid some
race issues.  Read the comments.  Also fixed some cache-enabling issues.
Also, Bochs doesn't like the HLT in smp_entry.S.  Don't care for now.

kern/init.c
kern/pmap.c
kern/smp_entry.S

index 2186dcc..9044be5 100644 (file)
@@ -24,7 +24,6 @@
 volatile uint32_t waiting = 1;
 volatile uint8_t num_cpus = 0xee;
 uintptr_t smp_stack_top;
-volatile uint32_t smp_boot_lock = 0;
 
 static void print_cpuinfo(void);
 void smp_boot(void);
@@ -77,11 +76,12 @@ void smp_boot(void)
        extern isr_t interrupt_handlers[];
        // NEED TO GRAB A LOWMEM FREE PAGE FOR AP BOOTUP CODE
        // page1 (2nd page) is reserved, hardcoded in pmap.c
-       extern smp_entry(), smp_entry_end();
+       extern smp_entry(), smp_entry_end(), smp_boot_lock(), smp_semaphore();
        memset(KADDR(0x00001000), 0, PGSIZE);           
        memcpy(KADDR(0x00001000), &smp_entry, &smp_entry_end - &smp_entry);             
 
-       // This mapping allows access with paging on and off
+       // This mapping allows access to the trampoline with paging on and off
+       // via 0x00001000
        page_insert(boot_pgdir, pa2page(0x00001000), (void*)0x00001000, PTE_W);
 
        // Allocate a stack for the cores starting up.  One for all, must share
@@ -91,27 +91,40 @@ void smp_boot(void)
 
        // set up the local APIC timer to fire 0xf0 once.  hardcoded to break
        // out of the spinloop on waiting.  really just want to wait a little
-       lapic_set_timer(0x0000ffff, 0xf0, 0);
+       lapic_set_timer(0x00000fff, 0xf0, 0); // TODO - fix timing
        // set the function handler to respond to this
        register_interrupt_handler(interrupt_handlers, 0xf0, smp_boot_handler);
-       // Start the IPI process (INIT, wait, SIPI)
+
+       // Start the IPI process (INIT, wait, SIPI, wait, SIPI, wait)
        send_init_ipi();
        enable_interrupts(); // LAPIC timer will fire, extINTs are blocked at LINT0 now
        while (waiting); // gets released in smp_boot_handler
-
-       // Since we don't know how many CPUs are out there (need to parse tables)
-       // we'll wait for a little bit, using the timer as above.  each core will
-       // also increment waiting, and decrement when it is done, all in smp_entry.
-       // core0 uses the timer for its decrement to signal "waiting a while".  
-       // Replace this shit when we parse the ACPI/MP tables (TODO)
        waiting = 1;
-       send_startup_ipi(0x01); // can also send another one if all don't report in
-       // If this timer isn't long enough, then we could beat an AP past the
-       // waiting loop and compete for the lock.
-       lapic_set_timer(0x00ffffff, 0xf0, 0);
-       while(waiting); // want other cores to do stuff for now
+       send_startup_ipi(0x01); // first SIPI
+       lapic_set_timer(0x00000fff, 0xf0, 0); // TODO - fix timing
+       while(waiting); // wait for the first SIPI to take effect
+       waiting = 1;
+       send_startup_ipi(0x01); // second SIPI
+       lapic_set_timer(0x000fffff, 0xf0, 0); // TODO - fix timing
+       while(waiting); // wait for the second SIPI to take effect
+       disable_interrupts();
+
+       // Each core will also increment smp_semaphore, and decrement when it is done, 
+       // all in smp_entry.  It's purpose is to keep Core0 from competing for the 
+       // smp_boot_lock.  So long as one AP increments the sem before the final 
+       // LAPIC timer goes off, all available cores will be initialized.
+       while(*(volatile uint32_t*)(&smp_semaphore - &smp_entry + 0x00001000));
+
        // From here on, no other cores are coming up.  Grab the lock to ensure it.
-       spin_lock(&smp_boot_lock);
+       // Another core could be in it's prelock phase and be trying to grab the lock
+       // forever.... 
+       // The lock exists on the trampoline, so it can be grabbed right away in 
+       // real mode.  If core0 wins the race and blocks other CPUs from coming up
+       // it can crash the machine if the other cores are allowed to proceed with
+       // booting.  Specifically, it's when they turn on paging and have that temp
+       // mapping pulled out from under them.  Now, if a core loses, it will spin
+       // on the trampoline (which we must be careful to not deallocate)
+       spin_lock((uint32_t*)(&smp_boot_lock - &smp_entry + 0x00001000));
        cprintf("Num_Cpus Detected: %d\n", num_cpus);
 
        // Deregister smp_boot_handler
@@ -119,7 +132,10 @@ void smp_boot(void)
        // Remove the mapping of the page used by the trampoline
        page_remove(boot_pgdir, (void*)0x00001000);
        // It had a refcount of 2 earlier, so we need to dec once more to free it
-       page_decref(pa2page(0x00001000));
+       // but only if all cores are in (or we reset / reinit those that failed)
+       // TODO after we parse ACPI tables
+       if (num_cpus == 8) // TODO - ghetto coded for our 8 way SMPs
+               page_decref(pa2page(0x00001000));
        // Dealloc the temp shared stack
        page_decref(smp_stack);
 
index 54d148f..7c9f977 100644 (file)
@@ -345,9 +345,11 @@ i386_vm_init(void)
 
        // APIC mapping, in lieu of MTRRs for now.  TODO: remove when MTRRs are up
        // IOAPIC
-       boot_map_segment(pgdir, (uintptr_t)IOAPIC_BASE, PGSIZE, IOAPIC_BASE, PTE_PCD|PTE_W);
+       boot_map_segment(pgdir, (uintptr_t)IOAPIC_BASE, PGSIZE, IOAPIC_BASE, 
+                        PTE_PCD | PTE_PWT | PTE_W);
        // Local APIC
-       boot_map_segment(pgdir, (uintptr_t)LAPIC_BASE, PGSIZE, LAPIC_BASE, PTE_PCD|PTE_W);
+       boot_map_segment(pgdir, (uintptr_t)LAPIC_BASE, PGSIZE, LAPIC_BASE,
+                        PTE_PCD | PTE_PWT | PTE_W);
 
        //////////////////////////////////////////////////////////////////////
        // Make 'pages' point to an array of size 'npage' of 'struct Page'.
@@ -423,10 +425,9 @@ i386_vm_init(void)
 
        // Turn on paging.
        cr0 = rcr0();
-       // not sure why they turned on TS and EM here.  or anything other 
-       // than PG and WP
-       cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_TS|CR0_EM|CR0_MP;
-       cr0 &= ~(CR0_TS|CR0_EM);
+       // CD and NW should already be on, but just in case these turn on caching
+       cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP;
+       cr0 &= ~(CR0_TS|CR0_EM|CR0_CD|CR0_NW);
        lcr0(cr0);
 
        // Current mapping: KERNBASE+x => x => x.
index f0d3da0..5ffe440 100644 (file)
@@ -9,6 +9,13 @@
 smp_entry:             .code16
        cli
        cld
+       lock incw       smp_semaphore - smp_entry + 0x1000  # announce our presence
+spin_start:                                            # grab lock in real mode
+       movw    $1, %ax
+       xchgw   %ax, smp_boot_lock - smp_entry + 0x1000
+       test    %ax, %ax
+       jne             spin_start
+
        # Set up rudimentary segmentation
        xorw    %ax, %ax                        # Segment number zero
        movw    %ax, %ds                        # -> Data Segment
@@ -50,7 +57,7 @@ past_pse:
        movl    %cr0, %eax      
        # These cr0 flags are the same as in pmap.c.  Keep them in sync
        orl             $(CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP), %eax  
-       andl    $(~(CR0_TS|CR0_EM)), %eax  
+       andl    $(~(CR0_TS|CR0_EM|CR0_CD|CR0_NW)), %eax  
        movl    %eax, %cr0
 
        # Reload Segments, using the same gdt_pd as Core 0
@@ -66,23 +73,17 @@ past_pse:
 here:
        xorl    %eax, %eax
        lldt    %ax
-spin_start:                                    # grab lock for smp_main
-       movl    $1, %eax
-       xchgl   %eax, smp_boot_lock
-       test    %eax, %eax
-       jne             spin_start
-
        incl    num_cpus
-       lock incl       waiting
        movl    (smp_stack_top), %esp
        movl    $0, %ebp                # so backtrace works
        call    smp_main
-       lock decl       waiting
        movl    %eax, %esp              # use our new stack, value returned from smp_main
-       movl    $0, smp_boot_lock       # release lock
+       # note the next two lines are using the direct mapping from smp_boot()
+       movw    $0, smp_boot_lock - smp_entry + 0x1000  # release lock
+       lock decw       smp_semaphore - smp_entry + 0x1000  # show we are done
 
        sti                                             # turn on interrupts, and wait for an IPI
-       hlt                                             # hlts, and PC advances to the spinwait
+       hlt                                             # hlts, else will just spin.
 spinwait:
        jmp             spinwait
 
@@ -95,6 +96,12 @@ gdt:
 gdtdesc:
        .word   gdtdesc - gdt - 1                       # sizeof(gdt) - 1
        .long   gdt - smp_entry + 0x1000        # address gdt
-
+       .p2align        2                                               # force 4 byte alignment
+.globl                 smp_boot_lock
+smp_boot_lock:                                                 # this lock word will be only used from
+       .word   0                                                       # its spot in the trampoline (0x1000)
+.globl                 smp_semaphore
+smp_semaphore:                                                 # poor man's polling semaphore
+       .word   0                                                       
 .globl                 smp_entry_end
 smp_entry_end: