Transitioning smp_call_function to use checklists
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 21 Apr 2009 01:16:31 +0000 (18:16 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 22 Apr 2009 05:09:55 +0000 (22:09 -0700)
WIP, will probably mash this commit.

kern/atomic.h
kern/init.c
kern/smp.c
kern/smp.h
kern/trap.c

index ee386ae..713f918 100644 (file)
@@ -28,7 +28,7 @@ typedef struct checklist_mask {
        volatile uint8_t (COUNT(BYTES_FOR_BITMASK(size)) bits)[];
 } checklist_mask_t;
 
-// mask contains an unspecified array, so it need to be at the bottom
+// mask contains an unspecified array, so it needs to be at the bottom
 typedef struct checklist {
        volatile uint32_t lock;
        checklist_mask_t mask;
@@ -49,10 +49,15 @@ int commit_checklist_wait(checklist_t* list, checklist_mask_t* mask);
 int commit_checklist_nowait(checklist_t* list, checklist_mask_t* mask);
 int waiton_checklist(checklist_t* list);
 void down_checklist(checklist_t* list);
-// TODO - do we want to adjust the size?
+// TODO - do we want to adjust the size?  (YES, don't want to check it all)
 // TODO - do we want to be able to call waiton without having called commit?
 //     - in the case of protected checklists
 // TODO - want a destroy checklist (when we have kmalloc, or whatever)
+// TODO - some sort of dynamic allocation of them in the future
+// TODO - think about deadlock issues with one core spinning on a lock for
+// something that it is the hold out for...
+//     - probably should have interrupts enabled, and never grab these locks
+//     from interrupt context (and not use irq_save)
 /**************************************************************/
 
 /* Barrier: currently made for everyone barriering.  Change to use checklist */
index 7c7ea69..0e8ba20 100644 (file)
@@ -53,15 +53,15 @@ void kernel_init(multiboot_info_t *mboot_info)
        smp_boot();
        
        panic("Don't Panic");
-       //test_checklists();
-       //test_barrier();
-
-       //test_print_info();
-       //test_ipi_sending();
-       //test_pit();
-       //test_barrier();
-       //test_print_info();
-       //test_ipi_sending();
+       test_checklists();
+       test_barrier();
+
+       test_print_info();
+       test_ipi_sending();
+       test_pit();
+       test_barrier();
+       test_print_info();
+       test_ipi_sending();
        
        //ENV_CREATE(user_faultread);
        //ENV_CREATE(user_faultreadkernel);
index 99b714f..5131f52 100644 (file)
@@ -32,28 +32,30 @@ static void smp_mtrr_handler(trapframe_t *tf)
 
 void smp_boot(void)
 {
+       #define boot_vector 0xeb
+       #define trampoline_pg 0x00001000
        page_t *smp_stack;
        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(), smp_boot_lock(), smp_semaphore();
-       memset(KADDR(0x00001000), 0, PGSIZE);           
-       memcpy(KADDR(0x00001000), &smp_entry, &smp_entry_end - &smp_entry);             
+       memset(KADDR(trampoline_pg), 0, PGSIZE);
+       memcpy(KADDR(trampoline_pg), &smp_entry, &smp_entry_end - &smp_entry);
 
        // This mapping allows access to the trampoline with paging on and off
-       // via 0x00001000
-       page_insert(boot_pgdir, pa2page(0x00001000), (void*)0x00001000, PTE_W);
+       // via trampoline_pg
+       page_insert(boot_pgdir, pa2page(trampoline_pg), (void*)trampoline_pg, PTE_W);
 
        // Allocate a stack for the cores starting up.  One for all, must share
        if (page_alloc(&smp_stack))
                panic("No memory for SMP boot stack!");
        smp_stack_top = (uintptr_t)(page2kva(smp_stack) + PGSIZE);
 
-       // set up the local APIC timer to fire 0xf0 once.  hardcoded to break
+       // set up the local APIC timer to fire boot_vector once.  hardcoded to break
        // out of the spinloop on waiting.  really just want to wait a little
-       lapic_set_timer(0x0000ffff, 0xf0, 0); // TODO - fix timing
+       lapic_set_timer(0x0000ffff, boot_vector, 0); // TODO - fix timing
        // set the function handler to respond to this
-       register_interrupt_handler(interrupt_handlers, 0xf0, smp_boot_handler);
+       register_interrupt_handler(interrupt_handlers, boot_vector, smp_boot_handler);
 
        // Start the IPI process (INIT, wait, SIPI, wait, SIPI, wait)
        send_init_ipi();
@@ -63,14 +65,14 @@ void smp_boot(void)
        // first SIPI
        waiting = 1;
        send_startup_ipi(0x01);
-       lapic_set_timer(SMP_BOOT_TIMEOUT, 0xf0, 0); // TODO - fix timing
+       lapic_set_timer(SMP_BOOT_TIMEOUT, boot_vector, 0); // TODO - fix timing
        while(waiting) // wait for the first SIPI to take effect
                cpu_relax();
        /* //BOCHS does not like this second SIPI.
        // second SIPI
        waiting = 1;
        send_startup_ipi(0x01);
-       lapic_set_timer(0x000fffff, 0xf0, 0); // TODO - fix timing
+       lapic_set_timer(0x000fffff, boot_vector, 0); // TODO - fix timing
        while(waiting) // wait for the second SIPI to take effect
                cpu_relax();
        */
@@ -80,7 +82,7 @@ void smp_boot(void)
        // 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));
+       while(*(volatile uint32_t*)(&smp_semaphore - &smp_entry + trampoline_pg));
 
        // From here on, no other cores are coming up.  Grab the lock to ensure it.
        // Another core could be in it's prelock phase and be trying to grab the lock
@@ -91,21 +93,24 @@ void smp_boot(void)
        // 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));
+       spin_lock((uint32_t*)(&smp_boot_lock - &smp_entry + trampoline_pg));
        cprintf("Num_Cpus Detected: %d\n", num_cpus);
 
        // Deregister smp_boot_handler
-       register_interrupt_handler(interrupt_handlers, 0xf0, 0);
+       register_interrupt_handler(interrupt_handlers, boot_vector, 0);
        // Remove the mapping of the page used by the trampoline
-       page_remove(boot_pgdir, (void*)0x00001000);
+       page_remove(boot_pgdir, (void*)trampoline_pg);
        // It had a refcount of 2 earlier, so we need to dec once more to free it
        // 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));
+               page_decref(pa2page(trampoline_pg));
        // Dealloc the temp shared stack
        page_decref(smp_stack);
 
+       // Set up the generic remote function call facility
+       init_smp_call_function();
+
        // Set up all cores to use the proper MTRRs
        init_barrier(&generic_barrier, num_cpus); // barrier used by smp_mtrr_handler
        smp_call_function_all(smp_mtrr_handler, 0);
@@ -188,17 +193,77 @@ uint32_t smp_main(void)
        return my_stack_top; // will be loaded in smp_entry.S
 }
 
-// as far as completion mechs go, we might want a bit mask that every sender 
-// has to toggle.  or a more general barrier that works on a queue / LL, 
-// instead of everyone.  TODO!
-static void smp_call_function(uint8_t type, uint8_t dest, isr_t handler, uint8_t vector)
+// checklists to protect the global interrupt_handlers for 0xf0, f1, f2, f3, f4
+// need to be global, since there is no function that will always exist for them
+INIT_CHECKLIST(handler_front_f0, MAX_NUM_CPUS);
+INIT_CHECKLIST(handler_back_f0, MAX_NUM_CPUS);
+
+handler_wrapper_t handler_wrappers[5];
+
+static void init_smp_call_function(void)
+{
+       handler_wrappers[0].vector = 0xf0;
+       handler_wrappers[0].frontend = &handler_front_f0;
+       handler_wrappers[0].frontend->mask.size = num_cpus;
+       handler_wrappers[0].backend = &handler_back_f0;
+       handler_wrappers[0].backend->mask.size = num_cpus;
+}
+
+// could have the backend checklists static/global too.  
+// or at least have the pointers global (save a little RAM)
+
+static void smp_call_function(uint8_t type, uint8_t dest, isr_t handler, bool wait)
 {
        extern isr_t interrupt_handlers[];
-       uint32_t i, amount = SMP_CALL_FUNCTION_TIMEOUT; // should calibrate this!!  just remove it!
        int8_t state = 0;
+       uint8_t vector;
+
+wait = 1;
 
-       if (!vector)
-               vector = 0xf1; //default value
+       // build the mask based on the type and destination
+       INIT_CHECKLIST_MASK(handler_mask, MAX_NUM_CPUS);
+       // set checklist mask's size dynamically to the num cpus actually present
+       handler_mask.size = num_cpus;
+       switch (type) {
+               case 1: // self
+                       SET_BITMASK_BIT(handler_mask.bits, lapic_get_id());
+                       break;
+               case 2: // all
+                       FILL_BITMASK(handler_mask.bits, num_cpus);
+                       break;
+               case 3: // all but self
+                       FILL_BITMASK(handler_mask.bits, num_cpus);
+                       CLR_BITMASK_BIT(handler_mask.bits, lapic_get_id());
+                       break;
+               case 4: // physical mode
+                       // note this only supports sending to one specific physical id
+                       // (only sets one bit, so if multiple cores have the same phys id
+                       // the first one through will set this).
+                       SET_BITMASK_BIT(handler_mask.bits, dest);
+                       break;
+               case 5: // logical mode
+                       // TODO
+                       warn("Logical mode bitmask handler protection not implemented!");
+                       break;
+               default:
+                       panic("Invalid type for cross-core function call!");
+       }
+
+       // TODO find an available vector
+       // just use the first for now.  should do a loop, checking error codes
+       // (requires adaptive support or something in atomic.c)
+       // need a way to return what vector number we are too
+       vector = 0xf0;
+       // the waiting happens on the contention by the next one
+       commit_checklist_nowait(handler_wrappers[0].frontend, &handler_mask);
+
+       // if we want to wait, we set the mask for our backend too
+       // no contention here, since this one is protected by the mask being present
+       // in the frontend
+       if (wait)
+               commit_checklist_wait(handler_wrappers[0].backend, &handler_mask);
+
+       // now register our handler to run
        register_interrupt_handler(interrupt_handlers, vector, handler);
        // WRITE MEMORY BARRIER HERE
        enable_irqsave(&state);
@@ -222,16 +287,14 @@ static void smp_call_function(uint8_t type, uint8_t dest, isr_t handler, uint8_t
                default:
                        panic("Invalid type for cross-core function call!");
        }
-       // wait some arbitrary amount til we think all the cores could be done.
-       // very wonky without an idea of how long the function takes.
-       // maybe should think of some sort of completion notification mech
-       for (i = 0; i < amount; i++)
-               asm volatile("nop;");
+       // wait long enough to receive our own broadcast (PROBABLY WORKS) TODO
+       lapic_wait_to_send();
        disable_irqsave(&state);
-       // TODO
-       // consider doing this, but we can't remove it before the receiver is done
-       //register_interrupt_handler(interrupt_handlers, vector, 0);
-       // we also will have issues if we call this function again too quickly
+       
+       // no longer need to wait to protect the vector (the checklist does that)
+       // if we want to wait, we need to wait on the backend checklist
+       if (wait)
+               waiton_checklist(handler_wrappers[0].backend);
 }
 
 // I'd rather have these functions take an arbitrary function and arguments...
index ac70f35..2090f7d 100644 (file)
@@ -6,6 +6,7 @@
 #include <inc/types.h>
 
 #include <kern/trap.h>
+#include <kern/atomic.h>
 
 #ifdef __BOCHS__
 #define SMP_CALL_FUNCTION_TIMEOUT    0x00ffffff
 #define SMP_BOOT_TIMEOUT             0x002fffff
 #endif
 
+typedef struct handler_wrapper {
+       checklist_t* frontend;
+       checklist_t* backend;
+       uint8_t vector;
+} handler_wrapper_t;
+
 void smp_boot(void);
 
 void smp_call_function_self(isr_t handler, uint8_t vector);
index 3d0b12c..232f68b 100644 (file)
@@ -13,6 +13,7 @@
 #include <kern/env.h>
 #include <kern/syscall.h>
 #include <kern/apic.h>
+#include <kern/smp.h>
 
 static taskstate_t ts;
 
@@ -69,7 +70,7 @@ void
 idt_init(void)
 {
        extern segdesc_t gdt[];
-       
+
        // This table is made in trapentry.S by each macro in that file.
        // It is layed out such that the ith entry is the ith's traphandler's
        // (uint32_t) trap addr, then (uint32_t) trap number
@@ -82,14 +83,14 @@ idt_init(void)
        // set all to default, to catch everything
        for(i = 0; i < 256; i++)
                SETGATE(idt[i], 0, GD_KT, &ISR_default, 0);
-       
+
        // set all entries that have real trap handlers
        // we need to stop short of the last one, since the last is the default
        // handler with a fake interrupt number (500) that is out of bounds of
        // the idt[]
        // if we set these to trap gates, be sure to handle the IRQs separately
        // and we might need to break our pretty tables
-       for(i = 0; i < trap_tbl_size - 1; i++) 
+       for(i = 0; i < trap_tbl_size - 1; i++)
                SETGATE(idt[trap_tbl[i].trapnumber], 0, GD_KT, trap_tbl[i].trapaddr, 0);
 
        // turn on syscall handling and other user-accessible ints
@@ -116,7 +117,7 @@ idt_init(void)
        // This will go away when we start using the IOAPIC properly
        pic_remap();
        // set LINT0 to receive ExtINTs (KVM's default).  At reset they are 0x1000.
-       write_mmreg32(LAPIC_LVT_LINT0, 0x700); 
+       write_mmreg32(LAPIC_LVT_LINT0, 0x700);
        // mask it to shut it up for now
        mask_lapic_lvt(LAPIC_LVT_LINT0);
        // and turn it on
@@ -156,7 +157,7 @@ static void
 (IN_HANDLER trap_dispatch)(trapframe_t *tf)
 {
        // Handle processor exceptions.
-       
+
        switch(tf->tf_trapno) {
                case T_BRKPT:
                        while (1)
@@ -169,9 +170,9 @@ static void
                case T_SYSCALL:
                        // check for userspace, for now
                        assert(tf->tf_cs != GD_KT);
-                       tf->tf_regs.reg_eax = 
-                               syscall(tf->tf_regs.reg_eax, tf->tf_regs.reg_edx, 
-                                       tf->tf_regs.reg_ecx, tf->tf_regs.reg_ebx, 
+                       tf->tf_regs.reg_eax =
+                               syscall(tf->tf_regs.reg_eax, tf->tf_regs.reg_edx,
+                                       tf->tf_regs.reg_ecx, tf->tf_regs.reg_ebx,
                                        tf->tf_regs.reg_edi, tf->tf_regs.reg_esi);
                        env_run(curenv);
                        break;
@@ -208,7 +209,7 @@ void
                // The trapframe on the stack should be ignored from here on.
                tf = &curenv->env_tf;
        }
-       
+
        // Dispatch based on what type of trap occurred
        trap_dispatch(tf);
 
@@ -227,6 +228,8 @@ void
        //      cprintf("Incoming IRQ, ISR: %d on core %d\n", tf->tf_trapno, lapic_get_id());
        // merge this with alltraps?  other than the EOI... or do the same in all traps
 
+       extern handler_wrapper_t handler_wrappers[5];
+
        // determine the interrupt handler table to use.  for now, pick the global
        isr_t* handler_table = interrupt_handlers;
 
@@ -234,6 +237,15 @@ void
        if (handler_table[tf->tf_trapno] != 0)
                handler_table[tf->tf_trapno](tf);
 
+       // if we're a general purpose IPI function call, toggle the front 
+       // and back ends.
+       // TODO - do the front end before actually servicing the call
+       // TODO - cover the range of 0xf0..f4, and don't hardcode it
+       if (tf->tf_trapno == 0xf0) {
+               down_checklist(handler_wrappers[0].frontend);
+               down_checklist(handler_wrappers[0].backend);
+       }
+
        // Send EOI.  might want to do this in assembly, and possibly earlier
        // This is set up to work with an old PIC for now
        // Convention is that all IRQs between 32 and 47 are for the PIC.
@@ -261,7 +273,7 @@ page_fault_handler(trapframe_t *tf)
        fault_va = rcr2();
 
        // Handle kernel-mode page faults.
-       
+
        // TODO - one day, we'll want to handle this.
        if ((tf->tf_cs & 3) == 0)
                panic("Page Fault in the Kernel!");
@@ -293,7 +305,7 @@ page_fault_handler(trapframe_t *tf)
        //   user_mem_assert() and env_run() are useful here.
        //   To change what the user environment runs, modify 'curenv->env_tf'
        //   (the 'tf' variable points at 'curenv->env_tf').
-       
+
        // LAB 4: Your code here.
 
        // Destroy the environment that caused the fault.