The Pedigree Project 0.1
system/kernel/core/main.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
88#include "pedigree/kernel/Archive.h"
89#include "pedigree/kernel/Atomic.h"
90#include "pedigree/kernel/BootstrapInfo.h"
91#include "pedigree/kernel/Log.h"
92#include "pedigree/kernel/ServiceManager.h"
93#include "pedigree/kernel/Subsystem.h"
94#include "pedigree/kernel/Version.h"
95#include "pedigree/kernel/compiler.h"
96#include "pedigree/kernel/core/BootIO.h"
97#include "pedigree/kernel/core/SlamAllocator.h"
98#include "pedigree/kernel/core/cppsupport.h"
99#include "pedigree/kernel/graphics/GraphicsService.h"
100#include "pedigree/kernel/linker/KernelElf.h"
101#include "pedigree/kernel/machine/InputManager.h"
102#include "pedigree/kernel/machine/Machine.h"
103#include "pedigree/kernel/machine/Trace.h"
104#include "pedigree/kernel/panic.h"
105#include "pedigree/kernel/process/InfoBlock.h"
106#include "pedigree/kernel/process/MemoryPressureKiller.h"
107#include "pedigree/kernel/process/MemoryPressureManager.h"
108#include "pedigree/kernel/process/OwnedThread.h"
109#include "pedigree/kernel/process/PerProcessorScheduler.h"
110#include "pedigree/kernel/process/Process.h"
111#include "pedigree/kernel/process/Scheduler.h"
112#include "pedigree/kernel/process/Thread.h"
113#include "pedigree/kernel/processor/KernelCoreSyscallManager.h"
114#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
115#include "pedigree/kernel/processor/Processor.h"
116#include "pedigree/kernel/processor/ProcessorInformation.h"
117#include "pedigree/kernel/processor/types.h"
118#include "pedigree/kernel/utilities/Cache.h"
119#include "pedigree/kernel/utilities/StaticString.h"
120#include "pedigree/kernel/utilities/String.h"
121#include "pedigree/kernel/utilities/new"
122#include "pedigree/kernel/utilities/utility.h"
123
124#if DEBUGGER
125#include "pedigree/kernel/debugger/Debugger.h"
126#include "pedigree/kernel/debugger/commands/LocksCommand.h"
127#endif
128
129#if THREADS
130#include "pedigree/kernel/utilities/ZombieQueue.h"
131#endif
132
133#if HOSTED
134namespace __pedigree_hosted {}; // namespace __pedigree_hosted
135using namespace __pedigree_hosted;
136#include <stdio.h>
137#endif
138
140EXPORTED_PUBLIC BootIO bootIO;
141
143BootstrapStruct_t* g_pBootstrapInfo;
144
146static Atomic<bool> g_NeedsShutdown(false);
147static Machine::ShutdownType g_ShutdownType = Machine::ShutdownType::Halt;
148static Atomic<bool> g_ShutdownCoordinator(false);
149
150#if HOSTED && PEDIGREE_HOSTED_IRQ_CLOSURE_TESTS
151extern bool runHostedIrqClosureRegressions();
152#endif
153
156 public:
157 virtual const char* getMemoryPressureDescription();
158 virtual bool compact();
159};
160
163#if MULTIPROCESSOR
164void apMain() {
165 NOTICE("Processor #" << Processor::id() << " started.");
166
167 EMIT_IF(THREADS) {
168 // Add us as the idle thread for this CPU.
169 Processor::information().getScheduler().setIdle(Processor::information().getCurrentThread());
170 }
171
172 for (;;) {
173#if THREADS
174 Processor::information().getScheduler().idleUntilInterrupt();
176#else
178#endif
179 }
180}
181#endif
182
183ModuleInfo* g_StaticDrivers[128];
184size_t g_StaticDriverN = 0;
185
187static int loadModules(void* inf) {
188 Archive* initrd = nullptr;
189
190 EMIT_IF(STATIC_DRIVERS) {
191 extern uintptr_t start_module_ctors;
192 extern uintptr_t end_module_ctors;
193
194 // Call static constructors before we start. If we don't... there won't
195 // be any properly initialised ModuleInfo structures :)
196 uintptr_t* iterator = &start_module_ctors;
197 while (iterator < &end_module_ctors) {
198 void (*fp)(void) = reinterpret_cast<void (*)(void)>(*iterator);
199 fp();
200 iterator++;
201 }
202
203 for (size_t i = 0; i < g_StaticDriverN; ++i) {
204 assert(g_StaticDrivers[i]->tag == MODULE_TAG);
205 KernelElf::instance().loadModule(g_StaticDrivers[i]);
206 }
207
209 }
210 else {
211 BootstrapStruct_t* bsInf = static_cast<BootstrapStruct_t*>(inf);
212
213 NOTICE("initrd @ " << Hex << bsInf->getInitrdAddress() << " -> "
214 << (bsInf->getInitrdAddress() + bsInf->getInitrdSize()) << ", " << Dec
215 << bsInf->getInitrdSize() << " bytes");
216
220 initrd = new Archive(bsInf->getInitrdAddress(), bsInf->getInitrdSize());
221 bsInf = nullptr;
222
223 size_t nFiles = initrd->getNumFiles();
224 NOTICE("there are " << nFiles << " files");
225 g_BootProgressTotal = nFiles * 2; // Each file has to be preloaded and executed.
226 for (size_t i = 0; i < nFiles; i++) {
227 // Handle archives with `._<filename>` entries from Apple tar file
228 // creation
229 if (!StringCompareN(initrd->getFileName(i), "._", 2)) {
230 continue;
231 }
232
233 NOTICE("loading module #" << i << " (" << initrd->getFileName(i) << ")...");
235 KernelElf::instance().loadModule(reinterpret_cast<uint8_t*>(initrd->getFile(i)),
236 initrd->getFileSize(i));
238 WARNING("A loaded module disabled interrupts.");
239 }
240
241 // Start any modules we can run already.
243 }
244
245 // Wait for all modules to finish loading before we continue.
247
248 // The initialisation is done here, unmap/free the .init section and on
249 // x86/64 the identity mapping of 0-4MB NOTE: BootstrapStruct_t unusable
250 // after this point
252
253#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
254 // The ISO-only smoke image deliberately has no root filesystem. Once every
255 // eligible module has retired, request a controlled shutdown instead of
256 // invoking userspace through the intentionally pending POSIX dependency.
257 g_NeedsShutdown = true;
258#else
259 // Now that we've cleaned up and are done loading modules, we can run the
260 // init module.
262
263 if (KernelElf::instance().hasPendingModules()) {
264 FATAL("At least one module's dependencies were never met.");
265 }
266#endif
267
268 // It's now safe to clean up the initrd archive
269 if (initrd) {
270 delete initrd;
271 }
272
273#if HOSTED
274 fprintf(stderr, "Pedigree has started: all modules have been loaded.\n");
275#endif
276
277 NOTICE("module load thread is terminating");
278 return 0;
279}
280
282extern "C" void _main(BootstrapStruct_t& bsInf) USED;
283void _cxx_main(BootstrapStruct_t& bsInf);
284extern "C" void _main(BootstrapStruct_t& bsInf) {
285 _cxx_main(bsInf);
286}
287
288void _cxx_main(BootstrapStruct_t& bsInf) {
289 TRACE("constructors");
290
291 // Firstly call the constructors of all global objects.
292 initialiseConstructors();
293
294 g_pBootstrapInfo = &bsInf;
295
296#if DEBUGGER && TRACK_LOCKS
297 g_LocksCommand.setReady();
298#endif
299
300 TRACE("Processor init");
301
302 // Initialise the processor-specific interface
304
305 TRACE("log init");
306
307 // Initialise the kernel log
309
310 TRACE("Machine init");
311
312 // Initialise the machine-specific interface
313 Machine& machine = Machine::instance();
314 Machine::instance().initialiseDeviceTree();
315
316 machine.initialise();
317
318#if DEBUGGER && (!defined(PEDIGREE_HOSTED_DARWIN) || !PEDIGREE_HOSTED_DARWIN)
319 TRACE("Debugger init");
321#endif
322
323 TRACE("Machine init2");
324
325 machine.initialise2();
326
327 TRACE("Log init2");
328
329 // Initialise the kernel log's callbacks
331
332 TRACE("Processor init2");
333
334 // Initialise the processor-specific interface
335 // Bootup of the other Application Processors and related tasks
337
338 TRACE("Machine init3");
339
340 machine.initialise3();
341
342 TRACE("KernelElf init");
343
344 // Initialise the Kernel Elf class
345 if (KernelElf::instance().initialise(bsInf) == false)
346 panic("KernelElf::initialise() failed");
347
348 EMIT_IF(!STATIC_DRIVERS) {
349 // initrd needed if drivers aren't statically linked.
350 if (bsInf.isInitrdLoaded() == false)
351 panic("Initrd module not loaded!");
352 }
353
354 TRACE("kernel syscall init");
355
357
358 TRACE("initial init done, enabling interrupts");
359
361
362 TRACE("bootIO init");
363
364 // Initialise the boot output.
365 bootIO.initialise();
366
367 // Spew out a starting string.
368 HugeStaticString str, ident;
369 str += "Pedigree - revision ";
370 str += g_pBuildRevision;
371 EMIT_IF(DONT_LOG_TO_SERIAL) {
372 str += "\n=======================\n";
373 }
374 else {
375 str += "\r\n=======================\r\n";
376 }
377 bootIO.write(str, BootIO::White, BootIO::Black);
378
379 str.clear();
380 str += "Built at ";
381 str += g_pBuildTime;
382 str += " by ";
383 str += g_pBuildUser;
384 str += " on ";
385 str += g_pBuildMachine;
386 EMIT_IF(DONT_LOG_TO_SERIAL) {
387 str += "\n";
388 }
389 else {
390 str += "\r\n";
391 }
392 bootIO.write(str, BootIO::LightGrey, BootIO::Black);
393
394 str.clear();
395 str += "Build flags: ";
396 str += g_pBuildFlags;
397 EMIT_IF(DONT_LOG_TO_SERIAL) {
398 str += "\n";
399 }
400 else {
401 str += "\r\n";
402 }
403 bootIO.write(str, BootIO::LightGrey, BootIO::Black);
404
405 str.clear();
406 str += "Processor information: ";
407 Processor::identify(ident);
408 str += ident;
409 EMIT_IF(DONT_LOG_TO_SERIAL) {
410 str += "\n";
411 }
412 else {
413 str += "\r\n";
414 }
415 bootIO.write(str, BootIO::LightGrey, BootIO::Black);
416
417 TRACE("creating graphics service");
418
419 // Set up the graphics service for drivers to register with
420 EMIT_IF(!NOGFX) {
421 GraphicsService* pService = new GraphicsService;
422 ServiceFeatures* pFeatures = new ServiceFeatures;
423 pFeatures->add(ServiceFeatures::touch);
424 pFeatures->add(ServiceFeatures::withdraw);
425 pFeatures->add(ServiceFeatures::probe);
426 ServiceManager::instance().addService(String("graphics"), pService, pFeatures);
427 }
428
429 TRACE("creating memory pressure handlers");
430
431 // Set up SLAM recovery memory pressure handler.
432 SlamRecovery recovery;
433 MemoryPressureManager::instance().registerHandler(MemoryPressureManager::HighestPriority,
434 &recovery);
435
436 // Set up the process killer memory pressure handler.
438 MemoryPressureManager::instance().registerHandler(MemoryPressureManager::LowestPriority, &killer);
439
440 // Set up the global info block manager.
441 TRACE("InfoBlockManager init");
442 InfoBlockManager::instance().initialise();
443
444 // Bring up the cache subsystem.
445 TRACE("CacheManager init");
446 CacheManager::instance().initialise();
447
448 // Initialise the input manager
449 TRACE("InputManager init");
451
452 EMIT_IF(THREADS) {
453 TRACE("ZombieQueue init");
454 ZombieQueue::instance().initialise();
455 }
456
458
459 OwnedThread moduleLoadThread;
460#if HOSTED && PEDIGREE_HOSTED_IRQ_CLOSURE_TESTS
461 runHostedIrqClosureRegressions();
462#else
463 TRACE("starting module load thread");
464
465 EMIT_IF(THREADS) {
466 Thread* pThread = new Thread(Processor::information().getCurrentThread()->getParent(),
467 &loadModules, static_cast<void*>(&bsInf), 0);
468 pThread->setName("module load thread");
469 moduleLoadThread.adopt(pThread);
470 }
471 else {
472 loadModules(&bsInf);
473 }
474#endif
475
476 EMIT_IF(DEBUGGER_RUN_AT_START) {
478 }
479
480 TRACE("becoming idle");
481
482 EMIT_IF(THREADS) {
483 // Add us as the idle thread for this CPU.
484 Processor::information().getScheduler().setIdle(Processor::information().getCurrentThread());
485 }
486
487 // This will run when nothing else is available to run
488 while (!g_NeedsShutdown) {
489#if THREADS
490 Processor::information().getScheduler().idleUntilInterrupt();
491#else
493#endif
494
495 // A shutdown wake can resume this halt before the next predicate check.
496 // Do not yield back to a busy ready queue before retiring the idle role.
497 if (!g_NeedsShutdown)
499 }
500
501 EMIT_IF(THREADS) {
502 // Shutdown joins need this Thread to enter an ordinary WaitQueue and
503 // be republished when a worker exits. An idle Thread is only a fallback
504 // and never commits Sleeping, so retire that role before the first
505 // join rather than leaving its wake behind unrelated ready workers.
506 Processor::information().getScheduler().setIdle(nullptr);
507
508 // A module can request shutdown from its entry point. The loader still
509 // has bookkeeping and initrd cleanup to finish after that request, so
510 // module teardown must not race it.
511 moduleLoadThread.join();
512#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS && !PEDIGREE_HOSTED_IRQ_CLOSURE_TESTS
513 NOTICE("HOSTED-WAIT-TEST: PASS module-loader-shutdown-join");
514#endif
515 }
516
517 NOTICE("Resetting...");
518
519 // Clean up all loaded modules (unmounts filesystems and the like).
520 Machine::setShutdownPhase(Machine::ShutdownPhase::Userspace);
521 if (!KernelElf::instance().unloadModules()) {
522 panic("Shutdown aborted: module quiesce failed");
523 }
524
525 Machine::setShutdownPhase(Machine::ShutdownPhase::Firmware);
526 if (!Machine::instance().prepareShutdown(g_ShutdownType)) {
527 panic("Shutdown aborted: firmware workers did not stop");
528 }
529
530 EMIT_IF(STATIC_DRIVERS) {
531 extern uintptr_t start_module_dtors;
532 extern uintptr_t end_module_dtors;
533
534 // Call all the module destructors now
535 uintptr_t* iterator = &start_module_dtors;
536 while (iterator < &end_module_dtors) {
537 void (*fp)(void) = reinterpret_cast<void (*)(void)>(*iterator);
538 fp();
539 iterator++;
540 }
541 }
542
543 EMIT_IF(THREADS) {
544 // Module exits may publish mandatory deferred destruction. Keep the
545 // worker alive through every exit, then join it before lower-level
546 // scheduler and interrupt teardown begins.
547 Machine::setShutdownPhase(Machine::ShutdownPhase::Destructors);
548 ZombieQueue::instance().destroy();
549 }
550
551 // No need for user input anymore.
552 Machine::setShutdownPhase(Machine::ShutdownPhase::Input);
554
555 // Pinned modules can retain caches and page loans through the final handoff.
556 // Their backends must remain available for this last synchronous writeback.
557 Machine::setShutdownPhase(Machine::ShutdownPhase::Caches);
558#if HOSTED
559 CacheManager::destroyInstance();
560#else
561 if (!CacheManager::instance().shutdown()) {
562 panic("Shutdown aborted: retained cache writeback failed");
563 }
564 NOTICE("CacheManager: terminal cache writeback complete");
565#endif
566
567 // The shared info block remains live until userspace and module teardown
568 // finish, but its callback must retire before the platform timer does.
569 Machine::setShutdownPhase(Machine::ShutdownPhase::Timers);
570 if (!InfoBlockManager::instance().shutdown()) {
571 panic("Shutdown aborted: info block timer callback did not drain");
572 }
573#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
574 NOTICE("QEMU-CONCURRENCY-TEST: PASS infoblock-timer-drain");
575#endif
576
577 // Stop active platform services while their worker/callback drains can
578 // still schedule.
579 Machine::setShutdownPhase(Machine::ShutdownPhase::Devices);
580 Machine::instance().deinitialise();
581
582 // Teardown above joins module, input, keyboard, and threaded-IRQ workers.
583 // Keep every scheduler CPU available until those drains have completed;
584 // once the remaining work is strictly local, retire the APs permanently.
585 Machine::setShutdownPhase(Machine::ShutdownPhase::Processors);
586 EMIT_IF(MULTIPROCESSOR) {
587 if (!Machine::instance().stopAllOtherProcessors()) {
588 ERROR_NOLOCK("Shutdown aborted: not all other processors stopped");
590 while (true)
592 }
593 }
594
595#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
596 NOTICE("QEMU-CONCURRENCY-TEST: PASS shutdown-worker-drain-smp");
597#endif
598
600
601#if !HOSTED
602 // Firmware register access can still allocate mappings. Keep physical-memory
603 // bookkeeping alive until the final attempt to power off or reset returns.
604 Machine::setShutdownPhase(Machine::ShutdownPhase::FinalAction);
605 StaticString<256> shutdownResult;
606 if (const char* result = Machine::instance().finalShutdown(g_ShutdownType)) {
607 shutdownResult = result;
608 }
609#endif
610
611 // Release processor bookkeeping before hosted global destruction or after a
612 // bare-metal firmware action returned without switching the machine off.
613#if HOSTED
614 Machine::setShutdownPhase(Machine::ShutdownPhase::ProcessorCleanup);
615#else
616 // Keep the firmware result visible even if processor cleanup itself fails.
617 Machine::setShutdownPhase(Machine::ShutdownPhase::ProcessorCleanup, shutdownResult);
618#endif
620
621#if HOSTED
622 NOTICE("Module shutdown complete. Running destructors and terminating...");
623 runKernelDestructors();
624#else
625 // Bare-metal ends in a terminal halt loop, not an owning runtime.
626 // Retain static storage because its providers are already quiesced and the
627 // active kernel page tables cannot be destroyed while still executing.
628 NOTICE("Module shutdown complete. Retaining kernel static storage and terminating...");
629#if PEDIGREE_CONCURRENCY_SMOKE_TESTS
630 TRACE("QEMU-CONCURRENCY-TEST: PASS kernel-static-storage-retained");
631#endif
632#endif
633
634 // Hosted returns to its owning runtime after the terminal trace.
635 TRACE("kernel main() terminating");
636
637#if !HOSTED
638 StaticString<512> shutdownMessage(
639 "Shutdown complete.\nIt's now safe to power off your computer.");
640 if (shutdownResult.length()) {
641 shutdownMessage += "\n\n";
642 shutdownMessage += shutdownResult;
643 }
644 Machine::instance().displayShutdownMessage(shutdownMessage);
645
646 // An NMI may wake HLT; return to the halted state with IRQs disabled.
647 while (true) {
650 }
651#endif
652}
653
654void EXPORTED_PUBLIC system_reset();
655void system_reset() {
656 // Close out the main thread even when user work keeps the CPU busy.
657 g_NeedsShutdown = true;
658 EMIT_IF(THREADS) {
659 if (auto* scheduler = Scheduler::instance().getBootstrapProcessorScheduler())
660 scheduler->requestIdleThreadWakeup();
661 }
662}
663
664void system_reboot(Machine::ShutdownType type) {
665 WARNING("System shutting down...");
666 Process* currentProcess = Processor::information().getCurrentThread()->getParent();
667
668 Subsystem* currentSubsystem = currentProcess->getSubsystem();
669 if (!currentSubsystem) {
670 panic("System reboot requires a userspace shutdown coordinator");
671 }
672
673 // Concurrent requests must not change the selected terminal action.
674 if (!g_ShutdownCoordinator.compareAndSwap(false, true)) {
675 currentSubsystem->exit(0);
676 panic("Concurrent shutdown caller returned from process exit");
677 }
678
679 // The caller may still be a shell child. Its parent, orphan exit path,
680 // or terminal process drain claims the reaper after ordinary termination.
681 g_ShutdownType = type;
682 Machine::setShutdownPhase(Machine::ShutdownPhase::Requested);
683 system_reset();
684 // Exit can block while main starts teardown and retires its idle role.
685 // Use ordinary scheduling instead of reserving a later handoff to that role.
686 currentSubsystem->exit(0);
687 panic("Shutdown coordinator returned from process exit");
688}
689
691 return "SLAM recovery; freeing unused slabs.";
692}
693
695 return SlamAllocator::instance().recovery(5) != 0;
696}
uintptr_t * getFile(size_t n)
Definition Archive.cc:71
size_t getNumFiles()
Definition Archive.cc:51
char * getFileName(size_t n)
Definition Archive.cc:67
size_t getFileSize(size_t n)
Definition Archive.cc:61
EXPORTED_PUBLIC void write(T &str, Colour foreColour, Colour backColour)
void initialise()
Definition BootIO.cc:32
void initialise()
Definition Cache.cc:193
static Debugger & instance()
Definition Debugger.h:47
void initialise()
Definition Debugger.cc:108
void shutdown()
Shuts down the worker thread, clears queues, and removes callbacks.
static InputManager & instance()
Singleton design.
void initialise()
Begins the worker thread.
static KernelCoreSyscallManager & instance()
void executeModules(bool silent=false, bool progress=true)
Definition KernelElf.cc:505
void waitForModulesToLoad()
void invokeInitModule()
Invokes the module named 'init'. When a module named init is discovered, rather than executing it in ...
static KernelElf & instance()
Definition KernelElf.h:135
Module * loadModule(uint8_t *pModule, size_t len, bool silent=false)
Definition KernelElf.cc:372
void initialise1()
Definition Log.cc:121
void initialise2()
Definition Log.cc:138
static EXPORTED_PUBLIC Log & instance()
Definition Log.cc:117
static void setShutdownPhase(ShutdownPhase phase, const char *detail=nullptr)
Definition Machine.cc:33
void displayShutdownMessage(const char *message)
Definition Machine.cc:100
void registerHandler(size_t prio, MemoryPressureHandler *pHandler)
void join()
Definition OwnedThread.h:49
Process * getParent()
Definition Process.h:620
static void halt()
static bool getInterrupts()
static ProcessorId id()
static void initialise2(const BootstrapStruct_t &Info) INITIALISATION_ONLY
second/last stage in the initialisation of the processor-specific interface
static void initialise1(const BootstrapStruct_t &Info) INITIALISATION_ONLY
first stage in the initialisation of the processor-specific interface
static void initialisationDone()
static ProcessorInformation & information()
static void identify(HugeStaticString &str)
static void breakpoint()
static void setInterrupts(bool bEnable)
static void deinitialise()
static void haltUntilInterrupt()
virtual void destroy()
virtual void initialise()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
virtual void add(Type s)
void addService(const String &serviceName, Service *s, ServiceFeatures *feats)
virtual const char * getMemoryPressureDescription()
virtual void exit(int code, ExitCause cause=ExitCause::Normal)=0
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
@ Dec
Definition Log.h:126
@ Hex
Definition Log.h:124
void apMain() NORETURN