kern, arm: Dump dmesg on kernel panic/oops
[gem5.git] / src / arch / arm / linux / system.cc
index 7aff2b6ef2e3a9fee1660120497d3a2de028b3a8..311d81a37b2bed5179d26b06452a6fe545f2fbfa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 ARM Limited
+ * Copyright (c) 2010-2013, 2016 ARM Limited
  * All rights reserved
  *
  * The license below extends only to copyright in the software and shall
  * Authors: Ali Saidi
  */
 
-#include "arch/arm/isa_traits.hh"
 #include "arch/arm/linux/atag.hh"
 #include "arch/arm/linux/system.hh"
+#include "arch/arm/isa_traits.hh"
 #include "arch/arm/utility.hh"
+#include "arch/generic/linux/threadinfo.hh"
+#include "base/loader/dtb_object.hh"
 #include "base/loader/object_file.hh"
 #include "base/loader/symtab.hh"
+#include "cpu/base.hh"
+#include "cpu/pc_event.hh"
 #include "cpu/thread_context.hh"
+#include "debug/Loader.hh"
 #include "kern/linux/events.hh"
+#include "kern/linux/helpers.hh"
+#include "mem/fs_translating_port_proxy.hh"
 #include "mem/physical.hh"
+#include "sim/stat_control.hh"
 
 using namespace ArmISA;
 using namespace Linux;
 
 LinuxArmSystem::LinuxArmSystem(Params *p)
