blob: d5120673d79ae602e8c0af1d9f98e6b1093c4937 [file] [log] [blame]
/*
* Copyright (c) 2010-2013, 2016, 2020 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Copyright (c) 2002-2006 The Regents of The University of Michigan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "arch/arm/linux/fs_workload.hh"
#include "arch/arm/linux/atag.hh"
#include "arch/arm/system.hh"
#include "arch/arm/utility.hh"
#include "arch/generic/linux/threadinfo.hh"
#include "base/loader/dtb_file.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 "kern/system_events.hh"
#include "mem/physical.hh"
#include "sim/stat_control.hh"
namespace gem5
{
using namespace linux;
namespace ArmISA
{
FsLinux::FsLinux(const Params &p) : ArmISA::FsWorkload(p),
enableContextSwitchStatsDump(p.enable_context_switch_stats_dump)
{}
void
FsLinux::initState()
{
ArmISA::FsWorkload::initState();
// Load symbols at physical address, we might not want
// to do this permanently, for but early bootup work
// it is helpful.
if (params().early_kernel_symbols) {
auto phys_globals = kernelObj->symtab().globals()->mask(_loadAddrMask);
kernelSymtab.insert(*phys_globals);
loader::debugSymbolTable.insert(*phys_globals);
}
// Setup boot data structure
// Check if the kernel image has a symbol that tells us it supports
// device trees.
bool kernel_has_fdt_support =
kernelSymtab.find("unflatten_device_tree") != kernelSymtab.end();
bool dtb_file_specified = params().dtb_filename != "";
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().dtb_addr);
auto *dtb_file = new loader::DtbFile(params().dtb_filename);
if (!dtb_file->addBootCmdLine(
commandLine.c_str(), commandLine.size())) {
warn("couldn't append bootargs to DTB file: %s\n",
params().dtb_filename);
}
dtb_file->buildImage().offset(params().dtb_addr)
.write(system->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");
}
AtagCore ac;
ac.flags(1); // read-only
ac.pagesize(8192);
ac.rootdev(0);
AddrRangeList atagRanges = system->getPhysMem().getConfAddrRanges();
fatal_if(atagRanges.size() != 1,
"Expected a single ATAG memory entry but got %d",
atagRanges.size());
AtagMem am;
am.memSize(atagRanges.begin()->size());
am.memStart(atagRanges.begin()->start());
AtagCmdline ad;
ad.cmdline(commandLine);
DPRINTF(Loader, "boot command line %d bytes: %s\n",
ad.size() << 2, commandLine);
AtagNone an;
uint32_t size = ac.size() + am.size() + ad.size() + an.size();
uint32_t offset = 0;
uint8_t *boot_data = new uint8_t[size << 2];
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);
system->physProxy.writeBlob(params().dtb_addr,
boot_data, size << 2);
delete[] boot_data;
}
if (getArch() == loader::Arm64) {
// We inform the bootloader of the kernel entry point. This was added
// originally done because the entry offset changed in kernel v5.8.
// Previously the bootloader just used a hardcoded address.
for (auto *tc: system->threads) {
tc->setIntReg(0, params().dtb_addr);
tc->setIntReg(5, params().cpu_release_addr);
}
} else {
// Kernel boot requirements to set up r0, r1 and r2 in ARMv7
for (auto *tc: system->threads) {
tc->setIntReg(0, 0);
tc->setIntReg(1, params().machine_type);
tc->setIntReg(2, params().dtb_addr);
}
}
}
FsLinux::~FsLinux()
{
delete debugPrintk;
delete skipUDelay;
delete skipConstUDelay;
delete kernelOops;
delete kernelPanic;
delete dumpStats;
}
void
FsLinux::startup()
{
FsWorkload::startup();
if (enableContextSwitchStatsDump) {
if (getArch() == loader::Arm64)
dumpStats = addKernelFuncEvent<DumpStats64>("__switch_to");
else
dumpStats = addKernelFuncEvent<DumpStats>("__switch_to");
panic_if(!dumpStats, "dumpStats not created!");
std::string task_filename = "tasks.txt";
taskFile = simout.create(name() + "." + task_filename);
for (auto *tc: system->threads) {
uint32_t pid = tc->getCpuPtr()->getPid();
if (pid != BaseCPU::invldPid) {
mapPid(tc, pid);
tc->getCpuPtr()->taskId(taskMap[pid]);
}
}
}
const std::string dmesg_output = name() + ".dmesg";
if (params().panic_on_panic) {
kernelPanic = addKernelFuncEventOrPanic<linux::KernelPanic>(
"panic", "Kernel panic in simulated kernel", dmesg_output);
} else {
kernelPanic = addKernelFuncEventOrPanic<linux::DmesgDump>(
"panic", "Kernel panic in simulated kernel", dmesg_output);
}
if (params().panic_on_oops) {
kernelOops = addKernelFuncEventOrPanic<linux::KernelPanic>(
"oops_exit", "Kernel oops in guest", dmesg_output);
} else {
kernelOops = addKernelFuncEventOrPanic<linux::DmesgDump>(
"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
skipUDelay = addSkipFunc<SkipUDelay>(
"__loop_udelay", "__udelay", 1000, 0);
if (!skipUDelay) {
skipUDelay = addSkipFuncOrPanic<SkipUDelay>(
"__udelay", "__udelay", 1000, 0);
}
// constant arguments to udelay() have some precomputation done ahead of
// time. Constant comes from code.
skipConstUDelay = addSkipFunc<SkipUDelay>(
"__loop_const_udelay", "__const_udelay", 1000, 107374);
if (!skipConstUDelay) {
skipConstUDelay = addSkipFuncOrPanic<SkipUDelay>(
"__const_udelay", "__const_udelay", 1000, 107374);
}
debugPrintk = addSkipFunc<DebugPrintk>("dprintk");
}
void
FsLinux::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 > context_switch_task_id::MaxNormalTaskId + 1) {
warn_once("Error out of identifiers for cache occupancy stats");
taskMap[pid] = context_switch_task_id::Unknown;
} else {
taskMap[pid] = map_size;
}
}
}
void
FsLinux::dumpDmesg()
{
linux::dumpDmesg(system->threads[0], std::cout);
}
/**
* Extracts the information used by the DumpStatsPCEvent by reading the
* thread_info pointer passed to __switch_to() in 32 bit ARM Linux
*
* r0 = task_struct of the previously running process
* r1 = thread_info of the previously running process
* r2 = thread_info of the next process to run
*/
void
DumpStats::getTaskDetails(ThreadContext *tc, uint32_t &pid,
uint32_t &tgid, std::string &next_task_str, int32_t &mm) {
linux::ThreadInfo ti(tc);
Addr task_descriptor = tc->readIntReg(2);
pid = ti.curTaskPID(task_descriptor);
tgid = ti.curTaskTGID(task_descriptor);
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)
mm = ti.curTaskMm(task_descriptor);
}
/**
* Extracts the information used by the DumpStatsPCEvent64 by reading the
* task_struct pointer passed to __switch_to() in 64 bit ARM Linux
*
* r0 = task_struct of the previously running process
* r1 = task_struct of next process to run
*/
void
DumpStats64::getTaskDetails(ThreadContext *tc, uint32_t &pid,
uint32_t &tgid, std::string &next_task_str, int32_t &mm) {
linux::ThreadInfo ti(tc);
Addr task_struct = tc->readIntReg(1);
pid = ti.curTaskPIDFromTaskStruct(task_struct);
tgid = ti.curTaskTGIDFromTaskStruct(task_struct);
next_task_str = ti.curTaskNameFromTaskStruct(task_struct);
// Streamline treats pid == -1 as the kernel process.
// Also pid == 0 implies idle process (except during Linux boot)
mm = ti.curTaskMmFromTaskStruct(task_struct);
}
/** This function is called whenever the the kernel function
* "__switch_to" is called to change running tasks.
*/
void
DumpStats::process(ThreadContext *tc)
{
uint32_t pid = 0;
uint32_t tgid = 0;
std::string next_task_str;
int32_t mm = 0;
getTaskDetails(tc, pid, tgid, next_task_str, mm);
bool is_kernel = (mm == 0);
if (is_kernel && (pid != 0)) {
pid = -1;
tgid = -1;
next_task_str = "kernel";
}
FsLinux* wl = dynamic_cast<FsLinux *>(tc->getSystemPtr()->workload);
panic_if(!wl, "System workload is not ARM Linux!");
std::map<uint32_t, uint32_t>& taskMap = wl->taskMap;
// Create a new unique identifier for this pid
wl->mapPid(tc, pid);
// Set cpu task id, output process info, and dump stats
tc->getCpuPtr()->taskId(taskMap[pid]);
tc->getCpuPtr()->setPid(pid);
OutputStream* taskFile = wl->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
statistics::schedStatEvent(true, true, curTick(), 0);
}
} // namespace ArmISA
} // namespace gem5