ARM: Detect and skip udelay() functions in linux kernel.
authorAli Saidi <Ali.Saidi@ARM.com>
Fri, 18 Mar 2011 00:20:20 +0000 (19:20 -0500)
committerAli Saidi <Ali.Saidi@ARM.com>
Fri, 18 Mar 2011 00:20:20 +0000 (19:20 -0500)
This change speeds up booting, especially in MP cases, by not executing
udelay() on the core but instead skipping ahead tha amount of time that is being
delayed.

src/arch/arm/linux/system.cc
src/arch/arm/linux/system.hh
src/cpu/simple/atomic.cc
src/cpu/simple/timing.cc
src/kern/linux/events.cc
src/kern/linux/events.hh

index 38024c0588d644702cac6672ed135138156bc8c9..7aff2b6ef2e3a9fee1660120497d3a2de028b3a8 100644 (file)
 #include "base/loader/object_file.hh"
 #include "base/loader/symtab.hh"
 #include "cpu/thread_context.hh"
+#include "kern/linux/events.hh"
 #include "mem/physical.hh"
 
 using namespace ArmISA;
+using namespace Linux;
 
 LinuxArmSystem::LinuxArmSystem(Params *p)
     : ArmSystem(p)
@@ -96,6 +98,24 @@ LinuxArmSystem::LinuxArmSystem(Params *p)
     if (!kernelPanicEvent)
         panic("could not find kernel symbol \'panic\'");
 #endif
+
+    // With ARM udelay() is #defined to __udelay
+    Addr addr = 0;
+    if (kernelSymtab->findAddress("__udelay", addr)) {
+        uDelaySkipEvent = new UDelayEvent(&pcEventQueue, "__udelay",
+                fixFuncEventAddr(addr), 1000, 0);
+    } else {
+        panic("couldn't find kernel symbol \'udelay\'");
+    }
+
+    // constant arguments to udelay() have some precomputation done ahead of
+    // time. Constant comes from code.
+    if (kernelSymtab->findAddress("__const_udelay", addr)) {
+        constUDelaySkipEvent = new UDelayEvent(&pcEventQueue, "__const_udelay",
+                fixFuncEventAddr(addr), 1000, 107374);
+    } else {
+        panic("couldn't find kernel symbol \'udelay\'");
+    }
 }
 
 void
@@ -115,6 +135,10 @@ LinuxArmSystem::initState()
 
 LinuxArmSystem::~LinuxArmSystem()
 {
+    if (uDelaySkipEvent)
+        delete uDelaySkipEvent;
+    if (constUDelaySkipEvent)
+        delete constUDelaySkipEvent;
 }
 
 LinuxArmSystem *
index 4e5ebcd7353292c292fc8b462e915e0d9296f8b1..2ef65fea28aad97b4e2da3b92d72a4d1fb42d967 100644 (file)
@@ -74,6 +74,19 @@ class LinuxArmSystem : public ArmSystem
     /** Event to halt the simulator if the kernel calls panic()  */
     BreakPCEvent *kernelPanicEvent;
 #endif
+    /**
+     * PC based event to skip udelay(<time>) calls and quiesce the
+     * processor for the appropriate amount of time. This is not functionally
+     * required but does speed up simulation.
+     */
+    Linux::UDelayEvent *uDelaySkipEvent;
+
+    /** Another PC based skip event for const_udelay(). Similar to the udelay
+     * skip, but this function precomputes the first multiply that is done
+     * in the generic case since the parameter is known at compile time.
+     * Thus we need to do some division to get back to us.
+     */
+    Linux::UDelayEvent *constUDelaySkipEvent;
 };
 
 #endif // __ARCH_ARM_LINUX_SYSTEM_HH__
index 27635d3ce680c395a0eb7f95cd51fbd41c3fc254..6aa0eb64e8e154f657f62afd17e1e36fe3fd29ad 100644 (file)
@@ -641,6 +641,9 @@ AtomicSimpleCPU::tick()
             checkForInterrupts();
 
         checkPcEventQueue();
+        // We must have just got suspended by a PC event
+        if (_status == Idle)
+            return;
 
         Fault fault = NoFault;
 
index 632e83356d128ac8903e508426803efc9a4161ea..aca48e5d4550edce98d5f39123fc5ac603f02da3 100644 (file)
@@ -714,6 +714,10 @@ TimingSimpleCPU::fetch()
 
     checkPcEventQueue();
 
+    // We must have just got suspended by a PC event
+    if (_status == Idle)
+        return;
+
     TheISA::PCState pcState = thread->pcState();
     bool needToFetch = !isRomMicroPC(pcState.microPC()) && !curMacroStaticInst;
 
index 60aa857acac8cafb832910ebd2338614ea1a0378..75c2b6f7f39c2a3cbd77b623ec3fe49c1fa1451f 100644 (file)
 #include <sstream>
 
 #include "base/trace.hh"
+#include "arch/utility.hh"
 #include "cpu/thread_context.hh"
 #include "kern/linux/events.hh"
 #include "kern/linux/printk.hh"
 #include "kern/system_events.hh"
 #include "sim/arguments.hh"
+#include "sim/pseudo_inst.hh"
 #include "sim/system.hh"
 
 namespace Linux {
@@ -66,4 +68,27 @@ DebugPrintkEvent::process(ThreadContext *tc)
     SkipFuncEvent::process(tc);
 }
 
+void
+UDelayEvent::process(ThreadContext *tc)
+{
+    int arg_num  = 0;
+
+    // Get the time in native size
+    uint64_t time = TheISA::getArgument(tc, arg_num,  (uint16_t)-1, false);
+
+    // convert parameter to ns
+    if (argDivToNs)
+        time /= argDivToNs;
+
+    time *= argMultToNs;
+
+    // Convert ns to ticks
+    time *= SimClock::Int::ns;
+
+    SkipFuncEvent::process(tc);
+
+    PseudoInst::quiesceNs(tc, time);
+}
+
+
 } // namespace linux
index e36a72ddea2051177f3fc9488cf6fd54a37119b0..3f5f2526f422c8f7272419713cd831da8274d16e 100644 (file)
@@ -44,6 +44,33 @@ class DebugPrintkEvent : public SkipFuncEvent
     virtual void process(ThreadContext *xc);
 };
 
+/** A class to skip udelay() and related calls in the kernel.
+ * This class has two additional parameters that take the argument to udelay and
+ * manipulated it to come up with ns and eventually ticks to quiesce for.
+ * See descriptions of argDivToNs and argMultToNs below.
+ */
+class UDelayEvent : public SkipFuncEvent
+{
+  private:
+    /** value to divide arg by to create ns. This is present beacues the linux
+     * kernel code sometime precomputes the first multiply that is done in
+     * udelay() if the parameter is a constant. We need to undo it so here is
+     * how. */
+    uint64_t argDivToNs;
+
+    /** value to multiple arg by to create ns. Nominally, this is 1000 to
+     * convert us to ns, but since linux can do some preprocessing of constant
+     * values something else might be required. */
+    uint64_t argMultToNs;
+
+  public:
+    UDelayEvent(PCEventQueue *q, const std::string &desc, Addr addr,
+            uint64_t mult, uint64_t div)
+        : SkipFuncEvent(q, desc, addr), argDivToNs(div), argMultToNs(mult) {}
+    virtual void process(ThreadContext *xc);
+};
+
+
 }
 
 #endif