-    : ArmSystem(p)
+    : GenericArmSystem(p), dumpStatsPCEvent(nullptr),
+      enableContextSwitchStatsDump(p->enable_context_switch_stats_dump),
+      taskFile(nullptr), kernelPanicEvent(nullptr), kernelOopsEvent(nullptr)
+{
+    const std::string dmesg_output = name() + ".dmesg";
+    if (p->panic_on_panic) {
+        kernelPanicEvent = addKernelFuncEventOrPanic<Linux::KernelPanicEvent>(
+            "panic", "Kernel panic in simulated kernel", dmesg_output);
+    } else {
+        kernelPanicEvent = addKernelFuncEventOrPanic<Linux::DmesgDumpEvent>(
+            "panic", "Kernel panic in simulated kernel", dmesg_output);
+    }
+
+    if (p->panic_on_oops) {
+        kernelOopsEvent = addKernelFuncEventOrPanic<Linux::KernelPanicEvent>(
+            "oops_exit", "Kernel oops in guest", dmesg_output);
+    } else {
+        kernelOopsEvent = addKernelFuncEventOrPanic<Linux::DmesgDumpEvent>(
+            "oops_exit", "Kernel oops in guest", dmesg_output);
+    }
+
+    // With ARM udelay() is #defined to __udelay
+    // newer kernels use __loop_udelay and __loop_const_udelay symbols
+    uDelaySkipEvent = addKernelFuncEvent<UDelayEvent>(
+        "__loop_udelay", "__udelay", 1000, 0);
+    if (!uDelaySkipEvent)
+        uDelaySkipEvent = addKernelFuncEventOrPanic<UDelayEvent>(
+         "__udelay", "__udelay", 1000, 0);
+
+    // constant arguments to udelay() have some precomputation done ahead of
+    // time. Constant comes from code.
+    constUDelaySkipEvent = addKernelFuncEvent<UDelayEvent>(
+        "__loop_const_udelay", "__const_udelay", 1000, 107374);
+    if (!constUDelaySkipEvent)
+        constUDelaySkipEvent = addKernelFuncEventOrPanic<UDelayEvent>(
+         "__const_udelay", "__const_udelay", 1000, 107374);
+
+}
+
+void
+LinuxArmSystem::initState()
 {
+    // Moved from the constructor to here since it relies on the
+    // address map being resolved in the interconnect
+
+    // Call the initialisation of the super class
+    GenericArmSystem::initState();
+
     // Load symbols at physical address, we might not want
-    // to do this perminately, for but early bootup work
-    // it is helpfulp.
-    kernel->loadGlobalSymbols(kernelSymtab, loadAddrMask);
-    kernel->loadGlobalSymbols(debugSymbolTable, loadAddrMask);
+    // to do this permanently, for but early bootup work
+    // it is helpful.
+    if (params()->early_kernel_symbols) {
+        kernel->loadGlobalSymbols(kernelSymtab, 0, 0, loadAddrMask);
+        kernel->loadGlobalSymbols(debugSymbolTable, 0, 0, loadAddrMask);
+    }
 
     // Setup boot data structure
-    AtagCore *ac = new AtagCore;
-    ac->flags(1); // read-only
-    ac->pagesize(8192);
-    ac->rootdev(0);
+    Addr addr = 0;
+    // Check if the kernel image has a symbol that tells us it supports
+    // device trees.
+    bool kernel_has_fdt_support =
+        kernelSymtab->findAddress("unflatten_device_tree", addr);
+    bool dtb_file_specified = params()->dtb_filename != "";
 
-    AtagMem *am = new AtagMem;
-    am->memSize(params()->physmem->size());
-    am->memStart(params()->physmem->start());
+    if (kernel_has_fdt_support && dtb_file_specified) {
+        // Kernel supports flattened device tree and dtb file specified.
+        // Using Device Tree Blob to describe system configuration.
+        inform("Loading DTB file: %s at address %#x\n", params()->dtb_filename,
+                params()->atags_addr + loadAddrOffset);
 
-    AtagCmdline *ad = new AtagCmdline;
-    ad->cmdline(params()->boot_osflags);
+        ObjectFile *dtb_file = createObjectFile(params()->dtb_filename, true);
+        if (!dtb_file) {
+            fatal("couldn't load DTB file: %s\n", params()->dtb_filename);
+        }
 
-    DPRINTF(Loader, "boot command line %d bytes: %s\n", ad->size() <<2, params()->boot_osflags.c_str());
+        DtbObject *_dtb_file = dynamic_cast<DtbObject*>(dtb_file);
 
-    AtagNone *an = new AtagNone;
+        if (_dtb_file) {
+            if (!_dtb_file->addBootCmdLine(params()->boot_osflags.c_str(),
+                                           params()->boot_osflags.size())) {
+                warn("couldn't append bootargs to DTB file: %s\n",
+                     params()->dtb_filename);
+            }
+        } else {
+            warn("dtb_file cast failed; couldn't append bootargs "
+                 "to DTB file: %s\n", params()->dtb_filename);
+        }
 
-    uint32_t size = ac->size() + am->size() + ad->size() + an->size();
-    uint32_t offset = 0;
-    uint8_t *boot_data = new uint8_t[size << 2];
+        dtb_file->setTextBase(params()->atags_addr + loadAddrOffset);
+        dtb_file->loadSections(physProxy);
+        delete dtb_file;
+    } else {
+        // Using ATAGS
+        // Warn if the kernel supports FDT and we haven't specified one
+        if (kernel_has_fdt_support) {
+            assert(!dtb_file_specified);
+            warn("Kernel supports device tree, but no DTB file specified\n");
+        }
+        // Warn if the kernel doesn't support FDT and we have specified one
+        if (dtb_file_specified) {
+            assert(!kernel_has_fdt_support);
+            warn("DTB file specified, but no device tree support in kernel\n");
+        }
 
-    offset += ac->copyOut(boot_data + offset);
-    offset += am->copyOut(boot_data + offset);
-    offset += ad->copyOut(boot_data + offset);
-    offset += an->copyOut(boot_data + offset);
+        AtagCore ac;
+        ac.flags(1); // read-only
+        ac.pagesize(8192);
+        ac.rootdev(0);
 
-    DPRINTF(Loader, "Boot atags was %d bytes in total\n", size << 2);
-    DDUMP(Loader, boot_data, size << 2);
+        AddrRangeList atagRanges = physmem.getConfAddrRanges();
+        if (atagRanges.size() != 1) {
+            fatal("Expected a single ATAG memory entry but got %d\n",
+                  atagRanges.size());
+        }
+        AtagMem am;
+        am.memSize(atagRanges.begin()->size());
+        am.memStart(atagRanges.begin()->start());
 
-    functionalPort->writeBlob(ParamsList, boot_data, size << 2);
+        AtagCmdline ad;
+        ad.cmdline(params()->boot_osflags);
 
-#ifndef NDEBUG
-    kernelPanicEvent = addKernelFuncEvent<BreakPCEvent>("panic");
-    if (!kernelPanicEvent)
-        panic("could not find kernel symbol \'panic\'");
-#endif
+        DPRINTF(Loader, "boot command line %d bytes: %s\n",
+                ad.size() <<2, params()->boot_osflags.c_str());
 
-    // 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\'");
-    }
+        AtagNone an;
 
-    // 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\'");
-    }
-}
+        uint32_t size = ac.size() + am.size() + ad.size() + an.size();
+        uint32_t offset = 0;
+        uint8_t *boot_data = new uint8_t[size << 2];
 
-void
-LinuxArmSystem::initState()
-{
-    ArmSystem::initState();
-    ThreadContext *tc = threadContexts[0];
+        offset += ac.copyOut(boot_data + offset);
+        offset += am.copyOut(boot_data + offset);
+        offset += ad.copyOut(boot_data + offset);
+        offset += an.copyOut(boot_data + offset);
+
+        DPRINTF(Loader, "Boot atags was %d bytes in total\n", size << 2);
+        DDUMP(Loader, boot_data, size << 2);
 
-    // Set the initial PC to be at start of the kernel code
-    tc->pcState(tc->getSystemPtr()->kernelEntry & loadAddrMask);
+        physProxy.writeBlob(params()->atags_addr + loadAddrOffset, boot_data,
+                size << 2);
 
-    // Setup the machine type
-    tc->setIntReg(0, 0);
-    tc->setIntReg(1, params()->machine_type);
-    tc->setIntReg(2, ParamsList);
+        delete[] boot_data;
+    }
+
+    // Kernel boot requirements to set up r0, r1 and r2 in ARMv7
+    for (int i = 0; i < threadContexts.size(); i++) {
+        threadContexts[i]->setIntReg(0, 0);
+        threadContexts[i]->setIntReg(1, params()->machine_type);
+        threadContexts[i]->setIntReg(2, params()->atags_addr + loadAddrOffset);
+    }
 }
 
 LinuxArmSystem::~LinuxArmSystem()
