gem5  v20.0.0.3
vm.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2014 Google, Inc.
3  * Copyright (c) 2012, 2015 ARM Limited
4  * All rights reserved
5  *
6  * The license below extends only to copyright in the software and shall
7  * not be construed as granting a license to any other intellectual
8  * property including but not limited to intellectual property relating
9  * to a hardware implementation of the functionality of the software
10  * licensed hereunder. You may use the software subject to the license
11  * terms below provided that you ensure that this notice is replicated
12  * unmodified and in its entirety in all distributions of the software,
13  * modified or unmodified, in source code or in binary form.
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions are
17  * met: redistributions of source code must retain the above copyright
18  * notice, this list of conditions and the following disclaimer;
19  * redistributions in binary form must reproduce the above copyright
20  * notice, this list of conditions and the following disclaimer in the
21  * documentation and/or other materials provided with the distribution;
22  * neither the name of the copyright holders nor the names of its
23  * contributors may be used to endorse or promote products derived from
24  * this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include "cpu/kvm/vm.hh"
40 
41 #include <fcntl.h>
42 #include <linux/kvm.h>
43 #include <sys/ioctl.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <unistd.h>
47 
48 #include <cerrno>
49 #include <memory>
50 
51 #include "cpu/kvm/base.hh"
52 #include "debug/Kvm.hh"
53 #include "params/KvmVM.hh"
54 #include "sim/system.hh"
55 
56 #define EXPECTED_KVM_API_VERSION 12
57 
58 #if EXPECTED_KVM_API_VERSION != KVM_API_VERSION
59 #error Unsupported KVM version
60 #endif
61 
62 Kvm *Kvm::instance = NULL;
63 
65  : kvmFD(-1), apiVersion(-1), vcpuMMapSize(0)
66 {
67  kvmFD = ::open("/dev/kvm", O_RDWR);
68  if (kvmFD == -1)
69  fatal("KVM: Failed to open /dev/kvm\n");
70 
71  apiVersion = ioctl(KVM_GET_API_VERSION);
73  fatal("KVM: Incompatible API version\n");
74 
75  vcpuMMapSize = ioctl(KVM_GET_VCPU_MMAP_SIZE);
76  if (vcpuMMapSize == -1)
77  panic("KVM: Failed to get virtual CPU MMAP size\n");
78 }
79 
81 {
82  close(kvmFD);
83 }
84 
85 Kvm *
87 {
88  if (!instance)
89  instance = new Kvm();
90 
91  return instance;
92 }
93 
94 bool
96 {
97  return checkExtension(KVM_CAP_USER_MEMORY) != 0;
98 }
99 
100 bool
102 {
103  return checkExtension(KVM_CAP_SET_TSS_ADDR) != 0;
104 }
105 
106 bool
108 {
109  return checkExtension(KVM_CAP_EXT_CPUID) != 0;
110 }
111 
112 bool
114 {
115 #ifdef KVM_CAP_USER_NMI
116  return checkExtension(KVM_CAP_USER_NMI) != 0;
117 #else
118  return false;
119 #endif
120 }
121 
122 int
124 {
125  return checkExtension(KVM_CAP_COALESCED_MMIO);
126 }
127 
128 int
130 {
131 #ifdef KVM_CAP_NR_MEMSLOTS
132  return checkExtension(KVM_CAP_NR_MEMSLOTS);
133 #else
134  return 0;
135 #endif
136 }
137 
138 bool
140 {
141 #ifdef KVM_CAP_ONE_REG
142  return checkExtension(KVM_CAP_ONE_REG) != 0;
143 #else
144  return false;
145 #endif
146 }
147 
148 bool
150 {
151  return checkExtension(KVM_CAP_IRQCHIP) != 0;
152 }
153 
154 bool
156 {
157 #ifdef KVM_CAP_VCPU_EVENTS
158  return checkExtension(KVM_CAP_VCPU_EVENTS) != 0;
159 #else
160  return false;
161 #endif
162 }
163 
164 bool
166 {
167 #ifdef KVM_CAP_DEBUGREGS
168  return checkExtension(KVM_CAP_DEBUGREGS) != 0;
169 #else
170  return false;
171 #endif
172 }
173 
174 bool
176 {
177 #ifdef KVM_CAP_XCRS
178  return checkExtension(KVM_CAP_XCRS) != 0;
179 #else
180  return false;
181 #endif
182 }
183 
184 bool
186 {
187 #ifdef KVM_CAP_XSAVE
188  return checkExtension(KVM_CAP_XSAVE) != 0;
189 #else
190  return false;
191 #endif
192 }
193 
194 
195 #if defined(__i386__) || defined(__x86_64__)
196 bool
197 Kvm::getSupportedCPUID(struct kvm_cpuid2 &cpuid) const
198 {
199  if (ioctl(KVM_GET_SUPPORTED_CPUID, (void *)&cpuid) == -1) {
200  if (errno == E2BIG)
201  return false;
202  else
203  panic("KVM: Failed to get supported CPUID (errno: %i)\n", errno);
204  } else
205  return true;
206 }
207 
208 const Kvm::CPUIDVector &
209 Kvm::getSupportedCPUID() const
210 {
211  if (supportedCPUIDCache.empty()) {
212  std::unique_ptr<struct kvm_cpuid2> cpuid;
213  int i(1);
214  do {
215  cpuid.reset((struct kvm_cpuid2 *)operator new(
216  sizeof(kvm_cpuid2) + i * sizeof(kvm_cpuid_entry2)));
217 
218  cpuid->nent = i;
219  ++i;
220  } while (!getSupportedCPUID(*cpuid));
221  supportedCPUIDCache.assign(cpuid->entries,
222  cpuid->entries + cpuid->nent);
223  }
224 
225  return supportedCPUIDCache;
226 }
227 
228 bool
229 Kvm::getSupportedMSRs(struct kvm_msr_list &msrs) const
230 {
231  if (ioctl(KVM_GET_MSR_INDEX_LIST, (void *)&msrs) == -1) {
232  if (errno == E2BIG)
233  return false;
234  else
235  panic("KVM: Failed to get supported CPUID (errno: %i)\n", errno);
236  } else
237  return true;
238 }
239 
240 const Kvm::MSRIndexVector &
241 Kvm::getSupportedMSRs() const
242 {
243  if (supportedMSRCache.empty()) {
244  std::unique_ptr<struct kvm_msr_list> msrs;
245  int i(0);
246  do {
247  msrs.reset((struct kvm_msr_list *)operator new(
248  sizeof(kvm_msr_list) + i * sizeof(uint32_t)));
249 
250  msrs->nmsrs = i;
251  ++i;
252  } while (!getSupportedMSRs(*msrs));
253  supportedMSRCache.assign(msrs->indices, msrs->indices + msrs->nmsrs);
254  }
255 
256  return supportedMSRCache;
257 }
258 
259 #endif // x86-specific
260 
261 
262 int
263 Kvm::checkExtension(int extension) const
264 {
265  int ret = ioctl(KVM_CHECK_EXTENSION, extension);
266  if (ret == -1)
267  panic("KVM: ioctl failed when checking for extension\n");
268  return ret;
269 }
270 
271 int
272 Kvm::ioctl(int request, long p1) const
273 {
274  assert(kvmFD != -1);
275 
276  return ::ioctl(kvmFD, request, p1);
277 }
278 
279 int
281 {
282  int vmFD;
283 
284  vmFD = ioctl(KVM_CREATE_VM);
285  if (vmFD == -1)
286  panic("Failed to create KVM VM\n");
287 
288  return vmFD;
289 }
290 
291 
292 KvmVM::KvmVM(KvmVMParams *params)
293  : SimObject(params),
294  kvm(new Kvm()), system(nullptr),
295  vmFD(kvm->createVM()),
296  started(false),
297  nextVCPUID(0)
298 {
300  /* If we couldn't determine how memory slots there are, guess 32. */
301  if (!maxMemorySlot)
302  maxMemorySlot = 32;
303  /* Setup the coalesced MMIO regions */
304  for (int i = 0; i < params->coalescedMMIO.size(); ++i)
305  coalesceMMIO(params->coalescedMMIO[i]);
306 }
307 
309 {
310  if (vmFD != -1)
311  close(vmFD);
312 
313  if (kvm)
314  delete kvm;
315 }
316 
317 void
319 {
320  if (vmFD != -1) {
321  if (close(vmFD) == -1)
322  warn("kvm VM: notifyFork failed to close vmFD\n");
323 
324  vmFD = -1;
325 
326  delete kvm;
327  kvm = NULL;
328  }
329 }
330 
331 void
333 {
334  if (started)
335  return;
336  started = true;
337 
338  delayedStartup();
339 }
340 
341 void
343 {
344  assert(system); // set by the system during its construction
345  const std::vector<BackingStoreEntry> &memories(
347 
348  DPRINTF(Kvm, "Mapping %i memory region(s)\n", memories.size());
349  for (int slot(0); slot < memories.size(); ++slot) {
350  if (!memories[slot].kvmMap) {
351  DPRINTF(Kvm, "Skipping region marked as not usable by KVM\n");
352  continue;
353  }
354 
355  const AddrRange &range(memories[slot].range);
356  void *pmem(memories[slot].pmem);
357 
358  if (pmem) {
359  DPRINTF(Kvm, "Mapping region: 0x%p -> 0x%llx [size: 0x%llx]\n",
360  pmem, range.start(), range.size());
361 
362  if (range.interleaved()) {
363  panic("Tried to map an interleaved memory range into "
364  "a KVM VM.\n");
365  }
366 
367  const MemSlot slot = allocMemSlot(range.size());
368  setupMemSlot(slot, pmem, range.start(), 0/* flags */);
369  } else {
370  DPRINTF(Kvm, "Zero-region not mapped: [0x%llx]\n", range.start());
371  hack("KVM: Zero memory handled as IO\n");
372  }
373  }
374 }
375 
376 const KvmVM::MemSlot
377 KvmVM::allocMemSlot(uint64_t size)
378 {
379  if (!size)
380  panic("Memory slots must have non-zero size.\n");
381 
383  for (pos = memorySlots.begin(); pos != memorySlots.end(); pos++) {
384  if (!pos->size) {
385  pos->size = size;
386  pos->active = false;
387  return pos->slot;
388  }
389  }
390 
391  uint32_t nextSlot = memorySlots.size();
392  if (nextSlot > maxMemorySlot)
393  panic("Out of memory slots.\n");
394 
395  MemorySlot slot;
396  slot.size = size;
397  slot.slot = nextSlot;
398  slot.active = false;
399 
400  memorySlots.push_back(slot);
401  return MemSlot(slot.slot);
402 }
403 
404 void
405 KvmVM::setupMemSlot(const KvmVM::MemSlot num, void *host_addr, Addr guest,
406  uint32_t flags)
407 {
408  MemorySlot &slot = memorySlots.at(num.num);
409  slot.active = true;
410  setUserMemoryRegion(num.num, host_addr, guest, slot.size, flags);
411 }
412 
413 void
415 {
416  MemorySlot &slot = memorySlots.at(num.num);
417  if (slot.active)
418  setUserMemoryRegion(num.num, NULL, 0, 0, 0);
419  slot.active = false;
420 }
421 
422 void
424 {
425  disableMemSlot(num.num);
426  MemorySlot &slot = memorySlots.at(num.num);
427  slot.size = 0;
428 }
429 
430 void
432  void *host_addr, Addr guest_addr,
433  uint64_t len, uint32_t flags)
434 {
435  struct kvm_userspace_memory_region m;
436 
437  memset(&m, 0, sizeof(m));
438  m.slot = slot;
439  m.flags = flags;
440  m.guest_phys_addr = (uint64_t)guest_addr;
441  m.memory_size = len;
442  m.userspace_addr = (__u64)host_addr;
443 
444  if (ioctl(KVM_SET_USER_MEMORY_REGION, (void *)&m) == -1) {
445  panic("Failed to setup KVM memory region:\n"
446  "\tHost Address: 0x%p\n"
447  "\tGuest Address: 0x%llx\n",
448  "\tSize: %ll\n",
449  "\tFlags: 0x%x\n",
450  m.userspace_addr, m.guest_phys_addr,
451  m.memory_size, m.flags);
452  }
453 }
454 
455 void
457 {
458  coalesceMMIO(range.start(), range.size());
459 }
460 
461 void
462 KvmVM::coalesceMMIO(Addr start, int size)
463 {
464  struct kvm_coalesced_mmio_zone zone;
465 
466  zone.addr = start;
467  zone.size = size;
468  zone.pad = 0;
469 
470  DPRINTF(Kvm, "KVM: Registering coalesced MMIO region [0x%x, 0x%x]\n",
471  zone.addr, zone.addr + zone.size - 1);
472  if (ioctl(KVM_REGISTER_COALESCED_MMIO, (void *)&zone) == -1)
473  panic("KVM: Failed to register coalesced MMIO region (%i)\n",
474  errno);
475 }
476 
477 void
479 {
480  if (ioctl(KVM_SET_TSS_ADDR, (unsigned long)tss_address) == -1)
481  panic("KVM: Failed to set VM TSS address\n");
482 }
483 
484 void
486 {
487  if (_hasKernelIRQChip)
488  panic("KvmVM::createIRQChip called twice.\n");
489 
490  if (ioctl(KVM_CREATE_IRQCHIP) != -1) {
491  _hasKernelIRQChip = true;
492  } else {
493  warn("KVM: Failed to create in-kernel IRQ chip (errno: %i)\n",
494  errno);
495  _hasKernelIRQChip = false;
496  }
497 }
498 
499 void
500 KvmVM::setIRQLine(uint32_t irq, bool high)
501 {
502  struct kvm_irq_level kvm_level;
503 
504  kvm_level.irq = irq;
505  kvm_level.level = high ? 1 : 0;
506 
507  if (ioctl(KVM_IRQ_LINE, &kvm_level) == -1)
508  panic("KVM: Failed to set IRQ line level (errno: %i)\n",
509  errno);
510 }
511 
512 int
513 KvmVM::createDevice(uint32_t type, uint32_t flags)
514 {
515 #if defined(KVM_CREATE_DEVICE)
516  struct kvm_create_device dev = { type, 0, flags };
517 
518  if (ioctl(KVM_CREATE_DEVICE, &dev) == -1) {
519  panic("KVM: Failed to create device (errno: %i)\n",
520  errno);
521  }
522 
523  return dev.fd;
524 #else
525  panic("Kernel headers don't support KVM_CREATE_DEVICE\n");
526 #endif
527 }
528 
529 void
531 {
532  panic_if(system != nullptr, "setSystem() can only be called once");
533  panic_if(s == nullptr, "setSystem() called with null System*");
534  system = s;
535 }
536 
537 long
539 {
540  assert(system != nullptr);
541  return dynamic_cast<BaseKvmCPU*>
542  (system->getThreadContext(ctx)->getCpuPtr())->getVCpuID();
543 }
544 
545 int
546 KvmVM::createVCPU(long vcpuID)
547 {
548  int fd;
549 
550  fd = ioctl(KVM_CREATE_VCPU, vcpuID);
551  if (fd == -1)
552  panic("KVM: Failed to create virtual CPU");
553 
554  return fd;
555 }
556 
557 long
559 {
560  return nextVCPUID++;
561 }
562 
563 #if defined(__aarch64__)
564 void
565 KvmVM::kvmArmPreferredTarget(struct kvm_vcpu_init &target) const
566 {
567  if (ioctl(KVM_ARM_PREFERRED_TARGET, &target) == -1) {
568  panic("KVM: Failed to get ARM preferred CPU target (errno: %i)\n",
569  errno);
570  }
571 }
572 #endif
573 
574 int
575 KvmVM::ioctl(int request, long p1) const
576 {
577  assert(vmFD != -1);
578 
579  return ::ioctl(vmFD, request, p1);
580 }
581 
582 
583 KvmVM *
584 KvmVMParams::create()
585 {
586  static bool created = false;
587  if (created)
588  warn_once("Use of multiple KvmVMs is currently untested!\n");
589 
590  created = true;
591 
592  return new KvmVM(this);
593 }
#define panic(...)
This implements a cprintf based panic() function.
Definition: logging.hh:163
int ioctl(int request, long p1) const
KVM VM ioctl interface.
Definition: vm.cc:575
#define DPRINTF(x,...)
Definition: trace.hh:225
int kvmFD
KVM VM file descriptor.
Definition: vm.hh:253
bool capExtendedCPUID() const
Support for BaseKvmCPU::setCPUID2 and getSupportedCPUID().
Definition: vm.cc:107
uint32_t maxMemorySlot
Definition: vm.hh:549
int vcpuMMapSize
Size of the MMAPed vCPU parameter area.
Definition: vm.hh:257
#define fatal(...)
This implements a cprintf based fatal() function.
Definition: logging.hh:171
Bitfield< 7 > i
Bitfield< 0 > m
void createIRQChip()
Create an in-kernel interrupt controller.
Definition: vm.cc:485
void cpuStartup()
VM CPU initialization code.
Definition: vm.cc:332
int apiVersion
KVM API version.
Definition: vm.hh:255
long allocVCPUID()
Allocate a new vCPU ID within the VM.
Definition: vm.cc:558
uint64_t size
Definition: vm.hh:544
std::vector< MemorySlot > memorySlots
Definition: vm.hh:548
int capNumMemSlots() const
Attempt to determine how many memory slots are available.
Definition: vm.cc:129
static Kvm * instance
Singleton instance.
Definition: vm.hh:260
Definition: system.hh:72
virtual BaseCPU * getCpuPtr()=0
Bitfield< 28, 21 > cpuid
Definition: dt_constants.hh:92
long nextVCPUID
Next unallocated vCPU ID.
Definition: vm.hh:536
int createVCPU(long vcpuID)
Create a new vCPU within a VM.
Definition: vm.cc:546
Base class for KVM based CPU models.
Definition: base.hh:77
bool capVCPUEvents() const
Support for getting and setting the kvm_vcpu_events structure.
Definition: vm.cc:155
int ioctl(int request, long p1) const
Main VM ioctl interface.
Definition: vm.cc:272
void setUserMemoryRegion(uint32_t slot, void *host_addr, Addr guest_addr, uint64_t len, uint32_t flags)
Setup a region of physical memory in the guest.
Definition: vm.cc:431
bool capOneReg() const
Support for reading and writing single registers.
Definition: vm.cc:139
bool _hasKernelIRQChip
Do we have in-kernel IRQ-chip emulation enabled?
Definition: vm.hh:533
void setIRQLine(uint32_t irq, bool high)
Set the status of an IRQ line using KVM_IRQ_LINE.
Definition: vm.cc:500
System * system
Definition: vm.hh:524
ThreadContext * getThreadContext(ContextID tid) const
Definition: system.hh:186
void setSystem(System *s)
Initialize system pointer.
Definition: vm.cc:530
bool capUserNMI() const
Support for BaseKvmCPU::kvmNonMaskableInterrupt().
Definition: vm.cc:113
The AddrRange class encapsulates an address range, and supports a number of tests to check if two ran...
Definition: addr_range.hh:68
uint8_t type
Definition: inet.hh:328
#define hack(...)
Definition: logging.hh:210
std::vector< BackingStoreEntry > getBackingStore() const
Get the pointers to the backing store for external host access.
Definition: physical.hh:212
void freeMemSlot(const MemSlot slot)
Free a previously allocated memory slot.
Definition: vm.cc:423
Bitfield< 4 > s
bool started
Has delayedStartup() already been called?
Definition: vm.hh:530
long contextIdToVCpuId(ContextID ctx) const
Get the VCPUID for a given context.
Definition: vm.cc:538
uint32_t slot
Definition: vm.hh:545
int checkExtension(int extension) const
Check for the presence of an extension to the KVM API.
Definition: vm.cc:263
void delayedStartup()
Delayed initialization, executed once before the first CPU starts.
Definition: vm.cc:342
virtual ~KvmVM()
Definition: vm.cc:308
Bitfield< 18, 16 > len
Kvm * create()
Definition: vm.cc:86
bool active
Definition: vm.hh:546
void coalesceMMIO(Addr start, int size)
Request coalescing MMIO for a memory range.
Definition: vm.cc:462
void setTSSAddress(Addr tss_address)
Setup a shared three-page memory region used by the internals of KVM.
Definition: vm.cc:478
int createVM()
Create a KVM Virtual Machine.
Definition: vm.cc:280
uint64_t Addr
Address type This will probably be moved somewhere else in the near future.
Definition: types.hh:140
Structures tracking memory slots.
Definition: vm.hh:541
void setupMemSlot(const MemSlot slot, void *host_addr, Addr guest_addr, uint32_t flags)
Setup a region of physical memory in the guest.
Definition: vm.cc:405
#define warn_once(...)
Definition: logging.hh:212
Kvm()
Definition: vm.cc:64
int32_t num
Definition: vm.hh:370
KvmVM(KvmVMParams *params)
Definition: vm.cc:292
Bitfield< 15 > system
Definition: misc.hh:997
int vmFD
KVM VM file descriptor.
Definition: vm.hh:527
int createDevice(uint32_t type, uint32_t flags=0)
Create an in-kernel device model.
Definition: vm.cc:513
KVM VM container.
Definition: vm.hh:289
Bitfield< 1 > irq
bool capSetTSSAddress() const
Support for KvmVM::setTSSAddress()
Definition: vm.cc:101
bool interleaved() const
Determine if the range is interleaved or not.
Definition: addr_range.hh:246
bool capUserMemory() const
Support for KvmVM::setUserMemoryRegion()
Definition: vm.cc:95
PhysicalMemory & getPhysMem()
Get a pointer to access the physical memory of the system.
Definition: system.hh:228
bool capXCRs() const
Support for getting and setting the x86 XCRs.
Definition: vm.cc:175
void notifyFork()
Notify a child process of a fork.
Definition: vm.cc:318
#define EXPECTED_KVM_API_VERSION
Definition: vm.cc:56
bool capXSave() const
Support for getting and setting the kvm_xsave structure.
Definition: vm.cc:185
Addr start() const
Get the start address of the range.
Definition: addr_range.hh:293
int capCoalescedMMIO() const
Check if coalesced MMIO is supported and which page in the MMAP&#39;ed structure it stores requests in...
Definition: vm.cc:123
Addr size() const
Get the size of the address range.
Definition: addr_range.hh:280
bool capIRQChip() const
Support for creating an in-kernel IRQ chip model.
Definition: vm.cc:149
Kvm * kvm
Global KVM interface.
Definition: vm.hh:409
#define warn(...)
Definition: logging.hh:208
bool capDebugRegs() const
Support for getting and setting the kvm_debugregs structure.
Definition: vm.cc:165
KVM parent interface.
Definition: vm.hh:72
Bitfield< 14, 12 > fd
Definition: types.hh:158
void disableMemSlot(const MemSlot slot)
Disable a memory slot.
Definition: vm.cc:414
#define panic_if(cond,...)
Conditional panic macro that checks the supplied condition and only panics if the condition is true a...
Definition: logging.hh:181
const MemSlot allocMemSlot(uint64_t size)
Allocate a memory slot within the VM.
Definition: vm.cc:377
Abstract superclass for simulation objects.
Definition: sim_object.hh:93
int ContextID
Globally unique thread context ID.
Definition: types.hh:229
virtual ~Kvm()
Definition: vm.cc:80

Generated on Fri Jul 3 2020 15:53:00 for gem5 by doxygen 1.8.13