- complete redesign of the APIC code
- MetalSVM detects the APIC and initializes the APIC timer - If an a APIC is available, the PIC timer will be disabled - SMP is currently not supported git-svn-id: http://svn.lfbs.rwth-aachen.de/svn/scc/trunk/MetalSVM@233 315a16e6-25f9-4109-90ae-ca3045a26c18
This commit is contained in:
parent
141c2c194e
commit
c8c3035a4d
11 changed files with 246 additions and 61 deletions
|
@ -72,6 +72,8 @@ typedef struct {
|
|||
} __attribute__ ((packed)) apic_io_entry_t;
|
||||
|
||||
int apic_init(void);
|
||||
void apic_eoi(void);
|
||||
int apic_calibration(void);
|
||||
int has_apic(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -98,6 +98,13 @@ inline static int system_init(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
inline static int system_calibration(void)
|
||||
{
|
||||
apic_calibration();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -22,24 +22,92 @@
|
|||
#include <metalsvm/string.h>
|
||||
#include <metalsvm/errno.h>
|
||||
#include <metalsvm/processor.h>
|
||||
#include <metalsvm/time.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/irqflags.h>
|
||||
#include <asm/apic.h>
|
||||
#include <asm/multiboot.h>
|
||||
|
||||
#define LAPIC_VERSION 0x0030
|
||||
#define LAPIC_SVR 0x00F0
|
||||
|
||||
// 8259 controllers' I/O ports
|
||||
#define MAST_8259_APORT 0x20
|
||||
#define MAST_8259_DPORT 0x21
|
||||
#define SLAV_8259_APORT 0xA0
|
||||
#define SLAV_8259_DPORT 0xA1
|
||||
#define MP_FLT_SIGNATURE 0x5f504d5f
|
||||
|
||||
#define APIC_VERSION 0x0030 // Local APIC Version Register
|
||||
#define APIC_TPR 0x0080 // Task Priority Regster
|
||||
#define APIC_EOI 0x00B0 // EOI Register
|
||||
#define APIC_SVR 0x00F0 // Spurious Interrupt Vector Register
|
||||
#define APIC_LVT_T 0x0320 // LVT Thermal Sensor Register (P4/Xeon only)
|
||||
#define APIC_LVT_PMC 0x0340 // LVT Performance Monitoring Counters Register
|
||||
#define APIC_LINT0 0x0350 // LVT LINT0 Register
|
||||
#define APIC_LINT1 0x0360 // LVT LINT1 Register
|
||||
#define APIC_LVT_ER 0x0370 // LVT Error Register
|
||||
#define APIC_ICR 0x0380 // Initial Count Register
|
||||
#define APIC_CCR 0x0390 // Current Count Register
|
||||
#define APIC_DCR 0x03E0 // Divide Configuration Register
|
||||
|
||||
static apic_mp_t* apic_mp = NULL;
|
||||
static apic_config_table_t* apic_config = NULL;
|
||||
static uint32_t ncores = 1;
|
||||
|
||||
int apic_init(void)
|
||||
#ifndef CONFIG_MULTIBOOT
|
||||
static unsigned int* search_apic(unsigned int base, unsigned int limit) {
|
||||
unsigned int *ptr;
|
||||
|
||||
for (ptr = (unsigned int *) base; (unsigned int) ptr < limit; ptr++) {
|
||||
if (*ptr == MP_FLT_SIGNATURE)
|
||||
return ptr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Send a 'End of Interrupt' command to the APIC
|
||||
*/
|
||||
void apic_eoi(void)
|
||||
{
|
||||
*((uint32_t*) (apic_config->lapic+APIC_EOI)) = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* detects the timer frequency of the APIC and restart
|
||||
* the APIC timer with the correct period
|
||||
*/
|
||||
int apic_calibration(void)
|
||||
{
|
||||
uint64_t ticks, old;
|
||||
uint32_t diff;
|
||||
|
||||
if (!has_apic())
|
||||
return -ENXIO;
|
||||
|
||||
old = get_clock_tick();
|
||||
|
||||
/* wait for the next time slice */
|
||||
while((ticks = get_clock_tick()) - old == 0)
|
||||
;
|
||||
|
||||
*((uint32_t*) (apic_config->lapic+APIC_DCR)) = 0xB; // set it to 1 clock increments
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LVT_T)) = 0x20030; // connects the timer to 48 and enables it
|
||||
*((uint32_t*) (apic_config->lapic+APIC_ICR)) = 0xFFFFFFFF;
|
||||
|
||||
/* wait 3 time slices to determine a ICR */
|
||||
while(get_clock_tick() - ticks < 3)
|
||||
;
|
||||
|
||||
diff = 0xFFFFFFFF - *((uint32_t*) (apic_config->lapic+APIC_CCR));
|
||||
|
||||
*((uint32_t*) (apic_config->lapic+APIC_DCR)) = 0xB; // set it to 1 clock increments
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LVT_T)) = 0x20030; // connects the timer to 48 and enables it
|
||||
*((uint32_t*) (apic_config->lapic+APIC_ICR)) = diff / 3;
|
||||
|
||||
// Now, MetalSVM is able to use the APIC => Therefore, we disable the PIC
|
||||
outportb(0xA1, 0xFF);
|
||||
outportb(0x21, 0xFF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apic_probe(void)
|
||||
{
|
||||
size_t addr;
|
||||
uint32_t i, count;
|
||||
|
@ -65,6 +133,10 @@ int apic_init(void)
|
|||
mmap++;
|
||||
}
|
||||
}
|
||||
#else
|
||||
apic_mp = (apic_mp_t*) search_apic(0xF0000, 0x100000);
|
||||
if (!apic_mp)
|
||||
apic_mp = (apic_mp_t*) search_apic(0x9F000, 0xA0000);
|
||||
#endif
|
||||
found_mp:
|
||||
|
||||
|
@ -104,12 +176,11 @@ found_mp:
|
|||
}
|
||||
ncores = count;
|
||||
|
||||
addr = apic_config->lapic;
|
||||
i = *((uint32_t*) (addr+LAPIC_VERSION));
|
||||
kprintf("Found LAPIC at 0x%x\n", addr);
|
||||
i = *((uint32_t*) (apic_config->lapic+APIC_VERSION));
|
||||
kprintf("Found APIC at 0x%x\n", apic_config->lapic);
|
||||
kprintf("Maximum LVT Entry: 0x%x\n", (i >> 16) & 0xFF);
|
||||
kprintf("LAPIC Version: 0x%x\n", i & 0xFF);
|
||||
#if 0
|
||||
kprintf("APIC Version: 0x%x\n", i & 0xFF);
|
||||
|
||||
cpuid(0x1, &i);
|
||||
if (!(i & (1 << 5))) {
|
||||
kputs("Unable to use Machine-Specific Registers (MSR)\n");
|
||||
|
@ -121,27 +192,36 @@ found_mp:
|
|||
goto out;
|
||||
}
|
||||
|
||||
i = *((uint32_t*) (addr+LAPIC_SVR));
|
||||
kprintf("Supurious Interrupt Vector Register: %x\n", i);
|
||||
i = i | (1 << 8);
|
||||
*((uint32_t*) (addr+LAPIC_SVR)) = i;
|
||||
|
||||
// Disable the 8259's because we are going to use the IOAPIC for interrupt processing
|
||||
outportb(MAST_8259_DPORT, 0xFF); // OCW1 master: inhibit all interrupts
|
||||
udelay(100);
|
||||
outportb(SLAV_8259_DPORT, 0xFF); // OCW1 slave: inhibit all interrupts
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
out:
|
||||
apic_mp = NULL;
|
||||
apic_config = NULL;
|
||||
ncores = 1;
|
||||
|
||||
return -EINVAL;
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
int apic_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = apic_probe();
|
||||
if (!ret)
|
||||
return ret;
|
||||
|
||||
*((uint32_t*) (apic_config->lapic+APIC_TPR)) = 0x20; // inhibit softint delivery
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LVT_T)) = 0x10000; // disable timer interrupt
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LVT_PMC)) = 0x10000; // disable performance counter interrupt
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LINT0)) = 0x31; // connect LINT0 to idt entry 49
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LINT1)) = 0x32; // connect LINT1 to idt entry 50
|
||||
*((uint32_t*) (apic_config->lapic+APIC_LVT_ER)) = 0x33; // connect error to idt entry 51
|
||||
*((uint32_t*) (apic_config->lapic+APIC_SVR)) = 0x134; // enable the apic and connect to the idt entry 52
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int has_apic(void)
|
||||
{
|
||||
return (apic_mp != NULL);
|
||||
return ((apic_mp != NULL) && (apic_config != NULL));
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ mboot:
|
|||
; Multiboot macros to make a few lines more readable later
|
||||
MULTIBOOT_PAGE_ALIGN equ 1<<0
|
||||
MULTIBOOT_MEMORY_INFO equ 1<<1
|
||||
MULTIBOOT_AOUT_KLUDGE equ 1<<16
|
||||
; MULTIBOOT_AOUT_KLUDGE equ 1<<16
|
||||
MULTIBOOT_HEADER_MAGIC equ 0x1BADB002
|
||||
MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO ; | MULTIBOOT_AOUT_KLUDGE
|
||||
MULTIBOOT_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
|
||||
|
@ -471,6 +471,11 @@ global irq12
|
|||
global irq13
|
||||
global irq14
|
||||
global irq15
|
||||
global apic_timer
|
||||
global apic_lint0
|
||||
global apic_lint1
|
||||
global apic_error
|
||||
global apic_svr
|
||||
|
||||
extern irq_handler
|
||||
extern get_current_task
|
||||
|
@ -513,6 +518,7 @@ irq0:
|
|||
push byte 32
|
||||
pusha
|
||||
|
||||
Lirq0:
|
||||
push esp
|
||||
call irq_handler
|
||||
add esp, 4
|
||||
|
@ -673,6 +679,49 @@ irq15:
|
|||
push byte 47
|
||||
jmp irq_common_stub
|
||||
|
||||
apic_timer:
|
||||
; apic timer is registered as "Interrupt Gate"
|
||||
; Therefore, the interrupt flag (IF) is already cleared.
|
||||
; cli
|
||||
push byte 0
|
||||
push byte 48
|
||||
pusha
|
||||
; we reuse code of the "traditional" timer interrupt (PIC)
|
||||
jmp Lirq0
|
||||
|
||||
apic_lint0:
|
||||
; lint0 is registered as "Interrupt Gate"
|
||||
; Therefore, the interrupt flag (IF) is already cleared.
|
||||
; cli
|
||||
push byte 0
|
||||
push byte 49
|
||||
jmp irq_common_stub
|
||||
|
||||
apic_lint1:
|
||||
; lint1 is registered as "Interrupt Gate"
|
||||
; Therefore, the interrupt flag (IF) is already cleared.
|
||||
; cli
|
||||
push byte 0
|
||||
push byte 50
|
||||
jmp irq_common_stub
|
||||
|
||||
apic_error:
|
||||
; LVT error interrupt is registered as "Interrupt Gate"
|
||||
; Therefore, the interrupt flag (IF) is already cleared.
|
||||
; cli
|
||||
push byte 0
|
||||
push byte 51
|
||||
jmp irq_common_stub
|
||||
|
||||
apic_svr:
|
||||
; SVR is registered as "Interrupt Gate"
|
||||
; Therefore, the interrupt flag (IF) is already cleared.
|
||||
; cli
|
||||
push byte 0
|
||||
push byte 52
|
||||
jmp irq_common_stub
|
||||
|
||||
|
||||
irq_common_stub:
|
||||
pusha
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <asm/irq.h>
|
||||
#include <asm/idt.h>
|
||||
#include <asm/isrs.h>
|
||||
#include <asm/apic.h>
|
||||
|
||||
/*
|
||||
* These are our own ISRs that point to our special IRQ handler
|
||||
|
@ -43,12 +44,17 @@ extern void irq12(void);
|
|||
extern void irq13(void);
|
||||
extern void irq14(void);
|
||||
extern void irq15(void);
|
||||
extern void apic_timer(void);
|
||||
extern void apic_lint0(void);
|
||||
extern void apic_lint1(void);
|
||||
extern void apic_error(void);
|
||||
extern void apic_svr(void);
|
||||
|
||||
/*
|
||||
* This array is actually an array of function pointers. We use
|
||||
* this to handle custom IRQ handlers for a given IRQ
|
||||
*/
|
||||
static void *irq_routines[16] = {[0 ... 15] = NULL };
|
||||
static void *irq_routines[32] = {[0 ... 31] = NULL };
|
||||
|
||||
/* This installs a custom IRQ handler for the given IRQ */
|
||||
void irq_install_handler(unsigned int irq, irq_handler_t handler)
|
||||
|
@ -94,6 +100,7 @@ static void irq_remap(void)
|
|||
static void irq_install(void)
|
||||
{
|
||||
irq_remap();
|
||||
|
||||
idt_set_gate(32, (unsigned)irq0, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
idt_set_gate(33, (unsigned)irq1, KERNEL_CODE_SELECTOR,
|
||||
|
@ -126,6 +133,19 @@ static void irq_install(void)
|
|||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
idt_set_gate(47, (unsigned)irq15, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
|
||||
if (has_apic()) {
|
||||
idt_set_gate(48, (unsigned)apic_timer, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
idt_set_gate(49, (unsigned)apic_lint0, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
idt_set_gate(50, (unsigned)apic_lint1, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
idt_set_gate(51, (unsigned)apic_error, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
idt_set_gate(52, (unsigned)apic_svr, KERNEL_CODE_SELECTOR,
|
||||
IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP);
|
||||
}
|
||||
}
|
||||
|
||||
void irq_init(void)
|
||||
|
@ -139,12 +159,12 @@ void irq_init(void)
|
|||
* Each of the IRQ ISRs point to this function, rather than
|
||||
* the 'fault_handler' in 'isrs.c'. The IRQ Controllers need
|
||||
* to be told when you are done servicing them, so you need
|
||||
* to send them an "End of Interrupt" command (0x20). There
|
||||
* are two 8259 chips: The first one exists at 0x20, the second
|
||||
* one exists at 0xA0. If the second controller (an IRQ from 8 to
|
||||
* 15) gets an interrupt, you need to acknowledge the
|
||||
* interrupt at BOTH controllers, otherwise, you only send
|
||||
* an EOI command to the first controller. If you don't send
|
||||
* to send them an "End of Interrupt" command. If we use the PIC
|
||||
* instead of the APIC, we have two 8259 chips: The first one
|
||||
* exists at 0x20, the second one exists at 0xA0. If the second
|
||||
* controller (an IRQ from 8 to 15) gets an interrupt, you need to
|
||||
* acknowledge the interrupt at BOTH controllers, otherwise, you
|
||||
* only send an EOI command to the first controller. If you don't send
|
||||
* an EOI, you won't raise any more IRQs
|
||||
*/
|
||||
void irq_handler(struct state *s)
|
||||
|
@ -156,22 +176,30 @@ void irq_handler(struct state *s)
|
|||
* Find out if we have a custom handler to run for this
|
||||
* IRQ and then finally, run it
|
||||
*/
|
||||
|
||||
handler = irq_routines[s->int_no - 32];
|
||||
if (handler) {
|
||||
if (handler)
|
||||
handler(s);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the IDT entry that was invoked was greater than 40
|
||||
* (meaning IRQ8 - 15), then we need to send an EOI to
|
||||
* the slave controller
|
||||
* If the IDT entry that was invoked was greater than 48,
|
||||
* then we use the APIC
|
||||
*/
|
||||
if (s->int_no >= 40) {
|
||||
outportb(0xA0, 0x20);
|
||||
if (s->int_no >= 48) {
|
||||
apic_eoi();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* In either case, we need to send an EOI to the master
|
||||
* If the IDT entry that was invoked was greater than 40
|
||||
* and lower than 48 (meaning IRQ8 - 15), then we need to
|
||||
* send an EOI to the slave controller (PIC)
|
||||
*/
|
||||
if (s->int_no >= 40)
|
||||
outportb(0xA0, 0x20);
|
||||
|
||||
/*
|
||||
* In either case, we need to send an EOI to the master (PIC)
|
||||
* interrupt controller too
|
||||
*/
|
||||
outportb(0x20, 0x20);
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <metalsvm/tasks.h>
|
||||
#include <metalsvm/time.h>
|
||||
#include <metalsvm/processor.h>
|
||||
#include <asm/apic.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/irqflags.h>
|
||||
#include <asm/gdt.h>
|
||||
|
@ -35,6 +34,11 @@
|
|||
*/
|
||||
static volatile uint64_t timer_ticks = 0;
|
||||
|
||||
uint64_t get_clock_tick(void)
|
||||
{
|
||||
return timer_ticks;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the timer. In this case, it's very simple: We
|
||||
* increment the 'timer_ticks' variable every time the
|
||||
|
@ -76,21 +80,24 @@ int timer_init(void)
|
|||
{
|
||||
uint64_t start;
|
||||
|
||||
/* Installs 'timer_handler' to IRQ0 */
|
||||
/*
|
||||
* Installs 'timer_handler' for the PIC (0) and APIC timer (16),
|
||||
* only one handler will be later used.
|
||||
*/
|
||||
irq_install_handler(0, timer_handler);
|
||||
irq_install_handler(16, timer_handler);
|
||||
|
||||
if (!has_apic()) {
|
||||
/* set default timer */
|
||||
outportb(0x43, 0x34);
|
||||
start = rdtsc();
|
||||
while(rdtsc() - start < 1000000)
|
||||
;
|
||||
outportb(0x40, LATCH(TIMER_FREQ) & 0xFF); /* low byte */
|
||||
start = rdtsc();
|
||||
while(rdtsc() - start < 1000000)
|
||||
;
|
||||
outportb(0x40, LATCH(TIMER_FREQ) >> 8); /* high byte */
|
||||
}
|
||||
outportb(0x43, 0x34);
|
||||
/* before we write to 0x40, we wait some time */
|
||||
start = rdtsc();
|
||||
while(rdtsc() - start < 1000000)
|
||||
;
|
||||
outportb(0x40, LATCH(TIMER_FREQ) & 0xFF); /* low byte */
|
||||
/* before we write to 0x40, we wait some time */
|
||||
start = rdtsc();
|
||||
while(rdtsc() - start < 1000000)
|
||||
;
|
||||
outportb(0x40, LATCH(TIMER_FREQ) >> 8); /* high byte */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ extern "C" {
|
|||
#define KERNEL_STACK_SIZE 8192
|
||||
#define KMSG_SIZE (128*1024)
|
||||
#define PAGE_SIZE 4096
|
||||
#define CACHE_LINE 64
|
||||
#define MAILBOX_SIZE 2
|
||||
#define TIMER_FREQ 100 /* in HZ */
|
||||
#define CLOCK_TICK_RATE 1193182 /* 8254 chip's internal oscillator frequency */
|
||||
|
@ -51,7 +52,6 @@ extern "C" {
|
|||
#define CONFIG_KEYBOARD
|
||||
#define CONFIG_MULTIBOOT
|
||||
//#define CONFIG_ROCKCREEK
|
||||
//#define CONFIG_LAPICTIMER
|
||||
|
||||
#define BUILTIN_EXPECT(exp, b) __builtin_expect((exp), (b))
|
||||
//#define BUILTIN_EXPECT(exp, b) (exp)
|
||||
|
|
|
@ -26,6 +26,7 @@ extern "C" {
|
|||
|
||||
int timer_init(void);
|
||||
void timer_wait(unsigned int);
|
||||
uint64_t get_clock_tick(void);
|
||||
|
||||
static inline void sleep(unsigned int i) { timer_wait(i*TIMER_FREQ); }
|
||||
|
||||
|
|
|
@ -160,7 +160,8 @@ int main(void)
|
|||
|
||||
kprintf("Kernel starts at %p and ends at %p\n", &kernel_start, &kernel_end);
|
||||
|
||||
detect_cpu_frequency();
|
||||
system_calibration();
|
||||
|
||||
kprintf("Processor frequency: %d MHz\n", get_cpu_frequency()/1000000);
|
||||
kprintf("Total memory: %u MBytes\n", atomic_int32_read(&total_pages)/((1024*1024)/PAGE_SIZE));
|
||||
kprintf("Current allocated memory: %u KBytes\n", atomic_int32_read(&total_allocated_pages)*(PAGE_SIZE/1024));
|
||||
|
|
|
@ -27,9 +27,18 @@ static int cpu_freq = 0;
|
|||
int detect_cpu_frequency(void)
|
||||
{
|
||||
uint64_t start, end;
|
||||
uint64_t ticks, old;
|
||||
|
||||
old = get_clock_tick();
|
||||
|
||||
/* wait for the next time slice */
|
||||
while((ticks = get_clock_tick()) - old == 0)
|
||||
;
|
||||
|
||||
start = rdtsc();
|
||||
timer_wait(1*TIMER_FREQ);
|
||||
/* wait 5 time slices to determine the frequency */
|
||||
while(get_clock_tick() - ticks < TIMER_FREQ)
|
||||
;
|
||||
end = rdtsc();
|
||||
|
||||
cpu_freq = end - start;
|
||||
|
|
|
@ -110,8 +110,9 @@ int mmu_init(void)
|
|||
}
|
||||
} else {
|
||||
kputs("Unable to initialize the memory management subsystem\n");
|
||||
while(1)
|
||||
while(1) {
|
||||
NOP8;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Add table
Reference in a new issue