Merge branch 'lab3' into lab4
This commit is contained in:
commit
4e4de7b836
19
inc/x86.h
19
inc/x86.h
@ -3,6 +3,10 @@
|
||||
|
||||
#include <inc/types.h>
|
||||
|
||||
#define MSR_IA32_SYSENTER_CS 0x174
|
||||
#define MSR_IA32_SYSENTER_EIP 0x176
|
||||
#define MSR_IA32_SYSENTER_ESP 0x175
|
||||
|
||||
static inline void
|
||||
breakpoint(void)
|
||||
{
|
||||
@ -261,4 +265,19 @@ xchg(volatile uint32_t *addr, uint32_t newval)
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline void
|
||||
write_msr(uint32_t reg, uint32_t low, uint32_t high) {
|
||||
asm volatile("wrmsr\n\t"
|
||||
:: "c" (reg), "a" (low), "d" (high));
|
||||
}
|
||||
|
||||
static inline void
|
||||
read_msr(uint32_t reg, uint32_t* low, uint32_t* high) {
|
||||
uint32_t eax, edx;
|
||||
asm volatile("rdmsr\n\t"
|
||||
: "=a" (eax), "=d" (edx) : "c" (reg));
|
||||
*low = eax;
|
||||
*high = edx;
|
||||
}
|
||||
|
||||
#endif /* !JOS_INC_X86_H */
|
||||
|
@ -17,6 +17,7 @@ KERN_SRCFILES := kern/entry.S \
|
||||
kern/entrypgdir.c \
|
||||
kern/init.c \
|
||||
kern/console.c \
|
||||
kern/ansi.c \
|
||||
kern/monitor.c \
|
||||
kern/pmap.c \
|
||||
kern/env.c \
|
||||
@ -55,7 +56,8 @@ KERN_BINFILES := user/hello \
|
||||
user/faultread \
|
||||
user/faultreadkernel \
|
||||
user/faultwrite \
|
||||
user/faultwritekernel
|
||||
user/faultwritekernel \
|
||||
user/getc
|
||||
|
||||
# Binary files for LAB4
|
||||
KERN_BINFILES += user/idle \
|
||||
|
49
kern/ansi.c
Normal file
49
kern/ansi.c
Normal file
@ -0,0 +1,49 @@
|
||||
#include <kern/ansi.h>
|
||||
|
||||
uint8_t vga_colors[8] = {
|
||||
[0] = 0,
|
||||
[1] = 4,
|
||||
[2] = 2,
|
||||
[3] = 6,
|
||||
[4] = 1,
|
||||
[5] = 5,
|
||||
[6] = 3,
|
||||
[7] = 7
|
||||
};
|
||||
|
||||
void
|
||||
ansi_step(void (*putcf)(int), int c, struct AttrState* s) {
|
||||
switch(s->state) {
|
||||
case NORMAL:
|
||||
if(c != 27) putcf(c | (s->cattrs << 8));
|
||||
else s->state = ESC;
|
||||
return;
|
||||
case ESC:
|
||||
s->state = (c == '[') ? BRACKET : NORMAL;
|
||||
return;
|
||||
case BRACKET:
|
||||
if(c == '3') {
|
||||
s->state = COLOR_FG;
|
||||
} else if(c == '0') {
|
||||
s->state = COLOR;
|
||||
s->attrs = 0;
|
||||
} else {
|
||||
s->state = NORMAL;
|
||||
}
|
||||
return;
|
||||
case COLOR_FG:
|
||||
if(c >= '0' && c <= '9') {
|
||||
s->attrs = (s->attrs & ~(0x07)) | vga_colors[c - '0'];
|
||||
c |= ((c - '0') << 8);
|
||||
s->state = COLOR;
|
||||
} else {
|
||||
s->state = NORMAL;
|
||||
}
|
||||
return;
|
||||
case COLOR:
|
||||
s->state = (c == ';') ? BRACKET : NORMAL;
|
||||
if(c == 'm') s->cattrs = s->attrs;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
36
kern/ansi.h
Normal file
36
kern/ansi.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef _ANSI_H_
|
||||
#define _ANSI_H_
|
||||
|
||||
#include <inc/types.h>
|
||||
|
||||
#define ACOL_WRAP(s) "\33[" s "m"
|
||||
#define ACOL_BLACK ACOL_WRAP("30")
|
||||
#define ACOL_RED ACOL_WRAP("31")
|
||||
#define ACOL_GREEN ACOL_WRAP("32")
|
||||
#define ACOL_YELLOW ACOL_WRAP("33")
|
||||
#define ACOL_BLUE ACOL_WRAP("34")
|
||||
#define ACOL_MAGENTA ACOL_WRAP("35")
|
||||
#define ACOL_CYAN ACOL_WRAP("36")
|
||||
#define ACOL_WHITE ACOL_WRAP("37")
|
||||
#define ACOL_CLEAR ACOL_WRAP("0")
|
||||
|
||||
#define ACOL_TAG(c, s) "[" c s ACOL_CLEAR "] "
|
||||
#define ACOL_NOTE(s) ACOL_TAG(ACOL_WHITE, " Note ") s
|
||||
#define ACOL_WARN(s) ACOL_TAG(ACOL_YELLOW, "Warning") s
|
||||
#define ACOL_ERR(s) ACOL_TAG(ACOL_RED, " Error ") s
|
||||
|
||||
struct AttrState {
|
||||
uint8_t cattrs;
|
||||
uint8_t attrs;
|
||||
enum {
|
||||
NORMAL,
|
||||
ESC,
|
||||
BRACKET,
|
||||
COLOR_FG,
|
||||
COLOR
|
||||
} state;
|
||||
};
|
||||
|
||||
void ansi_step(void (*putcf)(int), int c, struct AttrState* s);
|
||||
|
||||
#endif
|
@ -9,6 +9,7 @@
|
||||
#include <kern/console.h>
|
||||
#include <kern/trap.h>
|
||||
#include <kern/picirq.h>
|
||||
#include <kern/ansi.h>
|
||||
|
||||
static void cons_intr(int (*proc)(void));
|
||||
static void cons_putc(int c);
|
||||
@ -130,6 +131,7 @@ lpt_putc(int c)
|
||||
static unsigned addr_6845;
|
||||
static uint16_t *crt_buf;
|
||||
static uint16_t crt_pos;
|
||||
static struct AttrState attst;
|
||||
|
||||
static void
|
||||
cga_init(void)
|
||||
@ -138,6 +140,10 @@ cga_init(void)
|
||||
uint16_t was;
|
||||
unsigned pos;
|
||||
|
||||
attst.cattrs = 0;
|
||||
attst.attrs = 0;
|
||||
attst.state = NORMAL;
|
||||
|
||||
cp = (uint16_t*) (KERNBASE + CGA_BUF);
|
||||
was = *cp;
|
||||
*cp = (uint16_t) 0xA55A;
|
||||
@ -159,10 +165,15 @@ cga_init(void)
|
||||
crt_pos = pos;
|
||||
}
|
||||
|
||||
|
||||
static void cga_putc_internal(int c);
|
||||
|
||||
static void
|
||||
cga_putc(int c)
|
||||
cga_putc(int c){
|
||||
ansi_step(cga_putc_internal, c, &attst);
|
||||
}
|
||||
|
||||
static void
|
||||
cga_putc_internal(int c)
|
||||
{
|
||||
// if no attribute given, then use black on white
|
||||
if (!(c & ~0xFF))
|
||||
|
56
kern/env.c
56
kern/env.c
@ -119,6 +119,13 @@ env_init(void)
|
||||
{
|
||||
// Set up envs array
|
||||
// LAB 3: Your code here.
|
||||
size_t i = NENV;
|
||||
while(true) {
|
||||
i--;
|
||||
envs[i].env_link = env_free_list;
|
||||
env_free_list = &envs[i];
|
||||
if(i == 0) break;
|
||||
}
|
||||
|
||||
// Per-CPU part of the initialization
|
||||
env_init_percpu();
|
||||
@ -180,8 +187,9 @@ env_setup_vm(struct Env *e)
|
||||
// is an exception -- you need to increment env_pgdir's
|
||||
// pp_ref for env_free to work correctly.
|
||||
// - The functions in kern/pmap.h are handy.
|
||||
|
||||
// LAB 3: Your code here.
|
||||
p->pp_ref++;
|
||||
e->env_pgdir = page2kva(p);
|
||||
memcpy(e->env_pgdir + PDX(UTOP), kern_pgdir + PDX(UTOP), sizeof(pde_t) * (NPDENTRIES - PDX(UTOP)));
|
||||
|
||||
// UVPT maps the env's own page table read-only.
|
||||
// Permissions: kernel R, user R
|
||||
@ -279,6 +287,17 @@ region_alloc(struct Env *e, void *va, size_t len)
|
||||
// 'va' and 'len' values that are not page-aligned.
|
||||
// You should round va down, and round (va + len) up.
|
||||
// (Watch out for corner-cases!)
|
||||
va = ROUNDDOWN(va, PGSIZE);
|
||||
size_t count = ROUNDUP(len, PGSIZE) / PGSIZE;
|
||||
struct PageInfo* p;
|
||||
|
||||
while(count--) {
|
||||
if(!(p = page_alloc(0)))
|
||||
panic("Failed to allocate memory");
|
||||
if(page_insert(e->env_pgdir, p, va, PTE_U | PTE_W))
|
||||
panic("Failed to allocate memory for page table");
|
||||
va += PGSIZE;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -336,10 +355,24 @@ load_icode(struct Env *e, uint8_t *binary)
|
||||
|
||||
// LAB 3: Your code here.
|
||||
|
||||
// TODO validate the headers
|
||||
struct Elf* elf = (struct Elf*) binary;
|
||||
struct Proghdr* ph = (struct Proghdr*) (binary + elf->e_phoff);
|
||||
struct Proghdr* phend = ph + elf->e_phnum;
|
||||
for(; ph < phend; ph++) {
|
||||
if(ph->p_type != ELF_PROG_LOAD) continue;
|
||||
|
||||
region_alloc(e, (void*) ph->p_va, ph->p_memsz);
|
||||
lcr3(PADDR(e->env_pgdir));
|
||||
memcpy((void*) ph->p_va, binary + ph->p_offset, ph->p_filesz);
|
||||
memset((void*) ph->p_va + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz);
|
||||
lcr3(PADDR(kern_pgdir));
|
||||
}
|
||||
e->env_tf.tf_eip = elf->e_entry;
|
||||
|
||||
// Now map one page for the program's initial stack
|
||||
// at virtual address USTACKTOP - PGSIZE.
|
||||
|
||||
// LAB 3: Your code here.
|
||||
region_alloc(e, (void*) USTACKTOP - PGSIZE, PGSIZE);
|
||||
}
|
||||
|
||||
//
|
||||
@ -353,6 +386,11 @@ void
|
||||
env_create(uint8_t *binary, enum EnvType type)
|
||||
{
|
||||
// LAB 3: Your code here.
|
||||
struct Env* new_env;
|
||||
if(env_alloc(&new_env, 0) < 0)
|
||||
panic("Failed to allocate environment");
|
||||
new_env->env_type = type;
|
||||
load_icode(new_env, binary);
|
||||
}
|
||||
|
||||
//
|
||||
@ -483,7 +521,13 @@ env_run(struct Env *e)
|
||||
// e->env_tf to sensible values.
|
||||
|
||||
// LAB 3: Your code here.
|
||||
|
||||
panic("env_run not yet implemented");
|
||||
if(curenv && curenv->env_status == ENV_RUNNING) {
|
||||
curenv->env_status = ENV_RUNNABLE;
|
||||
}
|
||||
curenv = e;
|
||||
e->env_status = ENV_RUNNING;
|
||||
e->env_runs++;
|
||||
lcr3(PADDR(e->env_pgdir));
|
||||
env_pop_tf(&e->env_tf);
|
||||
}
|
||||
|
||||
|
12
kern/init.c
12
kern/init.c
@ -3,6 +3,7 @@
|
||||
#include <inc/stdio.h>
|
||||
#include <inc/string.h>
|
||||
#include <inc/assert.h>
|
||||
#include <inc/x86.h>
|
||||
|
||||
#include <kern/monitor.h>
|
||||
#include <kern/console.h>
|
||||
@ -17,6 +18,7 @@
|
||||
|
||||
static void boot_aps(void);
|
||||
|
||||
void sysenter_handler();
|
||||
|
||||
void
|
||||
i386_init(void)
|
||||
@ -26,6 +28,16 @@ i386_init(void)
|
||||
cons_init();
|
||||
|
||||
cprintf("444544 decimal is %o octal!\n", 444544);
|
||||
cprintf("\33[31m" "C"
|
||||
"\33[33m" "o"
|
||||
"\33[32m" "l"
|
||||
"\33[36m" "o"
|
||||
"\33[34m" "r"
|
||||
"\33[0m" " Works!" "\n");
|
||||
|
||||
write_msr(MSR_IA32_SYSENTER_EIP, (uint32_t) sysenter_handler, 0);
|
||||
write_msr(MSR_IA32_SYSENTER_ESP, KSTACKTOP, 0);
|
||||
write_msr(MSR_IA32_SYSENTER_CS, GD_KT, 0);
|
||||
|
||||
// Lab 2 memory management initialization functions
|
||||
mem_init();
|
||||
|
@ -196,7 +196,8 @@ debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info)
|
||||
|
||||
|
||||
// Search within [lline, rline] for the line number stab.
|
||||
// If found, set info->eip_line to the right line number.
|
||||
// If found, set info->eip_line to the correct line number.
|
||||
// e.g., info->eip_line = stabs[lline].n_desc
|
||||
// If not found, return -1.
|
||||
//
|
||||
// Hint:
|
||||
@ -204,6 +205,12 @@ debuginfo_eip(uintptr_t addr, struct Eipdebuginfo *info)
|
||||
// Look at the STABS documentation and <inc/stab.h> to find
|
||||
// which one.
|
||||
// Your code here.
|
||||
stab_binsearch(stabs, &lline, &rline, N_SLINE, addr);
|
||||
if(lline <= rline) {
|
||||
info->eip_line = stabs[lline].n_desc;
|
||||
} else {
|
||||
info->eip_line = -1;
|
||||
}
|
||||
|
||||
|
||||
// Search backwards from the line number for the relevant filename
|
||||
|
163
kern/monitor.c
163
kern/monitor.c
@ -7,10 +7,14 @@
|
||||
#include <inc/assert.h>
|
||||
#include <inc/x86.h>
|
||||
|
||||
#include <kern/env.h>
|
||||
#include <kern/ansi.h>
|
||||
#include <kern/console.h>
|
||||
#include <kern/monitor.h>
|
||||
#include <kern/kdebug.h>
|
||||
#include <kern/trap.h>
|
||||
#include <kern/pmap.h>
|
||||
#include <inc/string.h>
|
||||
|
||||
#define CMDBUF_SIZE 80 // enough for one VGA text line
|
||||
|
||||
@ -26,6 +30,12 @@ struct Command {
|
||||
static struct Command commands[] = {
|
||||
{ "help", "Display this list of commands", mon_help },
|
||||
{ "kerninfo", "Display information about the kernel", mon_kerninfo },
|
||||
{ "backtrace", "Display current backtrace", mon_backtrace },
|
||||
{ "showmappings", "Display the physical mappings for range", mon_showmappings },
|
||||
{ "mperms", "Change the permissions of a memory range", mon_mperms },
|
||||
{ "resume", "Resume from a breakpoint", mon_resume },
|
||||
{ "step", "Step to next instruction", mon_step }
|
||||
|
||||
};
|
||||
|
||||
/***** Implementations of basic kernel monitor commands *****/
|
||||
@ -46,7 +56,7 @@ mon_kerninfo(int argc, char **argv, struct Trapframe *tf)
|
||||
extern char _start[], entry[], etext[], edata[], end[];
|
||||
|
||||
cprintf("Special kernel symbols:\n");
|
||||
cprintf(" _start %08x (phys)\n", _start);
|
||||
cprintf(" _start %08x (phys)\n", _start);
|
||||
cprintf(" entry %08x (virt) %08x (phys)\n", entry, entry - KERNBASE);
|
||||
cprintf(" etext %08x (virt) %08x (phys)\n", etext, etext - KERNBASE);
|
||||
cprintf(" edata %08x (virt) %08x (phys)\n", edata, edata - KERNBASE);
|
||||
@ -62,10 +72,161 @@ mon_backtrace(int argc, char **argv, struct Trapframe *tf)
|
||||
// LAB 1: Your code here.
|
||||
// HINT 1: use read_ebp().
|
||||
// HINT 2: print the current ebp on the first line (not current_ebp[0])
|
||||
uint32_t* ebp;
|
||||
uint32_t eip;
|
||||
struct Eipdebuginfo info;
|
||||
|
||||
ebp = (uint32_t*) read_ebp();
|
||||
cprintf("Stack backtrace:\n");
|
||||
while(ebp) {
|
||||
eip = ebp[1];
|
||||
cprintf(" ebp %08x eip %08x args %08x %08x %08x %08x %08x\n",
|
||||
ebp, eip, ebp[2], ebp[3], ebp[4], ebp[5], ebp[6]);
|
||||
ebp = (uint32_t*) ebp[0];
|
||||
|
||||
debuginfo_eip(eip, &info);
|
||||
cprintf(" %s:%d: %.*s+%d\n",
|
||||
info.eip_file, info.eip_line,
|
||||
info.eip_fn_namelen, info.eip_fn_name,
|
||||
eip - info.eip_fn_addr);
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define EXPECT_ARGS(n, ac) if(ac - 1 != n) { \
|
||||
cprintf(ACOL_ERR("Expected %d arguments, " \
|
||||
"got %d\n"), n, ac - 1); \
|
||||
return 1; }
|
||||
|
||||
#define VA_TXT ACOL_CYAN "VA" ACOL_CLEAR
|
||||
#define PA_TXT ACOL_GREEN "PA" ACOL_CLEAR
|
||||
|
||||
char*
|
||||
decode_pte_perms(pte_t pte, char* buffer) {
|
||||
buffer[0] = (pte & PTE_W) ? 'w' : 'r';
|
||||
buffer[1] = (pte & PTE_U) ? 'u' : 'k';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void
|
||||
get_pagebounds(char* one, char* two, uintptr_t* pone, uintptr_t* ptwo) {
|
||||
long from = strtol(one, NULL, 0);
|
||||
long to = strtol(two, NULL, 0);
|
||||
*pone = ROUNDDOWN(from, PGSIZE);
|
||||
*ptwo = ROUNDUP(to, PGSIZE);
|
||||
if(*pone != from) cprintf(ACOL_WARN("Aligning start address %p down to %p\n"),
|
||||
from, *pone);
|
||||
if(*ptwo != to) cprintf(ACOL_WARN("Aligning end address %p up to %p\n"),
|
||||
to, *ptwo);
|
||||
}
|
||||
|
||||
int
|
||||
mon_showmappings(int argc, char** argv, struct Trapframe* tf) {
|
||||
EXPECT_ARGS(2, argc);
|
||||
uintptr_t va_start, va_end;
|
||||
char buffer[3] = {0};
|
||||
|
||||
get_pagebounds(argv[1], argv[2], &va_start, &va_end);
|
||||
|
||||
if(va_start == va_end) va_end += PGSIZE;
|
||||
while(va_start < va_end) {
|
||||
pte_t* pte = pgdir_walk(kern_pgdir, (const void*) va_start, 0);
|
||||
cprintf(VA_TXT " 0x%08p -> ", va_start);
|
||||
|
||||
if(pte && (*pte & PTE_P)) {
|
||||
cprintf(PA_TXT " 0x%08p (%s)\n", PTE_ADDR(*pte),
|
||||
decode_pte_perms(*pte, buffer));
|
||||
} else {
|
||||
cprintf(PA_TXT " ------------\n");
|
||||
}
|
||||
|
||||
va_start += PGSIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mon_mperms(int argc, char** argv, struct Trapframe* tf) {
|
||||
EXPECT_ARGS(3, argc);
|
||||
pte_t perms = 0;
|
||||
enum {
|
||||
PERM_ADD,
|
||||
PERM_REMOVE,
|
||||
PERM_SET
|
||||
} pmode;
|
||||
|
||||
const char* str = argv[1];
|
||||
if(str[0] == '+') { pmode = PERM_ADD; str++; }
|
||||
else if(str[0] == '-') { pmode = PERM_REMOVE; str++; }
|
||||
else pmode = PERM_SET;
|
||||
|
||||
while(*str) {
|
||||
if(*str == 'w') perms |= PTE_W;
|
||||
else if(*str == 'u') perms |= PTE_U;
|
||||
else {
|
||||
cprintf(ACOL_ERR("Unknown permission character %c\n"), *str);
|
||||
return 1;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
|
||||
uintptr_t va_start, va_end;
|
||||
get_pagebounds(argv[2], argv[3], &va_start, &va_end);
|
||||
if(va_start == va_end) va_end += PGSIZE;
|
||||
while(va_start < va_end) {
|
||||
pte_t* pte = pgdir_walk(kern_pgdir, (void*) va_start, 0);
|
||||
if(!pte || !(*pte & PTE_P)) { va_start += PGSIZE; continue; }
|
||||
|
||||
if(pmode == PERM_ADD) {
|
||||
*pte |= perms;
|
||||
} else if(pmode == PERM_REMOVE) {
|
||||
*pte &= ~perms;
|
||||
} else if(pmode == PERM_SET) {
|
||||
*pte = PTE_ADDR(*pte) | perms | PTE_P;
|
||||
}
|
||||
va_start += PGSIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is a nice thought and all...
|
||||
// But we should modify the trap frame.
|
||||
// I feel stupid.
|
||||
static inline void
|
||||
set_eflag(uint16_t flagno, int value) {
|
||||
uint32_t temp;
|
||||
uint32_t mask = ~(1 << flagno);
|
||||
uint32_t regv = value << flagno;
|
||||
asm volatile("pushfl\n\t"
|
||||
"pop %w0\n\t"
|
||||
"and %w1, %w0\n\t"
|
||||
"or %w2, %w0\n\t"
|
||||
"push %w0\n\t"
|
||||
"popfl\n\t"
|
||||
: "=r" (temp)
|
||||
: "r" (mask), "r" (regv));
|
||||
}
|
||||
|
||||
#define EXPECT_BRKPT if(!(tf->tf_trapno == T_BRKPT || tf->tf_trapno == T_DEBUG)) { \
|
||||
cprintf(ACOL_ERR("I don't think I should resume from this.\n")); \
|
||||
return 0; }
|
||||
#define EFLAGS_TF 0x8
|
||||
|
||||
int mon_resume(int argc, char** argv, struct Trapframe* tf) {
|
||||
EXPECT_BRKPT;
|
||||
tf->tf_eflags &= ~(1 << EFLAGS_TF);
|
||||
env_pop_tf(tf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mon_step(int argc, char** argv, struct Trapframe* tf) {
|
||||
EXPECT_BRKPT;
|
||||
tf->tf_eflags |= 1 << EFLAGS_TF;
|
||||
env_pop_tf(tf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***** Kernel monitor command interpreter *****/
|
||||
|
||||
|
@ -15,5 +15,9 @@ void monitor(struct Trapframe *tf);
|
||||
int mon_help(int argc, char **argv, struct Trapframe *tf);
|
||||
int mon_kerninfo(int argc, char **argv, struct Trapframe *tf);
|
||||
int mon_backtrace(int argc, char **argv, struct Trapframe *tf);
|
||||
int mon_showmappings(int argc, char **argv, struct Trapframe *tf);
|
||||
int mon_mperms(int argc, char** argv, struct Trapframe* tf);
|
||||
int mon_resume(int argc, char** argv, struct Trapframe* tf);
|
||||
int mon_step(int argc, char** argv, struct Trapframe* tf);
|
||||
|
||||
#endif // !JOS_KERN_MONITOR_H
|
||||
|
151
kern/pmap.c
151
kern/pmap.c
@ -6,6 +6,7 @@
|
||||
#include <inc/string.h>
|
||||
#include <inc/assert.h>
|
||||
|
||||
#include <kern/monitor.h>
|
||||
#include <kern/pmap.h>
|
||||
#include <kern/kclock.h>
|
||||
#include <kern/env.h>
|
||||
@ -105,8 +106,10 @@ boot_alloc(uint32_t n)
|
||||
// to a multiple of PGSIZE.
|
||||
//
|
||||
// LAB 2: Your code here.
|
||||
result = nextfree;
|
||||
nextfree = ROUNDUP(nextfree + n, PGSIZE);
|
||||
|
||||
return NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set up a two-level page table:
|
||||
@ -127,9 +130,6 @@ mem_init(void)
|
||||
// Find out how much memory the machine has (npages & npages_basemem).
|
||||
i386_detect_memory();
|
||||
|
||||
// Remove this line when you're ready to test this function.
|
||||
panic("mem_init: This function is not finished\n");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// create initial page directory.
|
||||
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
|
||||
@ -151,11 +151,16 @@ mem_init(void)
|
||||
// array. 'npages' is the number of physical pages in memory. Use memset
|
||||
// to initialize all fields of each struct PageInfo to 0.
|
||||
// Your code goes here:
|
||||
|
||||
size_t pages_size = sizeof(struct PageInfo) * npages;
|
||||
pages = boot_alloc(pages_size);
|
||||
memset(pages, 0, pages_size);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Make 'envs' point to an array of size 'NENV' of 'struct Env'.
|
||||
// LAB 3: Your code here.
|
||||
size_t envs_size = sizeof(struct Env) * NENV;
|
||||
envs = boot_alloc(envs_size);
|
||||
memset(envs, 0, envs_size);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Now that we've allocated the initial kernel data structures, we set
|
||||
@ -179,6 +184,11 @@ mem_init(void)
|
||||
// (ie. perm = PTE_U | PTE_P)
|
||||
// - pages itself -- kernel RW, user NONE
|
||||
// Your code goes here:
|
||||
boot_map_region(kern_pgdir,
|
||||
UPAGES, ROUNDUP(pages_size, PGSIZE),
|
||||
PADDR(pages), PTE_U);
|
||||
kern_pgdir[PDX(UPAGES)] |= PTE_U;
|
||||
kern_pgdir[PDX(UPAGES)] &= ~PTE_W;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Map the 'envs' array read-only by the user at linear address UENVS
|
||||
@ -187,6 +197,11 @@ mem_init(void)
|
||||
// - the new image at UENVS -- kernel R, user R
|
||||
// - envs itself -- kernel RW, user NONE
|
||||
// LAB 3: Your code here.
|
||||
boot_map_region(kern_pgdir,
|
||||
UENVS, ROUNDUP(envs_size, PGSIZE),
|
||||
PADDR(envs), PTE_U);
|
||||
kern_pgdir[PDX(UENVS)] |= PTE_U;
|
||||
kern_pgdir[PDX(UPAGES)] &= ~PTE_W;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Use the physical memory that 'bootstack' refers to as the kernel
|
||||
@ -199,6 +214,11 @@ mem_init(void)
|
||||
// overwrite memory. Known as a "guard page".
|
||||
// Permissions: kernel RW, user NONE
|
||||
// Your code goes here:
|
||||
boot_map_region(kern_pgdir,
|
||||
KSTACKTOP-KSTKSIZE, KSTKSIZE,
|
||||
PADDR(bootstack), PTE_W);
|
||||
kern_pgdir[PDX(KSTACKTOP-KSTKSIZE)] |= PTE_W;
|
||||
kern_pgdir[PDX(KSTACKTOP-KSTKSIZE)] &= ~PTE_U;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Map all of physical memory at KERNBASE.
|
||||
@ -208,6 +228,12 @@ mem_init(void)
|
||||
// we just set up the mapping anyway.
|
||||
// Permissions: kernel RW, user NONE
|
||||
// Your code goes here:
|
||||
boot_map_region(kern_pgdir,
|
||||
KERNBASE, 0x100000000 - KERNBASE,
|
||||
0, PTE_W);
|
||||
kern_pgdir[PDX(KERNBASE)] |= PTE_W | PTE_P;
|
||||
kern_pgdir[PDX(KERNBASE)] &= ~PTE_U;
|
||||
|
||||
|
||||
// Initialize the SMP-related parts of the memory map
|
||||
mem_init_mp();
|
||||
@ -267,6 +293,14 @@ mem_init_mp(void)
|
||||
// The 'pages' array has one 'struct PageInfo' entry per physical page.
|
||||
// Pages are reference counted, and free pages are kept on a linked list.
|
||||
// --------------------------------------------------------------
|
||||
bool
|
||||
is_reserved(size_t pagenum) {
|
||||
if(pagenum == 0) return true;
|
||||
if(pagenum >= PGNUM(IOPHYSMEM) &&
|
||||
pagenum < PGNUM(PADDR(boot_alloc(0)))) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize page structure and memory free list.
|
||||
@ -300,9 +334,13 @@ page_init(void)
|
||||
// free pages!
|
||||
size_t i;
|
||||
for (i = 0; i < npages; i++) {
|
||||
pages[i].pp_ref = 0;
|
||||
pages[i].pp_link = page_free_list;
|
||||
page_free_list = &pages[i];
|
||||
if(is_reserved(i)) {
|
||||
pages[i].pp_ref = 1;
|
||||
} else {
|
||||
pages[i].pp_ref = 0;
|
||||
pages[i].pp_link = page_free_list;
|
||||
page_free_list = &pages[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,8 +359,17 @@ page_init(void)
|
||||
struct PageInfo *
|
||||
page_alloc(int alloc_flags)
|
||||
{
|
||||
// Fill this function in
|
||||
return 0;
|
||||
struct PageInfo* to_return = page_free_list;
|
||||
if(to_return == 0) return NULL;
|
||||
|
||||
page_free_list = to_return->pp_link;
|
||||
to_return->pp_link = NULL;
|
||||
|
||||
if(alloc_flags & ALLOC_ZERO) {
|
||||
memset(page2kva(to_return), 0, PGSIZE);
|
||||
}
|
||||
|
||||
return to_return;
|
||||
}
|
||||
|
||||
//
|
||||
@ -332,9 +379,10 @@ page_alloc(int alloc_flags)
|
||||
void
|
||||
page_free(struct PageInfo *pp)
|
||||
{
|
||||
// Fill this function in
|
||||
// Hint: You may want to panic if pp->pp_ref is nonzero or
|
||||
// pp->pp_link is not NULL.
|
||||
if(pp->pp_ref || pp->pp_link != NULL)
|
||||
panic("Freeing page with nonzero reference count!");
|
||||
pp->pp_link = page_free_list;
|
||||
page_free_list = pp;
|
||||
}
|
||||
|
||||
//
|
||||
@ -373,8 +421,25 @@ page_decref(struct PageInfo* pp)
|
||||
pte_t *
|
||||
pgdir_walk(pde_t *pgdir, const void *va, int create)
|
||||
{
|
||||
pte_t* base_table = NULL;
|
||||
|
||||
if(pgdir[PDX(va)] & PTE_P) {
|
||||
// We have a valid page table; awesome!
|
||||
base_table = KADDR(PTE_ADDR(pgdir[PDX(va)]));
|
||||
} else {
|
||||
if(!create) return NULL;
|
||||
|
||||
struct PageInfo* page = page_alloc(ALLOC_ZERO);
|
||||
if(!page) return NULL;
|
||||
|
||||
page->pp_ref++;
|
||||
physaddr_t ppa = page2pa(page);
|
||||
pgdir[PDX(va)] = ppa | PTE_P | PTE_U | PTE_W;
|
||||
base_table = KADDR(ppa);
|
||||
}
|
||||
|
||||
// Fill this function in
|
||||
return NULL;
|
||||
return &base_table[PTX(va)];
|
||||
}
|
||||
|
||||
//
|
||||
@ -391,7 +456,16 @@ pgdir_walk(pde_t *pgdir, const void *va, int create)
|
||||
static void
|
||||
boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
|
||||
{
|
||||
// Fill this function in
|
||||
size_t count = size / PGSIZE;
|
||||
uintptr_t start_va = va;
|
||||
physaddr_t start_pa = pa;
|
||||
while(count-- && start_va <= va && start_pa <= pa) {
|
||||
pte_t* pte = pgdir_walk(pgdir, (void*) va, true);
|
||||
*pte = pa | perm | PTE_P;
|
||||
|
||||
va += PGSIZE;
|
||||
pa += PGSIZE;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -422,7 +496,14 @@ boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm
|
||||
int
|
||||
page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
|
||||
{
|
||||
// Fill this function in
|
||||
pte_t* pte;
|
||||
if(!(pte = pgdir_walk(pgdir, va, true))) return -E_NO_MEM;
|
||||
|
||||
pp->pp_ref++;
|
||||
if(*pte & PTE_P) page_remove(pgdir, va);
|
||||
*pte = page2pa(pp) | PTE_P | perm;
|
||||
tlb_invalidate(pgdir, va);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -440,8 +521,15 @@ page_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm)
|
||||
struct PageInfo *
|
||||
page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
|
||||
{
|
||||
// Fill this function in
|
||||
return NULL;
|
||||
pte_t* pte;
|
||||
if(!(pte = pgdir_walk(pgdir, va, false))) {
|
||||
if(pte_store) *pte_store = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct PageInfo* pp = pa2page(PTE_ADDR(*pte));
|
||||
if(pte_store) *pte_store = pte;
|
||||
return pp;
|
||||
}
|
||||
|
||||
//
|
||||
@ -462,7 +550,15 @@ page_lookup(pde_t *pgdir, void *va, pte_t **pte_store)
|
||||
void
|
||||
page_remove(pde_t *pgdir, void *va)
|
||||
{
|
||||
// Fill this function in
|
||||
pte_t* pte;
|
||||
struct PageInfo* pp;
|
||||
|
||||
pp = page_lookup(pgdir, va, &pte);
|
||||
if(!(*pte & PTE_P)) return;
|
||||
|
||||
if(!(--(pp->pp_ref))) page_free(pp);
|
||||
*pte = 0;
|
||||
tlb_invalidate(pgdir, va);
|
||||
}
|
||||
|
||||
//
|
||||
@ -536,6 +632,23 @@ int
|
||||
user_mem_check(struct Env *env, const void *va, size_t len, int perm)
|
||||
{
|
||||
// LAB 3: Your code here.
|
||||
uintptr_t to_report = (uintptr_t) va;
|
||||
const char* bottom = ROUNDDOWN(va, PGSIZE);
|
||||
size_t aligned_count = ROUNDUP(len, PGSIZE) / PGSIZE;
|
||||
pde_t mask = PTE_P | PTE_U | PTE_W;
|
||||
pde_t perms = mask;
|
||||
|
||||
#define VALID ((perms & (perm | PTE_P)) == (perm | PTE_P))
|
||||
#define DO_CHECK if(!VALID) { user_mem_check_addr = to_report; return -E_FAULT; }
|
||||
|
||||
while(aligned_count--) {
|
||||
perms &= env->env_pgdir[PDX(bottom)] & mask;
|
||||
DO_CHECK;
|
||||
perms &= (*pgdir_walk(env->env_pgdir, bottom, 0)) & mask;
|
||||
DO_CHECK;
|
||||
bottom += PGSIZE;
|
||||
to_report = (uintptr_t) bottom;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -20,9 +20,7 @@ sys_cputs(const char *s, size_t len)
|
||||
{
|
||||
// Check that the user has permission to read memory [s, s+len).
|
||||
// Destroy the environment if not.
|
||||
|
||||
// LAB 3: Your code here.
|
||||
|
||||
user_mem_assert(curenv, s, len, 0);
|
||||
// Print the string supplied by the user.
|
||||
cprintf("%.*s", len, s);
|
||||
}
|
||||
@ -271,9 +269,16 @@ syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4,
|
||||
// Return any appropriate return value.
|
||||
// LAB 3: Your code here.
|
||||
|
||||
panic("syscall not implemented");
|
||||
|
||||
switch (syscallno) {
|
||||
case SYS_cputs:
|
||||
sys_cputs((const char*) a1, a2);
|
||||
return 0;
|
||||
case SYS_cgetc:
|
||||
return sys_cgetc();
|
||||
case SYS_getenvid:
|
||||
return sys_getenvid();
|
||||
case SYS_env_destroy:
|
||||
return sys_env_destroy(a1);
|
||||
default:
|
||||
return -E_INVAL;
|
||||
}
|
||||
|
56
kern/trap.c
56
kern/trap.c
@ -68,6 +68,26 @@ static const char *trapname(int trapno)
|
||||
|
||||
// XYZ: write a function declaration here...
|
||||
// e.g., void t_divide();
|
||||
void t_divide();
|
||||
void t_debug();
|
||||
void t_nmi();
|
||||
void t_brkpt();
|
||||
void t_oflow();
|
||||
void t_bound();
|
||||
void t_illop();
|
||||
void t_device();
|
||||
void t_dblflt();
|
||||
void t_tss();
|
||||
void t_segnp();
|
||||
void t_stack();
|
||||
void t_gpflt();
|
||||
void t_pgflt();
|
||||
void t_fperr();
|
||||
void t_align();
|
||||
void t_mchk();
|
||||
void t_simderr();
|
||||
void t_syscall();
|
||||
void t_default();
|
||||
|
||||
void
|
||||
trap_init(void)
|
||||
@ -84,6 +104,26 @@ trap_init(void)
|
||||
*
|
||||
*/
|
||||
// LAB 3: Your code here.
|
||||
SETGATE(idt[T_DIVIDE], 0, GD_KT, t_divide, 0);
|
||||
SETGATE(idt[T_DEBUG], 0, GD_KT, t_debug, 0);
|
||||
SETGATE(idt[T_NMI], 0, GD_KT, t_nmi, 0);
|
||||
SETGATE(idt[T_BRKPT], 0, GD_KT, t_brkpt, 3);
|
||||
SETGATE(idt[T_OFLOW], 0, GD_KT, t_oflow, 0);
|
||||
SETGATE(idt[T_BOUND], 0, GD_KT, t_bound, 0);
|
||||
SETGATE(idt[T_ILLOP], 0, GD_KT, t_illop, 0);
|
||||
SETGATE(idt[T_DEVICE], 0, GD_KT, t_device, 0);
|
||||
SETGATE(idt[T_DBLFLT], 0, GD_KT, t_dblflt, 0);
|
||||
SETGATE(idt[T_TSS], 0, GD_KT, t_tss, 0);
|
||||
SETGATE(idt[T_SEGNP], 0, GD_KT, t_segnp, 0);
|
||||
SETGATE(idt[T_STACK], 0, GD_KT, t_stack, 0);
|
||||
SETGATE(idt[T_GPFLT], 0, GD_KT, t_gpflt, 0);
|
||||
SETGATE(idt[T_PGFLT], 0, GD_KT, t_pgflt, 0);
|
||||
SETGATE(idt[T_FPERR], 0, GD_KT, t_fperr, 0);
|
||||
SETGATE(idt[T_ALIGN], 0, GD_KT, t_align, 0);
|
||||
SETGATE(idt[T_MCHK], 0, GD_KT, t_mchk, 0);
|
||||
SETGATE(idt[T_SIMDERR], 0, GD_KT, t_simderr, 0);
|
||||
SETGATE(idt[T_SYSCALL], 0, GD_KT, t_syscall, 3);
|
||||
SETGATE(idt[T_DEFAULT], 0, GD_KT, t_default, 0);
|
||||
|
||||
// Per-CPU setup
|
||||
trap_init_percpu();
|
||||
@ -188,6 +228,22 @@ trap_dispatch(struct Trapframe *tf)
|
||||
{
|
||||
// Handle processor exceptions.
|
||||
// LAB 3: Your code here.
|
||||
if (tf->tf_trapno == T_PGFLT) {
|
||||
page_fault_handler(tf);
|
||||
return;
|
||||
} else if (tf->tf_trapno == T_BRKPT || tf->tf_trapno == T_DEBUG) {
|
||||
monitor(tf);
|
||||
return;
|
||||
} else if (tf->tf_trapno == T_SYSCALL) {
|
||||
int32_t returned = syscall(tf->tf_regs.reg_eax,
|
||||
tf->tf_regs.reg_edx,
|
||||
tf->tf_regs.reg_ecx,
|
||||
tf->tf_regs.reg_ebx,
|
||||
tf->tf_regs.reg_edi,
|
||||
tf->tf_regs.reg_esi);
|
||||
tf->tf_regs.reg_eax = returned;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle spurious interrupts
|
||||
// The hardware sometimes raises these because of noise on the
|
||||
|
@ -44,9 +44,44 @@
|
||||
|
||||
.text
|
||||
|
||||
.globl sysenter_handler
|
||||
sysenter_handler:
|
||||
push %ebp // holds env's stack pointer
|
||||
push %esi // holds the env's return addr
|
||||
push %edi
|
||||
push %ebx
|
||||
push %ecx
|
||||
push %edx
|
||||
push %eax
|
||||
call syscall
|
||||
add $0x14, %esp
|
||||
pop %edx
|
||||
pop %ecx
|
||||
sysexit
|
||||
|
||||
/*
|
||||
* Lab 3: Your code here for generating entry points for the different traps.
|
||||
*/
|
||||
TRAPHANDLER_NOEC(t_divide, T_DIVIDE);
|
||||
TRAPHANDLER_NOEC(t_debug, T_DEBUG);
|
||||
TRAPHANDLER_NOEC(t_nmi, T_NMI);
|
||||
TRAPHANDLER_NOEC(t_brkpt, T_BRKPT);
|
||||
TRAPHANDLER_NOEC(t_oflow, T_OFLOW);
|
||||
TRAPHANDLER_NOEC(t_bound, T_OFLOW);
|
||||
TRAPHANDLER_NOEC(t_illop, T_OFLOW);
|
||||
TRAPHANDLER_NOEC(t_device, T_OFLOW);
|
||||
TRAPHANDLER(t_dblflt, T_OFLOW);
|
||||
TRAPHANDLER(t_tss, T_TSS);
|
||||
TRAPHANDLER(t_segnp, T_SEGNP);
|
||||
TRAPHANDLER(t_stack, T_STACK);
|
||||
TRAPHANDLER(t_gpflt, T_GPFLT);
|
||||
TRAPHANDLER(t_pgflt, T_PGFLT);
|
||||
TRAPHANDLER_NOEC(t_fperr, T_FPERR);
|
||||
TRAPHANDLER(t_align, T_ALIGN);
|
||||
TRAPHANDLER_NOEC(t_mchk, T_MCHK);
|
||||
TRAPHANDLER_NOEC(t_simderr, T_SIMDERR);
|
||||
TRAPHANDLER_NOEC(t_syscall, T_SYSCALL);
|
||||
TRAPHANDLER(t_default, T_DEFAULT);
|
||||
|
||||
// HINT 1 : TRAPHANDLER_NOEC(t_divide, T_DIVIDE);
|
||||
// Do something like this if there is no error code for the trap
|
||||
@ -59,4 +94,15 @@
|
||||
* Lab 3: Your code here for _alltraps
|
||||
*/
|
||||
|
||||
|
||||
.globl _alltraps
|
||||
_alltraps:
|
||||
sub $0x2, %esp
|
||||
pushw %ds
|
||||
sub $0x2, %esp
|
||||
pushw %es
|
||||
pushal
|
||||
mov $(GD_KD), %eax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
pushl %esp
|
||||
call trap
|
||||
|
@ -13,7 +13,8 @@ libmain(int argc, char **argv)
|
||||
{
|
||||
// set thisenv to point at our Env structure in envs[].
|
||||
// LAB 3: Your code here.
|
||||
thisenv = 0;
|
||||
envid_t id = sys_getenvid();
|
||||
thisenv = &envs[ENVX(id)];
|
||||
|
||||
// save the name of the program so that panic() can use it
|
||||
if (argc > 0)
|
||||
|
@ -207,11 +207,9 @@ vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap)
|
||||
|
||||
// (unsigned) octal
|
||||
case 'o':
|
||||
// LAB 1: Replace this with your code.
|
||||
putch('X', putdat);
|
||||
putch('X', putdat);
|
||||
putch('X', putdat);
|
||||
break;
|
||||
num = getuint(&ap, lflag);
|
||||
base = 8;
|
||||
goto number;
|
||||
|
||||
// pointer
|
||||
case 'p':
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <inc/syscall.h>
|
||||
#include <inc/lib.h>
|
||||
#include <inc/x86.h>
|
||||
|
||||
static inline int32_t
|
||||
syscall(int num, int check, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
|
||||
@ -37,16 +38,30 @@ syscall(int num, int check, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int32_t
|
||||
fast_syscall(int num, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4) {
|
||||
asm volatile(
|
||||
"push %%ebp\n\t"
|
||||
"mov %%esp, %%ebp\n\t"
|
||||
"lea syscall_ret_%=, %%esi\n\t"
|
||||
"sysenter\n\t"
|
||||
"syscall_ret_%=: pop %%ebp\n\t"
|
||||
: "+a" (num)
|
||||
: "d" (a1), "c" (a2), "b" (a3), "D" (a4)
|
||||
: "esi");
|
||||
return num;
|
||||
}
|
||||
|
||||
void
|
||||
sys_cputs(const char *s, size_t len)
|
||||
{
|
||||
syscall(SYS_cputs, 0, (uint32_t)s, len, 0, 0, 0);
|
||||
fast_syscall(SYS_cputs, (uint32_t)s, len, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_cgetc(void)
|
||||
{
|
||||
return syscall(SYS_cgetc, 0, 0, 0, 0, 0, 0);
|
||||
return fast_syscall(SYS_cgetc, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -1,5 +1,5 @@
|
||||
OSU ID (xxx-yyy-zzz) : 933456789
|
||||
FLIP ID (e.g., jangye) : jangye
|
||||
Name : Yeongjin Jang
|
||||
OSU ID (xxx-yyy-zzz) : 932700867
|
||||
FLIP ID (e.g., jangye) : fedorind
|
||||
Name : Danila Fedorin
|
||||
CS 444/544 ? : 444
|
||||
Lab Class # : Lab 1
|
||||
Lab Class # : Lab 2
|
||||
|
9
user/getc.c
Normal file
9
user/getc.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
char c;
|
||||
while(!(c = sys_cgetc()));
|
||||
cprintf("got character %c\n", c);
|
||||
}
|
Loading…
Reference in New Issue
Block a user