@@ -139,6 +220,9 @@ LinuxArmSystem::~LinuxArmSystem()
         delete uDelaySkipEvent;
     if (constUDelaySkipEvent)
         delete constUDelaySkipEvent;
+
+    if (dumpStatsPCEvent)
+        delete dumpStatsPCEvent;
 }
 
 LinuxArmSystem *
@@ -146,3 +230,101 @@ LinuxArmSystemParams::create()
 {
     return new LinuxArmSystem(this);
 }
+
+void
+LinuxArmSystem::startup()
+{
+    if (enableContextSwitchStatsDump) {
+        dumpStatsPCEvent = addKernelFuncEvent<DumpStatsPCEvent>("__switch_to");
+        if (!dumpStatsPCEvent)
+           panic("dumpStatsPCEvent not created!");
+
+        std::string task_filename = "tasks.txt";
+        taskFile = simout.create(name() + "." + task_filename);
+
+        for (int i = 0; i < _numContexts; i++) {
+            ThreadContext *tc = threadContexts[i];
+            uint32_t pid = tc->getCpuPtr()->getPid();
+            if (pid != BaseCPU::invldPid) {
+                mapPid(tc, pid);
+                tc->getCpuPtr()->taskId(taskMap[pid]);
+            }
+        }
+    }
+}
+
+void
+LinuxArmSystem::mapPid(ThreadContext *tc, uint32_t pid)
+{
+    // Create a new unique identifier for this pid
+    std::map<uint32_t, uint32_t>::iterator itr = taskMap.find(pid);
+    if (itr == taskMap.end()) {
+        uint32_t map_size = taskMap.size();
+        if (map_size > ContextSwitchTaskId::MaxNormalTaskId + 1) {
+            warn_once("Error out of identifiers for cache occupancy stats");
+            taskMap[pid] = ContextSwitchTaskId::Unknown;
+        } else {
+            taskMap[pid] = map_size;
+        }
+    }
+}
+
+void
+LinuxArmSystem::dumpDmesg()
+{
+    Linux::dumpDmesg(getThreadContext(0), std::cout);
+}
+
+/** This function is called whenever the the kernel function
+ *  "__switch_to" is called to change running tasks.
+ *
+ *  r0 = task_struct of the previously running process
+ *  r1 = task_info of the previously running process
+ *  r2 = task_info of the next process to run
+ */
+void
+DumpStatsPCEvent::process(ThreadContext *tc)
+{
+    Linux::ThreadInfo ti(tc);
+    Addr task_descriptor = tc->readIntReg(2);
+    uint32_t pid = ti.curTaskPID(task_descriptor);
+    uint32_t tgid = ti.curTaskTGID(task_descriptor);
+    std::string next_task_str = ti.curTaskName(task_descriptor);
+
+    // Streamline treats pid == -1 as the kernel process.
+    // Also pid == 0 implies idle process (except during Linux boot)
+    int32_t mm = ti.curTaskMm(task_descriptor);
+    bool is_kernel = (mm == 0);
+    if (is_kernel && (pid != 0)) {
+        pid = -1;
+        tgid = -1;
+        next_task_str = "kernel";
+    }
+
+    LinuxArmSystem* sys = dynamic_cast<LinuxArmSystem *>(tc->getSystemPtr());
+    if (!sys) {
+        panic("System is not LinuxArmSystem while getting Linux process info!");
+    }
+    std::map<uint32_t, uint32_t>& taskMap = sys->taskMap;
+
+    // Create a new unique identifier for this pid
+    sys->mapPid(tc, pid);
+
+    // Set cpu task id, output process info, and dump stats
+    tc->getCpuPtr()->taskId(taskMap[pid]);
+    tc->getCpuPtr()->setPid(pid);
+
+    OutputStream* taskFile = sys->taskFile;
+
+    // Task file is read by cache occupancy plotting script or
+    // Streamline conversion script.
+    ccprintf(*(taskFile->stream()),
+             "tick=%lld %d cpu_id=%d next_pid=%d next_tgid=%d next_task=%s\n",
+             curTick(), taskMap[pid], tc->cpuId(), (int) pid, (int) tgid,
+             next_task_str);
+    taskFile->stream()->flush();
+
+    // Dump and reset statistics
+    Stats::schedStatEvent(true, true, curTick(), 0);
+}
+