diff --git a/nemu/.gitignore b/nemu/.gitignore new file mode 100644 index 0000000..fa779a6 --- /dev/null +++ b/nemu/.gitignore @@ -0,0 +1,12 @@ +*.* +* +!*/ +!Makefile +!*.mk +!*.[cSh] +!*.cc +!.gitignore +!README.md +!Kconfig +include/config +include/generated diff --git a/nemu/Kconfig b/nemu/Kconfig new file mode 100644 index 0000000..a1ed68e --- /dev/null +++ b/nemu/Kconfig @@ -0,0 +1,217 @@ +mainmenu "NEMU Configuration Menu" + +choice + prompt "Base ISA" + default ISA_riscv +config ISA_x86 + bool "x86" +config ISA_mips32 + bool "mips32" +config ISA_riscv + bool "riscv" +config ISA_loongarch32r + bool "loongarch32r" +endchoice + +config ISA + string + default "x86" if ISA_x86 + default "mips32" if ISA_mips32 + default "riscv32" if ISA_riscv && !RV64 + default "riscv64" if ISA_riscv && RV64 + default "loongarch32r" if ISA_loongarch32r + default "none" + +config ISA64 + depends on ISA_riscv && RV64 + bool + default y + + +if ISA_riscv +source "src/isa/riscv32/Kconfig" +endif + +choice + prompt "NEMU execution engine" + default ENGINE_INTERPRETER + +config ENGINE_INTERPRETER + bool "Interpreter" + help + Interpreter guest instructions one by one. +endchoice + +config ENGINE + string + default "interpreter" if ENGINE_INTERPRETER + default "none" + +choice + prompt "Running mode" + default MODE_SYSTEM + +config MODE_SYSTEM + bool "System mode" + help + Support full-system functionality, including privileged instructions, MMU and devices. +endchoice + +choice + prompt "Build target" + default TARGET_NATIVE_ELF +config TARGET_NATIVE_ELF + bool "Executable on Linux Native" +config TARGET_SHARE + bool "Shared object (used as REF for differential testing)" +config TARGET_AM + bool "Application on Abstract-Machine (DON'T CHOOSE)" +endchoice + +menu "Build Options" +choice + prompt "Compiler" + default CC_GCC +config CC_GCC + bool "gcc" +config CC_GPP + bool "g++" +config CC_CLANG + depends on !TARGET_AM + bool "clang" +endchoice + +config CC + string + default "gcc" if CC_GCC + default "g++" if CC_GPP + default "clang" if CC_CLANG + default "none" + +choice + prompt "Optimization Level" + default CC_O2 +config CC_O0 + bool "O0" +config CC_O1 + bool "O1" +config CC_O2 + bool "O2" +config CC_O3 + bool "O3" +endchoice + +config CC_OPT + string + default "-O0" if CC_O0 + default "-O1" if CC_O1 + default "-O2" if CC_O2 + default "-O3" if CC_O3 + default "none" + +config CC_LTO + depends on !TARGET_AM + bool "Enable link-time optimization" + default n + +config CC_DEBUG + bool "Enable debug information" + default n + +config CC_ASAN + depends on MODE_SYSTEM + bool "Enable address sanitizer" + default n +endmenu + +menu "Testing and Debugging" + + +config TRACE + bool "Enable tracer" + default y + +config TRACE_START + depends on TRACE + int "When tracing is enabled (unit: number of instructions)" + default 0 + +config TRACE_END + depends on TRACE + int "When tracing is disabled (unit: number of instructions)" + default 10000 + +config ITRACE + depends on TRACE && TARGET_NATIVE_ELF && ENGINE_INTERPRETER + bool "Enable instruction tracer" + default y + +config ITRACE_COND + depends on ITRACE + string "Only trace instructions when the condition is true" + default "true" + + +config DIFFTEST + depends on TARGET_NATIVE_ELF + bool "Enable differential testing" + default n + help + Enable differential testing with a reference design. + Note that this will significantly reduce the performance of NEMU. + +choice + prompt "Reference design" + default DIFFTEST_REF_SPIKE if ISA_riscv + default DIFFTEST_REF_KVM if ISA_x86 + default DIFFTEST_REF_QEMU + depends on DIFFTEST +config DIFFTEST_REF_QEMU + bool "QEMU, communicate with socket" +if ISA_riscv +config DIFFTEST_REF_SPIKE + bool "Spike" +endif +if ISA_x86 +config DIFFTEST_REF_KVM + bool "KVM" +endif +endchoice + +config DIFFTEST_REF_PATH + string + default "tools/qemu-diff" if DIFFTEST_REF_QEMU + default "tools/kvm-diff" if DIFFTEST_REF_KVM + default "tools/spike-diff" if DIFFTEST_REF_SPIKE + default "none" + +config DIFFTEST_REF_NAME + string + default "qemu" if DIFFTEST_REF_QEMU + default "kvm" if DIFFTEST_REF_KVM + default "spike" if DIFFTEST_REF_SPIKE + default "none" +endmenu + +if MODE_SYSTEM +source "src/memory/Kconfig" +source "src/device/Kconfig" +endif + + +menu "Miscellaneous" +choice + depends on !TARGET_AM + prompt "Host timer" + default TIMER_GETTIMEOFDAY +config TIMER_GETTIMEOFDAY + bool "gettimeofday" +config TIMER_CLOCK_GETTIME + bool "clock_gettime" +endchoice + +config RT_CHECK + bool "Enable runtime checking" + default y + +endmenu diff --git a/nemu/Makefile b/nemu/Makefile new file mode 100644 index 0000000..c94e04f --- /dev/null +++ b/nemu/Makefile @@ -0,0 +1,61 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +# Sanity check +ifeq ($(wildcard $(NEMU_HOME)/src/nemu-main.c),) + $(error NEMU_HOME=$(NEMU_HOME) is not a NEMU repo) +endif + +# Include variables and rules generated by menuconfig +-include $(NEMU_HOME)/include/config/auto.conf +-include $(NEMU_HOME)/include/config/auto.conf.cmd + +remove_quote = $(patsubst "%",%,$(1)) + +# Extract variabls from menuconfig +GUEST_ISA ?= $(call remove_quote,$(CONFIG_ISA)) +ENGINE ?= $(call remove_quote,$(CONFIG_ENGINE)) +NAME = $(GUEST_ISA)-nemu-$(ENGINE) + +# Include all filelist.mk to merge file lists +FILELIST_MK = $(shell find -L ./src -name "filelist.mk") +include $(FILELIST_MK) + +# Filter out directories and files in blacklist to obtain the final set of source files +DIRS-BLACKLIST-y += $(DIRS-BLACKLIST) +SRCS-BLACKLIST-y += $(SRCS-BLACKLIST) $(shell find -L $(DIRS-BLACKLIST-y) -name "*.c") +SRCS-y += $(shell find -L $(DIRS-y) -name "*.c") +SRCS = $(filter-out $(SRCS-BLACKLIST-y),$(SRCS-y)) + +# Extract compiler and options from menuconfig +CC = $(call remove_quote,$(CONFIG_CC)) +CFLAGS_BUILD += $(call remove_quote,$(CONFIG_CC_OPT)) +CFLAGS_BUILD += $(if $(CONFIG_CC_LTO),-flto,) +CFLAGS_BUILD += $(if $(CONFIG_CC_DEBUG),-Og -ggdb3,) +CFLAGS_BUILD += $(if $(CONFIG_CC_ASAN),-fsanitize=address,) +CFLAGS_TRACE += -DITRACE_COND=$(if $(CONFIG_ITRACE_COND),$(call remove_quote,$(CONFIG_ITRACE_COND)),true) +CFLAGS += $(CFLAGS_BUILD) $(CFLAGS_TRACE) -D__GUEST_ISA__=$(GUEST_ISA) +LDFLAGS += $(CFLAGS_BUILD) + +# Include rules for menuconfig +include $(NEMU_HOME)/scripts/config.mk + +ifdef CONFIG_TARGET_AM +include $(AM_HOME)/Makefile +LINKAGE += $(ARCHIVES) +else +# Include rules to build NEMU +include $(NEMU_HOME)/scripts/native.mk +endif diff --git a/nemu/README.md b/nemu/README.md new file mode 100644 index 0000000..68e55af --- /dev/null +++ b/nemu/README.md @@ -0,0 +1,35 @@ +# NEMU + +NEMU(NJU Emulator) is a simple but complete full-system emulator designed for teaching purpose. +Currently it supports x86, mips32, riscv32 and riscv64. +To build programs run above NEMU, refer to the [AM project](https://github.com/NJU-ProjectN/abstract-machine). + +The main features of NEMU include +* a small monitor with a simple debugger + * single step + * register/memory examination + * expression evaluation without the support of symbols + * watch point + * differential testing with reference design (e.g. QEMU) + * snapshot +* CPU core with support of most common used instructions + * x86 + * real mode is not supported + * x87 floating point instructions are not supported + * mips32 + * CP1 floating point instructions are not supported + * riscv32 + * only RV32IM + * riscv64 + * only RV64IM +* memory +* paging + * TLB is optional (but necessary for mips32) + * protection is not supported +* interrupt and exception + * protection is not supported +* 5 devices + * serial, timer, keyboard, VGA, audio + * most of them are simplified and unprogrammable +* 2 types of I/O + * port-mapped I/O and memory-mapped I/O diff --git a/nemu/configs/.gitignore b/nemu/configs/.gitignore new file mode 100644 index 0000000..31aa61c --- /dev/null +++ b/nemu/configs/.gitignore @@ -0,0 +1 @@ +!*_defconfig diff --git a/nemu/configs/mips32-am_defconfig b/nemu/configs/mips32-am_defconfig new file mode 100644 index 0000000..049545e --- /dev/null +++ b/nemu/configs/mips32-am_defconfig @@ -0,0 +1,5 @@ +CONFIG_ISA_mips32=y +CONFIG_TARGET_AM=y +# CONFIG_TRACE is not set +CONFIG_MSIZE=0x2000000 +CONFIG_DEVICE=y diff --git a/nemu/configs/riscv32-am_defconfig b/nemu/configs/riscv32-am_defconfig new file mode 100644 index 0000000..ad1e6a9 --- /dev/null +++ b/nemu/configs/riscv32-am_defconfig @@ -0,0 +1,4 @@ +CONFIG_TARGET_AM=y +# CONFIG_TRACE is not set +CONFIG_MSIZE=0x2000000 +CONFIG_DEVICE=y diff --git a/nemu/configs/riscv64-am_defconfig b/nemu/configs/riscv64-am_defconfig new file mode 100644 index 0000000..8110b15 --- /dev/null +++ b/nemu/configs/riscv64-am_defconfig @@ -0,0 +1,5 @@ +CONFIG_ISA_riscv64=y +CONFIG_TARGET_AM=y +# CONFIG_TRACE is not set +CONFIG_MSIZE=0x2000000 +CONFIG_DEVICE=y diff --git a/nemu/include/common.h b/nemu/include/common.h new file mode 100644 index 0000000..3c8e8fe --- /dev/null +++ b/nemu/include/common.h @@ -0,0 +1,49 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include +#include +#include +#include + +#include +#include + +#ifdef CONFIG_TARGET_AM +#include +#else +#include +#include +#endif + +#if CONFIG_MBASE + CONFIG_MSIZE > 0x100000000ul +#define PMEM64 1 +#endif + +typedef MUXDEF(CONFIG_ISA64, uint64_t, uint32_t) word_t; +typedef MUXDEF(CONFIG_ISA64, int64_t, int32_t) sword_t; +#define FMT_WORD MUXDEF(CONFIG_ISA64, "0x%016" PRIx64, "0x%08" PRIx32) + +typedef word_t vaddr_t; +typedef MUXDEF(PMEM64, uint64_t, uint32_t) paddr_t; +#define FMT_PADDR MUXDEF(PMEM64, "0x%016" PRIx64, "0x%08" PRIx32) +typedef uint16_t ioaddr_t; + +#include + +#endif diff --git a/nemu/include/cpu/cpu.h b/nemu/include/cpu/cpu.h new file mode 100644 index 0000000..2be0d77 --- /dev/null +++ b/nemu/include/cpu/cpu.h @@ -0,0 +1,29 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __CPU_CPU_H__ +#define __CPU_CPU_H__ + +#include + +void cpu_exec(uint64_t n); + +void set_nemu_state(int state, vaddr_t pc, int halt_ret); +void invalid_inst(vaddr_t thispc); + +#define NEMUTRAP(thispc, code) set_nemu_state(NEMU_END, thispc, code) +#define INV(thispc) invalid_inst(thispc) + +#endif diff --git a/nemu/include/cpu/decode.h b/nemu/include/cpu/decode.h new file mode 100644 index 0000000..915bcf2 --- /dev/null +++ b/nemu/include/cpu/decode.h @@ -0,0 +1,102 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __CPU_DECODE_H__ +#define __CPU_DECODE_H__ + +#include + +typedef struct Decode { + vaddr_t pc; + vaddr_t snpc; // static next pc + vaddr_t dnpc; // dynamic next pc + ISADecodeInfo isa; + IFDEF(CONFIG_ITRACE, char logbuf[128]); +} Decode; + +// --- pattern matching mechanism --- +__attribute__((always_inline)) +static inline void pattern_decode(const char *str, int len, + uint64_t *key, uint64_t *mask, uint64_t *shift) { + uint64_t __key = 0, __mask = 0, __shift = 0; +#define macro(i) \ + if ((i) >= len) goto finish; \ + else { \ + char c = str[i]; \ + if (c != ' ') { \ + Assert(c == '0' || c == '1' || c == '?', \ + "invalid character '%c' in pattern string", c); \ + __key = (__key << 1) | (c == '1' ? 1 : 0); \ + __mask = (__mask << 1) | (c == '?' ? 0 : 1); \ + __shift = (c == '?' ? __shift + 1 : 0); \ + } \ + } + +#define macro2(i) macro(i); macro((i) + 1) +#define macro4(i) macro2(i); macro2((i) + 2) +#define macro8(i) macro4(i); macro4((i) + 4) +#define macro16(i) macro8(i); macro8((i) + 8) +#define macro32(i) macro16(i); macro16((i) + 16) +#define macro64(i) macro32(i); macro32((i) + 32) + macro64(0); + panic("pattern too long"); +#undef macro +finish: + *key = __key >> __shift; + *mask = __mask >> __shift; + *shift = __shift; +} + +__attribute__((always_inline)) +static inline void pattern_decode_hex(const char *str, int len, + uint64_t *key, uint64_t *mask, uint64_t *shift) { + uint64_t __key = 0, __mask = 0, __shift = 0; +#define macro(i) \ + if ((i) >= len) goto finish; \ + else { \ + char c = str[i]; \ + if (c != ' ') { \ + Assert((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || c == '?', \ + "invalid character '%c' in pattern string", c); \ + __key = (__key << 4) | (c == '?' ? 0 : (c >= '0' && c <= '9') ? c - '0' : c - 'a' + 10); \ + __mask = (__mask << 4) | (c == '?' ? 0 : 0xf); \ + __shift = (c == '?' ? __shift + 4 : 0); \ + } \ + } + + macro16(0); + panic("pattern too long"); +#undef macro +finish: + *key = __key >> __shift; + *mask = __mask >> __shift; + *shift = __shift; +} + + +// --- pattern matching wrappers for decode --- +#define INSTPAT(pattern, ...) do { \ + uint64_t key, mask, shift; \ + pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift); \ + if ((((uint64_t)INSTPAT_INST(s) >> shift) & mask) == key) { \ + INSTPAT_MATCH(s, ##__VA_ARGS__); \ + goto *(__instpat_end); \ + } \ +} while (0) + +#define INSTPAT_START(name) { const void ** __instpat_end = &&concat(__instpat_end_, name); +#define INSTPAT_END(name) concat(__instpat_end_, name): ; } + +#endif diff --git a/nemu/include/cpu/difftest.h b/nemu/include/cpu/difftest.h new file mode 100644 index 0000000..0c60d3b --- /dev/null +++ b/nemu/include/cpu/difftest.h @@ -0,0 +1,53 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __CPU_DIFFTEST_H__ +#define __CPU_DIFFTEST_H__ + +#include +#include + +#ifdef CONFIG_DIFFTEST +void difftest_skip_ref(); +void difftest_skip_dut(int nr_ref, int nr_dut); +void difftest_set_patch(void (*fn)(void *arg), void *arg); +void difftest_step(vaddr_t pc, vaddr_t npc); +void difftest_detach(); +void difftest_attach(); +#else +static inline void difftest_skip_ref() {} +static inline void difftest_skip_dut(int nr_ref, int nr_dut) {} +static inline void difftest_set_patch(void (*fn)(void *arg), void *arg) {} +static inline void difftest_step(vaddr_t pc, vaddr_t npc) {} +static inline void difftest_detach() {} +static inline void difftest_attach() {} +#endif + +extern void (*ref_difftest_memcpy)(paddr_t addr, void *buf, size_t n, bool direction); +extern void (*ref_difftest_regcpy)(void *dut, bool direction); +extern void (*ref_difftest_exec)(uint64_t n); +extern void (*ref_difftest_raise_intr)(uint64_t NO); + +static inline bool difftest_check_reg(const char *name, vaddr_t pc, word_t ref, word_t dut) { + if (ref != dut) { + Log("%s is different after executing instruction at pc = " FMT_WORD + ", right = " FMT_WORD ", wrong = " FMT_WORD ", diff = " FMT_WORD, + name, pc, ref, dut, ref ^ dut); + return false; + } + return true; +} + +#endif diff --git a/nemu/include/cpu/ifetch.h b/nemu/include/cpu/ifetch.h new file mode 100644 index 0000000..b9ba8f5 --- /dev/null +++ b/nemu/include/cpu/ifetch.h @@ -0,0 +1,26 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __CPU_IFETCH_H__ + +#include + +static inline uint32_t inst_fetch(vaddr_t *pc, int len) { + uint32_t inst = vaddr_ifetch(*pc, len); + (*pc) += len; + return inst; +} + +#endif diff --git a/nemu/include/debug.h b/nemu/include/debug.h new file mode 100644 index 0000000..924bcf1 --- /dev/null +++ b/nemu/include/debug.h @@ -0,0 +1,43 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#include +#include +#include + +#define Log(format, ...) \ + _Log(ANSI_FMT("[%s:%d %s] " format, ANSI_FG_BLUE) "\n", \ + __FILE__, __LINE__, __func__, ## __VA_ARGS__) + +#define Assert(cond, format, ...) \ + do { \ + if (!(cond)) { \ + MUXDEF(CONFIG_TARGET_AM, printf(ANSI_FMT(format, ANSI_FG_RED) "\n", ## __VA_ARGS__), \ + (fflush(stdout), fprintf(stderr, ANSI_FMT(format, ANSI_FG_RED) "\n", ## __VA_ARGS__))); \ + IFNDEF(CONFIG_TARGET_AM, extern FILE* log_fp; fflush(log_fp)); \ + extern void assert_fail_msg(); \ + assert_fail_msg(); \ + assert(cond); \ + } \ + } while (0) + +#define panic(format, ...) Assert(0, format, ## __VA_ARGS__) + +#define TODO() panic("please implement me") + +#endif diff --git a/nemu/include/device/alarm.h b/nemu/include/device/alarm.h new file mode 100644 index 0000000..7a2eb54 --- /dev/null +++ b/nemu/include/device/alarm.h @@ -0,0 +1,24 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __DEVICE_ALARM_H__ +#define __DEVICE_ALARM_H__ + +#define TIMER_HZ 60 + +typedef void (*alarm_handler_t) (); +void add_alarm_handle(alarm_handler_t h); + +#endif diff --git a/nemu/include/device/map.h b/nemu/include/device/map.h new file mode 100644 index 0000000..841d36c --- /dev/null +++ b/nemu/include/device/map.h @@ -0,0 +1,56 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __DEVICE_MAP_H__ +#define __DEVICE_MAP_H__ + +#include + +typedef void(*io_callback_t)(uint32_t, int, bool); +uint8_t* new_space(int size); + +typedef struct { + const char *name; + // we treat ioaddr_t as paddr_t here + paddr_t low; + paddr_t high; + void *space; + io_callback_t callback; +} IOMap; + +static inline bool map_inside(IOMap *map, paddr_t addr) { + return (addr >= map->low && addr <= map->high); +} + +static inline int find_mapid_by_addr(IOMap *maps, int size, paddr_t addr) { + int i; + for (i = 0; i < size; i ++) { + if (map_inside(maps + i, addr)) { + difftest_skip_ref(); + return i; + } + } + return -1; +} + +void add_pio_map(const char *name, ioaddr_t addr, + void *space, uint32_t len, io_callback_t callback); +void add_mmio_map(const char *name, paddr_t addr, + void *space, uint32_t len, io_callback_t callback); + +word_t map_read(paddr_t addr, int len, IOMap *map); +void map_write(paddr_t addr, int len, word_t data, IOMap *map); + +#endif diff --git a/nemu/include/device/mmio.h b/nemu/include/device/mmio.h new file mode 100644 index 0000000..4994321 --- /dev/null +++ b/nemu/include/device/mmio.h @@ -0,0 +1,24 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __DEVICE_MMIO_H__ +#define __DEVICE_MMIO_H__ + +#include + +word_t mmio_read(paddr_t addr, int len); +void mmio_write(paddr_t addr, int len, word_t data); + +#endif diff --git a/nemu/include/difftest-def.h b/nemu/include/difftest-def.h new file mode 100644 index 0000000..77948fb --- /dev/null +++ b/nemu/include/difftest-def.h @@ -0,0 +1,40 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __DIFFTEST_DEF_H__ +#define __DIFFTEST_DEF_H__ + +#include +#include +#include + +#define __EXPORT __attribute__((visibility("default"))) +enum { DIFFTEST_TO_DUT, DIFFTEST_TO_REF }; + +#if defined(CONFIG_ISA_x86) +# define DIFFTEST_REG_SIZE (sizeof(uint32_t) * 9) // GPRs + pc +#elif defined(CONFIG_ISA_mips32) +# define DIFFTEST_REG_SIZE (sizeof(uint32_t) * 38) // GPRs + status + lo + hi + badvaddr + cause + pc +#elif defined(CONFIG_ISA_riscv) +#define RISCV_GPR_TYPE MUXDEF(CONFIG_RV64, uint64_t, uint32_t) +#define RISCV_GPR_NUM MUXDEF(CONFIG_RVE , 16, 32) +#define DIFFTEST_REG_SIZE (sizeof(RISCV_GPR_TYPE) * (RISCV_GPR_NUM + 1)) // GPRs + pc +#elif defined(CONFIG_ISA_loongarch32r) +# define DIFFTEST_REG_SIZE (sizeof(uint32_t) * 33) // GPRs + pc +#else +# error Unsupport ISA +#endif + +#endif diff --git a/nemu/include/isa.h b/nemu/include/isa.h new file mode 100644 index 0000000..5040349 --- /dev/null +++ b/nemu/include/isa.h @@ -0,0 +1,58 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __ISA_H__ +#define __ISA_H__ + +// Located at src/isa/$(GUEST_ISA)/include/isa-def.h +#include + +// The macro `__GUEST_ISA__` is defined in $(CFLAGS). +// It will be expanded as "x86" or "mips32" ... +typedef concat(__GUEST_ISA__, _CPU_state) CPU_state; +typedef concat(__GUEST_ISA__, _ISADecodeInfo) ISADecodeInfo; + +// monitor +extern unsigned char isa_logo[]; +void init_isa(); + +// reg +extern CPU_state cpu; +void isa_reg_display(); +word_t isa_reg_str2val(const char *name, bool *success); + +// exec +struct Decode; +int isa_exec_once(struct Decode *s); + +// memory +enum { MMU_DIRECT, MMU_TRANSLATE, MMU_FAIL }; +enum { MEM_TYPE_IFETCH, MEM_TYPE_READ, MEM_TYPE_WRITE }; +enum { MEM_RET_OK, MEM_RET_FAIL, MEM_RET_CROSS_PAGE }; +#ifndef isa_mmu_check +int isa_mmu_check(vaddr_t vaddr, int len, int type); +#endif +paddr_t isa_mmu_translate(vaddr_t vaddr, int len, int type); + +// interrupt/exception +vaddr_t isa_raise_intr(word_t NO, vaddr_t epc); +#define INTR_EMPTY ((word_t)-1) +word_t isa_query_intr(); + +// difftest +bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc); +void isa_difftest_attach(); + +#endif diff --git a/nemu/include/macro.h b/nemu/include/macro.h new file mode 100644 index 0000000..8aa38f8 --- /dev/null +++ b/nemu/include/macro.h @@ -0,0 +1,110 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __MACRO_H__ +#define __MACRO_H__ + +#include + +// macro stringizing +#define str_temp(x) #x +#define str(x) str_temp(x) + +// strlen() for string constant +#define STRLEN(CONST_STR) (sizeof(CONST_STR) - 1) + +// calculate the length of an array +#define ARRLEN(arr) (int)(sizeof(arr) / sizeof(arr[0])) + +// macro concatenation +#define concat_temp(x, y) x ## y +#define concat(x, y) concat_temp(x, y) +#define concat3(x, y, z) concat(concat(x, y), z) +#define concat4(x, y, z, w) concat3(concat(x, y), z, w) +#define concat5(x, y, z, v, w) concat4(concat(x, y), z, v, w) + +// macro testing +// See https://stackoverflow.com/questions/26099745/test-if-preprocessor-symbol-is-defined-inside-macro +#define CHOOSE2nd(a, b, ...) b +#define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b) +#define MUX_MACRO_PROPERTY(p, macro, a, b) MUX_WITH_COMMA(concat(p, macro), a, b) +// define placeholders for some property +#define __P_DEF_0 X, +#define __P_DEF_1 X, +#define __P_ONE_1 X, +#define __P_ZERO_0 X, +// define some selection functions based on the properties of BOOLEAN macro +#define MUXDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y) +#define MUXNDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, Y, X) +#define MUXONE(macro, X, Y) MUX_MACRO_PROPERTY(__P_ONE_, macro, X, Y) +#define MUXZERO(macro, X, Y) MUX_MACRO_PROPERTY(__P_ZERO_,macro, X, Y) + +// test if a boolean macro is defined +#define ISDEF(macro) MUXDEF(macro, 1, 0) +// test if a boolean macro is undefined +#define ISNDEF(macro) MUXNDEF(macro, 1, 0) +// test if a boolean macro is defined to 1 +#define ISONE(macro) MUXONE(macro, 1, 0) +// test if a boolean macro is defined to 0 +#define ISZERO(macro) MUXZERO(macro, 1, 0) +// test if a macro of ANY type is defined +// NOTE1: it ONLY works inside a function, since it calls `strcmp()` +// NOTE2: macros defined to themselves (#define A A) will get wrong results +#define isdef(macro) (strcmp("" #macro, "" str(macro)) != 0) + +// simplification for conditional compilation +#define __IGNORE(...) +#define __KEEP(...) __VA_ARGS__ +// keep the code if a boolean macro is defined +#define IFDEF(macro, ...) MUXDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__) +// keep the code if a boolean macro is undefined +#define IFNDEF(macro, ...) MUXNDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__) +// keep the code if a boolean macro is defined to 1 +#define IFONE(macro, ...) MUXONE(macro, __KEEP, __IGNORE)(__VA_ARGS__) +// keep the code if a boolean macro is defined to 0 +#define IFZERO(macro, ...) MUXZERO(macro, __KEEP, __IGNORE)(__VA_ARGS__) + +// functional-programming-like macro (X-macro) +// apply the function `f` to each element in the container `c` +// NOTE1: `c` should be defined as a list like: +// f(a0) f(a1) f(a2) ... +// NOTE2: each element in the container can be a tuple +#define MAP(c, f) c(f) + +#define BITMASK(bits) ((1ull << (bits)) - 1) +#define BITS(x, hi, lo) (((x) >> (lo)) & BITMASK((hi) - (lo) + 1)) // similar to x[hi:lo] in verilog +#define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (uint64_t)__x.n; }) + +#define ROUNDUP(a, sz) ((((uintptr_t)a) + (sz) - 1) & ~((sz) - 1)) +#define ROUNDDOWN(a, sz) ((((uintptr_t)a)) & ~((sz) - 1)) + +#define PG_ALIGN __attribute((aligned(4096))) + +#if !defined(likely) +#define likely(cond) __builtin_expect(cond, 1) +#define unlikely(cond) __builtin_expect(cond, 0) +#endif + +// for AM IOE +#define io_read(reg) \ + ({ reg##_T __io_param; \ + ioe_read(reg, &__io_param); \ + __io_param; }) + +#define io_write(reg, ...) \ + ({ reg##_T __io_param = (reg##_T) { __VA_ARGS__ }; \ + ioe_write(reg, &__io_param); }) + +#endif diff --git a/nemu/include/memory/host.h b/nemu/include/memory/host.h new file mode 100644 index 0000000..642a9f8 --- /dev/null +++ b/nemu/include/memory/host.h @@ -0,0 +1,41 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __MEMORY_HOST_H__ +#define __MEMORY_HOST_H__ + +#include + +static inline word_t host_read(void *addr, int len) { + switch (len) { + case 1: return *(uint8_t *)addr; + case 2: return *(uint16_t *)addr; + case 4: return *(uint32_t *)addr; + IFDEF(CONFIG_ISA64, case 8: return *(uint64_t *)addr); + default: MUXDEF(CONFIG_RT_CHECK, assert(0), return 0); + } +} + +static inline void host_write(void *addr, int len, word_t data) { + switch (len) { + case 1: *(uint8_t *)addr = data; return; + case 2: *(uint16_t *)addr = data; return; + case 4: *(uint32_t *)addr = data; return; + IFDEF(CONFIG_ISA64, case 8: *(uint64_t *)addr = data; return); + IFDEF(CONFIG_RT_CHECK, default: assert(0)); + } +} + +#endif diff --git a/nemu/include/memory/paddr.h b/nemu/include/memory/paddr.h new file mode 100644 index 0000000..141c785 --- /dev/null +++ b/nemu/include/memory/paddr.h @@ -0,0 +1,37 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __MEMORY_PADDR_H__ +#define __MEMORY_PADDR_H__ + +#include + +#define PMEM_LEFT ((paddr_t)CONFIG_MBASE) +#define PMEM_RIGHT ((paddr_t)CONFIG_MBASE + CONFIG_MSIZE - 1) +#define RESET_VECTOR (PMEM_LEFT + CONFIG_PC_RESET_OFFSET) + +/* convert the guest physical address in the guest program to host virtual address in NEMU */ +uint8_t* guest_to_host(paddr_t paddr); +/* convert the host virtual address in NEMU to guest physical address in the guest program */ +paddr_t host_to_guest(uint8_t *haddr); + +static inline bool in_pmem(paddr_t addr) { + return addr - CONFIG_MBASE < CONFIG_MSIZE; +} + +word_t paddr_read(paddr_t addr, int len); +void paddr_write(paddr_t addr, int len, word_t data); + +#endif diff --git a/nemu/include/memory/vaddr.h b/nemu/include/memory/vaddr.h new file mode 100644 index 0000000..ef8a1ab --- /dev/null +++ b/nemu/include/memory/vaddr.h @@ -0,0 +1,29 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __MEMORY_VADDR_H__ +#define __MEMORY_VADDR_H__ + +#include + +word_t vaddr_ifetch(vaddr_t addr, int len); +word_t vaddr_read(vaddr_t addr, int len); +void vaddr_write(vaddr_t addr, int len, word_t data); + +#define PAGE_SHIFT 12 +#define PAGE_SIZE (1ul << PAGE_SHIFT) +#define PAGE_MASK (PAGE_SIZE - 1) + +#endif diff --git a/nemu/include/utils.h b/nemu/include/utils.h new file mode 100644 index 0000000..2cd1561 --- /dev/null +++ b/nemu/include/utils.h @@ -0,0 +1,77 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include + +// ----------- state ----------- + +enum { NEMU_RUNNING, NEMU_STOP, NEMU_END, NEMU_ABORT, NEMU_QUIT }; + +typedef struct { + int state; + vaddr_t halt_pc; + uint32_t halt_ret; +} NEMUState; + +extern NEMUState nemu_state; + +// ----------- timer ----------- + +uint64_t get_time(); + +// ----------- log ----------- + +#define ANSI_FG_BLACK "\33[1;30m" +#define ANSI_FG_RED "\33[1;31m" +#define ANSI_FG_GREEN "\33[1;32m" +#define ANSI_FG_YELLOW "\33[1;33m" +#define ANSI_FG_BLUE "\33[1;34m" +#define ANSI_FG_MAGENTA "\33[1;35m" +#define ANSI_FG_CYAN "\33[1;36m" +#define ANSI_FG_WHITE "\33[1;37m" +#define ANSI_BG_BLACK "\33[1;40m" +#define ANSI_BG_RED "\33[1;41m" +#define ANSI_BG_GREEN "\33[1;42m" +#define ANSI_BG_YELLOW "\33[1;43m" +#define ANSI_BG_BLUE "\33[1;44m" +#define ANSI_BG_MAGENTA "\33[1;35m" +#define ANSI_BG_CYAN "\33[1;46m" +#define ANSI_BG_WHITE "\33[1;47m" +#define ANSI_NONE "\33[0m" + +#define ANSI_FMT(str, fmt) fmt str ANSI_NONE + +#define log_write(...) IFDEF(CONFIG_TARGET_NATIVE_ELF, \ + do { \ + extern FILE* log_fp; \ + extern bool log_enable(); \ + if (log_enable()) { \ + fprintf(log_fp, __VA_ARGS__); \ + fflush(log_fp); \ + } \ + } while (0) \ +) + +#define _Log(...) \ + do { \ + printf(__VA_ARGS__); \ + log_write(__VA_ARGS__); \ + } while (0) + + +#endif diff --git a/nemu/resource/debian/README.md b/nemu/resource/debian/README.md new file mode 100644 index 0000000..3723671 --- /dev/null +++ b/nemu/resource/debian/README.md @@ -0,0 +1,172 @@ + +# riscv64 debian镜像制作 + +制作需要`qemu-riscv64-static`, 建议在debian 10或ubuntu 19.04的系统(可尝试使用docker)中进行操作. + +* 创建空镜像和分区 +``` +dd if=/dev/zero of=debian.img bs=1G count=16 # 此处镜像大小为16GB +sudo cfdisk debian.img # 可创建两个分区, 第一个分区12GB作为rootfs, 第二个分区4GB作为swap +sudo losetup --partscan --show --find debian.img # 将debian.img作为loop设备 +ls /dev/loop0* # 此时应该能看到/dev/loop0p1和/dev/loop0p2两个分区 +``` + +* 创建ext4和swap文件系统 +``` +sudo mkfs.ext4 /dev/loop0p1 +sudo mkswap /dev/loop0p2 +``` + +* 挂载ext4分区 +``` +sudo mount /dev/loop0p1 /mnt +``` + +* 安装debian base system. +下面两条命令的操作来自[debian社区的安装指南](https://wiki.debian.org/RISC-V#debootstrap). +``` +sudo apt-get install debootstrap qemu-user-static binfmt-support debian-ports-archive-keyring +sudo debootstrap --arch=riscv64 --keyring /usr/share/keyrings/debian-ports-archive-keyring.gpg --include=debian-ports-archive-keyring unstable /mnt http://deb.debian.org/debian-ports +``` +若要安装x86系统, 则输入 +``` +sudo debootstrap --arch=i386 --keyring /usr/share/keyrings/debian-archive-keyring.gpg --include=debian-archive-keyring stable /mnt http://deb.debian.org/debian +``` + +* 进入镜像 +``` +sudo chroot /mnt /bin/bash +``` +此时实际上是通过`qemu-riscv64-static`来执行镜像中的riscv64可执行文件. + +* 安装所需工具(根据实际情况选择) +``` +apt-get update +apt-get install systemd +apt-get install gcc build-essential +apt-get install tmux libreadline-dev +apt-get install net-tools openssh-server +# fix long delay of openssh server +apt-get install haveged +apt-get install sbt +``` + +* 修复`NO_PUBKEY E852514F5DF312F6`错误, 见[这里](https://www.reddit.com/r/RISCV/comments/sn0cph/sipeed_debian_the_following_signatures_couldnt_be/) +``` +curl https://www.ports.debian.org/archive_2022.key | apt-key add - +``` + +* 同步时间 +``` +apt-get install ntpdate +ntpdate 0.asia.pool.ntp.org +``` + +* 在`/etc/fstab`中添加swap分区 +``` +/dev/mmcblk0p2 none swap sw 0 0 +``` + +* 添加/root/目录的写和执行权限, 使得host上的普通用户可以访问 +``` +chmod a+w,a+x /root +``` + +* 在/root/目录下提前写入所需的测试文件, 如hello.c等. + +* 在/root/.bashrc中添加如下内容, 可以实现登录后自动运行命令(根据实际情况修改测试的命令): +``` +TMP_DIR=/run/mytest + +cmd=( +# enbale swap + "swapon -a" + +# show system information + "uname -a" + "cat /etc/issue" + "cat /proc/cpuinfo" + "df -ah" + "free -h" + +# show time + "date" + "uptime" + +# create and switch to tmp directory + "mkdir $TMP_DIR" + "cd $TMP_DIR" + +# compile and run hello + "ls /root" + "ls /root/hello" + "cat /root/hello/hello.c" + "gcc -time /root/hello/hello.c -o $TMP_DIR/hello" + "ls -lh $TMP_DIR" + "$TMP_DIR/hello" + +# compile and run x86-nemu + "ls /root/nemu" + "cp -r /root/nemu $TMP_DIR" + "export NEMU_HOME=$TMP_DIR/nemu" + "make -C $TMP_DIR/nemu ISA=x86" + "ls -lh /root/nemu-prog" + "file /root/nemu-prog/amtest-x86-nemu.elf" + "$TMP_DIR/nemu/build/x86-nemu --batch --mainargs=h /root/nemu-prog/amtest-x86-nemu.bin" + "file /root/nemu-prog/microbench-x86-nemu.elf" + "$TMP_DIR/nemu/build/x86-nemu --batch --mainargs=test /root/nemu-prog/microbench-x86-nemu.bin" + +# compile and run riscv64-nemu + "make -C $TMP_DIR/nemu clean" + "make -C $TMP_DIR/nemu ISA=riscv64" + "$TMP_DIR/nemu/build/riscv64-nemu --batch /root/nemu-prog/linux-hello-riscv64-nemu.bin" +) + +prompt="`whoami`@`hostname`:`pwd`#" + +echo -e "\n============ Now running preset commands =============\n" + +for ((i = 0; i < ${#cmd[@]}; i++)); do + c=${cmd[$i]} + echo "$prompt $c" + $c + echo "" +done + +echo -e "\n============ End of preset commands =============\n" + +/root/nemutrap/good-trap +``` + +* 若在不方便输入的环境(如NEMU, verilator仿真等)中测试, 可采用如下两种方式的其中一种, 避免登录时输入 + * 通过紧急模式登录 +``` +cd /lib/systemd/system +# 通过紧急模式登录, 不启动非必须的服务, 节省将近一半的登录时间 +ln -sf emergency.target default.target +# 跳过登录提示符, 直接运行bash +vim emergency.service + -ExecStart=-/lib/systemd/systemd-sulogin-shell emergency + +ExecStart=-/bin/bash +``` + * 免密码登录, 见[这里](https://superuser.com/questions/969923/automatic-root-login-in-debian-8-0-console-only) +``` +cd /lib/systemd/system +vim serial-getty@.service + -ExecStart=-/sbin/agetty -o '-p -- \\u' --keep-baud 115200,57600,38400,9600 %I $TERM + +ExecStart=-/sbin/agetty -a root --keep-baud 115200,57600,38400,9600 %I $TERM +``` + +* 退出并卸载镜像 +``` +exit # 之前通过`chroot`方式进入 +sudo umount /mnt # 记得卸载! 在未卸载镜像的情况下通过可写方式再次打开`debian.img`(如作为qemu的文件系统), 镜像将会损坏! +sudo losetup -d /dev/loop0 # 删除loop设备 +``` + +* 修改`nemu/src/device/sdcard.c`中`init_sdcard()`中打开的镜像文件路径, 即可使用制作的镜像. +在i9-9900k上测试, 约90s后看到debian的登录提示符. + +* 当以可写方式启动镜像时, NEMU遇到错误或通过Ctrl+C直接退出NEMU时, 可能会损坏镜像的崩溃一致性, 此时可以通过fsck命令修复分区. + +* 更多命令可参考[这里](https://github.com/carlosedp/riscv-bringup/blob/master/Debian-Rootfs-Guide.md). diff --git a/nemu/resource/mips-elf/README.md b/nemu/resource/mips-elf/README.md new file mode 100644 index 0000000..57578e8 --- /dev/null +++ b/nemu/resource/mips-elf/README.md @@ -0,0 +1 @@ +This is a dummy ELF file used by qemu-system-mips32 to start. diff --git a/nemu/resource/sdcard/README.md b/nemu/resource/sdcard/README.md new file mode 100644 index 0000000..80324e0 --- /dev/null +++ b/nemu/resource/sdcard/README.md @@ -0,0 +1,58 @@ + +# NEMU sdhost驱动 + +本驱动裁剪自`linux/drivers/mmc/host/bcm2835.c`, 去除了DMA和中断, 改成直接轮询, 处理器无需支持DMA和中断即可运行. + +## 使用方法 + +* 将本目录下的`nemu.c`复制到`linux/drivers/mmc/host/`目录下 +* 在`linux/drivers/mmc/host/Makefile`中添加一行`obj-y += nemu.o` +* 在menuconfig中取消`General setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support` +* 在menuconfig中选中`Device Drivers -> MMC/SD/SDIO card support` +* 在dts中加入以下节点 +``` +/ { + soc { + sdhci: mmc { + compatible = "nemu-sdhost"; + reg = <0x0 0xa3000000 0x0 0x1000>; + }; + }; + + chosen { + bootargs = "root=/dev/mmcblk0p1 rootfstype=ext4 ro rootwait earlycon"; + }; +}; +``` + +## 在没有中断的处理器上访问SD卡 + +访问真实的SD卡需要等待一定的延迟, 这需要处理器的中断机制对内核支持计时的功能. +在没有中断机制的处理器上, 我们可以修改内核的部分代码, 使得无需等待这些延迟, +来达到确定性可重复的仿真效果. + +具体只需修改以下文件: +```diff +--- linux/drivers/mmc/core/block.c ++++ linux/drivers/mmc/core/block.c +@@ -983,6 +983,7 @@ static int card_busy_detect(struct mmc_card *card, unsigned int timeout_ms, + int err = 0; + u32 status; + ++ return err; + do { + bool done = time_after(jiffies, timeout); + +--- linux/drivers/mmc/core/core.h ++++ linux/drivers/mmc/core/core.h +@@ -64,6 +64,7 @@ void mmc_set_initial_state(struct mmc_host *host); + + static inline void mmc_delay(unsigned int ms) + { ++ return; + if (ms <= 20) + usleep_range(ms * 1000, ms * 1250); + else +``` + +注意: 上述修改仅能用于模拟和仿真, 修改后将不能在真实的SD卡上运行!!! diff --git a/nemu/resource/sdcard/nemu.c b/nemu/resource/sdcard/nemu.c new file mode 100644 index 0000000..8c823fa --- /dev/null +++ b/nemu/resource/sdcard/nemu.c @@ -0,0 +1,542 @@ +/* + * NEMU (NJU Emulator) sdhost driver. + * + * Author: Zihao Yu + * + * Based on + * bcm2835.c by Phil Elwell + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SDCMD 0x00 /* Command to SD card - 16 R/W */ +#define SDARG 0x04 /* Argument to SD card - 32 R/W */ +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ +#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */ +#define SDRSP0 0x10 /* SD card response (31:0) - 32 R */ +#define SDRSP1 0x14 /* SD card response (63:32) - 32 R */ +#define SDRSP2 0x18 /* SD card response (95:64) - 32 R */ +#define SDRSP3 0x1c /* SD card response (127:96) - 32 R */ +#define SDHSTS 0x20 /* SD host status - 11 R/W */ +#define SDVDD 0x30 /* SD card power control - 1 R/W */ +#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */ +#define SDHCFG 0x38 /* Host configuration - 2 R/W */ +#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */ +#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */ +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */ + +#define SDCMD_NEW_FLAG 0x8000 +#define SDCMD_FAIL_FLAG 0x4000 +#define SDCMD_BUSYWAIT 0x800 +#define SDCMD_NO_RESPONSE 0x400 +#define SDCMD_LONG_RESPONSE 0x200 +#define SDCMD_WRITE_CMD 0x80 +#define SDCMD_READ_CMD 0x40 +#define SDCMD_CMD_MASK 0x3f + +#define SDCDIV_MAX_CDIV 0x7ff + +#define SDDATA_FIFO_WORDS 16 + +#define FIFO_READ_THRESHOLD 4 +#define FIFO_WRITE_THRESHOLD 4 +#define SDDATA_FIFO_PIO_BURST 8 + +#define PIO_THRESHOLD 1 /* Maximum block count for PIO (0 = always DMA) */ + +struct nemu_host { + spinlock_t lock; + struct mutex mutex; + + void __iomem *ioaddr; + u32 phys_addr; + + struct mmc_host *mmc; + struct platform_device *pdev; + + int clock; /* Current clock speed */ + unsigned int max_clk; /* Max possible freq */ + struct sg_mapping_iter sg_miter; /* SG state for PIO */ + unsigned int blocks; /* remaining PIO blocks */ + + struct mmc_request *mrq; /* Current request */ + struct mmc_command *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + bool data_complete:1;/* Data finished before cmd */ + bool use_sbc:1; /* Send CMD23 */ +}; + +static void nemu_reset(struct mmc_host *mmc) +{ +} + +static void nemu_finish_command(struct nemu_host *host); + +static void nemu_transfer_block_pio(struct nemu_host *host, bool is_read) +{ + unsigned long flags; + size_t blksize; + + blksize = host->data->blksz; + + local_irq_save(flags); + + while (blksize) { + int copy_words; + size_t len; + u32 *buf; + + if (!sg_miter_next(&host->sg_miter)) { + host->data->error = -EINVAL; + break; + } + + len = min(host->sg_miter.length, blksize); + if (len % 4) { + host->data->error = -EINVAL; + break; + } + + blksize -= len; + host->sg_miter.consumed = len; + + buf = (u32 *)host->sg_miter.addr; + + copy_words = len / 4; + + while (copy_words) { + int burst_words, words; + u32 edm; + + burst_words = min(SDDATA_FIFO_PIO_BURST, copy_words); + edm = (8 << 4); + if (is_read) + words = ((edm >> 4) & 0x1f); + else + words = SDDATA_FIFO_WORDS - ((edm >> 4) & 0x1f); + + if (words < burst_words) { + continue; + } else if (words > copy_words) { + words = copy_words; + } + + copy_words -= words; + + while (words) { + if (is_read) + *(buf++) = readl(host->ioaddr + SDDATA); + else + writel(*(buf++), host->ioaddr + SDDATA); + words--; + } + } + } + + sg_miter_stop(&host->sg_miter); + + local_irq_restore(flags); +} + +static void nemu_transfer_pio(struct nemu_host *host) +{ + bool is_read = (host->data->flags & MMC_DATA_READ) != 0; + nemu_transfer_block_pio(host, is_read); +} + +static +void nemu_prepare_data(struct nemu_host *host, struct mmc_command *cmd) +{ + struct mmc_data *data = cmd->data; + int flags = SG_MITER_ATOMIC; + + WARN_ON(host->data); + + host->data = data; + if (!data) + return; + + host->data_complete = false; + host->data->bytes_xfered = 0; + + /* Use PIO */ + if (data->flags & MMC_DATA_READ) + flags |= SG_MITER_TO_SG; + else + flags |= SG_MITER_FROM_SG; + sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); + host->blocks = data->blocks; +} + +static void nemu_finish_request(struct nemu_host *host) +{ + struct mmc_request *mrq; + + mrq = host->mrq; + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + mmc_request_done(host->mmc, mrq); +} + +static +bool nemu_send_command(struct nemu_host *host, struct mmc_command *cmd) +{ + u32 sdcmd; + + WARN_ON(host->cmd); + + host->cmd = cmd; + + nemu_prepare_data(host, cmd); + + writel(cmd->arg, host->ioaddr + SDARG); + + sdcmd = cmd->opcode & SDCMD_CMD_MASK; + + if (!(cmd->flags & MMC_RSP_PRESENT)) { + sdcmd |= SDCMD_NO_RESPONSE; + } else { + if (cmd->flags & MMC_RSP_136) + sdcmd |= SDCMD_LONG_RESPONSE; + if (cmd->flags & MMC_RSP_BUSY) { + sdcmd |= SDCMD_BUSYWAIT; + } + } + + if (cmd->data) { + if (cmd->data->flags & MMC_DATA_WRITE) { + sdcmd |= SDCMD_WRITE_CMD; + } + if (cmd->data->flags & MMC_DATA_READ) + sdcmd |= SDCMD_READ_CMD; + } + + writel(sdcmd | SDCMD_NEW_FLAG, host->ioaddr + SDCMD); + + return true; +} + +static void nemu_transfer_complete(struct nemu_host *host) +{ + struct mmc_data *data; + + WARN_ON(!host->data_complete); + + data = host->data; + host->data = NULL; + + /* Need to send CMD12 if - + * a) open-ended multiblock transfer (no CMD23) + * b) error in multiblock transfer + */ + if (host->mrq->stop && (data->error || !host->use_sbc)) { + if (nemu_send_command(host, host->mrq->stop)) { + nemu_finish_command(host); + } + } else { + nemu_finish_request(host); + } +} + +static void nemu_finish_data(struct nemu_host *host) +{ + struct device *dev = &host->pdev->dev; + struct mmc_data *data; + + data = host->data; + + data->bytes_xfered = data->error ? 0 : (data->blksz * data->blocks); + + host->data_complete = true; + + if (host->cmd) { + /* Data managed to finish before the + * command completed. Make sure we do + * things in the proper order. + */ + dev_dbg(dev, "Finished early - HSTS %08x\n", + readl(host->ioaddr + SDHSTS)); + } else { + nemu_transfer_complete(host); + } +} + +static void nemu_finish_command(struct nemu_host *host) +{ + struct mmc_command *cmd = host->cmd; + int i; + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + for (i = 0; i < 4; i++) { + cmd->resp[3 - i] = + readl(host->ioaddr + SDRSP0 + i * 4); + } + } else { + cmd->resp[0] = readl(host->ioaddr + SDRSP0); + } + } + + if (cmd == host->mrq->sbc) { + /* Finished CMD23, now send actual command. */ + host->cmd = NULL; + if (nemu_send_command(host, host->mrq->cmd)) { + if (host->data) { + // start PIO right now + for (i = 0; i < host->data->blocks; i ++) { + nemu_transfer_pio(host); + } + + nemu_finish_data(host); + } + + nemu_finish_command(host); + } + } else if (cmd == host->mrq->stop) { + /* Finished CMD12 */ + nemu_finish_request(host); + } else { + /* Processed actual command. */ + host->cmd = NULL; + if (!host->data) { + nemu_finish_request(host); + } + else if (host->data_complete) { + nemu_transfer_complete(host); + } + } +} + +static void nemu_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct nemu_host *host = mmc_priv(mmc); + struct device *dev = &host->pdev->dev; + + /* Reset the error statuses in case this is a retry */ + if (mrq->sbc) + mrq->sbc->error = 0; + if (mrq->cmd) + mrq->cmd->error = 0; + if (mrq->data) + mrq->data->error = 0; + if (mrq->stop) + mrq->stop->error = 0; + + if (mrq->data && !is_power_of_2(mrq->data->blksz)) { + dev_err(dev, "unsupported block size (%d bytes)\n", + mrq->data->blksz); + + if (mrq->cmd) + mrq->cmd->error = -EINVAL; + + mmc_request_done(mmc, mrq); + return; + } + + mutex_lock(&host->mutex); + + WARN_ON(host->mrq); + host->mrq = mrq; + + host->use_sbc = !!mrq->sbc && host->mrq->data && + (host->mrq->data->flags & MMC_DATA_READ); + if (host->use_sbc) { + if (nemu_send_command(host, mrq->sbc)) { + nemu_finish_command(host); + } + } else if (mrq->cmd && nemu_send_command(host, mrq->cmd)) { + if (host->data) { + int i; + // start PIO right now + for (i = 0; i < host->data->blocks; i ++) { + nemu_transfer_pio(host); + } + nemu_finish_data(host); + } + + nemu_finish_command(host); + } + + mutex_unlock(&host->mutex); +} + +static void nemu_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ +} + +static const struct mmc_host_ops nemu_ops = { + .request = nemu_request, + .set_ios = nemu_set_ios, + .hw_reset = nemu_reset, +}; + +static int nemu_add_host(struct nemu_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct device *dev = &host->pdev->dev; + int ret; + + if (!mmc->f_max || mmc->f_max > host->max_clk) + mmc->f_max = host->max_clk; + mmc->f_min = host->max_clk / SDCDIV_MAX_CDIV; + + mmc->max_busy_timeout = ~0 / (mmc->f_max / 1000); + + dev_dbg(dev, "f_max %d, f_min %d, max_busy_timeout %d\n", + mmc->f_max, mmc->f_min, mmc->max_busy_timeout); + + /* host controller capabilities */ + mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | + MMC_CAP_NEEDS_POLL | MMC_CAP_HW_RESET | MMC_CAP_ERASE | + MMC_CAP_CMD23; + + spin_lock_init(&host->lock); + mutex_init(&host->mutex); + + mmc->max_segs = 128; + mmc->max_req_size = 524288; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_size = 1024; + mmc->max_blk_count = 65535; + + /* report supported voltage ranges */ + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + ret = mmc_add_host(mmc); + if (ret) { + return ret; + } + + dev_info(dev, "loaded - DMA %s\n", "disabled"); + + return 0; +} + +static int nemu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *iomem; + struct nemu_host *host; + struct mmc_host *mmc; + const __be32 *regaddr_p; + int ret; + + dev_dbg(dev, "%s\n", __func__); + mmc = mmc_alloc_host(sizeof(*host), dev); + if (!mmc) + return -ENOMEM; + + mmc->ops = &nemu_ops; + host = mmc_priv(mmc); + host->mmc = mmc; + host->pdev = pdev; + spin_lock_init(&host->lock); + + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->ioaddr = devm_ioremap_resource(dev, iomem); + if (IS_ERR(host->ioaddr)) { + ret = PTR_ERR(host->ioaddr); + goto err; + } + + /* Parse OF address directly to get the physical address for + * DMA to our registers. + */ + regaddr_p = of_get_address(pdev->dev.of_node, 0, NULL, NULL); + if (!regaddr_p) { + dev_err(dev, "Can't get phys address\n"); + ret = -EINVAL; + goto err; + } + + host->phys_addr = be32_to_cpup(regaddr_p); + + host->max_clk = 1000000; //clk_get_rate(clk); + + ret = mmc_of_parse(mmc); + if (ret) + goto err; + + ret = nemu_add_host(host); + if (ret) + goto err; + + platform_set_drvdata(pdev, host); + + dev_dbg(dev, "%s -> OK\n", __func__); + + return 0; + +err: + dev_dbg(dev, "%s -> err %d\n", __func__, ret); + mmc_free_host(mmc); + + return ret; +} + +static int nemu_remove(struct platform_device *pdev) +{ + struct nemu_host *host = platform_get_drvdata(pdev); + + mmc_remove_host(host->mmc); + + mmc_free_host(host->mmc); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id nemu_match[] = { + { .compatible = "nemu-sdhost" }, + { } +}; +MODULE_DEVICE_TABLE(of, nemu_match); + +static struct platform_driver nemu_driver = { + .probe = nemu_probe, + .remove = nemu_remove, + .driver = { + .name = "sdhost-nemu", + .of_match_table = nemu_match, + }, +}; +module_platform_driver(nemu_driver); + +MODULE_ALIAS("platform:sdhost-nemu"); +MODULE_DESCRIPTION("NEMU SDHost driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Zihao Yu"); diff --git a/nemu/scripts/build.mk b/nemu/scripts/build.mk new file mode 100644 index 0000000..51350e8 --- /dev/null +++ b/nemu/scripts/build.mk @@ -0,0 +1,57 @@ +.DEFAULT_GOAL = app + +# Add necessary options if the target is a shared library +ifeq ($(SHARE),1) +SO = -so +CFLAGS += -fPIC -fvisibility=hidden +LDFLAGS += -shared -fPIC +endif + +WORK_DIR = $(shell pwd) +BUILD_DIR = $(WORK_DIR)/build + +INC_PATH := $(WORK_DIR)/include $(INC_PATH) +OBJ_DIR = $(BUILD_DIR)/obj-$(NAME)$(SO) +BINARY = $(BUILD_DIR)/$(NAME)$(SO) + +# Compilation flags +ifeq ($(CC),clang) +CXX := clang++ +else +CXX := g++ +endif +LD := $(CXX) +INCLUDES = $(addprefix -I, $(INC_PATH)) +CFLAGS := -O2 -MMD -Wall -Werror $(INCLUDES) $(CFLAGS) +LDFLAGS := -O2 $(LDFLAGS) + +OBJS = $(SRCS:%.c=$(OBJ_DIR)/%.o) $(CXXSRC:%.cc=$(OBJ_DIR)/%.o) + +# Compilation patterns +$(OBJ_DIR)/%.o: %.c + @echo + CC $< + @mkdir -p $(dir $@) + @$(CC) $(CFLAGS) -c -o $@ $< + $(call call_fixdep, $(@:.o=.d), $@) + +$(OBJ_DIR)/%.o: %.cc + @echo + CXX $< + @mkdir -p $(dir $@) + @$(CXX) $(CFLAGS) $(CXXFLAGS) -c -o $@ $< + $(call call_fixdep, $(@:.o=.d), $@) + +# Depencies +-include $(OBJS:.o=.d) + +# Some convenient rules + +.PHONY: app clean + +app: $(BINARY) + +$(BINARY):: $(OBJS) $(ARCHIVES) + @echo + LD $@ + @$(LD) -o $@ $(OBJS) $(LDFLAGS) $(ARCHIVES) $(LIBS) + +clean: + -rm -rf $(BUILD_DIR) diff --git a/nemu/scripts/config.mk b/nemu/scripts/config.mk new file mode 100644 index 0000000..0525ee3 --- /dev/null +++ b/nemu/scripts/config.mk @@ -0,0 +1,70 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +COLOR_RED := $(shell echo "\033[1;31m") +COLOR_END := $(shell echo "\033[0m") + +ifeq ($(wildcard .config),) +$(warning $(COLOR_RED)Warning: .config does not exists!$(COLOR_END)) +$(warning $(COLOR_RED)To build the project, first run 'make menuconfig'.$(COLOR_END)) +endif + +Q := @ +KCONFIG_PATH := $(NEMU_HOME)/tools/kconfig +FIXDEP_PATH := $(NEMU_HOME)/tools/fixdep +Kconfig := $(NEMU_HOME)/Kconfig +rm-distclean += include/generated include/config .config .config.old +silent := -s + +CONF := $(KCONFIG_PATH)/build/conf +MCONF := $(KCONFIG_PATH)/build/mconf +FIXDEP := $(FIXDEP_PATH)/build/fixdep + +$(CONF): + $(Q)$(MAKE) $(silent) -C $(KCONFIG_PATH) NAME=conf + +$(MCONF): + $(Q)$(MAKE) $(silent) -C $(KCONFIG_PATH) NAME=mconf + +$(FIXDEP): + $(Q)$(MAKE) $(silent) -C $(FIXDEP_PATH) + +menuconfig: $(MCONF) $(CONF) $(FIXDEP) + $(Q)$(MCONF) $(Kconfig) + $(Q)$(CONF) $(silent) --syncconfig $(Kconfig) + +savedefconfig: $(CONF) + $(Q)$< $(silent) --$@=configs/defconfig $(Kconfig) + +%defconfig: $(CONF) $(FIXDEP) + $(Q)$< $(silent) --defconfig=configs/$@ $(Kconfig) + $(Q)$< $(silent) --syncconfig $(Kconfig) + +.PHONY: menuconfig savedefconfig defconfig + +# Help text used by make help +help: + @echo ' menuconfig - Update current config utilising a menu based program' + @echo ' savedefconfig - Save current config as configs/defconfig (minimal config)' + +distclean: clean + -@rm -rf $(rm-distclean) + +.PHONY: help distclean + +define call_fixdep + @$(FIXDEP) $(1) $(2) unused > $(1).tmp + @mv $(1).tmp $(1) +endef diff --git a/nemu/scripts/native.mk b/nemu/scripts/native.mk new file mode 100644 index 0000000..e9199a7 --- /dev/null +++ b/nemu/scripts/native.mk @@ -0,0 +1,50 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +-include $(NEMU_HOME)/../Makefile +include $(NEMU_HOME)/scripts/build.mk + +include $(NEMU_HOME)/tools/difftest.mk + +compile_git: + $(call git_commit, "compile NEMU") +$(BINARY):: compile_git + +# Some convenient rules + +override ARGS ?= --log=$(BUILD_DIR)/nemu-log.txt +override ARGS += $(ARGS_DIFF) + +# Command to execute NEMU +IMG ?= +NEMU_EXEC := $(BINARY) $(ARGS) $(IMG) + +run-env: $(BINARY) $(DIFF_REF_SO) + +run: run-env + $(call git_commit, "run NEMU") + $(NEMU_EXEC) + +gdb: run-env + $(call git_commit, "gdb NEMU") + gdb -s $(BINARY) --args $(NEMU_EXEC) + +clean-tools = $(dir $(shell find ./tools -maxdepth 2 -mindepth 2 -name "Makefile")) +$(clean-tools): + -@$(MAKE) -s -C $@ clean +clean-tools: $(clean-tools) +clean-all: clean distclean clean-tools + +.PHONY: run gdb run-env clean-tools clean-all $(clean-tools) diff --git a/nemu/src/am-bin.S b/nemu/src/am-bin.S new file mode 100644 index 0000000..8f59461 --- /dev/null +++ b/nemu/src/am-bin.S @@ -0,0 +1,22 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +.section .rodata +.globl bin_start, bin_end +bin_start: +#ifdef BIN_PATH +.incbin BIN_PATH +#endif +bin_end: diff --git a/nemu/src/cpu/cpu-exec.c b/nemu/src/cpu/cpu-exec.c new file mode 100644 index 0000000..0c244ea --- /dev/null +++ b/nemu/src/cpu/cpu-exec.c @@ -0,0 +1,128 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include + +/* The assembly code of instructions executed is only output to the screen + * when the number of instructions executed is less than this value. + * This is useful when you use the `si' command. + * You can modify this value as you want. + */ +#define MAX_INST_TO_PRINT 10 + +CPU_state cpu = {}; +uint64_t g_nr_guest_inst = 0; +static uint64_t g_timer = 0; // unit: us +static bool g_print_step = false; + +void device_update(); + +static void trace_and_difftest(Decode *_this, vaddr_t dnpc) { +#ifdef CONFIG_ITRACE_COND + if (ITRACE_COND) { log_write("%s\n", _this->logbuf); } +#endif + if (g_print_step) { IFDEF(CONFIG_ITRACE, puts(_this->logbuf)); } + IFDEF(CONFIG_DIFFTEST, difftest_step(_this->pc, dnpc)); +} + +static void exec_once(Decode *s, vaddr_t pc) { + s->pc = pc; + s->snpc = pc; + isa_exec_once(s); + cpu.pc = s->dnpc; +#ifdef CONFIG_ITRACE + char *p = s->logbuf; + p += snprintf(p, sizeof(s->logbuf), FMT_WORD ":", s->pc); + int ilen = s->snpc - s->pc; + int i; + uint8_t *inst = (uint8_t *)&s->isa.inst.val; + for (i = ilen - 1; i >= 0; i --) { + p += snprintf(p, 4, " %02x", inst[i]); + } + int ilen_max = MUXDEF(CONFIG_ISA_x86, 8, 4); + int space_len = ilen_max - ilen; + if (space_len < 0) space_len = 0; + space_len = space_len * 3 + 1; + memset(p, ' ', space_len); + p += space_len; + +#ifndef CONFIG_ISA_loongarch32r + void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte); + disassemble(p, s->logbuf + sizeof(s->logbuf) - p, + MUXDEF(CONFIG_ISA_x86, s->snpc, s->pc), (uint8_t *)&s->isa.inst.val, ilen); +#else + p[0] = '\0'; // the upstream llvm does not support loongarch32r +#endif +#endif +} + +static void execute(uint64_t n) { + Decode s; + for (;n > 0; n --) { + exec_once(&s, cpu.pc); + g_nr_guest_inst ++; + trace_and_difftest(&s, cpu.pc); + if (nemu_state.state != NEMU_RUNNING) break; + IFDEF(CONFIG_DEVICE, device_update()); + } +} + +static void statistic() { + IFNDEF(CONFIG_TARGET_AM, setlocale(LC_NUMERIC, "")); +#define NUMBERIC_FMT MUXDEF(CONFIG_TARGET_AM, "%", "%'") PRIu64 + Log("host time spent = " NUMBERIC_FMT " us", g_timer); + Log("total guest instructions = " NUMBERIC_FMT, g_nr_guest_inst); + if (g_timer > 0) Log("simulation frequency = " NUMBERIC_FMT " inst/s", g_nr_guest_inst * 1000000 / g_timer); + else Log("Finish running in less than 1 us and can not calculate the simulation frequency"); +} + +void assert_fail_msg() { + isa_reg_display(); + statistic(); +} + +/* Simulate how the CPU works. */ +void cpu_exec(uint64_t n) { + g_print_step = (n < MAX_INST_TO_PRINT); + switch (nemu_state.state) { + case NEMU_END: case NEMU_ABORT: + printf("Program execution has ended. To restart the program, exit NEMU and run again.\n"); + return; + default: nemu_state.state = NEMU_RUNNING; + } + + uint64_t timer_start = get_time(); + + execute(n); + + uint64_t timer_end = get_time(); + g_timer += timer_end - timer_start; + + switch (nemu_state.state) { + case NEMU_RUNNING: nemu_state.state = NEMU_STOP; break; + + case NEMU_END: case NEMU_ABORT: + Log("nemu: %s at pc = " FMT_WORD, + (nemu_state.state == NEMU_ABORT ? ANSI_FMT("ABORT", ANSI_FG_RED) : + (nemu_state.halt_ret == 0 ? ANSI_FMT("HIT GOOD TRAP", ANSI_FG_GREEN) : + ANSI_FMT("HIT BAD TRAP", ANSI_FG_RED))), + nemu_state.halt_pc); + // fall through + case NEMU_QUIT: statistic(); + } +} diff --git a/nemu/src/cpu/difftest/dut.c b/nemu/src/cpu/difftest/dut.c new file mode 100644 index 0000000..b003202 --- /dev/null +++ b/nemu/src/cpu/difftest/dut.c @@ -0,0 +1,132 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +void (*ref_difftest_memcpy)(paddr_t addr, void *buf, size_t n, bool direction) = NULL; +void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL; +void (*ref_difftest_exec)(uint64_t n) = NULL; +void (*ref_difftest_raise_intr)(uint64_t NO) = NULL; + +#ifdef CONFIG_DIFFTEST + +static bool is_skip_ref = false; +static int skip_dut_nr_inst = 0; + +// this is used to let ref skip instructions which +// can not produce consistent behavior with NEMU +void difftest_skip_ref() { + is_skip_ref = true; + // If such an instruction is one of the instruction packing in QEMU + // (see below), we end the process of catching up with QEMU's pc to + // keep the consistent behavior in our best. + // Note that this is still not perfect: if the packed instructions + // already write some memory, and the incoming instruction in NEMU + // will load that memory, we will encounter false negative. But such + // situation is infrequent. + skip_dut_nr_inst = 0; +} + +// this is used to deal with instruction packing in QEMU. +// Sometimes letting QEMU step once will execute multiple instructions. +// We should skip checking until NEMU's pc catches up with QEMU's pc. +// The semantic is +// Let REF run `nr_ref` instructions first. +// We expect that DUT will catch up with REF within `nr_dut` instructions. +void difftest_skip_dut(int nr_ref, int nr_dut) { + skip_dut_nr_inst += nr_dut; + + while (nr_ref -- > 0) { + ref_difftest_exec(1); + } +} + +void init_difftest(char *ref_so_file, long img_size, int port) { + assert(ref_so_file != NULL); + + void *handle; + handle = dlopen(ref_so_file, RTLD_LAZY); + assert(handle); + + ref_difftest_memcpy = dlsym(handle, "difftest_memcpy"); + assert(ref_difftest_memcpy); + + ref_difftest_regcpy = dlsym(handle, "difftest_regcpy"); + assert(ref_difftest_regcpy); + + ref_difftest_exec = dlsym(handle, "difftest_exec"); + assert(ref_difftest_exec); + + ref_difftest_raise_intr = dlsym(handle, "difftest_raise_intr"); + assert(ref_difftest_raise_intr); + + void (*ref_difftest_init)(int) = dlsym(handle, "difftest_init"); + assert(ref_difftest_init); + + Log("Differential testing: %s", ANSI_FMT("ON", ANSI_FG_GREEN)); + Log("The result of every instruction will be compared with %s. " + "This will help you a lot for debugging, but also significantly reduce the performance. " + "If it is not necessary, you can turn it off in menuconfig.", ref_so_file); + + ref_difftest_init(port); + ref_difftest_memcpy(RESET_VECTOR, guest_to_host(RESET_VECTOR), img_size, DIFFTEST_TO_REF); + ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF); +} + +static void checkregs(CPU_state *ref, vaddr_t pc) { + if (!isa_difftest_checkregs(ref, pc)) { + nemu_state.state = NEMU_ABORT; + nemu_state.halt_pc = pc; + isa_reg_display(); + } +} + +void difftest_step(vaddr_t pc, vaddr_t npc) { + CPU_state ref_r; + + if (skip_dut_nr_inst > 0) { + ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT); + if (ref_r.pc == npc) { + skip_dut_nr_inst = 0; + checkregs(&ref_r, npc); + return; + } + skip_dut_nr_inst --; + if (skip_dut_nr_inst == 0) + panic("can not catch up with ref.pc = " FMT_WORD " at pc = " FMT_WORD, ref_r.pc, pc); + return; + } + + if (is_skip_ref) { + // to skip the checking of an instruction, just copy the reg state to reference design + ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF); + is_skip_ref = false; + return; + } + + ref_difftest_exec(1); + ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT); + + checkregs(&ref_r, pc); +} +#else +void init_difftest(char *ref_so_file, long img_size, int port) { } +#endif diff --git a/nemu/src/cpu/difftest/ref.c b/nemu/src/cpu/difftest/ref.c new file mode 100644 index 0000000..ef89a61 --- /dev/null +++ b/nemu/src/cpu/difftest/ref.c @@ -0,0 +1,42 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include + +__EXPORT void difftest_memcpy(paddr_t addr, void *buf, size_t n, bool direction) { + assert(0); +} + +__EXPORT void difftest_regcpy(void *dut, bool direction) { + assert(0); +} + +__EXPORT void difftest_exec(uint64_t n) { + assert(0); +} + +__EXPORT void difftest_raise_intr(word_t NO) { + assert(0); +} + +__EXPORT void difftest_init(int port) { + void init_mem(); + init_mem(); + /* Perform ISA dependent initialization. */ + init_isa(); +} diff --git a/nemu/src/device/Kconfig b/nemu/src/device/Kconfig new file mode 100644 index 0000000..c4118d6 --- /dev/null +++ b/nemu/src/device/Kconfig @@ -0,0 +1,154 @@ +menuconfig DEVICE + depends on !TARGET_SHARE + bool "Devices" + default n + help + Provide device support for NEMU. + +if DEVICE + +config HAS_PORT_IO + bool + default y if ISA_x86 + default n + +menuconfig HAS_SERIAL + bool "Enable serial" + default y + +if HAS_SERIAL +config SERIAL_PORT + depends on HAS_PORT_IO + hex "Port address of the serial controller" + default 0x3f8 + +config SERIAL_MMIO + hex "MMIO address of the serial controller" + default 0xa00003f8 + +config SERIAL_INPUT_FIFO + bool "Enable input FIFO with /tmp/nemu.serial" + default n +endif # HAS_SERIAL + +menuconfig HAS_TIMER + bool "Enable timer" + default y + +if HAS_TIMER +config RTC_PORT + depends on HAS_PORT_IO + hex "Port address of the timer" + default 0x48 + +config RTC_MMIO + hex "MMIO address of the timer" + default 0xa0000048 +endif # HAS_TIMER + +menuconfig HAS_KEYBOARD + bool "Enable keyboard" + default y + +if HAS_KEYBOARD +config I8042_DATA_PORT + depends on HAS_PORT_IO + hex "Port address of the keyboard controller" + default 0x60 + +config I8042_DATA_MMIO + hex "MMIO address of the keyboard controller" + default 0xa0000060 +endif # HAS_KEYBOARD + +menuconfig HAS_VGA + bool "Enable VGA" + default y + +if HAS_VGA +config FB_ADDR + hex "Physical address of the VGA frame buffer" + default 0xa1000000 + +config VGA_CTL_PORT + depends on HAS_PORT_IO + hex "Port address of the VGA controller" + default 0x100 + +config VGA_CTL_MMIO + hex "MMIO address of the VGA controller" + default 0xa0000100 + +config VGA_SHOW_SCREEN + bool "Enable SDL SCREEN" + default y + +choice + prompt "Screen Size" + default VGA_SIZE_400x300 +config VGA_SIZE_400x300 + bool "400 x 300" +config VGA_SIZE_800x600 + bool "800 x 600" +endchoice +endif # HAS_VGA + +if !TARGET_AM +menuconfig HAS_AUDIO + bool "Enable audio" + default y + +if HAS_AUDIO +config SB_ADDR + hex "Physical address of the audio stream buffer" + default 0xa1200000 + +config SB_SIZE + hex "Size of the audio stream buffer" + default 0x10000 + +config AUDIO_CTL_PORT + depends on HAS_PORT_IO + hex "Port address of the audio controller" + default 0x200 + +config AUDIO_CTL_MMIO + hex "MMIO address of the audio controller" + default 0xa0000200 +endif # HAS_AUDIO + +menuconfig HAS_DISK + bool "Enable disk" + default y + +if HAS_DISK +config DISK_CTL_PORT + depends on HAS_PORT_IO + hex "Port address of the disk controller" + default 0x300 + +config DISK_CTL_MMIO + hex "MMIO address of the disk controller" + default 0xa0000300 + +config DISK_IMG_PATH + string "The path of disk image" + default "" +endif # HAS_DISK + +menuconfig HAS_SDCARD + bool "Enable sdcard" + default n + +if HAS_SDCARD +config SDCARD_CTL_MMIO + hex "MMIO address of the sdcard controller" + default 0xa3000000 + +config SDCARD_IMG_PATH + string "The path of sdcard image" + default "" +endif # HAS_SDCARD +endif + +endif # DEVICE diff --git a/nemu/src/device/alarm.c b/nemu/src/device/alarm.c new file mode 100644 index 0000000..0979706 --- /dev/null +++ b/nemu/src/device/alarm.c @@ -0,0 +1,51 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include + +#define MAX_HANDLER 8 + +static alarm_handler_t handler[MAX_HANDLER] = {}; +static int idx = 0; + +void add_alarm_handle(alarm_handler_t h) { + assert(idx < MAX_HANDLER); + handler[idx ++] = h; +} + +static void alarm_sig_handler(int signum) { + int i; + for (i = 0; i < idx; i ++) { + handler[i](); + } +} + +void init_alarm() { + struct sigaction s; + memset(&s, 0, sizeof(s)); + s.sa_handler = alarm_sig_handler; + int ret = sigaction(SIGVTALRM, &s, NULL); + Assert(ret == 0, "Can not set signal handler"); + + struct itimerval it = {}; + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 1000000 / TIMER_HZ; + it.it_interval = it.it_value; + ret = setitimer(ITIMER_VIRTUAL, &it, NULL); + Assert(ret == 0, "Can not set timer"); +} diff --git a/nemu/src/device/audio.c b/nemu/src/device/audio.c new file mode 100644 index 0000000..daf83f6 --- /dev/null +++ b/nemu/src/device/audio.c @@ -0,0 +1,47 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include + +enum { + reg_freq, + reg_channels, + reg_samples, + reg_sbuf_size, + reg_init, + reg_count, + nr_reg +}; + +static uint8_t *sbuf = NULL; +static uint32_t *audio_base = NULL; + +static void audio_io_handler(uint32_t offset, int len, bool is_write) { +} + +void init_audio() { + uint32_t space_size = sizeof(uint32_t) * nr_reg; + audio_base = (uint32_t *)new_space(space_size); +#ifdef CONFIG_HAS_PORT_IO + add_pio_map ("audio", CONFIG_AUDIO_CTL_PORT, audio_base, space_size, audio_io_handler); +#else + add_mmio_map("audio", CONFIG_AUDIO_CTL_MMIO, audio_base, space_size, audio_io_handler); +#endif + + sbuf = (uint8_t *)new_space(CONFIG_SB_SIZE); + add_mmio_map("audio-sbuf", CONFIG_SB_ADDR, sbuf, CONFIG_SB_SIZE, NULL); +} diff --git a/nemu/src/device/device.c b/nemu/src/device/device.c new file mode 100644 index 0000000..5c60412 --- /dev/null +++ b/nemu/src/device/device.c @@ -0,0 +1,89 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#ifndef CONFIG_TARGET_AM +#include +#endif + +void init_map(); +void init_serial(); +void init_timer(); +void init_vga(); +void init_i8042(); +void init_audio(); +void init_disk(); +void init_sdcard(); +void init_alarm(); + +void send_key(uint8_t, bool); +void vga_update_screen(); + +void device_update() { + static uint64_t last = 0; + uint64_t now = get_time(); + if (now - last < 1000000 / TIMER_HZ) { + return; + } + last = now; + + IFDEF(CONFIG_HAS_VGA, vga_update_screen()); + +#ifndef CONFIG_TARGET_AM + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + nemu_state.state = NEMU_QUIT; + break; +#ifdef CONFIG_HAS_KEYBOARD + // If a key was pressed + case SDL_KEYDOWN: + case SDL_KEYUP: { + uint8_t k = event.key.keysym.scancode; + bool is_keydown = (event.key.type == SDL_KEYDOWN); + send_key(k, is_keydown); + break; + } +#endif + default: break; + } + } +#endif +} + +void sdl_clear_event_queue() { +#ifndef CONFIG_TARGET_AM + SDL_Event event; + while (SDL_PollEvent(&event)); +#endif +} + +void init_device() { + IFDEF(CONFIG_TARGET_AM, ioe_init()); + init_map(); + + IFDEF(CONFIG_HAS_SERIAL, init_serial()); + IFDEF(CONFIG_HAS_TIMER, init_timer()); + IFDEF(CONFIG_HAS_VGA, init_vga()); + IFDEF(CONFIG_HAS_KEYBOARD, init_i8042()); + IFDEF(CONFIG_HAS_AUDIO, init_audio()); + IFDEF(CONFIG_HAS_DISK, init_disk()); + IFDEF(CONFIG_HAS_SDCARD, init_sdcard()); + + IFNDEF(CONFIG_TARGET_AM, init_alarm()); +} diff --git a/nemu/src/device/disk.c b/nemu/src/device/disk.c new file mode 100644 index 0000000..17a0589 --- /dev/null +++ b/nemu/src/device/disk.c @@ -0,0 +1,19 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +void init_disk() { +} diff --git a/nemu/src/device/filelist.mk b/nemu/src/device/filelist.mk new file mode 100644 index 0000000..74a957f --- /dev/null +++ b/nemu/src/device/filelist.mk @@ -0,0 +1,32 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +DIRS-y += src/device/io +SRCS-$(CONFIG_DEVICE) += src/device/device.c src/device/alarm.c src/device/intr.c +SRCS-$(CONFIG_HAS_SERIAL) += src/device/serial.c +SRCS-$(CONFIG_HAS_TIMER) += src/device/timer.c +SRCS-$(CONFIG_HAS_KEYBOARD) += src/device/keyboard.c +SRCS-$(CONFIG_HAS_VGA) += src/device/vga.c +SRCS-$(CONFIG_HAS_AUDIO) += src/device/audio.c +SRCS-$(CONFIG_HAS_DISK) += src/device/disk.c +SRCS-$(CONFIG_HAS_SDCARD) += src/device/sdcard.c + +SRCS-BLACKLIST-$(CONFIG_TARGET_AM) += src/device/alarm.c + +ifdef CONFIG_DEVICE +ifndef CONFIG_TARGET_AM +LIBS += -lSDL2 +endif +endif diff --git a/nemu/src/device/intr.c b/nemu/src/device/intr.c new file mode 100644 index 0000000..f74f577 --- /dev/null +++ b/nemu/src/device/intr.c @@ -0,0 +1,19 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +void dev_raise_intr() { +} diff --git a/nemu/src/device/io/map.c b/nemu/src/device/io/map.c new file mode 100644 index 0000000..5ccf508 --- /dev/null +++ b/nemu/src/device/io/map.c @@ -0,0 +1,70 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include + +#define IO_SPACE_MAX (2 * 1024 * 1024) + +static uint8_t *io_space = NULL; +static uint8_t *p_space = NULL; + +uint8_t* new_space(int size) { + uint8_t *p = p_space; + // page aligned; + size = (size + (PAGE_SIZE - 1)) & ~PAGE_MASK; + p_space += size; + assert(p_space - io_space < IO_SPACE_MAX); + return p; +} + +static void check_bound(IOMap *map, paddr_t addr) { + if (map == NULL) { + Assert(map != NULL, "address (" FMT_PADDR ") is out of bound at pc = " FMT_WORD, addr, cpu.pc); + } else { + Assert(addr <= map->high && addr >= map->low, + "address (" FMT_PADDR ") is out of bound {%s} [" FMT_PADDR ", " FMT_PADDR "] at pc = " FMT_WORD, + addr, map->name, map->low, map->high, cpu.pc); + } +} + +static void invoke_callback(io_callback_t c, paddr_t offset, int len, bool is_write) { + if (c != NULL) { c(offset, len, is_write); } +} + +void init_map() { + io_space = malloc(IO_SPACE_MAX); + assert(io_space); + p_space = io_space; +} + +word_t map_read(paddr_t addr, int len, IOMap *map) { + assert(len >= 1 && len <= 8); + check_bound(map, addr); + paddr_t offset = addr - map->low; + invoke_callback(map->callback, offset, len, false); // prepare data to read + word_t ret = host_read(map->space + offset, len); + return ret; +} + +void map_write(paddr_t addr, int len, word_t data, IOMap *map) { + assert(len >= 1 && len <= 8); + check_bound(map, addr); + paddr_t offset = addr - map->low; + host_write(map->space + offset, len, data); + invoke_callback(map->callback, offset, len, true); +} diff --git a/nemu/src/device/io/mmio.c b/nemu/src/device/io/mmio.c new file mode 100644 index 0000000..2d10564 --- /dev/null +++ b/nemu/src/device/io/mmio.c @@ -0,0 +1,63 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +#define NR_MAP 16 + +static IOMap maps[NR_MAP] = {}; +static int nr_map = 0; + +static IOMap* fetch_mmio_map(paddr_t addr) { + int mapid = find_mapid_by_addr(maps, nr_map, addr); + return (mapid == -1 ? NULL : &maps[mapid]); +} + +static void report_mmio_overlap(const char *name1, paddr_t l1, paddr_t r1, + const char *name2, paddr_t l2, paddr_t r2) { + panic("MMIO region %s@[" FMT_PADDR ", " FMT_PADDR "] is overlapped " + "with %s@[" FMT_PADDR ", " FMT_PADDR "]", name1, l1, r1, name2, l2, r2); +} + +/* device interface */ +void add_mmio_map(const char *name, paddr_t addr, void *space, uint32_t len, io_callback_t callback) { + assert(nr_map < NR_MAP); + paddr_t left = addr, right = addr + len - 1; + if (in_pmem(left) || in_pmem(right)) { + report_mmio_overlap(name, left, right, "pmem", PMEM_LEFT, PMEM_RIGHT); + } + for (int i = 0; i < nr_map; i++) { + if (left <= maps[i].high && right >= maps[i].low) { + report_mmio_overlap(name, left, right, maps[i].name, maps[i].low, maps[i].high); + } + } + + maps[nr_map] = (IOMap){ .name = name, .low = addr, .high = addr + len - 1, + .space = space, .callback = callback }; + Log("Add mmio map '%s' at [" FMT_PADDR ", " FMT_PADDR "]", + maps[nr_map].name, maps[nr_map].low, maps[nr_map].high); + + nr_map ++; +} + +/* bus interface */ +word_t mmio_read(paddr_t addr, int len) { + return map_read(addr, len, fetch_mmio_map(addr)); +} + +void mmio_write(paddr_t addr, int len, word_t data) { + map_write(addr, len, data, fetch_mmio_map(addr)); +} diff --git a/nemu/src/device/io/port-io.c b/nemu/src/device/io/port-io.c new file mode 100644 index 0000000..ba6977b --- /dev/null +++ b/nemu/src/device/io/port-io.c @@ -0,0 +1,49 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +#define PORT_IO_SPACE_MAX 65535 + +#define NR_MAP 16 +static IOMap maps[NR_MAP] = {}; +static int nr_map = 0; + +/* device interface */ +void add_pio_map(const char *name, ioaddr_t addr, void *space, uint32_t len, io_callback_t callback) { + assert(nr_map < NR_MAP); + assert(addr + len <= PORT_IO_SPACE_MAX); + maps[nr_map] = (IOMap){ .name = name, .low = addr, .high = addr + len - 1, + .space = space, .callback = callback }; + Log("Add port-io map '%s' at [" FMT_PADDR ", " FMT_PADDR "]", + maps[nr_map].name, maps[nr_map].low, maps[nr_map].high); + + nr_map ++; +} + +/* CPU interface */ +uint32_t pio_read(ioaddr_t addr, int len) { + assert(addr + len - 1 < PORT_IO_SPACE_MAX); + int mapid = find_mapid_by_addr(maps, nr_map, addr); + assert(mapid != -1); + return map_read(addr, len, &maps[mapid]); +} + +void pio_write(ioaddr_t addr, int len, uint32_t data) { + assert(addr + len - 1 < PORT_IO_SPACE_MAX); + int mapid = find_mapid_by_addr(maps, nr_map, addr); + assert(mapid != -1); + map_write(addr, len, data, &maps[mapid]); +} diff --git a/nemu/src/device/keyboard.c b/nemu/src/device/keyboard.c new file mode 100644 index 0000000..da699b5 --- /dev/null +++ b/nemu/src/device/keyboard.c @@ -0,0 +1,100 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +#define KEYDOWN_MASK 0x8000 + +#ifndef CONFIG_TARGET_AM +#include + +// Note that this is not the standard +#define NEMU_KEYS(f) \ + f(ESCAPE) f(F1) f(F2) f(F3) f(F4) f(F5) f(F6) f(F7) f(F8) f(F9) f(F10) f(F11) f(F12) \ +f(GRAVE) f(1) f(2) f(3) f(4) f(5) f(6) f(7) f(8) f(9) f(0) f(MINUS) f(EQUALS) f(BACKSPACE) \ +f(TAB) f(Q) f(W) f(E) f(R) f(T) f(Y) f(U) f(I) f(O) f(P) f(LEFTBRACKET) f(RIGHTBRACKET) f(BACKSLASH) \ +f(CAPSLOCK) f(A) f(S) f(D) f(F) f(G) f(H) f(J) f(K) f(L) f(SEMICOLON) f(APOSTROPHE) f(RETURN) \ +f(LSHIFT) f(Z) f(X) f(C) f(V) f(B) f(N) f(M) f(COMMA) f(PERIOD) f(SLASH) f(RSHIFT) \ +f(LCTRL) f(APPLICATION) f(LALT) f(SPACE) f(RALT) f(RCTRL) \ +f(UP) f(DOWN) f(LEFT) f(RIGHT) f(INSERT) f(DELETE) f(HOME) f(END) f(PAGEUP) f(PAGEDOWN) + +#define NEMU_KEY_NAME(k) NEMU_KEY_ ## k, + +enum { + NEMU_KEY_NONE = 0, + MAP(NEMU_KEYS, NEMU_KEY_NAME) +}; + +#define SDL_KEYMAP(k) keymap[SDL_SCANCODE_ ## k] = NEMU_KEY_ ## k; +static uint32_t keymap[256] = {}; + +static void init_keymap() { + MAP(NEMU_KEYS, SDL_KEYMAP) +} + +#define KEY_QUEUE_LEN 1024 +static int key_queue[KEY_QUEUE_LEN] = {}; +static int key_f = 0, key_r = 0; + +static void key_enqueue(uint32_t am_scancode) { + key_queue[key_r] = am_scancode; + key_r = (key_r + 1) % KEY_QUEUE_LEN; + Assert(key_r != key_f, "key queue overflow!"); +} + +static uint32_t key_dequeue() { + uint32_t key = NEMU_KEY_NONE; + if (key_f != key_r) { + key = key_queue[key_f]; + key_f = (key_f + 1) % KEY_QUEUE_LEN; + } + return key; +} + +void send_key(uint8_t scancode, bool is_keydown) { + if (nemu_state.state == NEMU_RUNNING && keymap[scancode] != NEMU_KEY_NONE) { + uint32_t am_scancode = keymap[scancode] | (is_keydown ? KEYDOWN_MASK : 0); + key_enqueue(am_scancode); + } +} +#else // !CONFIG_TARGET_AM +#define NEMU_KEY_NONE 0 + +static uint32_t key_dequeue() { + AM_INPUT_KEYBRD_T ev = io_read(AM_INPUT_KEYBRD); + uint32_t am_scancode = ev.keycode | (ev.keydown ? KEYDOWN_MASK : 0); + return am_scancode; +} +#endif + +static uint32_t *i8042_data_port_base = NULL; + +static void i8042_data_io_handler(uint32_t offset, int len, bool is_write) { + assert(!is_write); + assert(offset == 0); + i8042_data_port_base[0] = key_dequeue(); +} + +void init_i8042() { + i8042_data_port_base = (uint32_t *)new_space(4); + i8042_data_port_base[0] = NEMU_KEY_NONE; +#ifdef CONFIG_HAS_PORT_IO + add_pio_map ("keyboard", CONFIG_I8042_DATA_PORT, i8042_data_port_base, 4, i8042_data_io_handler); +#else + add_mmio_map("keyboard", CONFIG_I8042_DATA_MMIO, i8042_data_port_base, 4, i8042_data_io_handler); +#endif + IFNDEF(CONFIG_TARGET_AM, init_keymap()); +} diff --git a/nemu/src/device/mmc.h b/nemu/src/device/mmc.h new file mode 100644 index 0000000..7a68bb3 --- /dev/null +++ b/nemu/src/device/mmc.h @@ -0,0 +1,439 @@ +/* + * Header for MultiMediaCard (MMC) + * + * Copyright 2002 Hewlett-Packard Company + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED, + * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS + * FITNESS FOR ANY PARTICULAR PURPOSE. + * + * Many thanks to Alessandro Rubini and Jonathan Corbet! + * + * Based strongly on code by: + * + * Author: Yong-iL Joh + * + * Author: Andrew Christian + * 15 May 2002 + */ + +#ifndef LINUX_MMC_MMC_H +#define LINUX_MMC_MMC_H + +/* Standard MMC commands (4.1) type argument response */ + /* class 1 */ +#define MMC_GO_IDLE_STATE 0 /* bc */ +#define MMC_SEND_OP_COND 1 /* bcr [31:0] OCR R3 */ +#define MMC_ALL_SEND_CID 2 /* bcr R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* ac [31:16] RCA R1 */ +#define MMC_SET_DSR 4 /* bc [31:16] RCA */ +#define MMC_SLEEP_AWAKE 5 /* ac [31:16] RCA 15:flg R1b */ +#define MMC_SWITCH 6 /* ac [31:0] See below R1b */ +#define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */ +#define MMC_SEND_EXT_CSD 8 /* adtc R1 */ +#define MMC_SEND_CSD 9 /* ac [31:16] RCA R2 */ +#define MMC_SEND_CID 10 /* ac [31:16] RCA R2 */ +#define MMC_READ_DAT_UNTIL_STOP 11 /* adtc [31:0] dadr R1 */ +#define MMC_STOP_TRANSMISSION 12 /* ac R1b */ +#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */ +#define MMC_BUS_TEST_R 14 /* adtc R1 */ +#define MMC_GO_INACTIVE_STATE 15 /* ac [31:16] RCA */ +#define MMC_BUS_TEST_W 19 /* adtc R1 */ +#define MMC_SPI_READ_OCR 58 /* spi spi_R3 */ +#define MMC_SPI_CRC_ON_OFF 59 /* spi [0:0] flag spi_R1 */ + + /* class 2 */ +#define MMC_SET_BLOCKLEN 16 /* ac [31:0] block len R1 */ +#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */ +#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */ +#define MMC_SEND_TUNING_BLOCK 19 /* adtc R1 */ +#define MMC_SEND_TUNING_BLOCK_HS200 21 /* adtc R1 */ + + /* class 3 */ +#define MMC_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */ + + /* class 4 */ +#define MMC_SET_BLOCK_COUNT 23 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc R1 */ +#define MMC_PROGRAM_CID 26 /* adtc R1 */ +#define MMC_PROGRAM_CSD 27 /* adtc R1 */ + + /* class 6 */ +#define MMC_SET_WRITE_PROT 28 /* ac [31:0] data addr R1b */ +#define MMC_CLR_WRITE_PROT 29 /* ac [31:0] data addr R1b */ +#define MMC_SEND_WRITE_PROT 30 /* adtc [31:0] wpdata addr R1 */ + + /* class 5 */ +#define MMC_ERASE_GROUP_START 35 /* ac [31:0] data addr R1 */ +#define MMC_ERASE_GROUP_END 36 /* ac [31:0] data addr R1 */ +#define MMC_ERASE 38 /* ac R1b */ + + /* class 9 */ +#define MMC_FAST_IO 39 /* ac R4 */ +#define MMC_GO_IRQ_STATE 40 /* bcr R5 */ + + /* class 7 */ +#define MMC_LOCK_UNLOCK 42 /* adtc R1b */ + + /* class 8 */ +#define MMC_APP_CMD 55 /* ac [31:16] RCA R1 */ +#define MMC_GEN_CMD 56 /* adtc [0] RD/WR R1 */ + + /* class 11 */ +#define MMC_QUE_TASK_PARAMS 44 /* ac [20:16] task id R1 */ +#define MMC_QUE_TASK_ADDR 45 /* ac [31:0] data addr R1 */ +#define MMC_EXECUTE_READ_TASK 46 /* adtc [20:16] task id R1 */ +#define MMC_EXECUTE_WRITE_TASK 47 /* adtc [20:16] task id R1 */ +#define MMC_CMDQ_TASK_MGMT 48 /* ac [20:16] task id R1b */ + +//static inline bool mmc_op_multi(u32 opcode) +//{ +// return opcode == MMC_WRITE_MULTIPLE_BLOCK || +// opcode == MMC_READ_MULTIPLE_BLOCK; +//} + +/* + * MMC_SWITCH argument format: + * + * [31:26] Always 0 + * [25:24] Access Mode + * [23:16] Location of target Byte in EXT_CSD + * [15:08] Value Byte + * [07:03] Always 0 + * [02:00] Command Set + */ + +/* + MMC status in R1, for native mode (SPI bits are different) + Type + e : error bit + s : status bit + r : detected and set for the actual command response + x : detected and set during command execution. the host must poll + the card by sending status command in order to read these bits. + Clear condition + a : according to the card state + b : always related to the previous command. Reception of + a valid command will clear it (with a delay of one command) + c : clear by read + */ + +#define R1_OUT_OF_RANGE (1 << 31) /* er, c */ +#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */ +#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */ +#define R1_ERASE_PARAM (1 << 27) /* ex, c */ +#define R1_WP_VIOLATION (1 << 26) /* erx, c */ +#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */ +#define R1_COM_CRC_ERROR (1 << 23) /* er, b */ +#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */ +#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */ +#define R1_CC_ERROR (1 << 20) /* erx, c */ +#define R1_ERROR (1 << 19) /* erx, c */ +#define R1_UNDERRUN (1 << 18) /* ex, c */ +#define R1_OVERRUN (1 << 17) /* ex, c */ +#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */ +#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */ +#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */ +#define R1_ERASE_RESET (1 << 13) /* sr, c */ +#define R1_STATUS(x) (x & 0xFFFFE000) +#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ +#define R1_READY_FOR_DATA (1 << 8) /* sx, a */ +#define R1_SWITCH_ERROR (1 << 7) /* sx, c */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sr, a */ +#define R1_APP_CMD (1 << 5) /* sr, c */ + +#define R1_STATE_IDLE 0 +#define R1_STATE_READY 1 +#define R1_STATE_IDENT 2 +#define R1_STATE_STBY 3 +#define R1_STATE_TRAN 4 +#define R1_STATE_DATA 5 +#define R1_STATE_RCV 6 +#define R1_STATE_PRG 7 +#define R1_STATE_DIS 8 + +/* + * MMC/SD in SPI mode reports R1 status always, and R2 for SEND_STATUS + * R1 is the low order byte; R2 is the next highest byte, when present. + */ +#define R1_SPI_IDLE (1 << 0) +#define R1_SPI_ERASE_RESET (1 << 1) +#define R1_SPI_ILLEGAL_COMMAND (1 << 2) +#define R1_SPI_COM_CRC (1 << 3) +#define R1_SPI_ERASE_SEQ (1 << 4) +#define R1_SPI_ADDRESS (1 << 5) +#define R1_SPI_PARAMETER (1 << 6) +/* R1 bit 7 is always zero */ +#define R2_SPI_CARD_LOCKED (1 << 8) +#define R2_SPI_WP_ERASE_SKIP (1 << 9) /* or lock/unlock fail */ +#define R2_SPI_LOCK_UNLOCK_FAIL R2_SPI_WP_ERASE_SKIP +#define R2_SPI_ERROR (1 << 10) +#define R2_SPI_CC_ERROR (1 << 11) +#define R2_SPI_CARD_ECC_ERROR (1 << 12) +#define R2_SPI_WP_VIOLATION (1 << 13) +#define R2_SPI_ERASE_PARAM (1 << 14) +#define R2_SPI_OUT_OF_RANGE (1 << 15) /* or CSD overwrite */ +#define R2_SPI_CSD_OVERWRITE R2_SPI_OUT_OF_RANGE + +/* + * OCR bits are mostly in host.h + */ +#define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */ + +/* + * Card Command Classes (CCC) + */ +#define CCC_BASIC (1<<0) /* (0) Basic protocol functions */ + /* (CMD0,1,2,3,4,7,9,10,12,13,15) */ + /* (and for SPI, CMD58,59) */ +#define CCC_STREAM_READ (1<<1) /* (1) Stream read commands */ + /* (CMD11) */ +#define CCC_BLOCK_READ (1<<2) /* (2) Block read commands */ + /* (CMD16,17,18) */ +#define CCC_STREAM_WRITE (1<<3) /* (3) Stream write commands */ + /* (CMD20) */ +#define CCC_BLOCK_WRITE (1<<4) /* (4) Block write commands */ + /* (CMD16,24,25,26,27) */ +#define CCC_ERASE (1<<5) /* (5) Ability to erase blocks */ + /* (CMD32,33,34,35,36,37,38,39) */ +#define CCC_WRITE_PROT (1<<6) /* (6) Able to write protect blocks */ + /* (CMD28,29,30) */ +#define CCC_LOCK_CARD (1<<7) /* (7) Able to lock down card */ + /* (CMD16,CMD42) */ +#define CCC_APP_SPEC (1<<8) /* (8) Application specific */ + /* (CMD55,56,57,ACMD*) */ +#define CCC_IO_MODE (1<<9) /* (9) I/O mode */ + /* (CMD5,39,40,52,53) */ +#define CCC_SWITCH (1<<10) /* (10) High speed switch */ + /* (CMD6,34,35,36,37,50) */ + /* (11) Reserved */ + /* (CMD?) */ + +/* + * CSD field definitions + */ + +#define CSD_STRUCT_VER_1_0 0 /* Valid for system specification 1.0 - 1.2 */ +#define CSD_STRUCT_VER_1_1 1 /* Valid for system specification 1.4 - 2.2 */ +#define CSD_STRUCT_VER_1_2 2 /* Valid for system specification 3.1 - 3.2 - 3.31 - 4.0 - 4.1 */ +#define CSD_STRUCT_EXT_CSD 3 /* Version is coded in CSD_STRUCTURE in EXT_CSD */ + +#define CSD_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.2 */ +#define CSD_SPEC_VER_1 1 /* Implements system specification 1.4 */ +#define CSD_SPEC_VER_2 2 /* Implements system specification 2.0 - 2.2 */ +#define CSD_SPEC_VER_3 3 /* Implements system specification 3.1 - 3.2 - 3.31 */ +#define CSD_SPEC_VER_4 4 /* Implements system specification 4.0 - 4.1 */ + +/* + * EXT_CSD fields + */ + +#define EXT_CSD_CMDQ_MODE_EN 15 /* R/W */ +#define EXT_CSD_FLUSH_CACHE 32 /* W */ +#define EXT_CSD_CACHE_CTRL 33 /* R/W */ +#define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ +#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */ +#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */ +#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */ +#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */ +#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ +#define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ +#define EXT_CSD_PARTITION_SETTING_COMPLETED 155 /* R/W */ +#define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ +#define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ +#define EXT_CSD_HPI_MGMT 161 /* R/W */ +#define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_BKOPS_EN 163 /* R/W */ +#define EXT_CSD_BKOPS_START 164 /* W */ +#define EXT_CSD_SANITIZE_START 165 /* W */ +#define EXT_CSD_WR_REL_PARAM 166 /* RO */ +#define EXT_CSD_RPMB_MULT 168 /* RO */ +#define EXT_CSD_FW_CONFIG 169 /* R/W */ +#define EXT_CSD_BOOT_WP 173 /* R/W */ +#define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ +#define EXT_CSD_PART_CONFIG 179 /* R/W */ +#define EXT_CSD_ERASED_MEM_CONT 181 /* RO */ +#define EXT_CSD_BUS_WIDTH 183 /* R/W */ +#define EXT_CSD_STROBE_SUPPORT 184 /* RO */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_POWER_CLASS 187 /* R/W */ +#define EXT_CSD_REV 192 /* RO */ +#define EXT_CSD_STRUCTURE 194 /* RO */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_DRIVER_STRENGTH 197 /* RO */ +#define EXT_CSD_OUT_OF_INTERRUPT_TIME 198 /* RO */ +#define EXT_CSD_PART_SWITCH_TIME 199 /* RO */ +#define EXT_CSD_PWR_CL_52_195 200 /* RO */ +#define EXT_CSD_PWR_CL_26_195 201 /* RO */ +#define EXT_CSD_PWR_CL_52_360 202 /* RO */ +#define EXT_CSD_PWR_CL_26_360 203 /* RO */ +#define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ +#define EXT_CSD_S_A_TIMEOUT 217 /* RO */ +#define EXT_CSD_REL_WR_SEC_C 222 /* RO */ +#define EXT_CSD_HC_WP_GRP_SIZE 221 /* RO */ +#define EXT_CSD_ERASE_TIMEOUT_MULT 223 /* RO */ +#define EXT_CSD_HC_ERASE_GRP_SIZE 224 /* RO */ +#define EXT_CSD_BOOT_MULT 226 /* RO */ +#define EXT_CSD_SEC_TRIM_MULT 229 /* RO */ +#define EXT_CSD_SEC_ERASE_MULT 230 /* RO */ +#define EXT_CSD_SEC_FEATURE_SUPPORT 231 /* RO */ +#define EXT_CSD_TRIM_MULT 232 /* RO */ +#define EXT_CSD_PWR_CL_200_195 236 /* RO */ +#define EXT_CSD_PWR_CL_200_360 237 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_BKOPS_STATUS 246 /* RO */ +#define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ +#define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ +#define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ +#define EXT_CSD_PWR_CL_DDR_200_360 253 /* RO */ +#define EXT_CSD_FIRMWARE_VERSION 254 /* RO, 8 bytes */ +#define EXT_CSD_PRE_EOL_INFO 267 /* RO */ +#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A 268 /* RO */ +#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B 269 /* RO */ +#define EXT_CSD_CMDQ_DEPTH 307 /* RO */ +#define EXT_CSD_CMDQ_SUPPORT 308 /* RO */ +#define EXT_CSD_SUPPORTED_MODE 493 /* RO */ +#define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ +#define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ +#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */ +#define EXT_CSD_MAX_PACKED_READS 501 /* RO */ +#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */ +#define EXT_CSD_HPI_FEATURES 503 /* RO */ + +/* + * EXT_CSD field definitions + */ + +#define EXT_CSD_WR_REL_PARAM_EN (1<<2) + +#define EXT_CSD_BOOT_WP_B_PWR_WP_DIS (0x40) +#define EXT_CSD_BOOT_WP_B_PERM_WP_DIS (0x10) +#define EXT_CSD_BOOT_WP_B_PERM_WP_EN (0x04) +#define EXT_CSD_BOOT_WP_B_PWR_WP_EN (0x01) + +#define EXT_CSD_PART_CONFIG_ACC_MASK (0x7) +#define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1) +#define EXT_CSD_PART_CONFIG_ACC_RPMB (0x3) +#define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4) + +#define EXT_CSD_PART_SETTING_COMPLETED (0x1) +#define EXT_CSD_PART_SUPPORT_PART_EN (0x1) + +#define EXT_CSD_CMD_SET_NORMAL (1<<0) +#define EXT_CSD_CMD_SET_SECURE (1<<1) +#define EXT_CSD_CMD_SET_CPSECURE (1<<2) + +#define EXT_CSD_CARD_TYPE_HS_26 (1<<0) /* Card can run at 26MHz */ +#define EXT_CSD_CARD_TYPE_HS_52 (1<<1) /* Card can run at 52MHz */ +#define EXT_CSD_CARD_TYPE_HS (EXT_CSD_CARD_TYPE_HS_26 | \ + EXT_CSD_CARD_TYPE_HS_52) +#define EXT_CSD_CARD_TYPE_DDR_1_8V (1<<2) /* Card can run at 52MHz */ + /* DDR mode @1.8V or 3V I/O */ +#define EXT_CSD_CARD_TYPE_DDR_1_2V (1<<3) /* Card can run at 52MHz */ + /* DDR mode @1.2V I/O */ +#define EXT_CSD_CARD_TYPE_DDR_52 (EXT_CSD_CARD_TYPE_DDR_1_8V \ + | EXT_CSD_CARD_TYPE_DDR_1_2V) +#define EXT_CSD_CARD_TYPE_HS200_1_8V (1<<4) /* Card can run at 200MHz */ +#define EXT_CSD_CARD_TYPE_HS200_1_2V (1<<5) /* Card can run at 200MHz */ + /* SDR mode @1.2V I/O */ +#define EXT_CSD_CARD_TYPE_HS200 (EXT_CSD_CARD_TYPE_HS200_1_8V | \ + EXT_CSD_CARD_TYPE_HS200_1_2V) +#define EXT_CSD_CARD_TYPE_HS400_1_8V (1<<6) /* Card can run at 200MHz DDR, 1.8V */ +#define EXT_CSD_CARD_TYPE_HS400_1_2V (1<<7) /* Card can run at 200MHz DDR, 1.2V */ +#define EXT_CSD_CARD_TYPE_HS400 (EXT_CSD_CARD_TYPE_HS400_1_8V | \ + EXT_CSD_CARD_TYPE_HS400_1_2V) +#define EXT_CSD_CARD_TYPE_HS400ES (1<<8) /* Card can run at HS400ES */ + +#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */ +#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */ +#define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */ +#define EXT_CSD_DDR_BUS_WIDTH_4 5 /* Card is in 4 bit DDR mode */ +#define EXT_CSD_DDR_BUS_WIDTH_8 6 /* Card is in 8 bit DDR mode */ +#define EXT_CSD_BUS_WIDTH_STROBE BIT(7) /* Enhanced strobe mode */ + +#define EXT_CSD_TIMING_BC 0 /* Backwards compatility */ +#define EXT_CSD_TIMING_HS 1 /* High speed */ +#define EXT_CSD_TIMING_HS200 2 /* HS200 */ +#define EXT_CSD_TIMING_HS400 3 /* HS400 */ +#define EXT_CSD_DRV_STR_SHIFT 4 /* Driver Strength shift */ + +#define EXT_CSD_SEC_ER_EN BIT(0) +#define EXT_CSD_SEC_BD_BLK_EN BIT(2) +#define EXT_CSD_SEC_GB_CL_EN BIT(4) +#define EXT_CSD_SEC_SANITIZE BIT(6) /* v4.5 only */ + +#define EXT_CSD_RST_N_EN_MASK 0x3 +#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ + +#define EXT_CSD_NO_POWER_NOTIFICATION 0 +#define EXT_CSD_POWER_ON 1 +#define EXT_CSD_POWER_OFF_SHORT 2 +#define EXT_CSD_POWER_OFF_LONG 3 + +#define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_8BIT_SHIFT 4 +#define EXT_CSD_PWR_CL_4BIT_SHIFT 0 + +#define EXT_CSD_PACKED_EVENT_EN BIT(3) + +/* + * EXCEPTION_EVENT_STATUS field + */ +#define EXT_CSD_URGENT_BKOPS BIT(0) +#define EXT_CSD_DYNCAP_NEEDED BIT(1) +#define EXT_CSD_SYSPOOL_EXHAUSTED BIT(2) +#define EXT_CSD_PACKED_FAILURE BIT(3) + +#define EXT_CSD_PACKED_GENERIC_ERROR BIT(0) +#define EXT_CSD_PACKED_INDEXED_ERROR BIT(1) + +/* + * BKOPS status level + */ +#define EXT_CSD_BKOPS_LEVEL_2 0x2 + +/* + * BKOPS modes + */ +#define EXT_CSD_MANUAL_BKOPS_MASK 0x01 +#define EXT_CSD_AUTO_BKOPS_MASK 0x02 + +/* + * Command Queue + */ +#define EXT_CSD_CMDQ_MODE_ENABLED BIT(0) +#define EXT_CSD_CMDQ_DEPTH_MASK GENMASK(4, 0) +#define EXT_CSD_CMDQ_SUPPORTED BIT(0) + +/* + * MMC_SWITCH access modes + */ +#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */ +#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits which are 1 in value */ +#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */ +#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ + +/* + * Erase/trim/discard + */ +#define MMC_ERASE_ARG 0x00000000 +#define MMC_SECURE_ERASE_ARG 0x80000000 +#define MMC_TRIM_ARG 0x00000001 +#define MMC_DISCARD_ARG 0x00000003 +#define MMC_SECURE_TRIM1_ARG 0x80000001 +#define MMC_SECURE_TRIM2_ARG 0x80008000 +#define MMC_SECURE_ARGS 0x80000000 +#define MMC_TRIM_ARGS 0x00008001 + +#define mmc_driver_type_mask(n) (1 << (n)) + +#endif /* LINUX_MMC_MMC_H */ diff --git a/nemu/src/device/sdcard.c b/nemu/src/device/sdcard.c new file mode 100644 index 0000000..7e48634 --- /dev/null +++ b/nemu/src/device/sdcard.c @@ -0,0 +1,134 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include "mmc.h" + +// http://www.files.e-shop.co.il/pdastore/Tech-mmc-samsung/SEC%20MMC%20SPEC%20ver09.pdf + +// see page 26 of the manual above +#define MEMORY_SIZE (16ull * 1024 * 1024 * 1024) // 16GB +#define READ_BL_LEN 15 +#define BLOCK_LEN (1 << READ_BL_LEN) +#define NR_BLOCK (MEMORY_SIZE / BLOCK_LEN) +#define C_SIZE_MULT 7 // only 3 bits +#define MULT (1 << (C_SIZE_MULT + 2)) +#define C_SIZE (NR_BLOCK / MULT - 1) + +// This is a simple hardware implementation of linux/drivers/mmc/host/bcm2835.c +// No DMA and IRQ is supported, so the driver must be modified to start PIO +// right after sending the actual read/write commands. + +enum { + SDCMD, SDARG, SDTOUT, SDCDIV, + SDRSP0, SDRSP1, SDRSP2, SDRSP3, + SDHSTS, __PAD0, __PAD1, __PAD2, + SDVDD, SDEDM, SDHCFG, SDHBCT, + SDDATA, __PAD10, __PAD11, __PAD12, + SDHBLC +}; + +static FILE *fp = NULL; +static uint32_t *base = NULL; +static uint32_t blkcnt = 0; +static long blk_addr = 0; +static uint32_t addr = 0; +static bool write_cmd = 0; +static bool read_ext_csd = false; + +static void prepare_rw(int is_write) { + blk_addr = base[SDARG]; + addr = 0; + if (fp) fseek(fp, blk_addr << 9, SEEK_SET); + write_cmd = is_write; +} + +static void sdcard_handle_cmd(int cmd) { + switch (cmd) { + case MMC_GO_IDLE_STATE: break; + case MMC_SEND_OP_COND: base[SDRSP0] = 0x80ff8000; break; + case MMC_ALL_SEND_CID: + base[SDRSP0] = 0x00000001; + base[SDRSP1] = 0x00000000; + base[SDRSP2] = 0x00000000; + base[SDRSP3] = 0x15000000; + break; + case 52: // ??? + break; + case MMC_SEND_CSD: + base[SDRSP0] = 0x92404001; + base[SDRSP1] = 0x124b97e3 | ((C_SIZE & 0x3) << 30); + base[SDRSP2] = 0x0f508000 | (C_SIZE >> 2) | (READ_BL_LEN << 16); + base[SDRSP3] = 0x9026012a; + break; + case MMC_SEND_EXT_CSD: read_ext_csd = true; addr = 0; break; + case MMC_SLEEP_AWAKE: break; + case MMC_APP_CMD: break; + case MMC_SET_RELATIVE_ADDR: break; + case MMC_SELECT_CARD: break; + case MMC_SET_BLOCK_COUNT: blkcnt = base[SDARG] & 0xffff; break; + case MMC_READ_MULTIPLE_BLOCK: prepare_rw(false); break; + case MMC_WRITE_MULTIPLE_BLOCK: prepare_rw(true); break; + case MMC_SEND_STATUS: base[SDRSP0] = 0x900; base[SDRSP1] = base[SDRSP2] = base[SDRSP3] = 0; break; + case MMC_STOP_TRANSMISSION: break; + default: + panic("unhandled command = %d", cmd); + } +} + +static void sdcard_io_handler(uint32_t offset, int len, bool is_write) { + int idx = offset / 4; + switch (idx) { + case SDCMD: sdcard_handle_cmd(base[SDCMD] & 0x3f); break; + case SDARG: + case SDRSP0: + case SDRSP1: + case SDRSP2: + case SDRSP3: + break; + case SDDATA: + if (read_ext_csd) { + // See section 8.1 JEDEC Standard JED84-A441 + uint32_t data; + switch (addr) { + case 192: data = 2; break; // EXT_CSD_REV + case 212: data = MEMORY_SIZE / 512; break; + default: data = 0; + } + base[SDDATA] = data; + if (addr == 512 - 4) read_ext_csd = false; + } else if (fp) { + __attribute__((unused)) int ret; + if (!write_cmd) { ret = fread(&base[SDDATA], 4, 1, fp); } + else { ret = fwrite(&base[SDDATA], 4, 1, fp); } + } + addr += 4; + break; + default: + Log("offset = 0x%x(idx = %d), is_write = %d, data = 0x%x", offset, idx, is_write, base[idx]); + panic("unhandle offset = %d", offset); + } +} + +void init_sdcard() { + base = (uint32_t *)new_space(0x80); + add_mmio_map("sdhci", CONFIG_SDCARD_CTL_MMIO, base, 0x80, sdcard_io_handler); + + Assert(C_SIZE < (1 << 12), "shoule be fit in 12 bits"); + + const char *img = CONFIG_SDCARD_IMG_PATH; + fp = fopen(img, "r+"); + if (fp == NULL) Log("Can not find sdcard image: %s", img); +} diff --git a/nemu/src/device/serial.c b/nemu/src/device/serial.c new file mode 100644 index 0000000..b181c5f --- /dev/null +++ b/nemu/src/device/serial.c @@ -0,0 +1,51 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +/* http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming */ +// NOTE: this is compatible to 16550 + +#define CH_OFFSET 0 + +static uint8_t *serial_base = NULL; + + +static void serial_putc(char ch) { + MUXDEF(CONFIG_TARGET_AM, putch(ch), putc(ch, stderr)); +} + +static void serial_io_handler(uint32_t offset, int len, bool is_write) { + assert(len == 1); + switch (offset) { + /* We bind the serial port with the host stderr in NEMU. */ + case CH_OFFSET: + if (is_write) serial_putc(serial_base[0]); + else panic("do not support read"); + break; + default: panic("do not support offset = %d", offset); + } +} + +void init_serial() { + serial_base = new_space(8); +#ifdef CONFIG_HAS_PORT_IO + add_pio_map ("serial", CONFIG_SERIAL_PORT, serial_base, 8, serial_io_handler); +#else + add_mmio_map("serial", CONFIG_SERIAL_MMIO, serial_base, 8, serial_io_handler); +#endif + +} diff --git a/nemu/src/device/timer.c b/nemu/src/device/timer.c new file mode 100644 index 0000000..2aff819 --- /dev/null +++ b/nemu/src/device/timer.c @@ -0,0 +1,48 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include + +static uint32_t *rtc_port_base = NULL; + +static void rtc_io_handler(uint32_t offset, int len, bool is_write) { + assert(offset == 0 || offset == 4); + if (!is_write && offset == 4) { + uint64_t us = get_time(); + rtc_port_base[0] = (uint32_t)us; + rtc_port_base[1] = us >> 32; + } +} + +#ifndef CONFIG_TARGET_AM +static void timer_intr() { + if (nemu_state.state == NEMU_RUNNING) { + extern void dev_raise_intr(); + dev_raise_intr(); + } +} +#endif + +void init_timer() { + rtc_port_base = (uint32_t *)new_space(8); +#ifdef CONFIG_HAS_PORT_IO + add_pio_map ("rtc", CONFIG_RTC_PORT, rtc_port_base, 8, rtc_io_handler); +#else + add_mmio_map("rtc", CONFIG_RTC_MMIO, rtc_port_base, 8, rtc_io_handler); +#endif + IFNDEF(CONFIG_TARGET_AM, add_alarm_handle(timer_intr)); +} diff --git a/nemu/src/device/vga.c b/nemu/src/device/vga.c new file mode 100644 index 0000000..f30bf77 --- /dev/null +++ b/nemu/src/device/vga.c @@ -0,0 +1,92 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +#define SCREEN_W (MUXDEF(CONFIG_VGA_SIZE_800x600, 800, 400)) +#define SCREEN_H (MUXDEF(CONFIG_VGA_SIZE_800x600, 600, 300)) + +static uint32_t screen_width() { + return MUXDEF(CONFIG_TARGET_AM, io_read(AM_GPU_CONFIG).width, SCREEN_W); +} + +static uint32_t screen_height() { + return MUXDEF(CONFIG_TARGET_AM, io_read(AM_GPU_CONFIG).height, SCREEN_H); +} + +static uint32_t screen_size() { + return screen_width() * screen_height() * sizeof(uint32_t); +} + +static void *vmem = NULL; +static uint32_t *vgactl_port_base = NULL; + +#ifdef CONFIG_VGA_SHOW_SCREEN +#ifndef CONFIG_TARGET_AM +#include + +static SDL_Renderer *renderer = NULL; +static SDL_Texture *texture = NULL; + +static void init_screen() { + SDL_Window *window = NULL; + char title[128]; + sprintf(title, "%s-NEMU", str(__GUEST_ISA__)); + SDL_Init(SDL_INIT_VIDEO); + SDL_CreateWindowAndRenderer( + SCREEN_W * (MUXDEF(CONFIG_VGA_SIZE_400x300, 2, 1)), + SCREEN_H * (MUXDEF(CONFIG_VGA_SIZE_400x300, 2, 1)), + 0, &window, &renderer); + SDL_SetWindowTitle(window, title); + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STATIC, SCREEN_W, SCREEN_H); + SDL_RenderPresent(renderer); +} + +static inline void update_screen() { + SDL_UpdateTexture(texture, NULL, vmem, SCREEN_W * sizeof(uint32_t)); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); +} +#else +static void init_screen() {} + +static inline void update_screen() { + io_write(AM_GPU_FBDRAW, 0, 0, vmem, screen_width(), screen_height(), true); +} +#endif +#endif + +void vga_update_screen() { + // TODO: call `update_screen()` when the sync register is non-zero, + // then zero out the sync register +} + +void init_vga() { + vgactl_port_base = (uint32_t *)new_space(8); + vgactl_port_base[0] = (screen_width() << 16) | screen_height(); +#ifdef CONFIG_HAS_PORT_IO + add_pio_map ("vgactl", CONFIG_VGA_CTL_PORT, vgactl_port_base, 8, NULL); +#else + add_mmio_map("vgactl", CONFIG_VGA_CTL_MMIO, vgactl_port_base, 8, NULL); +#endif + + vmem = new_space(screen_size()); + add_mmio_map("vmem", CONFIG_FB_ADDR, vmem, screen_size(), NULL); + IFDEF(CONFIG_VGA_SHOW_SCREEN, init_screen()); + IFDEF(CONFIG_VGA_SHOW_SCREEN, memset(vmem, 0, screen_size())); +} diff --git a/nemu/src/engine/filelist.mk b/nemu/src/engine/filelist.mk new file mode 100644 index 0000000..b0d651b --- /dev/null +++ b/nemu/src/engine/filelist.mk @@ -0,0 +1,17 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +INC_PATH += $(NEMU_HOME)/src/engine/$(ENGINE) +DIRS-y += src/engine/$(ENGINE) diff --git a/nemu/src/engine/interpreter/hostcall.c b/nemu/src/engine/interpreter/hostcall.c new file mode 100644 index 0000000..d37d5b5 --- /dev/null +++ b/nemu/src/engine/interpreter/hostcall.c @@ -0,0 +1,51 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include + +void set_nemu_state(int state, vaddr_t pc, int halt_ret) { + difftest_skip_ref(); + nemu_state.state = state; + nemu_state.halt_pc = pc; + nemu_state.halt_ret = halt_ret; +} + +__attribute__((noinline)) +void invalid_inst(vaddr_t thispc) { + uint32_t temp[2]; + vaddr_t pc = thispc; + temp[0] = inst_fetch(&pc, 4); + temp[1] = inst_fetch(&pc, 4); + + uint8_t *p = (uint8_t *)temp; + printf("invalid opcode(PC = " FMT_WORD "):\n" + "\t%02x %02x %02x %02x %02x %02x %02x %02x ...\n" + "\t%08x %08x...\n", + thispc, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], temp[0], temp[1]); + + printf("There are two cases which will trigger this unexpected exception:\n" + "1. The instruction at PC = " FMT_WORD " is not implemented.\n" + "2. Something is implemented incorrectly.\n", thispc); + printf("Find this PC(" FMT_WORD ") in the disassembling result to distinguish which case it is.\n\n", thispc); + printf(ANSI_FMT("If it is the first case, see\n%s\nfor more details.\n\n" + "If it is the second case, remember:\n" + "* The machine is always right!\n" + "* Every line of untested code is always wrong!\n\n", ANSI_FG_RED), isa_logo); + + set_nemu_state(NEMU_ABORT, thispc, -1); +} diff --git a/nemu/src/engine/interpreter/init.c b/nemu/src/engine/interpreter/init.c new file mode 100644 index 0000000..a517c3e --- /dev/null +++ b/nemu/src/engine/interpreter/init.c @@ -0,0 +1,27 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +void sdb_mainloop(); + +void engine_start() { +#ifdef CONFIG_TARGET_AM + cpu_exec(-1); +#else + /* Receive commands from user. */ + sdb_mainloop(); +#endif +} diff --git a/nemu/src/filelist.mk b/nemu/src/filelist.mk new file mode 100644 index 0000000..03e05df --- /dev/null +++ b/nemu/src/filelist.mk @@ -0,0 +1,28 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +SRCS-y += src/nemu-main.c +DIRS-y += src/cpu src/monitor src/utils +DIRS-$(CONFIG_MODE_SYSTEM) += src/memory +DIRS-BLACKLIST-$(CONFIG_TARGET_AM) += src/monitor/sdb + +SHARE = $(if $(CONFIG_TARGET_SHARE),1,0) +LIBS += $(if $(CONFIG_TARGET_NATIVE_ELF),-lreadline -ldl -pie,) + +ifdef mainargs +ASFLAGS += -DBIN_PATH=\"$(mainargs)\" +endif +SRCS-$(CONFIG_TARGET_AM) += src/am-bin.S +.PHONY: src/am-bin.S diff --git a/nemu/src/isa/filelist.mk b/nemu/src/isa/filelist.mk new file mode 100644 index 0000000..18b8ec8 --- /dev/null +++ b/nemu/src/isa/filelist.mk @@ -0,0 +1,17 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +INC_PATH += $(NEMU_HOME)/src/isa/$(GUEST_ISA)/include +DIRS-y += src/isa/$(GUEST_ISA) diff --git a/nemu/src/isa/loongarch32r/difftest/dut.c b/nemu/src/isa/loongarch32r/difftest/dut.c new file mode 100644 index 0000000..c5ebf13 --- /dev/null +++ b/nemu/src/isa/loongarch32r/difftest/dut.c @@ -0,0 +1,25 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include "../local-include/reg.h" + +bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) { + return false; +} + +void isa_difftest_attach() { +} diff --git a/nemu/src/isa/loongarch32r/include/isa-def.h b/nemu/src/isa/loongarch32r/include/isa-def.h new file mode 100644 index 0000000..edb5599 --- /dev/null +++ b/nemu/src/isa/loongarch32r/include/isa-def.h @@ -0,0 +1,35 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __ISA_LOONGARCH32R_H__ +#define __ISA_LOONGARCH32R_H__ + +#include + +typedef struct { + word_t gpr[32]; + vaddr_t pc; +} loongarch32r_CPU_state; + +// decode +typedef struct { + union { + uint32_t val; + } inst; +} loongarch32r_ISADecodeInfo; + +#define isa_mmu_check(vaddr, len, type) (MMU_DIRECT) + +#endif diff --git a/nemu/src/isa/loongarch32r/init.c b/nemu/src/isa/loongarch32r/init.c new file mode 100644 index 0000000..0dc8bec --- /dev/null +++ b/nemu/src/isa/loongarch32r/init.c @@ -0,0 +1,43 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +// this is not consistent with uint8_t +// but it is ok since we do not access the array directly +static const uint32_t img [] = { + 0x1c00000c, // pcaddu12i $t0,0 + 0x29804180, // st.w $zero,$t0,16 + 0x28804184, // ld.w $a0,$t0,16 + 0x002a0000, // break 0 (used as nemu_trap) + 0xdeadbeef, // some data +}; + +static void restart() { + /* Set the initial program counter. */ + cpu.pc = RESET_VECTOR; + + /* The zero register is always 0. */ + cpu.gpr[0] = 0; +} + +void init_isa() { + /* Load built-in image. */ + memcpy(guest_to_host(RESET_VECTOR), img, sizeof(img)); + + /* Initialize this virtual computer system. */ + restart(); +} diff --git a/nemu/src/isa/loongarch32r/inst.c b/nemu/src/isa/loongarch32r/inst.c new file mode 100644 index 0000000..a5457a4 --- /dev/null +++ b/nemu/src/isa/loongarch32r/inst.c @@ -0,0 +1,72 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "local-include/reg.h" +#include +#include +#include + +#define R(i) gpr(i) +#define Mr vaddr_read +#define Mw vaddr_write + +enum { + TYPE_2RI12, TYPE_1RI20, + TYPE_N, // none +}; + +#define src1R() do { *src1 = R(rj); } while (0) +#define simm12() do { *imm = SEXT(BITS(i, 21, 10), 12); } while (0) +#define simm20() do { *imm = SEXT(BITS(i, 24, 5), 20) << 12; } while (0) + +static void decode_operand(Decode *s, int *rd_, word_t *src1, word_t *src2, word_t *imm, int type) { + uint32_t i = s->isa.inst.val; + int rj = BITS(i, 9, 5); + *rd_ = BITS(i, 4, 0); + switch (type) { + case TYPE_1RI20: simm20(); src1R(); break; + case TYPE_2RI12: simm12(); src1R(); break; + } +} + +static int decode_exec(Decode *s) { + int rd = 0; + word_t src1 = 0, src2 = 0, imm = 0; + s->dnpc = s->snpc; + +#define INSTPAT_INST(s) ((s)->isa.inst.val) +#define INSTPAT_MATCH(s, name, type, ... /* execute body */ ) { \ + decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \ + __VA_ARGS__ ; \ +} + + INSTPAT_START(); + INSTPAT("0001110 ????? ????? ????? ????? ?????" , pcaddu12i, 1RI20 , R(rd) = s->pc + imm); + INSTPAT("0010100010 ???????????? ????? ?????" , ld.w , 2RI12 , R(rd) = Mr(src1 + imm, 4)); + INSTPAT("0010100110 ???????????? ????? ?????" , st.w , 2RI12 , Mw(src1 + imm, 4, R(rd))); + + INSTPAT("0000 0000 0010 10100 ????? ????? ?????", break , N , NEMUTRAP(s->pc, R(4))); // R(4) is $a0 + INSTPAT("????????????????? ????? ????? ?????" , inv , N , INV(s->pc)); + INSTPAT_END(); + + R(0) = 0; // reset $zero to 0 + + return 0; +} + +int isa_exec_once(Decode *s) { + s->isa.inst.val = inst_fetch(&s->snpc, 4); + return decode_exec(s); +} diff --git a/nemu/src/isa/loongarch32r/local-include/reg.h b/nemu/src/isa/loongarch32r/local-include/reg.h new file mode 100644 index 0000000..08669bb --- /dev/null +++ b/nemu/src/isa/loongarch32r/local-include/reg.h @@ -0,0 +1,33 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __LOONGARCH32R_REG_H__ +#define __LOONGARCH32R_REG_H__ + +#include + +static inline int check_reg_idx(int idx) { + IFDEF(CONFIG_RT_CHECK, assert(idx >= 0 && idx < 32)); + return idx; +} + +#define gpr(idx) cpu.gpr[check_reg_idx(idx)] + +static inline const char* reg_name(int idx) { + extern const char* regs[]; + return regs[check_reg_idx(idx)]; +} + +#endif diff --git a/nemu/src/isa/loongarch32r/logo.c b/nemu/src/isa/loongarch32r/logo.c new file mode 100644 index 0000000..e6f4093 --- /dev/null +++ b/nemu/src/isa/loongarch32r/logo.c @@ -0,0 +1,16 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +unsigned char isa_logo[] = "loongarch32r manual"; diff --git a/nemu/src/isa/loongarch32r/reg.c b/nemu/src/isa/loongarch32r/reg.c new file mode 100644 index 0000000..d593198 --- /dev/null +++ b/nemu/src/isa/loongarch32r/reg.c @@ -0,0 +1,31 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include "local-include/reg.h" + +const char *regs[] = { + "$0", "ra", "tp", "sp", "a0", "a1", "a2", "a3", + "a4", "a5", "a6", "a7", "t0", "t1", "t2", "t3", + "t4", "t5", "t6", "t7", "t8", "rs", "fp", "s0", + "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8" +}; + +void isa_reg_display() { +} + +word_t isa_reg_str2val(const char *s, bool *success) { + return 0; +} diff --git a/nemu/src/isa/loongarch32r/system/intr.c b/nemu/src/isa/loongarch32r/system/intr.c new file mode 100644 index 0000000..3619b59 --- /dev/null +++ b/nemu/src/isa/loongarch32r/system/intr.c @@ -0,0 +1,28 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +word_t isa_raise_intr(word_t NO, vaddr_t epc) { + /* TODO: Trigger an interrupt/exception with ``NO''. + * Then return the address of the interrupt/exception vector. + */ + + return 0; +} + +word_t isa_query_intr() { + return INTR_EMPTY; +} diff --git a/nemu/src/isa/loongarch32r/system/mmu.c b/nemu/src/isa/loongarch32r/system/mmu.c new file mode 100644 index 0000000..55f8872 --- /dev/null +++ b/nemu/src/isa/loongarch32r/system/mmu.c @@ -0,0 +1,22 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include + +paddr_t isa_mmu_translate(vaddr_t vaddr, int len, int type) { + return MEM_RET_FAIL; +} diff --git a/nemu/src/isa/mips32/difftest/dut.c b/nemu/src/isa/mips32/difftest/dut.c new file mode 100644 index 0000000..c5ebf13 --- /dev/null +++ b/nemu/src/isa/mips32/difftest/dut.c @@ -0,0 +1,25 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include "../local-include/reg.h" + +bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) { + return false; +} + +void isa_difftest_attach() { +} diff --git a/nemu/src/isa/mips32/include/isa-def.h b/nemu/src/isa/mips32/include/isa-def.h new file mode 100644 index 0000000..9cab152 --- /dev/null +++ b/nemu/src/isa/mips32/include/isa-def.h @@ -0,0 +1,36 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __ISA_MIPS32_H__ +#define __ISA_MIPS32_H__ + +#include + +typedef struct { + word_t gpr[32]; + word_t pad[5]; + vaddr_t pc; +} mips32_CPU_state; + +// decode +typedef struct { + union { + uint32_t val; + } inst; +} mips32_ISADecodeInfo; + +#define isa_mmu_check(vaddr, len, type) (MMU_DIRECT) + +#endif diff --git a/nemu/src/isa/mips32/init.c b/nemu/src/isa/mips32/init.c new file mode 100644 index 0000000..026760b --- /dev/null +++ b/nemu/src/isa/mips32/init.c @@ -0,0 +1,42 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +// this is not consistent with uint8_t +// but it is ok since we do not access the array directly +static const uint32_t img [] = { + 0x3c048000, // lui a0, 0x8000 + 0xac800000, // sw zero, 0(a0) + 0x8c820000, // lw v0,0(a0) + 0x7000003f, // sdbbp (used as nemu_trap) +}; + +static void restart() { + /* Set the initial program counter. */ + cpu.pc = RESET_VECTOR; + + /* The zero register is always 0. */ + cpu.gpr[0] = 0; +} + +void init_isa() { + /* Load built-in image. */ + memcpy(guest_to_host(RESET_VECTOR), img, sizeof(img)); + + /* Initialize this virtual computer system. */ + restart(); +} diff --git a/nemu/src/isa/mips32/inst.c b/nemu/src/isa/mips32/inst.c new file mode 100644 index 0000000..581510c --- /dev/null +++ b/nemu/src/isa/mips32/inst.c @@ -0,0 +1,74 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "local-include/reg.h" +#include +#include +#include + +#define R(i) gpr(i) +#define Mr vaddr_read +#define Mw vaddr_write + +enum { + TYPE_I, TYPE_U, + TYPE_N, // none +}; + +#define src1R() do { *src1 = R(rs); } while (0) +#define src2R() do { *src2 = R(rt); } while (0) +#define immI() do { *imm = SEXT(BITS(i, 15, 0), 16); } while(0) +#define immU() do { *imm = BITS(i, 15, 0); } while(0) + +static void decode_operand(Decode *s, int *rd, word_t *src1, word_t *src2, word_t *imm, int type) { + uint32_t i = s->isa.inst.val; + int rt = BITS(i, 20, 16); + int rs = BITS(i, 25, 21); + *rd = (type == TYPE_U || type == TYPE_I) ? rt : BITS(i, 15, 11); + switch (type) { + case TYPE_I: src1R(); immI(); break; + case TYPE_U: src1R(); immU(); break; + } +} + +static int decode_exec(Decode *s) { + int rd = 0; + word_t src1 = 0, src2 = 0, imm = 0; + s->dnpc = s->snpc; + +#define INSTPAT_INST(s) ((s)->isa.inst.val) +#define INSTPAT_MATCH(s, name, type, ... /* execute body */ ) { \ + decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \ + __VA_ARGS__ ; \ +} + + INSTPAT_START(); + INSTPAT("001111 ????? ????? ????? ????? ??????", lui , U, R(rd) = imm << 16); + INSTPAT("100011 ????? ????? ????? ????? ??????", lw , I, R(rd) = Mr(src1 + imm, 4)); + INSTPAT("101011 ????? ????? ????? ????? ??????", sw , I, Mw(src1 + imm, 4, R(rd))); + + INSTPAT("011100 ????? ????? ????? ????? 111111", sdbbp , N, NEMUTRAP(s->pc, R(2))); // R(2) is $v0; + INSTPAT("?????? ????? ????? ????? ????? ??????", inv , N, INV(s->pc)); + INSTPAT_END(); + + R(0) = 0; // reset $zero to 0 + + return 0; +} + +int isa_exec_once(Decode *s) { + s->isa.inst.val = inst_fetch(&s->snpc, 4); + return decode_exec(s); +} diff --git a/nemu/src/isa/mips32/local-include/reg.h b/nemu/src/isa/mips32/local-include/reg.h new file mode 100644 index 0000000..b1c22ed --- /dev/null +++ b/nemu/src/isa/mips32/local-include/reg.h @@ -0,0 +1,33 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __MIPS32_REG_H__ +#define __MIPS32_REG_H__ + +#include + +static inline int check_reg_idx(int idx) { + IFDEF(CONFIG_RT_CHECK, assert(idx >= 0 && idx < 32)); + return idx; +} + +#define gpr(idx) cpu.gpr[check_reg_idx(idx)] + +static inline const char* reg_name(int idx) { + extern const char* regs[]; + return regs[check_reg_idx(idx)]; +} + +#endif diff --git a/nemu/src/isa/mips32/logo.c b/nemu/src/isa/mips32/logo.c new file mode 100644 index 0000000..9fedb02 --- /dev/null +++ b/nemu/src/isa/mips32/logo.c @@ -0,0 +1,78 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +// refer to http://www.patorjk.com/software/taag/#p=display&f=Big&t=Type%20Something%20 + +/* + _ ____ ___ __ __ _ + (_) |___ \__ \ | \/ | | | + _ __ ___ _ _ __ ___ __) | ) | | \ / | __ _ _ __ _ _ __ _| | + | '_ ` _ \| | '_ \/ __||__ < / / | |\/| |/ _` | '_ \| | | |/ _` | | + | | | | | | | |_) \__ \___) / /_ | | | | (_| | | | | |_| | (_| | | + |_| |_| |_|_| .__/|___/____/____| |_| |_|\__,_|_| |_|\__,_|\__,_|_| + | | + |_| + +*/ + +unsigned char isa_logo[] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x5f, 0x5f, 0x5f, 0x5f, 0x20, 0x5f, 0x5f, 0x5f, 0x20, 0x20, 0x20, 0x20, + 0x5f, 0x5f, 0x20, 0x20, 0x5f, 0x5f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x20, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x5f, 0x29, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7c, 0x5f, 0x5f, + 0x5f, 0x20, 0x5c, 0x5f, 0x5f, 0x20, 0x5c, 0x20, 0x20, 0x7c, 0x20, 0x20, + 0x5c, 0x2f, 0x20, 0x20, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7c, 0x20, 0x7c, 0x0a, 0x20, 0x20, 0x5f, 0x20, + 0x5f, 0x5f, 0x20, 0x5f, 0x5f, 0x5f, 0x20, 0x20, 0x5f, 0x20, 0x5f, 0x20, + 0x5f, 0x5f, 0x20, 0x20, 0x5f, 0x5f, 0x5f, 0x20, 0x20, 0x5f, 0x5f, 0x29, + 0x20, 0x7c, 0x20, 0x29, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x5c, 0x20, 0x20, + 0x2f, 0x20, 0x7c, 0x20, 0x5f, 0x5f, 0x20, 0x5f, 0x20, 0x5f, 0x20, 0x5f, + 0x5f, 0x20, 0x20, 0x5f, 0x20, 0x20, 0x20, 0x5f, 0x20, 0x20, 0x5f, 0x5f, + 0x20, 0x5f, 0x7c, 0x20, 0x7c, 0x0a, 0x20, 0x7c, 0x20, 0x27, 0x5f, 0x20, + 0x60, 0x20, 0x5f, 0x20, 0x5c, 0x7c, 0x20, 0x7c, 0x20, 0x27, 0x5f, 0x20, + 0x5c, 0x2f, 0x20, 0x5f, 0x5f, 0x7c, 0x7c, 0x5f, 0x5f, 0x20, 0x3c, 0x20, + 0x2f, 0x20, 0x2f, 0x20, 0x20, 0x7c, 0x20, 0x7c, 0x5c, 0x2f, 0x7c, 0x20, + 0x7c, 0x2f, 0x20, 0x5f, 0x60, 0x20, 0x7c, 0x20, 0x27, 0x5f, 0x20, 0x5c, + 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x2f, 0x20, 0x5f, 0x60, 0x20, + 0x7c, 0x20, 0x7c, 0x0a, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, + 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x5f, 0x29, 0x20, 0x5c, + 0x5f, 0x5f, 0x20, 0x5c, 0x5f, 0x5f, 0x5f, 0x29, 0x20, 0x2f, 0x20, 0x2f, + 0x5f, 0x20, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x20, 0x7c, 0x20, 0x7c, 0x20, + 0x28, 0x5f, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, + 0x7c, 0x5f, 0x7c, 0x20, 0x7c, 0x20, 0x28, 0x5f, 0x7c, 0x20, 0x7c, 0x20, + 0x7c, 0x0a, 0x20, 0x7c, 0x5f, 0x7c, 0x20, 0x7c, 0x5f, 0x7c, 0x20, 0x7c, + 0x5f, 0x7c, 0x5f, 0x7c, 0x20, 0x2e, 0x5f, 0x5f, 0x2f, 0x7c, 0x5f, 0x5f, + 0x5f, 0x2f, 0x5f, 0x5f, 0x5f, 0x5f, 0x2f, 0x5f, 0x5f, 0x5f, 0x5f, 0x7c, + 0x20, 0x7c, 0x5f, 0x7c, 0x20, 0x20, 0x7c, 0x5f, 0x7c, 0x5c, 0x5f, 0x5f, + 0x2c, 0x5f, 0x7c, 0x5f, 0x7c, 0x20, 0x7c, 0x5f, 0x7c, 0x5c, 0x5f, 0x5f, + 0x2c, 0x5f, 0x7c, 0x5c, 0x5f, 0x5f, 0x2c, 0x5f, 0x7c, 0x5f, 0x7c, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7c, + 0x5f, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, '\0' +}; diff --git a/nemu/src/isa/mips32/reg.c b/nemu/src/isa/mips32/reg.c new file mode 100644 index 0000000..0ed73ac --- /dev/null +++ b/nemu/src/isa/mips32/reg.c @@ -0,0 +1,31 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include "local-include/reg.h" + +const char *regs[] = { + "$0", "at", "v0", "v1", "a0", "a1", "a2", "a3", + "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", + "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", + "t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra" +}; + +void isa_reg_display() { +} + +word_t isa_reg_str2val(const char *s, bool *success) { + return 0; +} diff --git a/nemu/src/isa/mips32/system/intr.c b/nemu/src/isa/mips32/system/intr.c new file mode 100644 index 0000000..3619b59 --- /dev/null +++ b/nemu/src/isa/mips32/system/intr.c @@ -0,0 +1,28 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +word_t isa_raise_intr(word_t NO, vaddr_t epc) { + /* TODO: Trigger an interrupt/exception with ``NO''. + * Then return the address of the interrupt/exception vector. + */ + + return 0; +} + +word_t isa_query_intr() { + return INTR_EMPTY; +} diff --git a/nemu/src/isa/mips32/system/mmu.c b/nemu/src/isa/mips32/system/mmu.c new file mode 100644 index 0000000..55f8872 --- /dev/null +++ b/nemu/src/isa/mips32/system/mmu.c @@ -0,0 +1,22 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include + +paddr_t isa_mmu_translate(vaddr_t vaddr, int len, int type) { + return MEM_RET_FAIL; +} diff --git a/nemu/src/isa/riscv32/Kconfig b/nemu/src/isa/riscv32/Kconfig new file mode 100644 index 0000000..ff00b9e --- /dev/null +++ b/nemu/src/isa/riscv32/Kconfig @@ -0,0 +1,10 @@ +menu "ISA-dependent Options for riscv" + +config RV64 + bool "64-bit RISC-V architecture" + default n + +config RVE + bool "Use E extension" + default n +endmenu diff --git a/nemu/src/isa/riscv32/difftest/dut.c b/nemu/src/isa/riscv32/difftest/dut.c new file mode 100644 index 0000000..c5ebf13 --- /dev/null +++ b/nemu/src/isa/riscv32/difftest/dut.c @@ -0,0 +1,25 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include "../local-include/reg.h" + +bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc) { + return false; +} + +void isa_difftest_attach() { +} diff --git a/nemu/src/isa/riscv32/include/isa-def.h b/nemu/src/isa/riscv32/include/isa-def.h new file mode 100644 index 0000000..dd0105c --- /dev/null +++ b/nemu/src/isa/riscv32/include/isa-def.h @@ -0,0 +1,35 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __ISA_RISCV_H__ +#define __ISA_RISCV_H__ + +#include + +typedef struct { + word_t gpr[MUXDEF(CONFIG_RVE, 16, 32)]; + vaddr_t pc; +} MUXDEF(CONFIG_RV64, riscv64_CPU_state, riscv32_CPU_state); + +// decode +typedef struct { + union { + uint32_t val; + } inst; +} MUXDEF(CONFIG_RV64, riscv64_ISADecodeInfo, riscv32_ISADecodeInfo); + +#define isa_mmu_check(vaddr, len, type) (MMU_DIRECT) + +#endif diff --git a/nemu/src/isa/riscv32/init.c b/nemu/src/isa/riscv32/init.c new file mode 100644 index 0000000..ace9d44 --- /dev/null +++ b/nemu/src/isa/riscv32/init.c @@ -0,0 +1,43 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +// this is not consistent with uint8_t +// but it is ok since we do not access the array directly +static const uint32_t img [] = { + 0x00000297, // auipc t0,0 + 0x00028823, // sb zero,16(t0) + 0x0102c503, // lbu a0,16(t0) + 0x00100073, // ebreak (used as nemu_trap) + 0xdeadbeef, // some data +}; + +static void restart() { + /* Set the initial program counter. */ + cpu.pc = RESET_VECTOR; + + /* The zero register is always 0. */ + cpu.gpr[0] = 0; +} + +void init_isa() { + /* Load built-in image. */ + memcpy(guest_to_host(RESET_VECTOR), img, sizeof(img)); + + /* Initialize this virtual computer system. */ + restart(); +} diff --git a/nemu/src/isa/riscv32/inst.c b/nemu/src/isa/riscv32/inst.c new file mode 100644 index 0000000..cb0c44e --- /dev/null +++ b/nemu/src/isa/riscv32/inst.c @@ -0,0 +1,76 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "local-include/reg.h" +#include +#include +#include + +#define R(i) gpr(i) +#define Mr vaddr_read +#define Mw vaddr_write + +enum { + TYPE_I, TYPE_U, TYPE_S, + TYPE_N, // none +}; + +#define src1R() do { *src1 = R(rs1); } while (0) +#define src2R() do { *src2 = R(rs2); } while (0) +#define immI() do { *imm = SEXT(BITS(i, 31, 20), 12); } while(0) +#define immU() do { *imm = SEXT(BITS(i, 31, 12), 20) << 12; } while(0) +#define immS() do { *imm = (SEXT(BITS(i, 31, 25), 7) << 5) | BITS(i, 11, 7); } while(0) + +static void decode_operand(Decode *s, int *rd, word_t *src1, word_t *src2, word_t *imm, int type) { + uint32_t i = s->isa.inst.val; + int rs1 = BITS(i, 19, 15); + int rs2 = BITS(i, 24, 20); + *rd = BITS(i, 11, 7); + switch (type) { + case TYPE_I: src1R(); immI(); break; + case TYPE_U: immU(); break; + case TYPE_S: src1R(); src2R(); immS(); break; + } +} + +static int decode_exec(Decode *s) { + int rd = 0; + word_t src1 = 0, src2 = 0, imm = 0; + s->dnpc = s->snpc; + +#define INSTPAT_INST(s) ((s)->isa.inst.val) +#define INSTPAT_MATCH(s, name, type, ... /* execute body */ ) { \ + decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \ + __VA_ARGS__ ; \ +} + + INSTPAT_START(); + INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm); + INSTPAT("??????? ????? ????? 100 ????? 00000 11", lbu , I, R(rd) = Mr(src1 + imm, 1)); + INSTPAT("??????? ????? ????? 000 ????? 01000 11", sb , S, Mw(src1 + imm, 1, src2)); + + INSTPAT("0000000 00001 00000 000 00000 11100 11", ebreak , N, NEMUTRAP(s->pc, R(10))); // R(10) is $a0 + INSTPAT("??????? ????? ????? ??? ????? ????? ??", inv , N, INV(s->pc)); + INSTPAT_END(); + + R(0) = 0; // reset $zero to 0 + + return 0; +} + +int isa_exec_once(Decode *s) { + s->isa.inst.val = inst_fetch(&s->snpc, 4); + return decode_exec(s); +} diff --git a/nemu/src/isa/riscv32/local-include/reg.h b/nemu/src/isa/riscv32/local-include/reg.h new file mode 100644 index 0000000..049f9c2 --- /dev/null +++ b/nemu/src/isa/riscv32/local-include/reg.h @@ -0,0 +1,33 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __RISCV_REG_H__ +#define __RISCV_REG_H__ + +#include + +static inline int check_reg_idx(int idx) { + IFDEF(CONFIG_RT_CHECK, assert(idx >= 0 && idx < MUXDEF(CONFIG_RVE, 16, 32))); + return idx; +} + +#define gpr(idx) (cpu.gpr[check_reg_idx(idx)]) + +static inline const char* reg_name(int idx) { + extern const char* regs[]; + return regs[check_reg_idx(idx)]; +} + +#endif diff --git a/nemu/src/isa/riscv32/logo.c b/nemu/src/isa/riscv32/logo.c new file mode 100644 index 0000000..d693d43 --- /dev/null +++ b/nemu/src/isa/riscv32/logo.c @@ -0,0 +1,63 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +// refer to http://www.patorjk.com/software/taag/#p=display&f=Big&t=Type%20Something%20 + +/* + _ __ __ _ + (_) | \/ | | | + _ __ _ ___ ___ ________ __ | \ / | __ _ _ __ _ _ __ _| | + | '__| / __|/ __|______\ \ / / | |\/| |/ _` | '_ \| | | |/ _` | | + | | | \__ \ (__ \ V / | | | | (_| | | | | |_| | (_| | | + |_| |_|___/\___| \_/ |_| |_|\__,_|_| |_|\__,_|\__,_|_| + +*/ + +unsigned char isa_logo[] = { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x5f, 0x20, + 0x20, 0x5f, 0x5f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x28, 0x5f, 0x29, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7c, 0x20, 0x20, 0x5c, 0x2f, 0x20, 0x20, 0x7c, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7c, 0x20, + 0x7c, 0x0a, 0x20, 0x20, 0x5f, 0x20, 0x5f, 0x5f, 0x20, 0x5f, 0x20, 0x5f, + 0x5f, 0x5f, 0x20, 0x20, 0x5f, 0x5f, 0x5f, 0x20, 0x5f, 0x5f, 0x5f, 0x5f, + 0x5f, 0x5f, 0x5f, 0x5f, 0x20, 0x20, 0x20, 0x5f, 0x5f, 0x20, 0x7c, 0x20, + 0x5c, 0x20, 0x20, 0x2f, 0x20, 0x7c, 0x20, 0x5f, 0x5f, 0x20, 0x5f, 0x20, + 0x5f, 0x20, 0x5f, 0x5f, 0x20, 0x20, 0x5f, 0x20, 0x20, 0x20, 0x5f, 0x20, + 0x20, 0x5f, 0x5f, 0x20, 0x5f, 0x7c, 0x20, 0x7c, 0x0a, 0x20, 0x7c, 0x20, + 0x27, 0x5f, 0x5f, 0x7c, 0x20, 0x2f, 0x20, 0x5f, 0x5f, 0x7c, 0x2f, 0x20, + 0x5f, 0x5f, 0x7c, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5c, 0x20, 0x5c, + 0x20, 0x2f, 0x20, 0x2f, 0x20, 0x7c, 0x20, 0x7c, 0x5c, 0x2f, 0x7c, 0x20, + 0x7c, 0x2f, 0x20, 0x5f, 0x60, 0x20, 0x7c, 0x20, 0x27, 0x5f, 0x20, 0x5c, + 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x2f, 0x20, 0x5f, 0x60, 0x20, + 0x7c, 0x20, 0x7c, 0x0a, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x20, 0x7c, 0x20, + 0x5c, 0x5f, 0x5f, 0x20, 0x5c, 0x20, 0x28, 0x5f, 0x5f, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5c, 0x20, 0x56, 0x20, 0x2f, 0x20, 0x20, + 0x7c, 0x20, 0x7c, 0x20, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x28, 0x5f, 0x7c, + 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x5f, 0x7c, + 0x20, 0x7c, 0x20, 0x28, 0x5f, 0x7c, 0x20, 0x7c, 0x20, 0x7c, 0x0a, 0x20, + 0x7c, 0x5f, 0x7c, 0x20, 0x20, 0x7c, 0x5f, 0x7c, 0x5f, 0x5f, 0x5f, 0x2f, + 0x5c, 0x5f, 0x5f, 0x5f, 0x7c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5c, 0x5f, 0x2f, 0x20, 0x20, 0x20, 0x7c, 0x5f, 0x7c, 0x20, 0x20, + 0x7c, 0x5f, 0x7c, 0x5c, 0x5f, 0x5f, 0x2c, 0x5f, 0x7c, 0x5f, 0x7c, 0x20, + 0x7c, 0x5f, 0x7c, 0x5c, 0x5f, 0x5f, 0x2c, 0x5f, 0x7c, 0x5c, 0x5f, 0x5f, + 0x2c, 0x5f, 0x7c, 0x5f, 0x7c, 0x0a, '\0' /* Termination Character is indispensable! */ +}; diff --git a/nemu/src/isa/riscv32/reg.c b/nemu/src/isa/riscv32/reg.c new file mode 100644 index 0000000..5dac4fd --- /dev/null +++ b/nemu/src/isa/riscv32/reg.c @@ -0,0 +1,31 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include "local-include/reg.h" + +const char *regs[] = { + "$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2", + "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", + "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", + "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" +}; + +void isa_reg_display() { +} + +word_t isa_reg_str2val(const char *s, bool *success) { + return 0; +} diff --git a/nemu/src/isa/riscv32/system/intr.c b/nemu/src/isa/riscv32/system/intr.c new file mode 100644 index 0000000..3619b59 --- /dev/null +++ b/nemu/src/isa/riscv32/system/intr.c @@ -0,0 +1,28 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +word_t isa_raise_intr(word_t NO, vaddr_t epc) { + /* TODO: Trigger an interrupt/exception with ``NO''. + * Then return the address of the interrupt/exception vector. + */ + + return 0; +} + +word_t isa_query_intr() { + return INTR_EMPTY; +} diff --git a/nemu/src/isa/riscv32/system/mmu.c b/nemu/src/isa/riscv32/system/mmu.c new file mode 100644 index 0000000..71834a6 --- /dev/null +++ b/nemu/src/isa/riscv32/system/mmu.c @@ -0,0 +1,22 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include + +paddr_t isa_mmu_translate(vaddr_t vaddr, int len, int type) { + return MEM_RET_FAIL; +} diff --git a/nemu/src/memory/Kconfig b/nemu/src/memory/Kconfig new file mode 100644 index 0000000..ed94441 --- /dev/null +++ b/nemu/src/memory/Kconfig @@ -0,0 +1,34 @@ +menu "Memory Configuration" + +config MBASE + hex "Memory base address" + default 0x0 if ISA_x86 + default 0x80000000 + +config MSIZE + hex "Memory size" + default 0x8000000 + +config PC_RESET_OFFSET + hex "Offset of reset vector from the base of memory" + default 0x100000 if ISA_x86 + default 0 + +choice + prompt "Physical memory definition" + default PMEM_GARRAY +config PMEM_MALLOC + bool "Using malloc()" +config PMEM_GARRAY + depends on !TARGET_AM + bool "Using global array" +endchoice + +config MEM_RANDOM + depends on MODE_SYSTEM && !DIFFTEST && !TARGET_AM + bool "Initialize the memory with random values" + default y + help + This may help to find undefined behaviors. + +endmenu #MEMORY diff --git a/nemu/src/memory/paddr.c b/nemu/src/memory/paddr.c new file mode 100644 index 0000000..ee30e70 --- /dev/null +++ b/nemu/src/memory/paddr.c @@ -0,0 +1,64 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include + +#if defined(CONFIG_PMEM_MALLOC) +static uint8_t *pmem = NULL; +#else // CONFIG_PMEM_GARRAY +static uint8_t pmem[CONFIG_MSIZE] PG_ALIGN = {}; +#endif + +uint8_t* guest_to_host(paddr_t paddr) { return pmem + paddr - CONFIG_MBASE; } +paddr_t host_to_guest(uint8_t *haddr) { return haddr - pmem + CONFIG_MBASE; } + +static word_t pmem_read(paddr_t addr, int len) { + word_t ret = host_read(guest_to_host(addr), len); + return ret; +} + +static void pmem_write(paddr_t addr, int len, word_t data) { + host_write(guest_to_host(addr), len, data); +} + +static void out_of_bound(paddr_t addr) { + panic("address = " FMT_PADDR " is out of bound of pmem [" FMT_PADDR ", " FMT_PADDR "] at pc = " FMT_WORD, + addr, PMEM_LEFT, PMEM_RIGHT, cpu.pc); +} + +void init_mem() { +#if defined(CONFIG_PMEM_MALLOC) + pmem = malloc(CONFIG_MSIZE); + assert(pmem); +#endif + IFDEF(CONFIG_MEM_RANDOM, memset(pmem, rand(), CONFIG_MSIZE)); + Log("physical memory area [" FMT_PADDR ", " FMT_PADDR "]", PMEM_LEFT, PMEM_RIGHT); +} + +word_t paddr_read(paddr_t addr, int len) { + if (likely(in_pmem(addr))) return pmem_read(addr, len); + IFDEF(CONFIG_DEVICE, return mmio_read(addr, len)); + out_of_bound(addr); + return 0; +} + +void paddr_write(paddr_t addr, int len, word_t data) { + if (likely(in_pmem(addr))) { pmem_write(addr, len, data); return; } + IFDEF(CONFIG_DEVICE, mmio_write(addr, len, data); return); + out_of_bound(addr); +} diff --git a/nemu/src/memory/vaddr.c b/nemu/src/memory/vaddr.c new file mode 100644 index 0000000..52c2963 --- /dev/null +++ b/nemu/src/memory/vaddr.c @@ -0,0 +1,29 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +word_t vaddr_ifetch(vaddr_t addr, int len) { + return paddr_read(addr, len); +} + +word_t vaddr_read(vaddr_t addr, int len) { + return paddr_read(addr, len); +} + +void vaddr_write(vaddr_t addr, int len, word_t data) { + paddr_write(addr, len, data); +} diff --git a/nemu/src/monitor/monitor.c b/nemu/src/monitor/monitor.c new file mode 100644 index 0000000..acaed1b --- /dev/null +++ b/nemu/src/monitor/monitor.c @@ -0,0 +1,162 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include + +void init_rand(); +void init_log(const char *log_file); +void init_mem(); +void init_difftest(char *ref_so_file, long img_size, int port); +void init_device(); +void init_sdb(); +void init_disasm(const char *triple); + +static void welcome() { + Log("Trace: %s", MUXDEF(CONFIG_TRACE, ANSI_FMT("ON", ANSI_FG_GREEN), ANSI_FMT("OFF", ANSI_FG_RED))); + IFDEF(CONFIG_TRACE, Log("If trace is enabled, a log file will be generated " + "to record the trace. This may lead to a large log file. " + "If it is not necessary, you can disable it in menuconfig")); + Log("Build time: %s, %s", __TIME__, __DATE__); + printf("Welcome to %s-NEMU!\n", ANSI_FMT(str(__GUEST_ISA__), ANSI_FG_YELLOW ANSI_BG_RED)); + printf("For help, type \"help\"\n"); + Log("Exercise: Please remove me in the source code and compile NEMU again."); + assert(0); +} + +#ifndef CONFIG_TARGET_AM +#include + +void sdb_set_batch_mode(); + +static char *log_file = NULL; +static char *diff_so_file = NULL; +static char *img_file = NULL; +static int difftest_port = 1234; + +static long load_img() { + if (img_file == NULL) { + Log("No image is given. Use the default build-in image."); + return 4096; // built-in image size + } + + FILE *fp = fopen(img_file, "rb"); + Assert(fp, "Can not open '%s'", img_file); + + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + + Log("The image is %s, size = %ld", img_file, size); + + fseek(fp, 0, SEEK_SET); + int ret = fread(guest_to_host(RESET_VECTOR), size, 1, fp); + assert(ret == 1); + + fclose(fp); + return size; +} + +static int parse_args(int argc, char *argv[]) { + const struct option table[] = { + {"batch" , no_argument , NULL, 'b'}, + {"log" , required_argument, NULL, 'l'}, + {"diff" , required_argument, NULL, 'd'}, + {"port" , required_argument, NULL, 'p'}, + {"help" , no_argument , NULL, 'h'}, + {0 , 0 , NULL, 0 }, + }; + int o; + while ( (o = getopt_long(argc, argv, "-bhl:d:p:", table, NULL)) != -1) { + switch (o) { + case 'b': sdb_set_batch_mode(); break; + case 'p': sscanf(optarg, "%d", &difftest_port); break; + case 'l': log_file = optarg; break; + case 'd': diff_so_file = optarg; break; + case 1: img_file = optarg; return 0; + default: + printf("Usage: %s [OPTION...] IMAGE [args]\n\n", argv[0]); + printf("\t-b,--batch run with batch mode\n"); + printf("\t-l,--log=FILE output log to FILE\n"); + printf("\t-d,--diff=REF_SO run DiffTest with reference REF_SO\n"); + printf("\t-p,--port=PORT run DiffTest with port PORT\n"); + printf("\n"); + exit(0); + } + } + return 0; +} + +void init_monitor(int argc, char *argv[]) { + /* Perform some global initialization. */ + + /* Parse arguments. */ + parse_args(argc, argv); + + /* Set random seed. */ + init_rand(); + + /* Open the log file. */ + init_log(log_file); + + /* Initialize memory. */ + init_mem(); + + /* Initialize devices. */ + IFDEF(CONFIG_DEVICE, init_device()); + + /* Perform ISA dependent initialization. */ + init_isa(); + + /* Load the image to memory. This will overwrite the built-in image. */ + long img_size = load_img(); + + /* Initialize differential testing. */ + init_difftest(diff_so_file, img_size, difftest_port); + + /* Initialize the simple debugger. */ + init_sdb(); + +#ifndef CONFIG_ISA_loongarch32r + IFDEF(CONFIG_ITRACE, init_disasm( + MUXDEF(CONFIG_ISA_x86, "i686", + MUXDEF(CONFIG_ISA_mips32, "mipsel", + MUXDEF(CONFIG_ISA_riscv, + MUXDEF(CONFIG_RV64, "riscv64", + "riscv32"), + "bad"))) "-pc-linux-gnu" + )); +#endif + + /* Display welcome message. */ + welcome(); +} +#else // CONFIG_TARGET_AM +static long load_img() { + extern char bin_start, bin_end; + size_t size = &bin_end - &bin_start; + Log("img size = %ld", size); + memcpy(guest_to_host(RESET_VECTOR), &bin_start, size); + return size; +} + +void am_init_monitor() { + init_rand(); + init_mem(); + init_isa(); + load_img(); + IFDEF(CONFIG_DEVICE, init_device()); + welcome(); +} +#endif diff --git a/nemu/src/monitor/sdb/expr.c b/nemu/src/monitor/sdb/expr.c new file mode 100644 index 0000000..cdcdafc --- /dev/null +++ b/nemu/src/monitor/sdb/expr.c @@ -0,0 +1,125 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +/* We use the POSIX regex functions to process regular expressions. + * Type 'man regex' for more information about POSIX regex functions. + */ +#include + +enum { + TK_NOTYPE = 256, TK_EQ, + + /* TODO: Add more token types */ + +}; + +static struct rule { + const char *regex; + int token_type; +} rules[] = { + + /* TODO: Add more rules. + * Pay attention to the precedence level of different rules. + */ + + {" +", TK_NOTYPE}, // spaces + {"\\+", '+'}, // plus + {"==", TK_EQ}, // equal +}; + +#define NR_REGEX ARRLEN(rules) + +static regex_t re[NR_REGEX] = {}; + +/* Rules are used for many times. + * Therefore we compile them only once before any usage. + */ +void init_regex() { + int i; + char error_msg[128]; + int ret; + + for (i = 0; i < NR_REGEX; i ++) { + ret = regcomp(&re[i], rules[i].regex, REG_EXTENDED); + if (ret != 0) { + regerror(ret, &re[i], error_msg, 128); + panic("regex compilation failed: %s\n%s", error_msg, rules[i].regex); + } + } +} + +typedef struct token { + int type; + char str[32]; +} Token; + +static Token tokens[32] __attribute__((used)) = {}; +static int nr_token __attribute__((used)) = 0; + +static bool make_token(char *e) { + int position = 0; + int i; + regmatch_t pmatch; + + nr_token = 0; + + while (e[position] != '\0') { + /* Try all rules one by one. */ + for (i = 0; i < NR_REGEX; i ++) { + if (regexec(&re[i], e + position, 1, &pmatch, 0) == 0 && pmatch.rm_so == 0) { + char *substr_start = e + position; + int substr_len = pmatch.rm_eo; + + Log("match rules[%d] = \"%s\" at position %d with len %d: %.*s", + i, rules[i].regex, position, substr_len, substr_len, substr_start); + + position += substr_len; + + /* TODO: Now a new token is recognized with rules[i]. Add codes + * to record the token in the array `tokens'. For certain types + * of tokens, some extra actions should be performed. + */ + + switch (rules[i].token_type) { + default: TODO(); + } + + break; + } + } + + if (i == NR_REGEX) { + printf("no match at position %d\n%s\n%*.s^\n", position, e, position, ""); + return false; + } + } + + return true; +} + + +word_t expr(char *e, bool *success) { + if (!make_token(e)) { + *success = false; + return 0; + } + + /* TODO: Insert codes to evaluate the expression. */ + TODO(); + + return 0; +} diff --git a/nemu/src/monitor/sdb/sdb.c b/nemu/src/monitor/sdb/sdb.c new file mode 100644 index 0000000..725bce8 --- /dev/null +++ b/nemu/src/monitor/sdb/sdb.c @@ -0,0 +1,143 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include +#include "sdb.h" + +static int is_batch_mode = false; + +void init_regex(); +void init_wp_pool(); + +/* We use the `readline' library to provide more flexibility to read from stdin. */ +static char* rl_gets() { + static char *line_read = NULL; + + if (line_read) { + free(line_read); + line_read = NULL; + } + + line_read = readline("(nemu) "); + + if (line_read && *line_read) { + add_history(line_read); + } + + return line_read; +} + +static int cmd_c(char *args) { + cpu_exec(-1); + return 0; +} + + +static int cmd_q(char *args) { + return -1; +} + +static int cmd_help(char *args); + +static struct { + const char *name; + const char *description; + int (*handler) (char *); +} cmd_table [] = { + { "help", "Display information about all supported commands", cmd_help }, + { "c", "Continue the execution of the program", cmd_c }, + { "q", "Exit NEMU", cmd_q }, + + /* TODO: Add more commands */ + +}; + +#define NR_CMD ARRLEN(cmd_table) + +static int cmd_help(char *args) { + /* extract the first argument */ + char *arg = strtok(NULL, " "); + int i; + + if (arg == NULL) { + /* no argument given */ + for (i = 0; i < NR_CMD; i ++) { + printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description); + } + } + else { + for (i = 0; i < NR_CMD; i ++) { + if (strcmp(arg, cmd_table[i].name) == 0) { + printf("%s - %s\n", cmd_table[i].name, cmd_table[i].description); + return 0; + } + } + printf("Unknown command '%s'\n", arg); + } + return 0; +} + +void sdb_set_batch_mode() { + is_batch_mode = true; +} + +void sdb_mainloop() { + if (is_batch_mode) { + cmd_c(NULL); + return; + } + + for (char *str; (str = rl_gets()) != NULL; ) { + char *str_end = str + strlen(str); + + /* extract the first token as the command */ + char *cmd = strtok(str, " "); + if (cmd == NULL) { continue; } + + /* treat the remaining string as the arguments, + * which may need further parsing + */ + char *args = cmd + strlen(cmd) + 1; + if (args >= str_end) { + args = NULL; + } + +#ifdef CONFIG_DEVICE + extern void sdl_clear_event_queue(); + sdl_clear_event_queue(); +#endif + + int i; + for (i = 0; i < NR_CMD; i ++) { + if (strcmp(cmd, cmd_table[i].name) == 0) { + if (cmd_table[i].handler(args) < 0) { return; } + break; + } + } + + if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); } + } +} + +void init_sdb() { + /* Compile the regular expressions. */ + init_regex(); + + /* Initialize the watchpoint pool. */ + init_wp_pool(); +} diff --git a/nemu/src/monitor/sdb/sdb.h b/nemu/src/monitor/sdb/sdb.h new file mode 100644 index 0000000..d2800d1 --- /dev/null +++ b/nemu/src/monitor/sdb/sdb.h @@ -0,0 +1,23 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __SDB_H__ +#define __SDB_H__ + +#include + +word_t expr(char *e, bool *success); + +#endif diff --git a/nemu/src/monitor/sdb/watchpoint.c b/nemu/src/monitor/sdb/watchpoint.c new file mode 100644 index 0000000..922c8f4 --- /dev/null +++ b/nemu/src/monitor/sdb/watchpoint.c @@ -0,0 +1,43 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "sdb.h" + +#define NR_WP 32 + +typedef struct watchpoint { + int NO; + struct watchpoint *next; + + /* TODO: Add more members if necessary */ + +} WP; + +static WP wp_pool[NR_WP] = {}; +static WP *head = NULL, *free_ = NULL; + +void init_wp_pool() { + int i; + for (i = 0; i < NR_WP; i ++) { + wp_pool[i].NO = i; + wp_pool[i].next = (i == NR_WP - 1 ? NULL : &wp_pool[i + 1]); + } + + head = NULL; + free_ = wp_pool; +} + +/* TODO: Implement the functionality of watchpoint */ + diff --git a/nemu/src/nemu-main.c b/nemu/src/nemu-main.c new file mode 100644 index 0000000..5ad4d5d --- /dev/null +++ b/nemu/src/nemu-main.c @@ -0,0 +1,35 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +void init_monitor(int, char *[]); +void am_init_monitor(); +void engine_start(); +int is_exit_status_bad(); + +int main(int argc, char *argv[]) { + /* Initialize the monitor. */ +#ifdef CONFIG_TARGET_AM + am_init_monitor(); +#else + init_monitor(argc, argv); +#endif + + /* Start engine. */ + engine_start(); + + return is_exit_status_bad(); +} diff --git a/nemu/src/utils/disasm.cc b/nemu/src/utils/disasm.cc new file mode 100644 index 0000000..7d98c2e --- /dev/null +++ b/nemu/src/utils/disasm.cc @@ -0,0 +1,109 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDisassembler/MCDisassembler.h" +#include "llvm/MC/MCInstPrinter.h" +#if LLVM_VERSION_MAJOR >= 14 +#include "llvm/MC/TargetRegistry.h" +#if LLVM_VERSION_MAJOR >= 15 +#include "llvm/MC/MCSubtargetInfo.h" +#endif +#else +#include "llvm/Support/TargetRegistry.h" +#endif +#include "llvm/Support/TargetSelect.h" + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#if LLVM_VERSION_MAJOR < 11 +#error Please use LLVM with major version >= 11 +#endif + +using namespace llvm; + +static llvm::MCDisassembler *gDisassembler = nullptr; +static llvm::MCSubtargetInfo *gSTI = nullptr; +static llvm::MCInstPrinter *gIP = nullptr; + +extern "C" void init_disasm(const char *triple) { + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllDisassemblers(); + + std::string errstr; + std::string gTriple(triple); + + llvm::MCInstrInfo *gMII = nullptr; + llvm::MCRegisterInfo *gMRI = nullptr; + auto target = llvm::TargetRegistry::lookupTarget(gTriple, errstr); + if (!target) { + llvm::errs() << "Can't find target for " << gTriple << ": " << errstr << "\n"; + assert(0); + } + + MCTargetOptions MCOptions; + gSTI = target->createMCSubtargetInfo(gTriple, "", ""); + std::string isa = target->getName(); + if (isa == "riscv32" || isa == "riscv64") { + gSTI->ApplyFeatureFlag("+m"); + gSTI->ApplyFeatureFlag("+a"); + gSTI->ApplyFeatureFlag("+c"); + gSTI->ApplyFeatureFlag("+f"); + gSTI->ApplyFeatureFlag("+d"); + } + gMII = target->createMCInstrInfo(); + gMRI = target->createMCRegInfo(gTriple); + auto AsmInfo = target->createMCAsmInfo(*gMRI, gTriple, MCOptions); +#if LLVM_VERSION_MAJOR >= 13 + auto llvmTripleTwine = Twine(triple); + auto llvmtriple = llvm::Triple(llvmTripleTwine); + auto Ctx = new llvm::MCContext(llvmtriple,AsmInfo, gMRI, nullptr); +#else + auto Ctx = new llvm::MCContext(AsmInfo, gMRI, nullptr); +#endif + gDisassembler = target->createMCDisassembler(*gSTI, *Ctx); + gIP = target->createMCInstPrinter(llvm::Triple(gTriple), + AsmInfo->getAssemblerDialect(), *AsmInfo, *gMII, *gMRI); + gIP->setPrintImmHex(true); + gIP->setPrintBranchImmAsAddress(true); + if (isa == "riscv32" || isa == "riscv64") + gIP->applyTargetSpecificCLOption("no-aliases"); +} + +extern "C" void disassemble(char *str, int size, uint64_t pc, uint8_t *code, int nbyte) { + MCInst inst; + llvm::ArrayRef arr(code, nbyte); + uint64_t dummy_size = 0; + gDisassembler->getInstruction(inst, dummy_size, arr, pc, llvm::nulls()); + + std::string s; + raw_string_ostream os(s); + gIP->printInst(&inst, pc, "", *gSTI, os); + + int skip = s.find_first_not_of('\t'); + const char *p = s.c_str() + skip; + assert((int)s.length() - skip < size); + strcpy(str, p); +} diff --git a/nemu/src/utils/filelist.mk b/nemu/src/utils/filelist.mk new file mode 100644 index 0000000..bcd69c5 --- /dev/null +++ b/nemu/src/utils/filelist.mk @@ -0,0 +1,20 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +ifneq ($(CONFIG_ITRACE)$(CONFIG_IQUEUE),) +CXXSRC = src/utils/disasm.cc +CXXFLAGS += $(shell llvm-config --cxxflags) -fPIE +LIBS += $(shell llvm-config --libs) +endif diff --git a/nemu/src/utils/log.c b/nemu/src/utils/log.c new file mode 100644 index 0000000..a9bb9a7 --- /dev/null +++ b/nemu/src/utils/log.c @@ -0,0 +1,37 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +extern uint64_t g_nr_guest_inst; + +#ifndef CONFIG_TARGET_AM +FILE *log_fp = NULL; + +void init_log(const char *log_file) { + log_fp = stdout; + if (log_file != NULL) { + FILE *fp = fopen(log_file, "w"); + Assert(fp, "Can not open '%s'", log_file); + log_fp = fp; + } + Log("Log is written to %s", log_file ? log_file : "stdout"); +} + +bool log_enable() { + return MUXDEF(CONFIG_TRACE, (g_nr_guest_inst >= CONFIG_TRACE_START) && + (g_nr_guest_inst <= CONFIG_TRACE_END), false); +} +#endif diff --git a/nemu/src/utils/state.c b/nemu/src/utils/state.c new file mode 100644 index 0000000..6dfafb0 --- /dev/null +++ b/nemu/src/utils/state.c @@ -0,0 +1,24 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include + +NEMUState nemu_state = { .state = NEMU_STOP }; + +int is_exit_status_bad() { + int good = (nemu_state.state == NEMU_END && nemu_state.halt_ret == 0) || + (nemu_state.state == NEMU_QUIT); + return !good; +} diff --git a/nemu/src/utils/timer.c b/nemu/src/utils/timer.c new file mode 100644 index 0000000..cfeaa09 --- /dev/null +++ b/nemu/src/utils/timer.c @@ -0,0 +1,49 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include MUXDEF(CONFIG_TIMER_GETTIMEOFDAY, , ) + +IFDEF(CONFIG_TIMER_CLOCK_GETTIME, + static_assert(CLOCKS_PER_SEC == 1000000, "CLOCKS_PER_SEC != 1000000")); +IFDEF(CONFIG_TIMER_CLOCK_GETTIME, + static_assert(sizeof(clock_t) == 8, "sizeof(clock_t) != 8")); + +static uint64_t boot_time = 0; + +static uint64_t get_time_internal() { +#if defined(CONFIG_TARGET_AM) + uint64_t us = io_read(AM_TIMER_UPTIME).us; +#elif defined(CONFIG_TIMER_GETTIMEOFDAY) + struct timeval now; + gettimeofday(&now, NULL); + uint64_t us = now.tv_sec * 1000000 + now.tv_usec; +#else + struct timespec now; + clock_gettime(CLOCK_MONOTONIC_COARSE, &now); + uint64_t us = now.tv_sec * 1000000 + now.tv_nsec / 1000; +#endif + return us; +} + +uint64_t get_time() { + if (boot_time == 0) boot_time = get_time_internal(); + uint64_t now = get_time_internal(); + return now - boot_time; +} + +void init_rand() { + srand(get_time_internal()); +} diff --git a/nemu/tools/difftest.mk b/nemu/tools/difftest.mk new file mode 100644 index 0000000..f670d9e --- /dev/null +++ b/nemu/tools/difftest.mk @@ -0,0 +1,28 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +ifdef CONFIG_DIFFTEST +DIFF_REF_PATH = $(NEMU_HOME)/$(call remove_quote,$(CONFIG_DIFFTEST_REF_PATH)) +DIFF_REF_SO = $(DIFF_REF_PATH)/build/$(GUEST_ISA)-$(call remove_quote,$(CONFIG_DIFFTEST_REF_NAME))-so +MKFLAGS = GUEST_ISA=$(GUEST_ISA) SHARE=1 ENGINE=interpreter +ARGS_DIFF = --diff=$(DIFF_REF_SO) + +ifndef CONFIG_DIFFTEST_REF_NEMU +$(DIFF_REF_SO): + $(MAKE) -s -C $(DIFF_REF_PATH) $(MKFLAGS) +endif + +.PHONY: $(DIFF_REF_SO) +endif diff --git a/nemu/tools/fixdep/Makefile b/nemu/tools/fixdep/Makefile new file mode 100644 index 0000000..b8641b0 --- /dev/null +++ b/nemu/tools/fixdep/Makefile @@ -0,0 +1,3 @@ +NAME = fixdep +SRCS = fixdep.c +include $(NEMU_HOME)/scripts/build.mk diff --git a/nemu/tools/fixdep/fixdep.c b/nemu/tools/fixdep/fixdep.c new file mode 100644 index 0000000..d985405 --- /dev/null +++ b/nemu/tools/fixdep/fixdep.c @@ -0,0 +1,404 @@ +/* + * "Optimize" a list of dependencies as spit out by gcc -MD + * for the kernel build + * =========================================================================== + * + * Author Kai Germaschewski + * Copyright 2002 by Kai Germaschewski + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * + * Introduction: + * + * gcc produces a very nice and correct list of dependencies which + * tells make when to remake a file. + * + * To use this list as-is however has the drawback that virtually + * every file in the kernel includes autoconf.h. + * + * If the user re-runs make *config, autoconf.h will be + * regenerated. make notices that and will rebuild every file which + * includes autoconf.h, i.e. basically all files. This is extremely + * annoying if the user just changed CONFIG_HIS_DRIVER from n to m. + * + * So we play the same trick that "mkdep" played before. We replace + * the dependency on autoconf.h by a dependency on every config + * option which is mentioned in any of the listed prerequisites. + * + * kconfig populates a tree in include/config/ with an empty file + * for each config symbol and when the configuration is updated + * the files representing changed config options are touched + * which then let make pick up the changes and the files that use + * the config symbols are rebuilt. + * + * So if the user changes his CONFIG_HIS_DRIVER option, only the objects + * which depend on "include/config/his/driver.h" will be rebuilt, + * so most likely only his driver ;-) + * + * The idea above dates, by the way, back to Michael E Chastain, AFAIK. + * + * So to get dependencies right, there are two issues: + * o if any of the files the compiler read changed, we need to rebuild + * o if the command line given to the compile the file changed, we + * better rebuild as well. + * + * The former is handled by using the -MD output, the later by saving + * the command line used to compile the old object and comparing it + * to the one we would now use. + * + * Again, also this idea is pretty old and has been discussed on + * kbuild-devel a long time ago. I don't have a sensibly working + * internet connection right now, so I rather don't mention names + * without double checking. + * + * This code here has been based partially based on mkdep.c, which + * says the following about its history: + * + * Copyright abandoned, Michael Chastain, . + * This is a C version of syncdep.pl by Werner Almesberger. + * + * + * It is invoked as + * + * fixdep + * + * and will read the dependency file + * + * The transformed dependency snipped is written to stdout. + * + * It first generates a line + * + * cmd_ = + * + * and then basically copies the ..d file to stdout, in the + * process filtering out the dependency on autoconf.h and adding + * dependencies on include/config/my/option.h for every + * CONFIG_MY_OPTION encountered in any of the prerequisites. + * + * We don't even try to really parse the header files, but + * merely grep, i.e. if CONFIG_FOO is mentioned in a comment, it will + * be picked up as well. It's not a problem with respect to + * correctness, since that can only give too many dependencies, thus + * we cannot miss a rebuild. Since people tend to not mention totally + * unrelated CONFIG_ options all over the place, it's not an + * efficiency problem either. + * + * (Note: it'd be easy to port over the complete mkdep state machine, + * but I don't think the added complexity is worth it) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(void) +{ + fprintf(stderr, "Usage: fixdep \n"); + exit(1); +} + +/* + * In the intended usage of this program, the stdout is redirected to .*.cmd + * files. The return value of printf() and putchar() must be checked to catch + * any error, e.g. "No space left on device". + */ +static void xprintf(const char *format, ...) +{ + va_list ap; + int ret; + + va_start(ap, format); + ret = vprintf(format, ap); + if (ret < 0) { + perror("fixdep"); + exit(1); + } + va_end(ap); +} + +static void xputchar(int c) +{ + int ret; + + ret = putchar(c); + if (ret == EOF) { + perror("fixdep"); + exit(1); + } +} + +/* + * Print out a dependency path from a symbol name + */ +static void print_dep(const char *m, int slen, const char *dir) +{ + int c, prev_c = '/', i; + + xprintf(" $(wildcard %s/", dir); + for (i = 0; i < slen; i++) { + c = m[i]; + if (c == '_') + c = '/'; + else + c = tolower(c); + if (c != '/' || prev_c != '/') + xputchar(c); + prev_c = c; + } + xprintf(".h) \\\n"); +} + +struct item { + struct item *next; + unsigned int len; + unsigned int hash; + char name[]; +}; + +#define HASHSZ 256 +static struct item *hashtab[HASHSZ]; + +static unsigned int strhash(const char *str, unsigned int sz) +{ + /* fnv32 hash */ + unsigned int i, hash = 2166136261U; + + for (i = 0; i < sz; i++) + hash = (hash ^ str[i]) * 0x01000193; + return hash; +} + +/* + * Lookup a value in the configuration string. + */ +static int is_defined_config(const char *name, int len, unsigned int hash) +{ + struct item *aux; + + for (aux = hashtab[hash % HASHSZ]; aux; aux = aux->next) { + if (aux->hash == hash && aux->len == len && + memcmp(aux->name, name, len) == 0) + return 1; + } + return 0; +} + +/* + * Add a new value to the configuration string. + */ +static void define_config(const char *name, int len, unsigned int hash) +{ + struct item *aux = malloc(sizeof(*aux) + len); + + if (!aux) { + perror("fixdep:malloc"); + exit(1); + } + memcpy(aux->name, name, len); + aux->len = len; + aux->hash = hash; + aux->next = hashtab[hash % HASHSZ]; + hashtab[hash % HASHSZ] = aux; +} + +/* + * Record the use of a CONFIG_* word. + */ +static void use_config(const char *m, int slen) +{ + unsigned int hash = strhash(m, slen); + + if (is_defined_config(m, slen, hash)) + return; + + define_config(m, slen, hash); + print_dep(m, slen, "include/config"); +} + +/* test if s ends in sub */ +static int str_ends_with(const char *s, int slen, const char *sub) +{ + int sublen = strlen(sub); + + if (sublen > slen) + return 0; + + return !memcmp(s + slen - sublen, sub, sublen); +} + +static void parse_config_file(const char *p) +{ + const char *q, *r; + const char *start = p; + + while ((p = strstr(p, "CONFIG_"))) { + if (p > start && (isalnum(p[-1]) || p[-1] == '_')) { + p += 7; + continue; + } + p += 7; + q = p; + while (isalnum(*q) || *q == '_') + q++; + if (str_ends_with(p, q - p, "_MODULE")) + r = q - 7; + else + r = q; + if (r > p) + use_config(p, r - p); + p = q; + } +} + +static void *read_file(const char *filename) +{ + struct stat st; + int fd; + char *buf; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "fixdep: error opening file: "); + perror(filename); + exit(2); + } + if (fstat(fd, &st) < 0) { + fprintf(stderr, "fixdep: error fstat'ing file: "); + perror(filename); + exit(2); + } + buf = malloc(st.st_size + 1); + if (!buf) { + perror("fixdep: malloc"); + exit(2); + } + if (read(fd, buf, st.st_size) != st.st_size) { + perror("fixdep: read"); + exit(2); + } + buf[st.st_size] = '\0'; + close(fd); + + return buf; +} + +/* Ignore certain dependencies */ +static int is_ignored_file(const char *s, int len) +{ + return str_ends_with(s, len, "include/generated/autoconf.h") || + str_ends_with(s, len, "include/generated/autoksyms.h"); +} + +/* + * Important: The below generated source_foo.o and deps_foo.o variable + * assignments are parsed not only by make, but also by the rather simple + * parser in scripts/mod/sumversion.c. + */ +static void parse_dep_file(char *m, const char *target) +{ + char *p; + int is_last, is_target; + int saw_any_target = 0; + int is_first_dep = 0; + void *buf; + + while (1) { + /* Skip any "white space" */ + while (*m == ' ' || *m == '\\' || *m == '\n') + m++; + + if (!*m) + break; + + /* Find next "white space" */ + p = m; + while (*p && *p != ' ' && *p != '\\' && *p != '\n') + p++; + is_last = (*p == '\0'); + /* Is the token we found a target name? */ + is_target = (*(p-1) == ':'); + /* Don't write any target names into the dependency file */ + if (is_target) { + /* The /next/ file is the first dependency */ + is_first_dep = 1; + } else if (!is_ignored_file(m, p - m)) { + *p = '\0'; + + /* + * Do not list the source file as dependency, so that + * kbuild is not confused if a .c file is rewritten + * into .S or vice versa. Storing it in source_* is + * needed for modpost to compute srcversions. + */ + if (is_first_dep) { + /* + * If processing the concatenation of multiple + * dependency files, only process the first + * target name, which will be the original + * source name, and ignore any other target + * names, which will be intermediate temporary + * files. + */ + if (!saw_any_target) { + saw_any_target = 1; + xprintf("source_%s := %s\n\n", + target, m); + xprintf("deps_%s := \\\n", target); + } + is_first_dep = 0; + } else { + xprintf(" %s \\\n", m); + } + + buf = read_file(m); + parse_config_file(buf); + free(buf); + } + + if (is_last) + break; + + /* + * Start searching for next token immediately after the first + * "whitespace" character that follows this token. + */ + m = p + 1; + } + + if (!saw_any_target) { + fprintf(stderr, "fixdep: parse error; no targets found\n"); + exit(1); + } + + xprintf("\n%s: $(deps_%s)\n\n", target, target); + xprintf("$(deps_%s):\n", target); +} + +int main(int argc, char *argv[]) +{ + const char *depfile, *target, *cmdline; + void *buf; + + if (argc != 4) + usage(); + + depfile = argv[1]; + target = argv[2]; + cmdline = argv[3]; + + xprintf("cmd_%s := %s\n\n", target, cmdline); + + buf = read_file(depfile); + parse_dep_file(buf, target); + free(buf); + + return 0; +} diff --git a/nemu/tools/gen-expr/.gitignore b/nemu/tools/gen-expr/.gitignore new file mode 100644 index 0000000..90b7a4a --- /dev/null +++ b/nemu/tools/gen-expr/.gitignore @@ -0,0 +1 @@ +.code.c diff --git a/nemu/tools/gen-expr/Makefile b/nemu/tools/gen-expr/Makefile new file mode 100644 index 0000000..296983c --- /dev/null +++ b/nemu/tools/gen-expr/Makefile @@ -0,0 +1,18 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +NAME = gen-expr +SRCS = gen-expr.c +include $(NEMU_HOME)/scripts/build.mk diff --git a/nemu/tools/gen-expr/gen-expr.c b/nemu/tools/gen-expr/gen-expr.c new file mode 100644 index 0000000..af294e3 --- /dev/null +++ b/nemu/tools/gen-expr/gen-expr.c @@ -0,0 +1,69 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include +#include +#include +#include +#include +#include + +// this should be enough +static char buf[65536] = {}; +static char code_buf[65536 + 128] = {}; // a little larger than `buf` +static char *code_format = +"#include \n" +"int main() { " +" unsigned result = %s; " +" printf(\"%%u\", result); " +" return 0; " +"}"; + +static void gen_rand_expr() { + buf[0] = '\0'; +} + +int main(int argc, char *argv[]) { + int seed = time(0); + srand(seed); + int loop = 1; + if (argc > 1) { + sscanf(argv[1], "%d", &loop); + } + int i; + for (i = 0; i < loop; i ++) { + gen_rand_expr(); + + sprintf(code_buf, code_format, buf); + + FILE *fp = fopen("/tmp/.code.c", "w"); + assert(fp != NULL); + fputs(code_buf, fp); + fclose(fp); + + int ret = system("gcc /tmp/.code.c -o /tmp/.expr"); + if (ret != 0) continue; + + fp = popen("/tmp/.expr", "r"); + assert(fp != NULL); + + int result; + ret = fscanf(fp, "%d", &result); + pclose(fp); + + printf("%u %s\n", result, buf); + } + return 0; +} diff --git a/nemu/tools/kconfig/.gitignore b/nemu/tools/kconfig/.gitignore new file mode 100644 index 0000000..14779be --- /dev/null +++ b/nemu/tools/kconfig/.gitignore @@ -0,0 +1,3 @@ +build/ +!lexer.l +!parser.y diff --git a/nemu/tools/kconfig/Makefile b/nemu/tools/kconfig/Makefile new file mode 100644 index 0000000..d853482 --- /dev/null +++ b/nemu/tools/kconfig/Makefile @@ -0,0 +1,40 @@ +NAME = conf +obj := build +SRCS += confdata.c expr.c preprocess.c symbol.c util.c +SRCS += $(obj)/lexer.lex.c $(obj)/parser.tab.c +CC = gcc +CFLAGS += -DYYDEBUG +INC_PATH += . +DISTRO = $(shell cat /etc/os-release | grep PRETTY_NAME | sed 's/PRETTY_NAME=//') + +ifeq ($(DISTRO),"Gentoo Linux") +LIBS += -ltinfo +endif + +ifeq ($(NAME),conf) +SRCS += conf.c +else ifeq ($(NAME),mconf) +SRCS += mconf.c $(shell find lxdialog/ -name "*.c") +LIBS += -lncurses +else +$(error bad target=$(NAME)) +endif + +include $(NEMU_HOME)/scripts/build.mk + +$(obj)/lexer.lex.o: $(obj)/parser.tab.h +$(obj)/lexer.lex.c: lexer.l $(obj)/parser.tab.h + @echo + LEX $@ + @flex -o $@ $< + +$(obj)/parser.tab.c $(obj)/parser.tab.h: parser.y + @echo + YACC $@ + @bison -v $< --defines=$(obj)/parser.tab.h -o $(obj)/parser.tab.c + +conf: + @$(MAKE) -s + +mconf: + @$(MAKE) -s NAME=mconf + +.PHONY: conf mconf diff --git a/nemu/tools/kconfig/conf.c b/nemu/tools/kconfig/conf.c new file mode 100644 index 0000000..f6e548b --- /dev/null +++ b/nemu/tools/kconfig/conf.c @@ -0,0 +1,727 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lkc.h" + +static void conf(struct menu *menu); +static void check_conf(struct menu *menu); + +enum input_mode { + oldaskconfig, + syncconfig, + oldconfig, + allnoconfig, + allyesconfig, + allmodconfig, + alldefconfig, + randconfig, + defconfig, + savedefconfig, + listnewconfig, + helpnewconfig, + olddefconfig, + yes2modconfig, + mod2yesconfig, +}; +static enum input_mode input_mode = oldaskconfig; + +static int indent = 1; +static int tty_stdio; +static int sync_kconfig; +static int conf_cnt; +static char line[PATH_MAX]; +static struct menu *rootEntry; + +static void print_help(struct menu *menu) +{ + struct gstr help = str_new(); + + menu_get_ext_help(menu, &help); + + printf("\n%s\n", str_get(&help)); + str_free(&help); +} + +static void strip(char *str) +{ + char *p = str; + int l; + + while ((isspace(*p))) + p++; + l = strlen(p); + if (p != str) + memmove(str, p, l + 1); + if (!l) + return; + p = str + l - 1; + while ((isspace(*p))) + *p-- = 0; +} + +/* Helper function to facilitate fgets() by Jean Sacren. */ +static void xfgets(char *str, int size, FILE *in) +{ + if (!fgets(str, size, in)) + fprintf(stderr, "\nError in reading or end of file.\n"); + + if (!tty_stdio) + printf("%s", str); +} + +static int conf_askvalue(struct symbol *sym, const char *def) +{ + enum symbol_type type = sym_get_type(sym); + + if (!sym_has_value(sym)) + printf("(NEW) "); + + line[0] = '\n'; + line[1] = 0; + + if (!sym_is_changeable(sym)) { + printf("%s\n", def); + line[0] = '\n'; + line[1] = 0; + return 0; + } + + switch (input_mode) { + case oldconfig: + case syncconfig: + if (sym_has_value(sym)) { + printf("%s\n", def); + return 0; + } + /* fall through */ + case oldaskconfig: + fflush(stdout); + xfgets(line, sizeof(line), stdin); + return 1; + default: + break; + } + + switch (type) { + case S_INT: + case S_HEX: + case S_STRING: + printf("%s\n", def); + return 1; + default: + ; + } + printf("%s", line); + return 1; +} + +static int conf_string(struct menu *menu) +{ + struct symbol *sym = menu->sym; + const char *def; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + printf("(%s) ", sym->name); + def = sym_get_string_value(sym); + if (sym_get_string_value(sym)) + printf("[%s] ", def); + if (!conf_askvalue(sym, def)) + return 0; + switch (line[0]) { + case '\n': + break; + case '?': + /* print help */ + if (line[1] == '\n') { + print_help(menu); + def = NULL; + break; + } + /* fall through */ + default: + line[strlen(line)-1] = 0; + def = line; + } + if (def && sym_set_string_value(sym, def)) + return 0; + } +} + +static int conf_sym(struct menu *menu) +{ + struct symbol *sym = menu->sym; + tristate oldval, newval; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + if (sym->name) + printf("(%s) ", sym->name); + putchar('['); + oldval = sym_get_tristate_value(sym); + switch (oldval) { + case no: + putchar('N'); + break; + case mod: + putchar('M'); + break; + case yes: + putchar('Y'); + break; + } + if (oldval != no && sym_tristate_within_range(sym, no)) + printf("/n"); + if (oldval != mod && sym_tristate_within_range(sym, mod)) + printf("/m"); + if (oldval != yes && sym_tristate_within_range(sym, yes)) + printf("/y"); + printf("/?] "); + if (!conf_askvalue(sym, sym_get_string_value(sym))) + return 0; + strip(line); + + switch (line[0]) { + case 'n': + case 'N': + newval = no; + if (!line[1] || !strcmp(&line[1], "o")) + break; + continue; + case 'm': + case 'M': + newval = mod; + if (!line[1]) + break; + continue; + case 'y': + case 'Y': + newval = yes; + if (!line[1] || !strcmp(&line[1], "es")) + break; + continue; + case 0: + newval = oldval; + break; + case '?': + goto help; + default: + continue; + } + if (sym_set_tristate_value(sym, newval)) + return 0; +help: + print_help(menu); + } +} + +static int conf_choice(struct menu *menu) +{ + struct symbol *sym, *def_sym; + struct menu *child; + bool is_new; + + sym = menu->sym; + is_new = !sym_has_value(sym); + if (sym_is_changeable(sym)) { + conf_sym(menu); + sym_calc_value(sym); + switch (sym_get_tristate_value(sym)) { + case no: + return 1; + case mod: + return 0; + case yes: + break; + } + } else { + switch (sym_get_tristate_value(sym)) { + case no: + return 1; + case mod: + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + return 0; + case yes: + break; + } + } + + while (1) { + int cnt, def; + + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + def_sym = sym_get_choice_value(sym); + cnt = def = 0; + line[0] = 0; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + if (!child->sym) { + printf("%*c %s\n", indent, '*', menu_get_prompt(child)); + continue; + } + cnt++; + if (child->sym == def_sym) { + def = cnt; + printf("%*c", indent, '>'); + } else + printf("%*c", indent, ' '); + printf(" %d. %s", cnt, menu_get_prompt(child)); + if (child->sym->name) + printf(" (%s)", child->sym->name); + if (!sym_has_value(child->sym)) + printf(" (NEW)"); + printf("\n"); + } + printf("%*schoice", indent - 1, ""); + if (cnt == 1) { + printf("[1]: 1\n"); + goto conf_childs; + } + printf("[1-%d?]: ", cnt); + switch (input_mode) { + case oldconfig: + case syncconfig: + if (!is_new) { + cnt = def; + printf("%d\n", cnt); + break; + } + /* fall through */ + case oldaskconfig: + fflush(stdout); + xfgets(line, sizeof(line), stdin); + strip(line); + if (line[0] == '?') { + print_help(menu); + continue; + } + if (!line[0]) + cnt = def; + else if (isdigit(line[0])) + cnt = atoi(line); + else + continue; + break; + default: + break; + } + + conf_childs: + for (child = menu->list; child; child = child->next) { + if (!child->sym || !menu_is_visible(child)) + continue; + if (!--cnt) + break; + } + if (!child) + continue; + if (line[0] && line[strlen(line) - 1] == '?') { + print_help(child); + continue; + } + sym_set_choice_value(sym, child->sym); + for (child = child->list; child; child = child->next) { + indent += 2; + conf(child); + indent -= 2; + } + return 1; + } +} + +static void conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + prop = menu->prompt; + if (prop) { + const char *prompt; + + switch (prop->type) { + case P_MENU: + /* + * Except in oldaskconfig mode, we show only menus that + * contain new symbols. + */ + if (input_mode != oldaskconfig && rootEntry != menu) { + check_conf(menu); + return; + } + /* fall through */ + case P_COMMENT: + prompt = menu_get_prompt(menu); + if (prompt) + printf("%*c\n%*c %s\n%*c\n", + indent, '*', + indent, '*', prompt, + indent, '*'); + default: + ; + } + } + + if (!sym) + goto conf_childs; + + if (sym_is_choice(sym)) { + conf_choice(menu); + if (sym->curr.tri != mod) + return; + goto conf_childs; + } + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + conf_string(menu); + break; + default: + conf_sym(menu); + break; + } + +conf_childs: + if (sym) + indent += 2; + for (child = menu->list; child; child = child->next) + conf(child); + if (sym) + indent -= 2; +} + +static void check_conf(struct menu *menu) +{ + struct symbol *sym; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + if (sym && !sym_has_value(sym)) { + if (sym_is_changeable(sym) || + (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) { + if (input_mode == listnewconfig) { + if (sym->name) { + const char *str; + + if (sym->type == S_STRING) { + str = sym_get_string_value(sym); + str = sym_escape_string_value(str); + printf("%s%s=%s\n", CONFIG_, sym->name, str); + free((void *)str); + } else { + str = sym_get_string_value(sym); + printf("%s%s=%s\n", CONFIG_, sym->name, str); + } + } + } else if (input_mode == helpnewconfig) { + printf("-----\n"); + print_help(menu); + printf("-----\n"); + + } else { + if (!conf_cnt++) + printf("*\n* Restart config...\n*\n"); + rootEntry = menu_get_parent_menu(menu); + conf(rootEntry); + } + } + } + + for (child = menu->list; child; child = child->next) + check_conf(child); +} + +static struct option long_opts[] = { + {"oldaskconfig", no_argument, NULL, oldaskconfig}, + {"oldconfig", no_argument, NULL, oldconfig}, + {"syncconfig", no_argument, NULL, syncconfig}, + {"defconfig", required_argument, NULL, defconfig}, + {"savedefconfig", required_argument, NULL, savedefconfig}, + {"allnoconfig", no_argument, NULL, allnoconfig}, + {"allyesconfig", no_argument, NULL, allyesconfig}, + {"allmodconfig", no_argument, NULL, allmodconfig}, + {"alldefconfig", no_argument, NULL, alldefconfig}, + {"randconfig", no_argument, NULL, randconfig}, + {"listnewconfig", no_argument, NULL, listnewconfig}, + {"helpnewconfig", no_argument, NULL, helpnewconfig}, + {"olddefconfig", no_argument, NULL, olddefconfig}, + {"yes2modconfig", no_argument, NULL, yes2modconfig}, + {"mod2yesconfig", no_argument, NULL, mod2yesconfig}, + {NULL, 0, NULL, 0} +}; + +static void conf_usage(const char *progname) +{ + + printf("Usage: %s [-s] [option] \n", progname); + printf("[option] is _one_ of the following:\n"); + printf(" --listnewconfig List new options\n"); + printf(" --helpnewconfig List new options and help text\n"); + printf(" --oldaskconfig Start a new configuration using a line-oriented program\n"); + printf(" --oldconfig Update a configuration using a provided .config as base\n"); + printf(" --syncconfig Similar to oldconfig but generates configuration in\n" + " include/{generated/,config/}\n"); + printf(" --olddefconfig Same as oldconfig but sets new symbols to their default value\n"); + printf(" --defconfig New config with default defined in \n"); + printf(" --savedefconfig Save the minimal current configuration to \n"); + printf(" --allnoconfig New config where all options are answered with no\n"); + printf(" --allyesconfig New config where all options are answered with yes\n"); + printf(" --allmodconfig New config where all options are answered with mod\n"); + printf(" --alldefconfig New config with all symbols set to default\n"); + printf(" --randconfig New config with random answer to all options\n"); + printf(" --yes2modconfig Change answers from yes to mod if possible\n"); + printf(" --mod2yesconfig Change answers from mod to yes if possible\n"); +} + +int main(int ac, char **av) +{ + const char *progname = av[0]; + int opt; + const char *name, *defconfig_file = NULL /* gcc uninit */; + int no_conf_write = 0; + + tty_stdio = isatty(0) && isatty(1); + + while ((opt = getopt_long(ac, av, "s", long_opts, NULL)) != -1) { + if (opt == 's') { + conf_set_message_callback(NULL); + continue; + } + input_mode = (enum input_mode)opt; + switch (opt) { + case syncconfig: + /* + * syncconfig is invoked during the build stage. + * Suppress distracting "configuration written to ..." + */ + conf_set_message_callback(NULL); + sync_kconfig = 1; + break; + case defconfig: + case savedefconfig: + defconfig_file = optarg; + break; + case randconfig: + { + struct timeval now; + unsigned int seed; + char *seed_env; + + /* + * Use microseconds derived seed, + * compensate for systems where it may be zero + */ + gettimeofday(&now, NULL); + seed = (unsigned int)((now.tv_sec + 1) * (now.tv_usec + 1)); + + seed_env = getenv("KCONFIG_SEED"); + if( seed_env && *seed_env ) { + char *endp; + int tmp = (int)strtol(seed_env, &endp, 0); + if (*endp == '\0') { + seed = tmp; + } + } + fprintf( stderr, "KCONFIG_SEED=0x%X\n", seed ); + srand(seed); + break; + } + case oldaskconfig: + case oldconfig: + case allnoconfig: + case allyesconfig: + case allmodconfig: + case alldefconfig: + case listnewconfig: + case helpnewconfig: + case olddefconfig: + case yes2modconfig: + case mod2yesconfig: + break; + case '?': + conf_usage(progname); + exit(1); + break; + } + } + if (ac == optind) { + fprintf(stderr, "%s: Kconfig file missing\n", av[0]); + conf_usage(progname); + exit(1); + } + name = av[optind]; + conf_parse(name); + //zconfdump(stdout); + + switch (input_mode) { + case defconfig: + if (conf_read(defconfig_file)) { + fprintf(stderr, + "***\n" + "*** Can't find default configuration \"%s\"!\n" + "***\n", + defconfig_file); + exit(1); + } + break; + case savedefconfig: + case syncconfig: + case oldaskconfig: + case oldconfig: + case listnewconfig: + case helpnewconfig: + case olddefconfig: + case yes2modconfig: + case mod2yesconfig: + conf_read(NULL); + break; + case allnoconfig: + case allyesconfig: + case allmodconfig: + case alldefconfig: + case randconfig: + name = getenv("KCONFIG_ALLCONFIG"); + if (!name) + break; + if ((strcmp(name, "") != 0) && (strcmp(name, "1") != 0)) { + if (conf_read_simple(name, S_DEF_USER)) { + fprintf(stderr, + "*** Can't read seed configuration \"%s\"!\n", + name); + exit(1); + } + break; + } + switch (input_mode) { + case allnoconfig: name = "allno.config"; break; + case allyesconfig: name = "allyes.config"; break; + case allmodconfig: name = "allmod.config"; break; + case alldefconfig: name = "alldef.config"; break; + case randconfig: name = "allrandom.config"; break; + default: break; + } + if (conf_read_simple(name, S_DEF_USER) && + conf_read_simple("all.config", S_DEF_USER)) { + fprintf(stderr, + "*** KCONFIG_ALLCONFIG set, but no \"%s\" or \"all.config\" file found\n", + name); + exit(1); + } + break; + default: + break; + } + + if (sync_kconfig) { + name = getenv("KCONFIG_NOSILENTUPDATE"); + if (name && *name) { + if (conf_get_changed()) { + fprintf(stderr, + "\n*** The configuration requires explicit update.\n\n"); + return 1; + } + no_conf_write = 1; + } + } + + switch (input_mode) { + case allnoconfig: + conf_set_all_new_symbols(def_no); + break; + case allyesconfig: + conf_set_all_new_symbols(def_yes); + break; + case allmodconfig: + conf_set_all_new_symbols(def_mod); + break; + case alldefconfig: + conf_set_all_new_symbols(def_default); + break; + case randconfig: + /* Really nothing to do in this loop */ + while (conf_set_all_new_symbols(def_random)) ; + break; + case defconfig: + conf_set_all_new_symbols(def_default); + break; + case savedefconfig: + break; + case yes2modconfig: + conf_rewrite_mod_or_yes(def_y2m); + break; + case mod2yesconfig: + conf_rewrite_mod_or_yes(def_m2y); + break; + case oldaskconfig: + rootEntry = &rootmenu; + conf(&rootmenu); + input_mode = oldconfig; + /* fall through */ + case oldconfig: + case listnewconfig: + case helpnewconfig: + case syncconfig: + /* Update until a loop caused no more changes */ + do { + conf_cnt = 0; + check_conf(&rootmenu); + } while (conf_cnt); + break; + case olddefconfig: + default: + break; + } + + if (input_mode == savedefconfig) { + if (conf_write_defconfig(defconfig_file)) { + fprintf(stderr, "n*** Error while saving defconfig to: %s\n\n", + defconfig_file); + return 1; + } + } else if (input_mode != listnewconfig && input_mode != helpnewconfig) { + if (!no_conf_write && conf_write(NULL)) { + fprintf(stderr, "\n*** Error during writing of the configuration.\n\n"); + exit(1); + } + + /* + * Create auto.conf if it does not exist. + * This prevents GNU Make 4.1 or older from emitting + * "include/config/auto.conf: No such file or directory" + * in the top-level Makefile + * + * syncconfig always creates or updates auto.conf because it is + * used during the build. + */ + if (conf_write_autoconf(sync_kconfig) && sync_kconfig) { + fprintf(stderr, + "\n*** Error during sync of the configuration.\n\n"); + return 1; + } + } + return 0; +} diff --git a/nemu/tools/kconfig/confdata.c b/nemu/tools/kconfig/confdata.c new file mode 100644 index 0000000..a39d93e --- /dev/null +++ b/nemu/tools/kconfig/confdata.c @@ -0,0 +1,1338 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lkc.h" + +/* return true if 'path' exists, false otherwise */ +static bool is_present(const char *path) +{ + struct stat st; + + return !stat(path, &st); +} + +/* return true if 'path' exists and it is a directory, false otherwise */ +static bool is_dir(const char *path) +{ + struct stat st; + + if (stat(path, &st)) + return 0; + + return S_ISDIR(st.st_mode); +} + +/* return true if the given two files are the same, false otherwise */ +static bool is_same(const char *file1, const char *file2) +{ + int fd1, fd2; + struct stat st1, st2; + void *map1, *map2; + bool ret = false; + + fd1 = open(file1, O_RDONLY); + if (fd1 < 0) + return ret; + + fd2 = open(file2, O_RDONLY); + if (fd2 < 0) + goto close1; + + ret = fstat(fd1, &st1); + if (ret) + goto close2; + ret = fstat(fd2, &st2); + if (ret) + goto close2; + + if (st1.st_size != st2.st_size) + goto close2; + + map1 = mmap(NULL, st1.st_size, PROT_READ, MAP_PRIVATE, fd1, 0); + if (map1 == MAP_FAILED) + goto close2; + + map2 = mmap(NULL, st2.st_size, PROT_READ, MAP_PRIVATE, fd2, 0); + if (map2 == MAP_FAILED) + goto close2; + + if (bcmp(map1, map2, st1.st_size)) + goto close2; + + ret = true; +close2: + close(fd2); +close1: + close(fd1); + + return ret; +} + +/* + * Create the parent directory of the given path. + * + * For example, if 'include/config/auto.conf' is given, create 'include/config'. + */ +static int make_parent_dir(const char *path) +{ + char tmp[PATH_MAX + 1]; + char *p; + + strncpy(tmp, path, sizeof(tmp)); + tmp[sizeof(tmp) - 1] = 0; + + /* Remove the base name. Just return if nothing is left */ + p = strrchr(tmp, '/'); + if (!p) + return 0; + *(p + 1) = 0; + + /* Just in case it is an absolute path */ + p = tmp; + while (*p == '/') + p++; + + while ((p = strchr(p, '/'))) { + *p = 0; + + /* skip if the directory exists */ + if (!is_dir(tmp) && mkdir(tmp, 0755)) + return -1; + + *p = '/'; + while (*p == '/') + p++; + } + + return 0; +} + +static char depfile_path[PATH_MAX]; +static size_t depfile_prefix_len; + +/* touch depfile for symbol 'name' */ +static int conf_touch_dep(const char *name) +{ + int fd, ret; + const char *s; + char *d, c; + + /* check overflow: prefix + name + ".h" + '\0' must fit in buffer. */ + if (depfile_prefix_len + strlen(name) + 3 > sizeof(depfile_path)) + return -1; + + d = depfile_path + depfile_prefix_len; + s = name; + + while ((c = *s++)) + *d++ = (c == '_') ? '/' : tolower(c); + strcpy(d, ".h"); + + /* Assume directory path already exists. */ + fd = open(depfile_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + if (errno != ENOENT) + return -1; + + ret = make_parent_dir(depfile_path); + if (ret) + return ret; + + /* Try it again. */ + fd = open(depfile_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) + return -1; + } + close(fd); + + return 0; +} + +struct conf_printer { + void (*print_symbol)(FILE *, struct symbol *, const char *, void *); + void (*print_comment)(FILE *, const char *, void *); +}; + +static void conf_warning(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static void conf_message(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static const char *conf_filename; +static int conf_lineno, conf_warnings; + +static void conf_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", conf_filename, conf_lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + conf_warnings++; +} + +static void conf_default_message_callback(const char *s) +{ + printf("#\n# "); + printf("%s", s); + printf("\n#\n"); +} + +static void (*conf_message_callback)(const char *s) = + conf_default_message_callback; +void conf_set_message_callback(void (*fn)(const char *s)) +{ + conf_message_callback = fn; +} + +static void conf_message(const char *fmt, ...) +{ + va_list ap; + char buf[4096]; + + if (!conf_message_callback) + return; + + va_start(ap, fmt); + + vsnprintf(buf, sizeof(buf), fmt, ap); + conf_message_callback(buf); + va_end(ap); +} + +const char *conf_get_configname(void) +{ + char *name = getenv("KCONFIG_CONFIG"); + + return name ? name : ".config"; +} + +static const char *conf_get_autoconfig_name(void) +{ + char *name = getenv("KCONFIG_AUTOCONFIG"); + + return name ? name : "include/config/auto.conf"; +} + +static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p) +{ + char *p2; + + switch (sym->type) { + case S_TRISTATE: + if (p[0] == 'm') { + sym->def[def].tri = mod; + sym->flags |= def_flags; + break; + } + /* fall through */ + case S_BOOLEAN: + if (p[0] == 'y') { + sym->def[def].tri = yes; + sym->flags |= def_flags; + break; + } + if (p[0] == 'n') { + sym->def[def].tri = no; + sym->flags |= def_flags; + break; + } + if (def != S_DEF_AUTO) + conf_warning("symbol value '%s' invalid for %s", + p, sym->name); + return 1; + case S_STRING: + if (*p++ != '"') + break; + for (p2 = p; (p2 = strpbrk(p2, "\"\\")); p2++) { + if (*p2 == '"') { + *p2 = 0; + break; + } + memmove(p2, p2 + 1, strlen(p2)); + } + if (!p2) { + if (def != S_DEF_AUTO) + conf_warning("invalid string found"); + return 1; + } + /* fall through */ + case S_INT: + case S_HEX: + if (sym_string_valid(sym, p)) { + sym->def[def].val = xstrdup(p); + sym->flags |= def_flags; + } else { + if (def != S_DEF_AUTO) + conf_warning("symbol value '%s' invalid for %s", + p, sym->name); + return 1; + } + break; + default: + ; + } + return 0; +} + +#define LINE_GROWTH 16 +static int add_byte(int c, char **lineptr, size_t slen, size_t *n) +{ + char *nline; + size_t new_size = slen + 1; + if (new_size > *n) { + new_size += LINE_GROWTH - 1; + new_size *= 2; + nline = xrealloc(*lineptr, new_size); + if (!nline) + return -1; + + *lineptr = nline; + *n = new_size; + } + + (*lineptr)[slen] = c; + + return 0; +} + +static ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream) +{ + char *line = *lineptr; + size_t slen = 0; + + for (;;) { + int c = getc(stream); + + switch (c) { + case '\n': + if (add_byte(c, &line, slen, n) < 0) + goto e_out; + slen++; + /* fall through */ + case EOF: + if (add_byte('\0', &line, slen, n) < 0) + goto e_out; + *lineptr = line; + if (slen == 0) + return -1; + return slen; + default: + if (add_byte(c, &line, slen, n) < 0) + goto e_out; + slen++; + } + } + +e_out: + line[slen-1] = '\0'; + *lineptr = line; + return -1; +} + +int conf_read_simple(const char *name, int def) +{ + FILE *in = NULL; + char *line = NULL; + size_t line_asize = 0; + char *p, *p2; + struct symbol *sym; + int i, def_flags; + + if (name) { + in = zconf_fopen(name); + } else { + struct property *prop; + + name = conf_get_configname(); + in = zconf_fopen(name); + if (in) + goto load; + sym_add_change_count(1); + if (!sym_defconfig_list) + return 1; + + for_all_defaults(sym_defconfig_list, prop) { + if (expr_calc_value(prop->visible.expr) == no || + prop->expr->type != E_SYMBOL) + continue; + sym_calc_value(prop->expr->left.sym); + name = sym_get_string_value(prop->expr->left.sym); + in = zconf_fopen(name); + if (in) { + conf_message("using defaults found in %s", + name); + goto load; + } + } + } + if (!in) + return 1; + +load: + conf_filename = name; + conf_lineno = 0; + conf_warnings = 0; + + def_flags = SYMBOL_DEF << def; + for_all_symbols(i, sym) { + sym->flags |= SYMBOL_CHANGED; + sym->flags &= ~(def_flags|SYMBOL_VALID); + if (sym_is_choice(sym)) + sym->flags |= def_flags; + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + if (sym->def[def].val) + free(sym->def[def].val); + /* fall through */ + default: + sym->def[def].val = NULL; + sym->def[def].tri = no; + } + } + + while (compat_getline(&line, &line_asize, in) != -1) { + conf_lineno++; + sym = NULL; + if (line[0] == '#') { + if (memcmp(line + 2, CONFIG_, strlen(CONFIG_))) + continue; + p = strchr(line + 2 + strlen(CONFIG_), ' '); + if (!p) + continue; + *p++ = 0; + if (strncmp(p, "is not set", 10)) + continue; + if (def == S_DEF_USER) { + sym = sym_find(line + 2 + strlen(CONFIG_)); + if (!sym) { + sym_add_change_count(1); + continue; + } + } else { + sym = sym_lookup(line + 2 + strlen(CONFIG_), 0); + if (sym->type == S_UNKNOWN) + sym->type = S_BOOLEAN; + } + if (sym->flags & def_flags) { + conf_warning("override: reassigning to symbol %s", sym->name); + } + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + sym->def[def].tri = no; + sym->flags |= def_flags; + break; + default: + ; + } + } else if (memcmp(line, CONFIG_, strlen(CONFIG_)) == 0) { + p = strchr(line + strlen(CONFIG_), '='); + if (!p) + continue; + *p++ = 0; + p2 = strchr(p, '\n'); + if (p2) { + *p2-- = 0; + if (*p2 == '\r') + *p2 = 0; + } + + sym = sym_find(line + strlen(CONFIG_)); + if (!sym) { + if (def == S_DEF_AUTO) + /* + * Reading from include/config/auto.conf + * If CONFIG_FOO previously existed in + * auto.conf but it is missing now, + * include/config/foo.h must be touched. + */ + conf_touch_dep(line + strlen(CONFIG_)); + else + sym_add_change_count(1); + continue; + } + + if (sym->flags & def_flags) { + conf_warning("override: reassigning to symbol %s", sym->name); + } + if (conf_set_sym_val(sym, def, def_flags, p)) + continue; + } else { + if (line[0] != '\r' && line[0] != '\n') + conf_warning("unexpected data: %.*s", + (int)strcspn(line, "\r\n"), line); + + continue; + } + + if (sym && sym_is_choice_value(sym)) { + struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); + switch (sym->def[def].tri) { + case no: + break; + case mod: + if (cs->def[def].tri == yes) { + conf_warning("%s creates inconsistent choice state", sym->name); + cs->flags &= ~def_flags; + } + break; + case yes: + if (cs->def[def].tri != no) + conf_warning("override: %s changes choice state", sym->name); + cs->def[def].val = sym; + break; + } + cs->def[def].tri = EXPR_OR(cs->def[def].tri, sym->def[def].tri); + } + } + free(line); + fclose(in); + return 0; +} + +int conf_read(const char *name) +{ + struct symbol *sym; + int conf_unsaved = 0; + int i; + + sym_set_change_count(0); + + if (conf_read_simple(name, S_DEF_USER)) { + sym_calc_value(modules_sym); + return 1; + } + + sym_calc_value(modules_sym); + + for_all_symbols(i, sym) { + sym_calc_value(sym); + if (sym_is_choice(sym) || (sym->flags & SYMBOL_NO_WRITE)) + continue; + if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) { + /* check that calculated value agrees with saved value */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym->def[S_DEF_USER].tri == sym_get_tristate_value(sym)) + continue; + break; + default: + if (!strcmp(sym->curr.val, sym->def[S_DEF_USER].val)) + continue; + break; + } + } else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE)) + /* no previous value and not saved */ + continue; + conf_unsaved++; + /* maybe print value in verbose mode... */ + } + + for_all_symbols(i, sym) { + if (sym_has_value(sym) && !sym_is_choice_value(sym)) { + /* Reset values of generates values, so they'll appear + * as new, if they should become visible, but that + * doesn't quite work if the Kconfig and the saved + * configuration disagree. + */ + if (sym->visible == no && !conf_unsaved) + sym->flags &= ~SYMBOL_DEF_USER; + switch (sym->type) { + case S_STRING: + case S_INT: + case S_HEX: + /* Reset a string value if it's out of range */ + if (sym_string_within_range(sym, sym->def[S_DEF_USER].val)) + break; + sym->flags &= ~(SYMBOL_VALID|SYMBOL_DEF_USER); + conf_unsaved++; + break; + default: + break; + } + } + } + + sym_add_change_count(conf_warnings || conf_unsaved); + + return 0; +} + +/* + * Kconfig configuration printer + * + * This printer is used when generating the resulting configuration after + * kconfig invocation and `defconfig' files. Unset symbol might be omitted by + * passing a non-NULL argument to the printer. + * + */ +static void +kconfig_print_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg) +{ + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (*value == 'n') { + bool skip_unset = (arg != NULL); + + if (!skip_unset) + fprintf(fp, "# %s%s is not set\n", + CONFIG_, sym->name); + return; + } + break; + default: + break; + } + + fprintf(fp, "%s%s=%s\n", CONFIG_, sym->name, value); +} + +static void +kconfig_print_comment(FILE *fp, const char *value, void *arg) +{ + const char *p = value; + size_t l; + + for (;;) { + l = strcspn(p, "\n"); + fprintf(fp, "#"); + if (l) { + fprintf(fp, " "); + xfwrite(p, l, 1, fp); + p += l; + } + fprintf(fp, "\n"); + if (*p++ == '\0') + break; + } +} + +static struct conf_printer kconfig_printer_cb = +{ + .print_symbol = kconfig_print_symbol, + .print_comment = kconfig_print_comment, +}; + +/* + * Header printer + * + * This printer is used when generating the `include/generated/autoconf.h' file. + */ +static void +header_print_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg) +{ + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: { + const char *suffix = ""; + + switch (*value) { + case 'n': + break; + case 'm': + suffix = "_MODULE"; + /* fall through */ + default: + fprintf(fp, "#define %s%s%s 1\n", + CONFIG_, sym->name, suffix); + } + break; + } + case S_HEX: { + const char *prefix = ""; + + if (value[0] != '0' || (value[1] != 'x' && value[1] != 'X')) + prefix = "0x"; + fprintf(fp, "#define %s%s %s%s\n", + CONFIG_, sym->name, prefix, value); + break; + } + case S_STRING: + case S_INT: + fprintf(fp, "#define %s%s %s\n", + CONFIG_, sym->name, value); + break; + default: + break; + } + +} + +static void +header_print_comment(FILE *fp, const char *value, void *arg) +{ + const char *p = value; + size_t l; + + fprintf(fp, "/*\n"); + for (;;) { + l = strcspn(p, "\n"); + fprintf(fp, " *"); + if (l) { + fprintf(fp, " "); + xfwrite(p, l, 1, fp); + p += l; + } + fprintf(fp, "\n"); + if (*p++ == '\0') + break; + } + fprintf(fp, " */\n"); +} + +static struct conf_printer header_printer_cb = +{ + .print_symbol = header_print_symbol, + .print_comment = header_print_comment, +}; + +static void conf_write_symbol(FILE *fp, struct symbol *sym, + struct conf_printer *printer, void *printer_arg) +{ + const char *str; + + switch (sym->type) { + case S_UNKNOWN: + break; + case S_STRING: + str = sym_get_string_value(sym); + str = sym_escape_string_value(str); + printer->print_symbol(fp, sym, str, printer_arg); + free((void *)str); + break; + default: + str = sym_get_string_value(sym); + printer->print_symbol(fp, sym, str, printer_arg); + } +} + +static void +conf_write_heading(FILE *fp, struct conf_printer *printer, void *printer_arg) +{ + char buf[256]; + + snprintf(buf, sizeof(buf), + "\n" + "Automatically generated file; DO NOT EDIT.\n" + "%s\n", + rootmenu.prompt->text); + + printer->print_comment(fp, buf, printer_arg); +} + +/* + * Write out a minimal config. + * All values that has default values are skipped as this is redundant. + */ +int conf_write_defconfig(const char *filename) +{ + struct symbol *sym; + struct menu *menu; + FILE *out; + + out = fopen(filename, "w"); + if (!out) + return 1; + + sym_clear_all_valid(); + + /* Traverse all menus to find all relevant symbols */ + menu = rootmenu.list; + + while (menu != NULL) + { + sym = menu->sym; + if (sym == NULL) { + if (!menu_is_visible(menu)) + goto next_menu; + } else if (!sym_is_choice(sym)) { + sym_calc_value(sym); + if (!(sym->flags & SYMBOL_WRITE)) + goto next_menu; + sym->flags &= ~SYMBOL_WRITE; + /* If we cannot change the symbol - skip */ + if (!sym_is_changeable(sym)) + goto next_menu; + /* If symbol equals to default value - skip */ + if (strcmp(sym_get_string_value(sym), sym_get_string_default(sym)) == 0) + goto next_menu; + + /* + * If symbol is a choice value and equals to the + * default for a choice - skip. + * But only if value is bool and equal to "y" and + * choice is not "optional". + * (If choice is "optional" then all values can be "n") + */ + if (sym_is_choice_value(sym)) { + struct symbol *cs; + struct symbol *ds; + + cs = prop_get_symbol(sym_get_choice_prop(sym)); + ds = sym_choice_default(cs); + if (!sym_is_optional(cs) && sym == ds) { + if ((sym->type == S_BOOLEAN) && + sym_get_tristate_value(sym) == yes) + goto next_menu; + } + } + conf_write_symbol(out, sym, &kconfig_printer_cb, NULL); + } +next_menu: + if (menu->list != NULL) { + menu = menu->list; + } + else if (menu->next != NULL) { + menu = menu->next; + } else { + while ((menu = menu->parent)) { + if (menu->next != NULL) { + menu = menu->next; + break; + } + } + } + } + fclose(out); + return 0; +} + +int conf_write(const char *name) +{ + FILE *out; + struct symbol *sym; + struct menu *menu; + const char *str; + char tmpname[PATH_MAX + 1], oldname[PATH_MAX + 1]; + char *env; + int i; + bool need_newline = false; + + if (!name) + name = conf_get_configname(); + + if (!*name) { + fprintf(stderr, "config name is empty\n"); + return -1; + } + + if (is_dir(name)) { + fprintf(stderr, "%s: Is a directory\n", name); + return -1; + } + + if (make_parent_dir(name)) + return -1; + + env = getenv("KCONFIG_OVERWRITECONFIG"); + if (env && *env) { + *tmpname = 0; + out = fopen(name, "w"); + } else { + snprintf(tmpname, sizeof(tmpname), "%s.%d.tmp", + name, (int)getpid()); + out = fopen(tmpname, "w"); + } + if (!out) + return 1; + + conf_write_heading(out, &kconfig_printer_cb, NULL); + + if (!conf_get_changed()) + sym_clear_all_valid(); + + menu = rootmenu.list; + while (menu) { + sym = menu->sym; + if (!sym) { + if (!menu_is_visible(menu)) + goto next; + str = menu_get_prompt(menu); + fprintf(out, "\n" + "#\n" + "# %s\n" + "#\n", str); + need_newline = false; + } else if (!(sym->flags & SYMBOL_CHOICE) && + !(sym->flags & SYMBOL_WRITTEN)) { + sym_calc_value(sym); + if (!(sym->flags & SYMBOL_WRITE)) + goto next; + if (need_newline) { + fprintf(out, "\n"); + need_newline = false; + } + sym->flags |= SYMBOL_WRITTEN; + conf_write_symbol(out, sym, &kconfig_printer_cb, NULL); + } + +next: + if (menu->list) { + menu = menu->list; + continue; + } + if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (!menu->sym && menu_is_visible(menu) && + menu != &rootmenu) { + str = menu_get_prompt(menu); + fprintf(out, "# end of %s\n", str); + need_newline = true; + } + if (menu->next) { + menu = menu->next; + break; + } + } + } + fclose(out); + + for_all_symbols(i, sym) + sym->flags &= ~SYMBOL_WRITTEN; + + if (*tmpname) { + if (is_same(name, tmpname)) { + conf_message("No change to %s", name); + unlink(tmpname); + sym_set_change_count(0); + return 0; + } + + snprintf(oldname, sizeof(oldname), "%s.old", name); + rename(name, oldname); + if (rename(tmpname, name)) + return 1; + } + + conf_message("configuration written to %s", name); + + sym_set_change_count(0); + + return 0; +} + +/* write a dependency file as used by kbuild to track dependencies */ +static int conf_write_dep(const char *name) +{ + struct file *file; + FILE *out; + + out = fopen("..config.tmp", "w"); + if (!out) + return 1; + fprintf(out, "deps_config := \\\n"); + for (file = file_list; file; file = file->next) { + if (file->next) + fprintf(out, "\t%s \\\n", file->name); + else + fprintf(out, "\t%s\n", file->name); + } + fprintf(out, "\n%s: \\\n" + "\t$(deps_config)\n\n", conf_get_autoconfig_name()); + + env_write_dep(out, conf_get_autoconfig_name()); + + fprintf(out, "\n$(deps_config): ;\n"); + fclose(out); + + if (make_parent_dir(name)) + return 1; + rename("..config.tmp", name); + return 0; +} + +static int conf_touch_deps(void) +{ + const char *name; + struct symbol *sym; + int res, i; + + strcpy(depfile_path, "include/config/"); + depfile_prefix_len = strlen(depfile_path); + + name = conf_get_autoconfig_name(); + conf_read_simple(name, S_DEF_AUTO); + sym_calc_value(modules_sym); + + for_all_symbols(i, sym) { + sym_calc_value(sym); + if ((sym->flags & SYMBOL_NO_WRITE) || !sym->name) + continue; + if (sym->flags & SYMBOL_WRITE) { + if (sym->flags & SYMBOL_DEF_AUTO) { + /* + * symbol has old and new value, + * so compare them... + */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_get_tristate_value(sym) == + sym->def[S_DEF_AUTO].tri) + continue; + break; + case S_STRING: + case S_HEX: + case S_INT: + if (!strcmp(sym_get_string_value(sym), + sym->def[S_DEF_AUTO].val)) + continue; + break; + default: + break; + } + } else { + /* + * If there is no old value, only 'no' (unset) + * is allowed as new value. + */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_get_tristate_value(sym) == no) + continue; + break; + default: + break; + } + } + } else if (!(sym->flags & SYMBOL_DEF_AUTO)) + /* There is neither an old nor a new value. */ + continue; + /* else + * There is an old value, but no new value ('no' (unset) + * isn't saved in auto.conf, so the old value is always + * different from 'no'). + */ + + res = conf_touch_dep(sym->name); + if (res) + return res; + } + + return 0; +} + +int conf_write_autoconf(int overwrite) +{ + struct symbol *sym; + const char *name; + const char *autoconf_name = conf_get_autoconfig_name(); + FILE *out, *out_h; + int i; + + if (!overwrite && is_present(autoconf_name)) + return 0; + + conf_write_dep("include/config/auto.conf.cmd"); + + if (conf_touch_deps()) + return 1; + + out = fopen(".tmpconfig", "w"); + if (!out) + return 1; + + out_h = fopen(".tmpconfig.h", "w"); + if (!out_h) { + fclose(out); + return 1; + } + + conf_write_heading(out, &kconfig_printer_cb, NULL); + conf_write_heading(out_h, &header_printer_cb, NULL); + + for_all_symbols(i, sym) { + sym_calc_value(sym); + if (!(sym->flags & SYMBOL_WRITE) || !sym->name) + continue; + + /* write symbols to auto.conf and autoconf.h */ + conf_write_symbol(out, sym, &kconfig_printer_cb, (void *)1); + conf_write_symbol(out_h, sym, &header_printer_cb, NULL); + } + fclose(out); + fclose(out_h); + + name = getenv("KCONFIG_AUTOHEADER"); + if (!name) + name = "include/generated/autoconf.h"; + if (make_parent_dir(name)) + return 1; + if (rename(".tmpconfig.h", name)) + return 1; + + if (make_parent_dir(autoconf_name)) + return 1; + /* + * This must be the last step, kbuild has a dependency on auto.conf + * and this marks the successful completion of the previous steps. + */ + if (rename(".tmpconfig", autoconf_name)) + return 1; + + return 0; +} + +static int sym_change_count; +static void (*conf_changed_callback)(void); + +void sym_set_change_count(int count) +{ + int _sym_change_count = sym_change_count; + sym_change_count = count; + if (conf_changed_callback && + (bool)_sym_change_count != (bool)count) + conf_changed_callback(); +} + +void sym_add_change_count(int count) +{ + sym_set_change_count(count + sym_change_count); +} + +bool conf_get_changed(void) +{ + return sym_change_count; +} + +void conf_set_changed_callback(void (*fn)(void)) +{ + conf_changed_callback = fn; +} + +static bool randomize_choice_values(struct symbol *csym) +{ + struct property *prop; + struct symbol *sym; + struct expr *e; + int cnt, def; + + /* + * If choice is mod then we may have more items selected + * and if no then no-one. + * In both cases stop. + */ + if (csym->curr.tri != yes) + return false; + + prop = sym_get_choice_prop(csym); + + /* count entries in choice block */ + cnt = 0; + expr_list_for_each_sym(prop->expr, e, sym) + cnt++; + + /* + * find a random value and set it to yes, + * set the rest to no so we have only one set + */ + def = (rand() % cnt); + + cnt = 0; + expr_list_for_each_sym(prop->expr, e, sym) { + if (def == cnt++) { + sym->def[S_DEF_USER].tri = yes; + csym->def[S_DEF_USER].val = sym; + } + else { + sym->def[S_DEF_USER].tri = no; + } + sym->flags |= SYMBOL_DEF_USER; + /* clear VALID to get value calculated */ + sym->flags &= ~SYMBOL_VALID; + } + csym->flags |= SYMBOL_DEF_USER; + /* clear VALID to get value calculated */ + csym->flags &= ~(SYMBOL_VALID); + + return true; +} + +void set_all_choice_values(struct symbol *csym) +{ + struct property *prop; + struct symbol *sym; + struct expr *e; + + prop = sym_get_choice_prop(csym); + + /* + * Set all non-assinged choice values to no + */ + expr_list_for_each_sym(prop->expr, e, sym) { + if (!sym_has_value(sym)) + sym->def[S_DEF_USER].tri = no; + } + csym->flags |= SYMBOL_DEF_USER; + /* clear VALID to get value calculated */ + csym->flags &= ~(SYMBOL_VALID | SYMBOL_NEED_SET_CHOICE_VALUES); +} + +bool conf_set_all_new_symbols(enum conf_def_mode mode) +{ + struct symbol *sym, *csym; + int i, cnt, pby, pty, ptm; /* pby: probability of bool = y + * pty: probability of tristate = y + * ptm: probability of tristate = m + */ + + pby = 50; pty = ptm = 33; /* can't go as the default in switch-case + * below, otherwise gcc whines about + * -Wmaybe-uninitialized */ + if (mode == def_random) { + int n, p[3]; + char *env = getenv("KCONFIG_PROBABILITY"); + n = 0; + while( env && *env ) { + char *endp; + int tmp = strtol( env, &endp, 10 ); + if( tmp >= 0 && tmp <= 100 ) { + p[n++] = tmp; + } else { + errno = ERANGE; + perror( "KCONFIG_PROBABILITY" ); + exit( 1 ); + } + env = (*endp == ':') ? endp+1 : endp; + if( n >=3 ) { + break; + } + } + switch( n ) { + case 1: + pby = p[0]; ptm = pby/2; pty = pby-ptm; + break; + case 2: + pty = p[0]; ptm = p[1]; pby = pty + ptm; + break; + case 3: + pby = p[0]; pty = p[1]; ptm = p[2]; + break; + } + + if( pty+ptm > 100 ) { + errno = ERANGE; + perror( "KCONFIG_PROBABILITY" ); + exit( 1 ); + } + } + bool has_changed = false; + + for_all_symbols(i, sym) { + if (sym_has_value(sym) || (sym->flags & SYMBOL_VALID)) + continue; + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + has_changed = true; + switch (mode) { + case def_yes: + sym->def[S_DEF_USER].tri = yes; + break; + case def_mod: + sym->def[S_DEF_USER].tri = mod; + break; + case def_no: + if (sym->flags & SYMBOL_ALLNOCONFIG_Y) + sym->def[S_DEF_USER].tri = yes; + else + sym->def[S_DEF_USER].tri = no; + break; + case def_random: + sym->def[S_DEF_USER].tri = no; + cnt = rand() % 100; + if (sym->type == S_TRISTATE) { + if (cnt < pty) + sym->def[S_DEF_USER].tri = yes; + else if (cnt < (pty+ptm)) + sym->def[S_DEF_USER].tri = mod; + } else if (cnt < pby) + sym->def[S_DEF_USER].tri = yes; + break; + default: + continue; + } + if (!(sym_is_choice(sym) && mode == def_random)) + sym->flags |= SYMBOL_DEF_USER; + break; + default: + break; + } + + } + + sym_clear_all_valid(); + + /* + * We have different type of choice blocks. + * If curr.tri equals to mod then we can select several + * choice symbols in one block. + * In this case we do nothing. + * If curr.tri equals yes then only one symbol can be + * selected in a choice block and we set it to yes, + * and the rest to no. + */ + if (mode != def_random) { + for_all_symbols(i, csym) { + if ((sym_is_choice(csym) && !sym_has_value(csym)) || + sym_is_choice_value(csym)) + csym->flags |= SYMBOL_NEED_SET_CHOICE_VALUES; + } + } + + for_all_symbols(i, csym) { + if (sym_has_value(csym) || !sym_is_choice(csym)) + continue; + + sym_calc_value(csym); + if (mode == def_random) + has_changed |= randomize_choice_values(csym); + else { + set_all_choice_values(csym); + has_changed = true; + } + } + + return has_changed; +} + +void conf_rewrite_mod_or_yes(enum conf_def_mode mode) +{ + struct symbol *sym; + int i; + tristate old_val = (mode == def_y2m) ? yes : mod; + tristate new_val = (mode == def_y2m) ? mod : yes; + + for_all_symbols(i, sym) { + if (sym_get_type(sym) == S_TRISTATE && + sym->def[S_DEF_USER].tri == old_val) + sym->def[S_DEF_USER].tri = new_val; + } + sym_clear_all_valid(); +} diff --git a/nemu/tools/kconfig/expr.c b/nemu/tools/kconfig/expr.c new file mode 100644 index 0000000..81ebf81 --- /dev/null +++ b/nemu/tools/kconfig/expr.c @@ -0,0 +1,1303 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel + */ + +#include +#include +#include +#include +#include + +#include "lkc.h" + +#define DEBUG_EXPR 0 + +static struct expr *expr_eliminate_yn(struct expr *e); + +struct expr *expr_alloc_symbol(struct symbol *sym) +{ + struct expr *e = xcalloc(1, sizeof(*e)); + e->type = E_SYMBOL; + e->left.sym = sym; + return e; +} + +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce) +{ + struct expr *e = xcalloc(1, sizeof(*e)); + e->type = type; + e->left.expr = ce; + return e; +} + +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2) +{ + struct expr *e = xcalloc(1, sizeof(*e)); + e->type = type; + e->left.expr = e1; + e->right.expr = e2; + return e; +} + +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2) +{ + struct expr *e = xcalloc(1, sizeof(*e)); + e->type = type; + e->left.sym = s1; + e->right.sym = s2; + return e; +} + +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_AND, e1, e2) : e1; +} + +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_OR, e1, e2) : e1; +} + +struct expr *expr_copy(const struct expr *org) +{ + struct expr *e; + + if (!org) + return NULL; + + e = xmalloc(sizeof(*org)); + memcpy(e, org, sizeof(*org)); + switch (org->type) { + case E_SYMBOL: + e->left = org->left; + break; + case E_NOT: + e->left.expr = expr_copy(org->left.expr); + break; + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + e->left.sym = org->left.sym; + e->right.sym = org->right.sym; + break; + case E_AND: + case E_OR: + case E_LIST: + e->left.expr = expr_copy(org->left.expr); + e->right.expr = expr_copy(org->right.expr); + break; + default: + fprintf(stderr, "can't copy type %d\n", e->type); + free(e); + e = NULL; + break; + } + + return e; +} + +void expr_free(struct expr *e) +{ + if (!e) + return; + + switch (e->type) { + case E_SYMBOL: + break; + case E_NOT: + expr_free(e->left.expr); + break; + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + break; + case E_OR: + case E_AND: + expr_free(e->left.expr); + expr_free(e->right.expr); + break; + default: + fprintf(stderr, "how to free type %d?\n", e->type); + break; + } + free(e); +} + +static int trans_count; + +#define e1 (*ep1) +#define e2 (*ep2) + +/* + * expr_eliminate_eq() helper. + * + * Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does + * not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared + * against all other leaves. Two equal leaves are both replaced with either 'y' + * or 'n' as appropriate for 'type', to be eliminated later. + */ +static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ + /* Recurse down to leaves */ + + if (e1->type == type) { + __expr_eliminate_eq(type, &e1->left.expr, &e2); + __expr_eliminate_eq(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + __expr_eliminate_eq(type, &e1, &e2->left.expr); + __expr_eliminate_eq(type, &e1, &e2->right.expr); + return; + } + + /* e1 and e2 are leaves. Compare them. */ + + if (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym && + (e1->left.sym == &symbol_yes || e1->left.sym == &symbol_no)) + return; + if (!expr_eq(e1, e2)) + return; + + /* e1 and e2 are equal leaves. Prepare them for elimination. */ + + trans_count++; + expr_free(e1); expr_free(e2); + switch (type) { + case E_OR: + e1 = expr_alloc_symbol(&symbol_no); + e2 = expr_alloc_symbol(&symbol_no); + break; + case E_AND: + e1 = expr_alloc_symbol(&symbol_yes); + e2 = expr_alloc_symbol(&symbol_yes); + break; + default: + ; + } +} + +/* + * Rewrites the expressions 'ep1' and 'ep2' to remove operands common to both. + * Example reductions: + * + * ep1: A && B -> ep1: y + * ep2: A && B && C -> ep2: C + * + * ep1: A || B -> ep1: n + * ep2: A || B || C -> ep2: C + * + * ep1: A && (B && FOO) -> ep1: FOO + * ep2: (BAR && B) && A -> ep2: BAR + * + * ep1: A && (B || C) -> ep1: y + * ep2: (C || B) && A -> ep2: y + * + * Comparisons are done between all operands at the same "level" of && or ||. + * For example, in the expression 'e1 && (e2 || e3) && (e4 || e5)', the + * following operands will be compared: + * + * - 'e1', 'e2 || e3', and 'e4 || e5', against each other + * - e2 against e3 + * - e4 against e5 + * + * Parentheses are irrelevant within a single level. 'e1 && (e2 && e3)' and + * '(e1 && e2) && e3' are both a single level. + * + * See __expr_eliminate_eq() as well. + */ +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2) +{ + if (!e1 || !e2) + return; + switch (e1->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq(e1->type, ep1, ep2); + default: + ; + } + if (e1->type != e2->type) switch (e2->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq(e2->type, ep1, ep2); + default: + ; + } + e1 = expr_eliminate_yn(e1); + e2 = expr_eliminate_yn(e2); +} + +#undef e1 +#undef e2 + +/* + * Returns true if 'e1' and 'e2' are equal, after minor simplification. Two + * &&/|| expressions are considered equal if every operand in one expression + * equals some operand in the other (operands do not need to appear in the same + * order), recursively. + */ +int expr_eq(struct expr *e1, struct expr *e2) +{ + int res, old_count; + + /* + * A NULL expr is taken to be yes, but there's also a different way to + * represent yes. expr_is_yes() checks for either representation. + */ + if (!e1 || !e2) + return expr_is_yes(e1) && expr_is_yes(e2); + + if (e1->type != e2->type) + return 0; + switch (e1->type) { + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + return e1->left.sym == e2->left.sym && e1->right.sym == e2->right.sym; + case E_SYMBOL: + return e1->left.sym == e2->left.sym; + case E_NOT: + return expr_eq(e1->left.expr, e2->left.expr); + case E_AND: + case E_OR: + e1 = expr_copy(e1); + e2 = expr_copy(e2); + old_count = trans_count; + expr_eliminate_eq(&e1, &e2); + res = (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym); + expr_free(e1); + expr_free(e2); + trans_count = old_count; + return res; + case E_LIST: + case E_RANGE: + case E_NONE: + /* panic */; + } + + if (DEBUG_EXPR) { + expr_fprint(e1, stdout); + printf(" = "); + expr_fprint(e2, stdout); + printf(" ?\n"); + } + + return 0; +} + +/* + * Recursively performs the following simplifications in-place (as well as the + * corresponding simplifications with swapped operands): + * + * expr && n -> n + * expr && y -> expr + * expr || n -> expr + * expr || y -> y + * + * Returns the optimized expression. + */ +static struct expr *expr_eliminate_yn(struct expr *e) +{ + struct expr *tmp; + + if (e) switch (e->type) { + case E_AND: + e->left.expr = expr_eliminate_yn(e->left.expr); + e->right.expr = expr_eliminate_yn(e->right.expr); + if (e->left.expr->type == E_SYMBOL) { + if (e->left.expr->left.sym == &symbol_no) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.expr = NULL; + return e; + } else if (e->left.expr->left.sym == &symbol_yes) { + free(e->left.expr); + tmp = e->right.expr; + *e = *(e->right.expr); + free(tmp); + return e; + } + } + if (e->right.expr->type == E_SYMBOL) { + if (e->right.expr->left.sym == &symbol_no) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.expr = NULL; + return e; + } else if (e->right.expr->left.sym == &symbol_yes) { + free(e->right.expr); + tmp = e->left.expr; + *e = *(e->left.expr); + free(tmp); + return e; + } + } + break; + case E_OR: + e->left.expr = expr_eliminate_yn(e->left.expr); + e->right.expr = expr_eliminate_yn(e->right.expr); + if (e->left.expr->type == E_SYMBOL) { + if (e->left.expr->left.sym == &symbol_no) { + free(e->left.expr); + tmp = e->right.expr; + *e = *(e->right.expr); + free(tmp); + return e; + } else if (e->left.expr->left.sym == &symbol_yes) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.expr = NULL; + return e; + } + } + if (e->right.expr->type == E_SYMBOL) { + if (e->right.expr->left.sym == &symbol_no) { + free(e->right.expr); + tmp = e->left.expr; + *e = *(e->left.expr); + free(tmp); + return e; + } else if (e->right.expr->left.sym == &symbol_yes) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.expr = NULL; + return e; + } + } + break; + default: + ; + } + return e; +} + +/* + * bool FOO!=n => FOO + */ +struct expr *expr_trans_bool(struct expr *e) +{ + if (!e) + return NULL; + switch (e->type) { + case E_AND: + case E_OR: + case E_NOT: + e->left.expr = expr_trans_bool(e->left.expr); + e->right.expr = expr_trans_bool(e->right.expr); + break; + case E_UNEQUAL: + // FOO!=n -> FOO + if (e->left.sym->type == S_TRISTATE) { + if (e->right.sym == &symbol_no) { + e->type = E_SYMBOL; + e->right.sym = NULL; + } + } + break; + default: + ; + } + return e; +} + +/* + * e1 || e2 -> ? + */ +static struct expr *expr_join_or(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + return expr_copy(e1); + if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) + return NULL; + if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='m') -> (a!='n') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_no); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='n') -> (a!='m') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_mod); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) { + // (a='m') || (a='n') -> (a!='y') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_yes); + } + } + if (sym1->type == S_BOOLEAN && sym1 == sym2) { + if ((e1->type == E_NOT && e1->left.expr->type == E_SYMBOL && e2->type == E_SYMBOL) || + (e2->type == E_NOT && e2->left.expr->type == E_SYMBOL && e1->type == E_SYMBOL)) + return expr_alloc_symbol(&symbol_yes); + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") || ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +static struct expr *expr_join_and(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + return expr_copy(e1); + if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) + return NULL; + if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_yes)) + // (a) && (a='y') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_no) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_no)) + // (a) && (a!='n') -> (a) + return expr_alloc_symbol(sym1); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_mod)) + // (a) && (a!='m') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_UNEQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e1->right.sym; + if ((e2->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e2->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_EQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e2->right.sym; + if ((e1->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e1->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_mod); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='m') -> (a='n') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_no); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) + // (a!='m') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_mod) || + (e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_yes)) + return NULL; + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") && ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +/* + * expr_eliminate_dups() helper. + * + * Walks the two expression trees given in 'ep1' and 'ep2'. Any node that does + * not have type 'type' (E_OR/E_AND) is considered a leaf, and is compared + * against all other leaves to look for simplifications. + */ +static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + struct expr *tmp; + + /* Recurse down to leaves */ + + if (e1->type == type) { + expr_eliminate_dups1(type, &e1->left.expr, &e2); + expr_eliminate_dups1(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_eliminate_dups1(type, &e1, &e2->left.expr); + expr_eliminate_dups1(type, &e1, &e2->right.expr); + return; + } + + /* e1 and e2 are leaves. Compare and process them. */ + + if (e1 == e2) + return; + + switch (e1->type) { + case E_OR: case E_AND: + expr_eliminate_dups1(e1->type, &e1, &e1); + default: + ; + } + + switch (type) { + case E_OR: + tmp = expr_join_or(e1, e2); + if (tmp) { + expr_free(e1); expr_free(e2); + e1 = expr_alloc_symbol(&symbol_no); + e2 = tmp; + trans_count++; + } + break; + case E_AND: + tmp = expr_join_and(e1, e2); + if (tmp) { + expr_free(e1); expr_free(e2); + e1 = expr_alloc_symbol(&symbol_yes); + e2 = tmp; + trans_count++; + } + break; + default: + ; + } +#undef e1 +#undef e2 +} + +/* + * Rewrites 'e' in-place to remove ("join") duplicate and other redundant + * operands. + * + * Example simplifications: + * + * A || B || A -> A || B + * A && B && A=y -> A=y && B + * + * Returns the deduplicated expression. + */ +struct expr *expr_eliminate_dups(struct expr *e) +{ + int oldcount; + if (!e) + return e; + + oldcount = trans_count; + while (1) { + trans_count = 0; + switch (e->type) { + case E_OR: case E_AND: + expr_eliminate_dups1(e->type, &e, &e); + default: + ; + } + if (!trans_count) + /* No simplifications done in this pass. We're done */ + break; + e = expr_eliminate_yn(e); + } + trans_count = oldcount; + return e; +} + +/* + * Performs various simplifications involving logical operators and + * comparisons. + * + * Allocates and returns a new expression. + */ +struct expr *expr_transform(struct expr *e) +{ + struct expr *tmp; + + if (!e) + return NULL; + switch (e->type) { + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + case E_SYMBOL: + case E_LIST: + break; + default: + e->left.expr = expr_transform(e->left.expr); + e->right.expr = expr_transform(e->right.expr); + } + + switch (e->type) { + case E_EQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + e->type = E_NOT; + e->left.expr = expr_alloc_symbol(e->left.sym); + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_mod) { + printf("boolean symbol %s tested for 'm'? test forced to 'n'\n", e->left.sym->name); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_yes) { + e->type = E_SYMBOL; + e->right.sym = NULL; + break; + } + break; + case E_UNEQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + e->type = E_SYMBOL; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_mod) { + printf("boolean symbol %s tested for 'm'? test forced to 'y'\n", e->left.sym->name); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_yes) { + e->type = E_NOT; + e->left.expr = expr_alloc_symbol(e->left.sym); + e->right.sym = NULL; + break; + } + break; + case E_NOT: + switch (e->left.expr->type) { + case E_NOT: + // !!a -> a + tmp = e->left.expr->left.expr; + free(e->left.expr); + free(e); + e = tmp; + e = expr_transform(e); + break; + case E_EQUAL: + case E_UNEQUAL: + // !a='x' -> a!='x' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = e->type == E_EQUAL ? E_UNEQUAL : E_EQUAL; + break; + case E_LEQ: + case E_GEQ: + // !a<='x' -> a>'x' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = e->type == E_LEQ ? E_GTH : E_LTH; + break; + case E_LTH: + case E_GTH: + // !a<'x' -> a>='x' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = e->type == E_LTH ? E_GEQ : E_LEQ; + break; + case E_OR: + // !(a || b) -> !a && !b + tmp = e->left.expr; + e->type = E_AND; + e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); + tmp->type = E_NOT; + tmp->right.expr = NULL; + e = expr_transform(e); + break; + case E_AND: + // !(a && b) -> !a || !b + tmp = e->left.expr; + e->type = E_OR; + e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); + tmp->type = E_NOT; + tmp->right.expr = NULL; + e = expr_transform(e); + break; + case E_SYMBOL: + if (e->left.expr->left.sym == &symbol_yes) { + // !'y' -> 'n' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + break; + } + if (e->left.expr->left.sym == &symbol_mod) { + // !'m' -> 'm' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_mod; + break; + } + if (e->left.expr->left.sym == &symbol_no) { + // !'n' -> 'y' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + break; + } + break; + default: + ; + } + break; + default: + ; + } + return e; +} + +int expr_contains_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return 0; + + switch (dep->type) { + case E_AND: + case E_OR: + return expr_contains_symbol(dep->left.expr, sym) || + expr_contains_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + return dep->left.sym == sym || + dep->right.sym == sym; + case E_NOT: + return expr_contains_symbol(dep->left.expr, sym); + default: + ; + } + return 0; +} + +bool expr_depends_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return false; + + switch (dep->type) { + case E_AND: + return expr_depends_symbol(dep->left.expr, sym) || + expr_depends_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_yes || dep->right.sym == &symbol_mod) + return true; + } + break; + case E_UNEQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_no) + return true; + } + break; + default: + ; + } + return false; +} + +/* + * Inserts explicit comparisons of type 'type' to symbol 'sym' into the + * expression 'e'. + * + * Examples transformations for type == E_UNEQUAL, sym == &symbol_no: + * + * A -> A!=n + * !A -> A=n + * A && B -> !(A=n || B=n) + * A || B -> !(A=n && B=n) + * A && (B || C) -> !(A=n || (B=n && C=n)) + * + * Allocates and returns a new expression. + */ +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym) +{ + struct expr *e1, *e2; + + if (!e) { + e = expr_alloc_symbol(sym); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + } + switch (e->type) { + case E_AND: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_AND, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_OR, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_OR: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_OR, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_AND, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_NOT: + return expr_trans_compare(e->left.expr, type == E_EQUAL ? E_UNEQUAL : E_EQUAL, sym); + case E_UNEQUAL: + case E_LTH: + case E_LEQ: + case E_GTH: + case E_GEQ: + case E_EQUAL: + if (type == E_EQUAL) { + if (sym == &symbol_yes) + return expr_copy(e); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_no); + if (sym == &symbol_no) + return expr_alloc_one(E_NOT, expr_copy(e)); + } else { + if (sym == &symbol_yes) + return expr_alloc_one(E_NOT, expr_copy(e)); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_yes); + if (sym == &symbol_no) + return expr_copy(e); + } + break; + case E_SYMBOL: + return expr_alloc_comp(type, e->left.sym, sym); + case E_LIST: + case E_RANGE: + case E_NONE: + /* panic */; + } + return NULL; +} + +enum string_value_kind { + k_string, + k_signed, + k_unsigned, +}; + +union string_value { + unsigned long long u; + signed long long s; +}; + +static enum string_value_kind expr_parse_string(const char *str, + enum symbol_type type, + union string_value *val) +{ + char *tail; + enum string_value_kind kind; + + errno = 0; + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + val->s = !strcmp(str, "n") ? 0 : + !strcmp(str, "m") ? 1 : + !strcmp(str, "y") ? 2 : -1; + return k_signed; + case S_INT: + val->s = strtoll(str, &tail, 10); + kind = k_signed; + break; + case S_HEX: + val->u = strtoull(str, &tail, 16); + kind = k_unsigned; + break; + default: + val->s = strtoll(str, &tail, 0); + kind = k_signed; + break; + } + return !errno && !*tail && tail > str && isxdigit(tail[-1]) + ? kind : k_string; +} + +tristate expr_calc_value(struct expr *e) +{ + tristate val1, val2; + const char *str1, *str2; + enum string_value_kind k1 = k_string, k2 = k_string; + union string_value lval = {}, rval = {}; + int res; + + if (!e) + return yes; + + switch (e->type) { + case E_SYMBOL: + sym_calc_value(e->left.sym); + return e->left.sym->curr.tri; + case E_AND: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return EXPR_AND(val1, val2); + case E_OR: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return EXPR_OR(val1, val2); + case E_NOT: + val1 = expr_calc_value(e->left.expr); + return EXPR_NOT(val1); + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + break; + default: + printf("expr_calc_value: %d?\n", e->type); + return no; + } + + sym_calc_value(e->left.sym); + sym_calc_value(e->right.sym); + str1 = sym_get_string_value(e->left.sym); + str2 = sym_get_string_value(e->right.sym); + + if (e->left.sym->type != S_STRING || e->right.sym->type != S_STRING) { + k1 = expr_parse_string(str1, e->left.sym->type, &lval); + k2 = expr_parse_string(str2, e->right.sym->type, &rval); + } + + if (k1 == k_string || k2 == k_string) + res = strcmp(str1, str2); + else if (k1 == k_unsigned || k2 == k_unsigned) + res = (lval.u > rval.u) - (lval.u < rval.u); + else /* if (k1 == k_signed && k2 == k_signed) */ + res = (lval.s > rval.s) - (lval.s < rval.s); + + switch(e->type) { + case E_EQUAL: + return res ? no : yes; + case E_GEQ: + return res >= 0 ? yes : no; + case E_GTH: + return res > 0 ? yes : no; + case E_LEQ: + return res <= 0 ? yes : no; + case E_LTH: + return res < 0 ? yes : no; + case E_UNEQUAL: + return res ? yes : no; + default: + printf("expr_calc_value: relation %d?\n", e->type); + return no; + } +} + +static int expr_compare_type(enum expr_type t1, enum expr_type t2) +{ + if (t1 == t2) + return 0; + switch (t1) { + case E_LEQ: + case E_LTH: + case E_GEQ: + case E_GTH: + if (t2 == E_EQUAL || t2 == E_UNEQUAL) + return 1; + case E_EQUAL: + case E_UNEQUAL: + if (t2 == E_NOT) + return 1; + case E_NOT: + if (t2 == E_AND) + return 1; + case E_AND: + if (t2 == E_OR) + return 1; + case E_OR: + if (t2 == E_LIST) + return 1; + case E_LIST: + if (t2 == 0) + return 1; + default: + return -1; + } + printf("[%dgt%d?]", t1, t2); + return 0; +} + +void expr_print(struct expr *e, + void (*fn)(void *, struct symbol *, const char *), + void *data, int prevtoken) +{ + if (!e) { + fn(data, NULL, "y"); + return; + } + + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, NULL, "("); + switch (e->type) { + case E_SYMBOL: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, ""); + break; + case E_NOT: + fn(data, NULL, "!"); + expr_print(e->left.expr, fn, data, E_NOT); + break; + case E_EQUAL: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, ""); + fn(data, NULL, "="); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_LEQ: + case E_LTH: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, ""); + fn(data, NULL, e->type == E_LEQ ? "<=" : "<"); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_GEQ: + case E_GTH: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, ""); + fn(data, NULL, e->type == E_GEQ ? ">=" : ">"); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_UNEQUAL: + if (e->left.sym->name) + fn(data, e->left.sym, e->left.sym->name); + else + fn(data, NULL, ""); + fn(data, NULL, "!="); + fn(data, e->right.sym, e->right.sym->name); + break; + case E_OR: + expr_print(e->left.expr, fn, data, E_OR); + fn(data, NULL, " || "); + expr_print(e->right.expr, fn, data, E_OR); + break; + case E_AND: + expr_print(e->left.expr, fn, data, E_AND); + fn(data, NULL, " && "); + expr_print(e->right.expr, fn, data, E_AND); + break; + case E_LIST: + fn(data, e->right.sym, e->right.sym->name); + if (e->left.expr) { + fn(data, NULL, " ^ "); + expr_print(e->left.expr, fn, data, E_LIST); + } + break; + case E_RANGE: + fn(data, NULL, "["); + fn(data, e->left.sym, e->left.sym->name); + fn(data, NULL, " "); + fn(data, e->right.sym, e->right.sym->name); + fn(data, NULL, "]"); + break; + default: + { + char buf[32]; + sprintf(buf, "", e->type); + fn(data, NULL, buf); + break; + } + } + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, NULL, ")"); +} + +static void expr_print_file_helper(void *data, struct symbol *sym, const char *str) +{ + xfwrite(str, strlen(str), 1, data); +} + +void expr_fprint(struct expr *e, FILE *out) +{ + expr_print(e, expr_print_file_helper, out, E_NONE); +} + +static void expr_print_gstr_helper(void *data, struct symbol *sym, const char *str) +{ + struct gstr *gs = (struct gstr*)data; + const char *sym_str = NULL; + + if (sym) + sym_str = sym_get_string_value(sym); + + if (gs->max_width) { + unsigned extra_length = strlen(str); + const char *last_cr = strrchr(gs->s, '\n'); + unsigned last_line_length; + + if (sym_str) + extra_length += 4 + strlen(sym_str); + + if (!last_cr) + last_cr = gs->s; + + last_line_length = strlen(gs->s) - (last_cr - gs->s); + + if ((last_line_length + extra_length) > gs->max_width) + str_append(gs, "\\\n"); + } + + str_append(gs, str); + if (sym && sym->type != S_UNKNOWN) + str_printf(gs, " [=%s]", sym_str); +} + +void expr_gstr_print(struct expr *e, struct gstr *gs) +{ + expr_print(e, expr_print_gstr_helper, gs, E_NONE); +} + +/* + * Transform the top level "||" tokens into newlines and prepend each + * line with a minus. This makes expressions much easier to read. + * Suitable for reverse dependency expressions. + */ +static void expr_print_revdep(struct expr *e, + void (*fn)(void *, struct symbol *, const char *), + void *data, tristate pr_type, const char **title) +{ + if (e->type == E_OR) { + expr_print_revdep(e->left.expr, fn, data, pr_type, title); + expr_print_revdep(e->right.expr, fn, data, pr_type, title); + } else if (expr_calc_value(e) == pr_type) { + if (*title) { + fn(data, NULL, *title); + *title = NULL; + } + + fn(data, NULL, " - "); + expr_print(e, fn, data, E_NONE); + fn(data, NULL, "\n"); + } +} + +void expr_gstr_print_revdep(struct expr *e, struct gstr *gs, + tristate pr_type, const char *title) +{ + expr_print_revdep(e, expr_print_gstr_helper, gs, pr_type, &title); +} diff --git a/nemu/tools/kconfig/expr.h b/nemu/tools/kconfig/expr.h new file mode 100644 index 0000000..5c34436 --- /dev/null +++ b/nemu/tools/kconfig/expr.h @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel + */ + +#ifndef EXPR_H +#define EXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "list.h" +#ifndef __cplusplus +#include +#endif + +struct file { + struct file *next; + struct file *parent; + const char *name; + int lineno; +}; + +typedef enum tristate { + no, mod, yes +} tristate; + +enum expr_type { + E_NONE, E_OR, E_AND, E_NOT, + E_EQUAL, E_UNEQUAL, E_LTH, E_LEQ, E_GTH, E_GEQ, + E_LIST, E_SYMBOL, E_RANGE +}; + +union expr_data { + struct expr *expr; + struct symbol *sym; +}; + +struct expr { + enum expr_type type; + union expr_data left, right; +}; + +#define EXPR_OR(dep1, dep2) (((dep1)>(dep2))?(dep1):(dep2)) +#define EXPR_AND(dep1, dep2) (((dep1)<(dep2))?(dep1):(dep2)) +#define EXPR_NOT(dep) (2-(dep)) + +#define expr_list_for_each_sym(l, e, s) \ + for (e = (l); e && (s = e->right.sym); e = e->left.expr) + +struct expr_value { + struct expr *expr; + tristate tri; +}; + +struct symbol_value { + void *val; + tristate tri; +}; + +enum symbol_type { + S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING +}; + +/* enum values are used as index to symbol.def[] */ +enum { + S_DEF_USER, /* main user value */ + S_DEF_AUTO, /* values read from auto.conf */ + S_DEF_DEF3, /* Reserved for UI usage */ + S_DEF_DEF4, /* Reserved for UI usage */ + S_DEF_COUNT +}; + +/* + * Represents a configuration symbol. + * + * Choices are represented as a special kind of symbol and have the + * SYMBOL_CHOICE bit set in 'flags'. + */ +struct symbol { + /* The next symbol in the same bucket in the symbol hash table */ + struct symbol *next; + + /* The name of the symbol, e.g. "FOO" for 'config FOO' */ + char *name; + + /* S_BOOLEAN, S_TRISTATE, ... */ + enum symbol_type type; + + /* + * The calculated value of the symbol. The SYMBOL_VALID bit is set in + * 'flags' when this is up to date. Note that this value might differ + * from the user value set in e.g. a .config file, due to visibility. + */ + struct symbol_value curr; + + /* + * Values for the symbol provided from outside. def[S_DEF_USER] holds + * the .config value. + */ + struct symbol_value def[S_DEF_COUNT]; + + /* + * An upper bound on the tristate value the user can set for the symbol + * if it is a boolean or tristate. Calculated from prompt dependencies, + * which also inherit dependencies from enclosing menus, choices, and + * ifs. If 'n', the user value will be ignored. + * + * Symbols lacking prompts always have visibility 'n'. + */ + tristate visible; + + /* SYMBOL_* flags */ + int flags; + + /* List of properties. See prop_type. */ + struct property *prop; + + /* Dependencies from enclosing menus, choices, and ifs */ + struct expr_value dir_dep; + + /* Reverse dependencies through being selected by other symbols */ + struct expr_value rev_dep; + + /* + * "Weak" reverse dependencies through being implied by other symbols + */ + struct expr_value implied; +}; + +#define for_all_symbols(i, sym) for (i = 0; i < SYMBOL_HASHSIZE; i++) for (sym = symbol_hash[i]; sym; sym = sym->next) + +#define SYMBOL_CONST 0x0001 /* symbol is const */ +#define SYMBOL_CHECK 0x0008 /* used during dependency checking */ +#define SYMBOL_CHOICE 0x0010 /* start of a choice block (null name) */ +#define SYMBOL_CHOICEVAL 0x0020 /* used as a value in a choice block */ +#define SYMBOL_VALID 0x0080 /* set when symbol.curr is calculated */ +#define SYMBOL_OPTIONAL 0x0100 /* choice is optional - values can be 'n' */ +#define SYMBOL_WRITE 0x0200 /* write symbol to file (KCONFIG_CONFIG) */ +#define SYMBOL_CHANGED 0x0400 /* ? */ +#define SYMBOL_WRITTEN 0x0800 /* track info to avoid double-write to .config */ +#define SYMBOL_NO_WRITE 0x1000 /* Symbol for internal use only; it will not be written */ +#define SYMBOL_CHECKED 0x2000 /* used during dependency checking */ +#define SYMBOL_WARNED 0x8000 /* warning has been issued */ + +/* Set when symbol.def[] is used */ +#define SYMBOL_DEF 0x10000 /* First bit of SYMBOL_DEF */ +#define SYMBOL_DEF_USER 0x10000 /* symbol.def[S_DEF_USER] is valid */ +#define SYMBOL_DEF_AUTO 0x20000 /* symbol.def[S_DEF_AUTO] is valid */ +#define SYMBOL_DEF3 0x40000 /* symbol.def[S_DEF_3] is valid */ +#define SYMBOL_DEF4 0x80000 /* symbol.def[S_DEF_4] is valid */ + +/* choice values need to be set before calculating this symbol value */ +#define SYMBOL_NEED_SET_CHOICE_VALUES 0x100000 + +/* Set symbol to y if allnoconfig; used for symbols that hide others */ +#define SYMBOL_ALLNOCONFIG_Y 0x200000 + +#define SYMBOL_MAXLENGTH 256 +#define SYMBOL_HASHSIZE 9973 + +/* A property represent the config options that can be associated + * with a config "symbol". + * Sample: + * config FOO + * default y + * prompt "foo prompt" + * select BAR + * config BAZ + * int "BAZ Value" + * range 1..255 + * + * Please, also check parser.y:print_symbol() when modifying the + * list of property types! + */ +enum prop_type { + P_UNKNOWN, + P_PROMPT, /* prompt "foo prompt" or "BAZ Value" */ + P_COMMENT, /* text associated with a comment */ + P_MENU, /* prompt associated with a menu or menuconfig symbol */ + P_DEFAULT, /* default y */ + P_CHOICE, /* choice value */ + P_SELECT, /* select BAR */ + P_IMPLY, /* imply BAR */ + P_RANGE, /* range 7..100 (for a symbol) */ + P_SYMBOL, /* where a symbol is defined */ +}; + +struct property { + struct property *next; /* next property - null if last */ + enum prop_type type; /* type of property */ + const char *text; /* the prompt value - P_PROMPT, P_MENU, P_COMMENT */ + struct expr_value visible; + struct expr *expr; /* the optional conditional part of the property */ + struct menu *menu; /* the menu the property are associated with + * valid for: P_SELECT, P_RANGE, P_CHOICE, + * P_PROMPT, P_DEFAULT, P_MENU, P_COMMENT */ + struct file *file; /* what file was this property defined */ + int lineno; /* what lineno was this property defined */ +}; + +#define for_all_properties(sym, st, tok) \ + for (st = sym->prop; st; st = st->next) \ + if (st->type == (tok)) +#define for_all_defaults(sym, st) for_all_properties(sym, st, P_DEFAULT) +#define for_all_choices(sym, st) for_all_properties(sym, st, P_CHOICE) +#define for_all_prompts(sym, st) \ + for (st = sym->prop; st; st = st->next) \ + if (st->text) + +/* + * Represents a node in the menu tree, as seen in e.g. menuconfig (though used + * for all front ends). Each symbol, menu, etc. defined in the Kconfig files + * gets a node. A symbol defined in multiple locations gets one node at each + * location. + */ +struct menu { + /* The next menu node at the same level */ + struct menu *next; + + /* The parent menu node, corresponding to e.g. a menu or choice */ + struct menu *parent; + + /* The first child menu node, for e.g. menus and choices */ + struct menu *list; + + /* + * The symbol associated with the menu node. Choices are implemented as + * a special kind of symbol. NULL for menus, comments, and ifs. + */ + struct symbol *sym; + + /* + * The prompt associated with the node. This holds the prompt for a + * symbol as well as the text for a menu or comment, along with the + * type (P_PROMPT, P_MENU, etc.) + */ + struct property *prompt; + + /* + * 'visible if' dependencies. If more than one is given, they will be + * ANDed together. + */ + struct expr *visibility; + + /* + * Ordinary dependencies from e.g. 'depends on' and 'if', ANDed + * together + */ + struct expr *dep; + + /* MENU_* flags */ + unsigned int flags; + + /* Any help text associated with the node */ + char *help; + + /* The location where the menu node appears in the Kconfig files */ + struct file *file; + int lineno; + + /* For use by front ends that need to store auxiliary data */ + void *data; +}; + +/* + * Set on a menu node when the corresponding symbol changes state in some way. + * Can be checked by front ends. + */ +#define MENU_CHANGED 0x0001 + +#define MENU_ROOT 0x0002 + +struct jump_key { + struct list_head entries; + size_t offset; + struct menu *target; + int index; +}; + +#define JUMP_NB 9 + +extern struct file *file_list; +extern struct file *current_file; +struct file *lookup_file(const char *name); + +extern struct symbol symbol_yes, symbol_no, symbol_mod; +extern struct symbol *modules_sym; +extern struct symbol *sym_defconfig_list; +extern int cdebug; +struct expr *expr_alloc_symbol(struct symbol *sym); +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce); +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2); +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2); +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2); +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2); +struct expr *expr_copy(const struct expr *org); +void expr_free(struct expr *e); +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2); +int expr_eq(struct expr *e1, struct expr *e2); +tristate expr_calc_value(struct expr *e); +struct expr *expr_trans_bool(struct expr *e); +struct expr *expr_eliminate_dups(struct expr *e); +struct expr *expr_transform(struct expr *e); +int expr_contains_symbol(struct expr *dep, struct symbol *sym); +bool expr_depends_symbol(struct expr *dep, struct symbol *sym); +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym); + +void expr_fprint(struct expr *e, FILE *out); +struct gstr; /* forward */ +void expr_gstr_print(struct expr *e, struct gstr *gs); +void expr_gstr_print_revdep(struct expr *e, struct gstr *gs, + tristate pr_type, const char *title); + +static inline int expr_is_yes(struct expr *e) +{ + return !e || (e->type == E_SYMBOL && e->left.sym == &symbol_yes); +} + +static inline int expr_is_no(struct expr *e) +{ + return e && (e->type == E_SYMBOL && e->left.sym == &symbol_no); +} + +#ifdef __cplusplus +} +#endif + +#endif /* EXPR_H */ diff --git a/nemu/tools/kconfig/lexer.l b/nemu/tools/kconfig/lexer.l new file mode 100644 index 0000000..240109f --- /dev/null +++ b/nemu/tools/kconfig/lexer.l @@ -0,0 +1,471 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel + */ +%option nostdinit noyywrap never-interactive full ecs +%option 8bit nodefault yylineno +%x ASSIGN_VAL HELP STRING +%{ + +#include +#include +#include +#include +#include +#include + +#include "lkc.h" +#include "parser.tab.h" + +#define YY_DECL static int yylex1(void) + +#define START_STRSIZE 16 + +static struct { + struct file *file; + int lineno; +} current_pos; + +static int prev_prev_token = T_EOL; +static int prev_token = T_EOL; +static char *text; +static int text_size, text_asize; + +struct buffer { + struct buffer *parent; + YY_BUFFER_STATE state; +}; + +static struct buffer *current_buf; + +static int last_ts, first_ts; + +static char *expand_token(const char *in, size_t n); +static void append_expanded_string(const char *in); +static void zconf_endhelp(void); +static void zconf_endfile(void); + +static void new_string(void) +{ + text = xmalloc(START_STRSIZE); + text_asize = START_STRSIZE; + text_size = 0; + *text = 0; +} + +static void append_string(const char *str, int size) +{ + int new_size = text_size + size + 1; + if (new_size > text_asize) { + new_size += START_STRSIZE - 1; + new_size &= -START_STRSIZE; + text = xrealloc(text, new_size); + text_asize = new_size; + } + memcpy(text + text_size, str, size); + text_size += size; + text[text_size] = 0; +} + +static void alloc_string(const char *str, int size) +{ + text = xmalloc(size + 1); + memcpy(text, str, size); + text[size] = 0; +} + +static void warn_ignored_character(char chr) +{ + fprintf(stderr, + "%s:%d:warning: ignoring unsupported character '%c'\n", + current_file->name, yylineno, chr); +} +%} + +n [A-Za-z0-9_-] + +%% + int str = 0; + int ts, i; + +#.* /* ignore comment */ +[ \t]* /* whitespaces */ +\\\n /* escaped new line */ +\n return T_EOL; +"allnoconfig_y" return T_ALLNOCONFIG_Y; +"bool" return T_BOOL; +"choice" return T_CHOICE; +"comment" return T_COMMENT; +"config" return T_CONFIG; +"def_bool" return T_DEF_BOOL; +"def_tristate" return T_DEF_TRISTATE; +"default" return T_DEFAULT; +"defconfig_list" return T_DEFCONFIG_LIST; +"depends" return T_DEPENDS; +"endchoice" return T_ENDCHOICE; +"endif" return T_ENDIF; +"endmenu" return T_ENDMENU; +"help" return T_HELP; +"hex" return T_HEX; +"if" return T_IF; +"imply" return T_IMPLY; +"int" return T_INT; +"mainmenu" return T_MAINMENU; +"menu" return T_MENU; +"menuconfig" return T_MENUCONFIG; +"modules" return T_MODULES; +"on" return T_ON; +"option" return T_OPTION; +"optional" return T_OPTIONAL; +"prompt" return T_PROMPT; +"range" return T_RANGE; +"select" return T_SELECT; +"source" return T_SOURCE; +"string" return T_STRING; +"tristate" return T_TRISTATE; +"visible" return T_VISIBLE; +"||" return T_OR; +"&&" return T_AND; +"=" return T_EQUAL; +"!=" return T_UNEQUAL; +"<" return T_LESS; +"<=" return T_LESS_EQUAL; +">" return T_GREATER; +">=" return T_GREATER_EQUAL; +"!" return T_NOT; +"(" return T_OPEN_PAREN; +")" return T_CLOSE_PAREN; +":=" return T_COLON_EQUAL; +"+=" return T_PLUS_EQUAL; +\"|\' { + str = yytext[0]; + new_string(); + BEGIN(STRING); + } +{n}+ { + alloc_string(yytext, yyleng); + yylval.string = text; + return T_WORD; + } +({n}|$)+ { + /* this token includes at least one '$' */ + yylval.string = expand_token(yytext, yyleng); + if (strlen(yylval.string)) + return T_WORD; + free(yylval.string); + } +. warn_ignored_character(*yytext); + +{ + [^[:blank:]\n]+.* { + alloc_string(yytext, yyleng); + yylval.string = text; + return T_ASSIGN_VAL; + } + \n { BEGIN(INITIAL); return T_EOL; } + . +} + +{ + "$".* append_expanded_string(yytext); + [^$'"\\\n]+ { + append_string(yytext, yyleng); + } + \\.? { + append_string(yytext + 1, yyleng - 1); + } + \'|\" { + if (str == yytext[0]) { + BEGIN(INITIAL); + yylval.string = text; + return T_WORD_QUOTE; + } else + append_string(yytext, 1); + } + \n { + fprintf(stderr, + "%s:%d:warning: multi-line strings not supported\n", + zconf_curname(), zconf_lineno()); + unput('\n'); + BEGIN(INITIAL); + yylval.string = text; + return T_WORD_QUOTE; + } + <> { + BEGIN(INITIAL); + yylval.string = text; + return T_WORD_QUOTE; + } +} + +{ + [ \t]+ { + ts = 0; + for (i = 0; i < yyleng; i++) { + if (yytext[i] == '\t') + ts = (ts & ~7) + 8; + else + ts++; + } + last_ts = ts; + if (first_ts) { + if (ts < first_ts) { + zconf_endhelp(); + return T_HELPTEXT; + } + ts -= first_ts; + while (ts > 8) { + append_string(" ", 8); + ts -= 8; + } + append_string(" ", ts); + } + } + [ \t]*\n/[^ \t\n] { + zconf_endhelp(); + return T_HELPTEXT; + } + [ \t]*\n { + append_string("\n", 1); + } + [^ \t\n].* { + while (yyleng) { + if ((yytext[yyleng-1] != ' ') && (yytext[yyleng-1] != '\t')) + break; + yyleng--; + } + append_string(yytext, yyleng); + if (!first_ts) + first_ts = last_ts; + } + <> { + zconf_endhelp(); + return T_HELPTEXT; + } +} + +<> { + BEGIN(INITIAL); + + if (prev_token != T_EOL && prev_token != T_HELPTEXT) + fprintf(stderr, "%s:%d:warning: no new line at end of file\n", + current_file->name, yylineno); + + if (current_file) { + zconf_endfile(); + return T_EOL; + } + fclose(yyin); + yyterminate(); +} + +%% + +/* second stage lexer */ +int yylex(void) +{ + int token; + +repeat: + token = yylex1(); + + if (prev_token == T_EOL || prev_token == T_HELPTEXT) { + if (token == T_EOL) { + /* Do not pass unneeded T_EOL to the parser. */ + goto repeat; + } else { + /* + * For the parser, update file/lineno at the first token + * of each statement. Generally, \n is a statement + * terminator in Kconfig, but it is not always true + * because \n could be escaped by a backslash. + */ + current_pos.file = current_file; + current_pos.lineno = yylineno; + } + } + + if (prev_prev_token == T_EOL && prev_token == T_WORD && + (token == T_EQUAL || token == T_COLON_EQUAL || token == T_PLUS_EQUAL)) + BEGIN(ASSIGN_VAL); + + prev_prev_token = prev_token; + prev_token = token; + + return token; +} + +static char *expand_token(const char *in, size_t n) +{ + char *out; + int c; + char c2; + const char *rest, *end; + + new_string(); + append_string(in, n); + + /* get the whole line because we do not know the end of token. */ + while ((c = input()) != EOF) { + if (c == '\n') { + unput(c); + break; + } + c2 = c; + append_string(&c2, 1); + } + + rest = text; + out = expand_one_token(&rest); + + /* push back unused characters to the input stream */ + end = rest + strlen(rest); + while (end > rest) + unput(*--end); + + free(text); + + return out; +} + +static void append_expanded_string(const char *str) +{ + const char *end; + char *res; + + str++; + + res = expand_dollar(&str); + + /* push back unused characters to the input stream */ + end = str + strlen(str); + while (end > str) + unput(*--end); + + append_string(res, strlen(res)); + + free(res); +} + +void zconf_starthelp(void) +{ + new_string(); + last_ts = first_ts = 0; + BEGIN(HELP); +} + +static void zconf_endhelp(void) +{ + yylval.string = text; + BEGIN(INITIAL); +} + + +/* + * Try to open specified file with following names: + * ./name + * $(srctree)/name + * The latter is used when srctree is separate from objtree + * when compiling the kernel. + * Return NULL if file is not found. + */ +FILE *zconf_fopen(const char *name) +{ + char *env, fullname[PATH_MAX+1]; + FILE *f; + + f = fopen(name, "r"); + if (!f && name != NULL && name[0] != '/') { + env = getenv(SRCTREE); + if (env) { + snprintf(fullname, sizeof(fullname), + "%s/%s", env, name); + f = fopen(fullname, "r"); + } + } + return f; +} + +void zconf_initscan(const char *name) +{ + yyin = zconf_fopen(name); + if (!yyin) { + fprintf(stderr, "can't find file %s\n", name); + exit(1); + } + + current_buf = xmalloc(sizeof(*current_buf)); + memset(current_buf, 0, sizeof(*current_buf)); + + current_file = file_lookup(name); + yylineno = 1; +} + +void zconf_nextfile(const char *name) +{ + struct file *iter; + struct file *file = file_lookup(name); + struct buffer *buf = xmalloc(sizeof(*buf)); + memset(buf, 0, sizeof(*buf)); + + current_buf->state = YY_CURRENT_BUFFER; + yyin = zconf_fopen(file->name); + if (!yyin) { + fprintf(stderr, "%s:%d: can't open file \"%s\"\n", + zconf_curname(), zconf_lineno(), file->name); + exit(1); + } + yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); + buf->parent = current_buf; + current_buf = buf; + + current_file->lineno = yylineno; + file->parent = current_file; + + for (iter = current_file; iter; iter = iter->parent) { + if (!strcmp(iter->name, file->name)) { + fprintf(stderr, + "Recursive inclusion detected.\n" + "Inclusion path:\n" + " current file : %s\n", file->name); + iter = file; + do { + iter = iter->parent; + fprintf(stderr, " included from: %s:%d\n", + iter->name, iter->lineno - 1); + } while (strcmp(iter->name, file->name)); + exit(1); + } + } + + yylineno = 1; + current_file = file; +} + +static void zconf_endfile(void) +{ + struct buffer *parent; + + current_file = current_file->parent; + if (current_file) + yylineno = current_file->lineno; + + parent = current_buf->parent; + if (parent) { + fclose(yyin); + yy_delete_buffer(YY_CURRENT_BUFFER); + yy_switch_to_buffer(parent->state); + } + free(current_buf); + current_buf = parent; +} + +int zconf_lineno(void) +{ + return current_pos.lineno; +} + +const char *zconf_curname(void) +{ + return current_pos.file ? current_pos.file->name : ""; +} diff --git a/nemu/tools/kconfig/list.h b/nemu/tools/kconfig/list.h new file mode 100644 index 0000000..45cb237 --- /dev/null +++ b/nemu/tools/kconfig/list.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef LIST_H +#define LIST_H + +/* + * Copied from include/linux/... + */ + +#undef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + + +struct list_head { + struct list_head *next, *prev; +}; + + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_head within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_head within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_head within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *_new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = _new; + _new->next = next; + _new->prev = prev; + prev->next = _new; +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *_new, struct list_head *head) +{ + __list_add(_new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head *prev, struct list_head *next) +{ + next->prev = prev; + prev->next = next; +} + +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = (struct list_head*)LIST_POISON1; + entry->prev = (struct list_head*)LIST_POISON2; +} +#endif diff --git a/nemu/tools/kconfig/lkc.h b/nemu/tools/kconfig/lkc.h new file mode 100644 index 0000000..8454649 --- /dev/null +++ b/nemu/tools/kconfig/lkc.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel + */ + +#ifndef LKC_H +#define LKC_H + +#include "expr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lkc_proto.h" + +#define SRCTREE "srctree" + +#ifndef PACKAGE +#define PACKAGE "linux" +#endif + +#ifndef CONFIG_ +#define CONFIG_ "CONFIG_" +#endif +static inline const char *CONFIG_prefix(void) +{ + return getenv( "CONFIG_" ) ?: CONFIG_; +} +#undef CONFIG_ +#define CONFIG_ CONFIG_prefix() + +enum conf_def_mode { + def_default, + def_yes, + def_mod, + def_y2m, + def_m2y, + def_no, + def_random +}; + +extern int yylineno; +void zconfdump(FILE *out); +void zconf_starthelp(void); +FILE *zconf_fopen(const char *name); +void zconf_initscan(const char *name); +void zconf_nextfile(const char *name); +int zconf_lineno(void); +const char *zconf_curname(void); + +/* confdata.c */ +const char *conf_get_configname(void); +void sym_set_change_count(int count); +void sym_add_change_count(int count); +bool conf_set_all_new_symbols(enum conf_def_mode mode); +void conf_rewrite_mod_or_yes(enum conf_def_mode mode); +void set_all_choice_values(struct symbol *csym); + +/* confdata.c and expr.c */ +static inline void xfwrite(const void *str, size_t len, size_t count, FILE *out) +{ + assert(len != 0); + + if (fwrite(str, len, count, out) != count) + fprintf(stderr, "Error in writing or end of file.\n"); +} + +/* util.c */ +struct file *file_lookup(const char *name); +void *xmalloc(size_t size); +void *xcalloc(size_t nmemb, size_t size); +void *xrealloc(void *p, size_t size); +char *xstrdup(const char *s); +char *xstrndup(const char *s, size_t n); + +/* lexer.l */ +int yylex(void); + +struct gstr { + size_t len; + char *s; + /* + * when max_width is not zero long lines in string s (if any) get + * wrapped not to exceed the max_width value + */ + int max_width; +}; +struct gstr str_new(void); +void str_free(struct gstr *gs); +void str_append(struct gstr *gs, const char *s); +void str_printf(struct gstr *gs, const char *fmt, ...); +const char *str_get(struct gstr *gs); + +/* menu.c */ +void _menu_init(void); +void menu_warn(struct menu *menu, const char *fmt, ...); +struct menu *menu_add_menu(void); +void menu_end_menu(void); +void menu_add_entry(struct symbol *sym); +void menu_add_dep(struct expr *dep); +void menu_add_visibility(struct expr *dep); +struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep); +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep); +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep); +void menu_add_option_modules(void); +void menu_add_option_defconfig_list(void); +void menu_add_option_allnoconfig_y(void); +void menu_finalize(struct menu *parent); +void menu_set_type(int type); + +extern struct menu rootmenu; + +bool menu_is_empty(struct menu *menu); +bool menu_is_visible(struct menu *menu); +bool menu_has_prompt(struct menu *menu); +const char *menu_get_prompt(struct menu *menu); +struct menu *menu_get_root_menu(struct menu *menu); +struct menu *menu_get_parent_menu(struct menu *menu); +bool menu_has_help(struct menu *menu); +const char *menu_get_help(struct menu *menu); +struct gstr get_relations_str(struct symbol **sym_arr, struct list_head *head); +void menu_get_ext_help(struct menu *menu, struct gstr *help); + +/* symbol.c */ +void sym_clear_all_valid(void); +struct symbol *sym_choice_default(struct symbol *sym); +struct property *sym_get_range_prop(struct symbol *sym); +const char *sym_get_string_default(struct symbol *sym); +struct symbol *sym_check_deps(struct symbol *sym); +struct symbol *prop_get_symbol(struct property *prop); + +static inline tristate sym_get_tristate_value(struct symbol *sym) +{ + return sym->curr.tri; +} + + +static inline struct symbol *sym_get_choice_value(struct symbol *sym) +{ + return (struct symbol *)sym->curr.val; +} + +static inline bool sym_set_choice_value(struct symbol *ch, struct symbol *chval) +{ + return sym_set_tristate_value(chval, yes); +} + +static inline bool sym_is_choice(struct symbol *sym) +{ + return sym->flags & SYMBOL_CHOICE ? true : false; +} + +static inline bool sym_is_choice_value(struct symbol *sym) +{ + return sym->flags & SYMBOL_CHOICEVAL ? true : false; +} + +static inline bool sym_is_optional(struct symbol *sym) +{ + return sym->flags & SYMBOL_OPTIONAL ? true : false; +} + +static inline bool sym_has_value(struct symbol *sym) +{ + return sym->flags & SYMBOL_DEF_USER ? true : false; +} + +#ifdef __cplusplus +} +#endif + +#endif /* LKC_H */ diff --git a/nemu/tools/kconfig/lkc_proto.h b/nemu/tools/kconfig/lkc_proto.h new file mode 100644 index 0000000..9e81be3 --- /dev/null +++ b/nemu/tools/kconfig/lkc_proto.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include + +/* confdata.c */ +void conf_parse(const char *name); +int conf_read(const char *name); +int conf_read_simple(const char *name, int); +int conf_write_defconfig(const char *name); +int conf_write(const char *name); +int conf_write_autoconf(int overwrite); +bool conf_get_changed(void); +void conf_set_changed_callback(void (*fn)(void)); +void conf_set_message_callback(void (*fn)(const char *s)); + +/* symbol.c */ +extern struct symbol * symbol_hash[SYMBOL_HASHSIZE]; + +struct symbol * sym_lookup(const char *name, int flags); +struct symbol * sym_find(const char *name); +const char * sym_escape_string_value(const char *in); +struct symbol ** sym_re_search(const char *pattern); +const char * sym_type_name(enum symbol_type type); +void sym_calc_value(struct symbol *sym); +enum symbol_type sym_get_type(struct symbol *sym); +bool sym_tristate_within_range(struct symbol *sym,tristate tri); +bool sym_set_tristate_value(struct symbol *sym,tristate tri); +tristate sym_toggle_tristate_value(struct symbol *sym); +bool sym_string_valid(struct symbol *sym, const char *newval); +bool sym_string_within_range(struct symbol *sym, const char *str); +bool sym_set_string_value(struct symbol *sym, const char *newval); +bool sym_is_changeable(struct symbol *sym); +struct property * sym_get_choice_prop(struct symbol *sym); +const char * sym_get_string_value(struct symbol *sym); + +const char * prop_get_type_name(enum prop_type type); + +/* preprocess.c */ +enum variable_flavor { + VAR_SIMPLE, + VAR_RECURSIVE, + VAR_APPEND, +}; +void env_write_dep(FILE *f, const char *auto_conf_name); +void variable_add(const char *name, const char *value, + enum variable_flavor flavor); +void variable_all_del(void); +char *expand_dollar(const char **str); +char *expand_one_token(const char **str); + +/* expr.c */ +void expr_print(struct expr *e, void (*fn)(void *, struct symbol *, const char *), void *data, int prevtoken); diff --git a/nemu/tools/kconfig/lxdialog/checklist.c b/nemu/tools/kconfig/lxdialog/checklist.c new file mode 100644 index 0000000..fd161cf --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/checklist.c @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * checklist.c -- implements the checklist box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension + * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +static int list_width, check_x, item_x; + +/* + * Print list item + */ +static void print_item(WINDOW * win, int choice, int selected) +{ + int i; + char *list_item = malloc(list_width + 1); + + strncpy(list_item, item_str(), list_width - item_x); + list_item[list_width - item_x] = '\0'; + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, choice, 0); + for (i = 0; i < list_width; i++) + waddch(win, ' '); + + wmove(win, choice, check_x); + wattrset(win, selected ? dlg.check_selected.atr + : dlg.check.atr); + if (!item_is_tag(':')) + wprintw(win, "(%c)", item_is_tag('X') ? 'X' : ' '); + + wattrset(win, selected ? dlg.tag_selected.atr : dlg.tag.atr); + mvwaddch(win, choice, item_x, list_item[0]); + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + waddstr(win, list_item + 1); + if (selected) { + wmove(win, choice, check_x + 1); + wrefresh(win); + } + free(list_item); +} + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int choice, int item_no, int scroll, + int y, int x, int height) +{ + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + + if ((height < item_no) && (scroll + choice < item_no - 1)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } +} + +/* + * Display the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, "Select", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with a list of options that can be turned on or off + * in the style of radiolist (only one option turned on at a time). + */ +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height) +{ + int i, x, y, box_x, box_y; + int key = 0, button = 0, choice = 0, scroll = 0, max_choice; + WINDOW *dialog, *list; + + /* which item to highlight */ + item_foreach() { + if (item_is_tag('X')) + choice = item_n(); + if (item_is_selected()) { + choice = item_n(); + break; + } + } + +do_resize: + if (getmaxy(stdscr) < (height + CHECKLIST_HEIGTH_MIN)) + return -ERRDISPLAYTOOSMALL; + if (getmaxx(stdscr) < (width + CHECKLIST_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + max_choice = MIN(list_height, item_count()); + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + list_width = width - 6; + box_y = height - list_height - 5; + box_x = (width - list_width) / 2 - 1; + + /* create new window for the list */ + list = subwin(dialog, list_height, list_width, y + box_y + 1, + x + box_x + 1); + + keypad(list, TRUE); + + /* draw a box around the list items */ + draw_box(dialog, box_y, box_x, list_height + 2, list_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + /* Find length of longest item in order to center checklist */ + check_x = 0; + item_foreach() + check_x = MAX(check_x, strlen(item_str()) + 4); + check_x = MIN(check_x, list_width); + + check_x = (list_width - check_x) / 2; + item_x = check_x + 4; + + if (choice >= list_height) { + scroll = choice - list_height + 1; + choice -= scroll; + } + + /* Print the list */ + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + print_item(list, i, i == choice); + } + + print_arrows(dialog, choice, item_count(), scroll, + box_y, box_x + check_x + 5, list_height); + + print_buttons(dialog, height, width, 0); + + wnoutrefresh(dialog); + wnoutrefresh(list); + doupdate(); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + for (i = 0; i < max_choice; i++) { + item_set(i + scroll); + if (toupper(key) == toupper(item_str()[0])) + break; + } + + if (i < max_choice || key == KEY_UP || key == KEY_DOWN || + key == '+' || key == '-') { + if (key == KEY_UP || key == '-') { + if (!choice) { + if (!scroll) + continue; + /* Scroll list down */ + if (list_height > 1) { + /* De-highlight current first item */ + item_set(scroll); + print_item(list, 0, FALSE); + scrollok(list, TRUE); + wscrl(list, -1); + scrollok(list, FALSE); + } + scroll--; + item_set(scroll); + print_item(list, 0, TRUE); + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice - 1; + } else if (key == KEY_DOWN || key == '+') { + if (choice == max_choice - 1) { + if (scroll + choice >= item_count() - 1) + continue; + /* Scroll list up */ + if (list_height > 1) { + /* De-highlight current last item before scrolling up */ + item_set(scroll + max_choice - 1); + print_item(list, + max_choice - 1, + FALSE); + scrollok(list, TRUE); + wscrl(list, 1); + scrollok(list, FALSE); + } + scroll++; + item_set(scroll + max_choice - 1); + print_item(list, max_choice - 1, TRUE); + + print_arrows(dialog, choice, item_count(), + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice + 1; + } + if (i != choice) { + /* De-highlight current item */ + item_set(scroll + choice); + print_item(list, choice, FALSE); + /* Highlight new item */ + choice = i; + item_set(scroll + choice); + print_item(list, choice, TRUE); + wnoutrefresh(dialog); + wrefresh(list); + } + continue; /* wait for another key press */ + } + switch (key) { + case 'H': + case 'h': + case '?': + button = 1; + /* fall-through */ + case 'S': + case 's': + case ' ': + case '\n': + item_foreach() + item_set_selected(0); + item_set(scroll + choice); + item_set_selected(1); + delwin(list); + delwin(dialog); + return button; + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(list); + delwin(dialog); + on_key_resize(); + goto do_resize; + } + + /* Now, update everything... */ + doupdate(); + } + delwin(list); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/nemu/tools/kconfig/lxdialog/dialog.h b/nemu/tools/kconfig/lxdialog/dialog.h new file mode 100644 index 0000000..68b565e --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/dialog.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * dialog.h -- common declarations for all dialog modules + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __sun__ +#define CURS_MACROS +#endif +#include + +/* + * Colors in ncurses 1.9.9e do not work properly since foreground and + * background colors are OR'd rather than separately masked. This version + * of dialog was hacked to work with ncurses 1.9.9e, making it incompatible + * with standard curses. The simplest fix (to make this work with standard + * curses) uses the wbkgdset() function, not used in the original hack. + * Turn it off if we're building with 1.9.9e, since it just confuses things. + */ +#if defined(NCURSES_VERSION) && defined(_NEED_WRAP) && !defined(GCC_PRINTFLIKE) +#define OLD_NCURSES 1 +#undef wbkgdset +#define wbkgdset(w,p) /*nothing */ +#else +#define OLD_NCURSES 0 +#endif + +#define TR(params) _tracef params + +#define KEY_ESC 27 +#define TAB 9 +#define MAX_LEN 2048 +#define BUF_SIZE (10*1024) +#define MIN(x,y) (x < y ? x : y) +#define MAX(x,y) (x > y ? x : y) + +#ifndef ACS_ULCORNER +#define ACS_ULCORNER '+' +#endif +#ifndef ACS_LLCORNER +#define ACS_LLCORNER '+' +#endif +#ifndef ACS_URCORNER +#define ACS_URCORNER '+' +#endif +#ifndef ACS_LRCORNER +#define ACS_LRCORNER '+' +#endif +#ifndef ACS_HLINE +#define ACS_HLINE '-' +#endif +#ifndef ACS_VLINE +#define ACS_VLINE '|' +#endif +#ifndef ACS_LTEE +#define ACS_LTEE '+' +#endif +#ifndef ACS_RTEE +#define ACS_RTEE '+' +#endif +#ifndef ACS_UARROW +#define ACS_UARROW '^' +#endif +#ifndef ACS_DARROW +#define ACS_DARROW 'v' +#endif + +/* error return codes */ +#define ERRDISPLAYTOOSMALL (KEY_MAX + 1) + +/* + * Color definitions + */ +struct dialog_color { + chtype atr; /* Color attribute */ + int fg; /* foreground */ + int bg; /* background */ + int hl; /* highlight this item */ +}; + +struct subtitle_list { + struct subtitle_list *next; + const char *text; +}; + +struct dialog_info { + const char *backtitle; + struct subtitle_list *subtitles; + struct dialog_color screen; + struct dialog_color shadow; + struct dialog_color dialog; + struct dialog_color title; + struct dialog_color border; + struct dialog_color button_active; + struct dialog_color button_inactive; + struct dialog_color button_key_active; + struct dialog_color button_key_inactive; + struct dialog_color button_label_active; + struct dialog_color button_label_inactive; + struct dialog_color inputbox; + struct dialog_color inputbox_border; + struct dialog_color searchbox; + struct dialog_color searchbox_title; + struct dialog_color searchbox_border; + struct dialog_color position_indicator; + struct dialog_color menubox; + struct dialog_color menubox_border; + struct dialog_color item; + struct dialog_color item_selected; + struct dialog_color tag; + struct dialog_color tag_selected; + struct dialog_color tag_key; + struct dialog_color tag_key_selected; + struct dialog_color check; + struct dialog_color check_selected; + struct dialog_color uarrow; + struct dialog_color darrow; +}; + +/* + * Global variables + */ +extern struct dialog_info dlg; +extern char dialog_input_result[]; +extern int saved_x, saved_y; /* Needed in signal handler in mconf.c */ + +/* + * Function prototypes + */ + +/* item list as used by checklist and menubox */ +void item_reset(void); +void item_make(const char *fmt, ...); +void item_add_str(const char *fmt, ...); +void item_set_tag(char tag); +void item_set_data(void *p); +void item_set_selected(int val); +int item_activate_selected(void); +void *item_data(void); +char item_tag(void); + +/* item list manipulation for lxdialog use */ +#define MAXITEMSTR 200 +struct dialog_item { + char str[MAXITEMSTR]; /* prompt displayed */ + char tag; + void *data; /* pointer to menu item - used by menubox+checklist */ + int selected; /* Set to 1 by dialog_*() function if selected. */ +}; + +/* list of lialog_items */ +struct dialog_list { + struct dialog_item node; + struct dialog_list *next; +}; + +extern struct dialog_list *item_cur; +extern struct dialog_list item_nil; +extern struct dialog_list *item_head; + +int item_count(void); +void item_set(int n); +int item_n(void); +const char *item_str(void); +int item_is_selected(void); +int item_is_tag(char tag); +#define item_foreach() \ + for (item_cur = item_head ? item_head: item_cur; \ + item_cur && (item_cur != &item_nil); item_cur = item_cur->next) + +/* generic key handlers */ +int on_key_esc(WINDOW *win); +int on_key_resize(void); + +/* minimum (re)size values */ +#define CHECKLIST_HEIGTH_MIN 6 /* For dialog_checklist() */ +#define CHECKLIST_WIDTH_MIN 6 +#define INPUTBOX_HEIGTH_MIN 2 /* For dialog_inputbox() */ +#define INPUTBOX_WIDTH_MIN 2 +#define MENUBOX_HEIGTH_MIN 15 /* For dialog_menu() */ +#define MENUBOX_WIDTH_MIN 65 +#define TEXTBOX_HEIGTH_MIN 8 /* For dialog_textbox() */ +#define TEXTBOX_WIDTH_MIN 8 +#define YESNO_HEIGTH_MIN 4 /* For dialog_yesno() */ +#define YESNO_WIDTH_MIN 4 +#define WINDOW_HEIGTH_MIN 19 /* For init_dialog() */ +#define WINDOW_WIDTH_MIN 80 + +int init_dialog(const char *backtitle); +void set_dialog_backtitle(const char *backtitle); +void set_dialog_subtitles(struct subtitle_list *subtitles); +void end_dialog(int x, int y); +void attr_clear(WINDOW * win, int height, int width, chtype attr); +void dialog_clear(void); +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x); +void print_button(WINDOW * win, const char *label, int y, int x, int selected); +void print_title(WINDOW *dialog, const char *title, int width); +void draw_box(WINDOW * win, int y, int x, int height, int width, chtype box, + chtype border); +void draw_shadow(WINDOW * win, int y, int x, int height, int width); + +int first_alpha(const char *string, const char *exempt); +int dialog_yesno(const char *title, const char *prompt, int height, int width); +int dialog_msgbox(const char *title, const char *prompt, int height, + int width, int pause); + + +typedef void (*update_text_fn)(char *buf, size_t start, size_t end, void + *_data); +int dialog_textbox(const char *title, char *tbuf, int initial_height, + int initial_width, int *keys, int *_vscroll, int *_hscroll, + update_text_fn update_text, void *data); +int dialog_menu(const char *title, const char *prompt, + const void *selected, int *s_scroll); +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height); +int dialog_inputbox(const char *title, const char *prompt, int height, + int width, const char *init); + +/* + * This is the base for fictitious keys, which activate + * the buttons. + * + * Mouse-generated keys are the following: + * -- the first 32 are used as numbers, in addition to '0'-'9' + * -- the lowercase are used to signal mouse-enter events (M_EVENT + 'o') + * -- uppercase chars are used to invoke the button (M_EVENT + 'O') + */ +#define M_EVENT (KEY_MAX+1) diff --git a/nemu/tools/kconfig/lxdialog/inputbox.c b/nemu/tools/kconfig/lxdialog/inputbox.c new file mode 100644 index 0000000..1dcfb28 --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/inputbox.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * inputbox.c -- implements the input box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +char dialog_input_result[MAX_LEN + 1]; + +/* + * Print the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, " Ok ", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box for inputing a string + */ +int dialog_inputbox(const char *title, const char *prompt, int height, int width, + const char *init) +{ + int i, x, y, box_y, box_x, box_width; + int input_x = 0, key = 0, button = -1; + int show_x, len, pos; + char *instr = dialog_input_result; + WINDOW *dialog; + + if (!init) + instr[0] = '\0'; + else + strcpy(instr, init); + +do_resize: + if (getmaxy(stdscr) <= (height - INPUTBOX_HEIGTH_MIN)) + return -ERRDISPLAYTOOSMALL; + if (getmaxx(stdscr) <= (width - INPUTBOX_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + /* Draw the input field box */ + box_width = width - 6; + getyx(dialog, y, x); + box_y = y + 2; + box_x = (width - box_width) / 2; + draw_box(dialog, y + 1, box_x - 1, 3, box_width + 2, + dlg.dialog.atr, dlg.border.atr); + + print_buttons(dialog, height, width, 0); + + /* Set up the initial value */ + wmove(dialog, box_y, box_x); + wattrset(dialog, dlg.inputbox.atr); + + len = strlen(instr); + pos = len; + + if (len >= box_width) { + show_x = len - box_width + 1; + input_x = box_width - 1; + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr[show_x + i]); + } else { + show_x = 0; + input_x = len; + waddstr(dialog, instr); + } + + wmove(dialog, box_y, box_x + input_x); + + wrefresh(dialog); + + while (key != KEY_ESC) { + key = wgetch(dialog); + + if (button == -1) { /* Input box selected */ + switch (key) { + case TAB: + case KEY_UP: + case KEY_DOWN: + break; + case KEY_BACKSPACE: + case 8: /* ^H */ + case 127: /* ^? */ + if (pos) { + wattrset(dialog, dlg.inputbox.atr); + if (input_x == 0) { + show_x--; + } else + input_x--; + + if (pos < len) { + for (i = pos - 1; i < len; i++) { + instr[i] = instr[i+1]; + } + } + + pos--; + len--; + instr[len] = '\0'; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } + continue; + case KEY_LEFT: + if (pos > 0) { + if (input_x > 0) { + wmove(dialog, box_y, --input_x + box_x); + } else if (input_x == 0) { + show_x--; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, box_x); + } + pos--; + } + continue; + case KEY_RIGHT: + if (pos < len) { + if (input_x < box_width - 1) { + wmove(dialog, box_y, ++input_x + box_x); + } else if (input_x == box_width - 1) { + show_x++; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + } + pos++; + } + continue; + default: + if (key < 0x100 && isprint(key)) { + if (len < MAX_LEN) { + wattrset(dialog, dlg.inputbox.atr); + if (pos < len) { + for (i = len; i > pos; i--) + instr[i] = instr[i-1]; + instr[pos] = key; + } else { + instr[len] = key; + } + pos++; + len++; + instr[len] = '\0'; + + if (input_x == box_width - 1) { + show_x++; + } else { + input_x++; + } + + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) { + if (!instr[show_x + i]) { + waddch(dialog, ' '); + break; + } + waddch(dialog, instr[show_x + i]); + } + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } else + flash(); /* Alarm user about overflow */ + continue; + } + } + } + switch (key) { + case 'O': + case 'o': + delwin(dialog); + return 0; + case 'H': + case 'h': + delwin(dialog); + return 1; + case KEY_UP: + case KEY_LEFT: + switch (button) { + case -1: + button = 1; /* Indicates "Help" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 0: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + case 1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + } + break; + case TAB: + case KEY_DOWN: + case KEY_RIGHT: + switch (button) { + case -1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + case 0: + button = 1; /* Indicates "Help" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 1: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + } + break; + case ' ': + case '\n': + delwin(dialog); + return (button == -1 ? 0 : button); + case 'X': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(dialog); + on_key_resize(); + goto do_resize; + } + } + + delwin(dialog); + return KEY_ESC; /* ESC pressed */ +} diff --git a/nemu/tools/kconfig/lxdialog/menubox.c b/nemu/tools/kconfig/lxdialog/menubox.c new file mode 100644 index 0000000..58c2f8a --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/menubox.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * menubox.c -- implements the menu box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + */ + +/* + * Changes by Clifford Wolf (god@clifford.at) + * + * [ 1998-06-13 ] + * + * *) A bugfix for the Page-Down problem + * + * *) Formerly when I used Page Down and Page Up, the cursor would be set + * to the first position in the menu box. Now lxdialog is a bit + * smarter and works more like other menu systems (just have a look at + * it). + * + * *) Formerly if I selected something my scrolling would be broken because + * lxdialog is re-invoked by the Menuconfig shell script, can't + * remember the last scrolling position, and just sets it so that the + * cursor is at the bottom of the box. Now it writes the temporary file + * lxdialog.scrltmp which contains this information. The file is + * deleted by lxdialog if the user leaves a submenu or enters a new + * one, but it would be nice if Menuconfig could make another "rm -f" + * just to be sure. Just try it out - you will recognise a difference! + * + * [ 1998-06-14 ] + * + * *) Now lxdialog is crash-safe against broken "lxdialog.scrltmp" files + * and menus change their size on the fly. + * + * *) If for some reason the last scrolling position is not saved by + * lxdialog, it sets the scrolling so that the selected item is in the + * middle of the menu box, not at the bottom. + * + * 02 January 1999, Michael Elizabeth Chastain (mec@shout.net) + * Reset 'scroll' to 0 if the value from lxdialog.scrltmp is bogus. + * This fixes a bug in Menuconfig where using ' ' to descend into menus + * would leave mis-synchronized lxdialog.scrltmp files lying around, + * fscanf would read in 'scroll', and eventually that value would get used. + */ + +#include "dialog.h" + +static int menu_width, item_x; + +/* + * Print menu item + */ +static void do_print_item(WINDOW * win, const char *item, int line_y, + int selected, int hotkey) +{ + int j; + char *menu_item = malloc(menu_width + 1); + + strncpy(menu_item, item, menu_width - item_x); + menu_item[menu_width - item_x] = '\0'; + j = first_alpha(menu_item, "YyNnMmHh"); + + /* Clear 'residue' of last item */ + wattrset(win, dlg.menubox.atr); + wmove(win, line_y, 0); +#if OLD_NCURSES + { + int i; + for (i = 0; i < menu_width; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif + wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr); + mvwaddstr(win, line_y, item_x, menu_item); + if (hotkey) { + wattrset(win, selected ? dlg.tag_key_selected.atr + : dlg.tag_key.atr); + mvwaddch(win, line_y, item_x + j, menu_item[j]); + } + if (selected) { + wmove(win, line_y, item_x + 1); + } + free(menu_item); + wrefresh(win); +} + +#define print_item(index, choice, selected) \ +do { \ + item_set(index); \ + do_print_item(menu, item_str(), choice, selected, !item_is_tag(':')); \ +} while (0) + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int item_no, int scroll, int y, int x, + int height) +{ + int cur_y, cur_x; + + getyx(win, cur_y, cur_x); + + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, dlg.uarrow.atr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, dlg.menubox.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + wrefresh(win); + + if ((height < item_no) && (scroll + height < item_no)) { + wattrset(win, dlg.darrow.atr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, dlg.menubox_border.atr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + wmove(win, cur_y, cur_x); + wrefresh(win); +} + +/* + * Display the termination buttons. + */ +static void print_buttons(WINDOW * win, int height, int width, int selected) +{ + int x = width / 2 - 28; + int y = height - 2; + + print_button(win, "Select", y, x, selected == 0); + print_button(win, " Exit ", y, x + 12, selected == 1); + print_button(win, " Help ", y, x + 24, selected == 2); + print_button(win, " Save ", y, x + 36, selected == 3); + print_button(win, " Load ", y, x + 48, selected == 4); + + wmove(win, y, x + 1 + 12 * selected); + wrefresh(win); +} + +/* scroll up n lines (n may be negative) */ +static void do_scroll(WINDOW *win, int *scroll, int n) +{ + /* Scroll menu up */ + scrollok(win, TRUE); + wscrl(win, n); + scrollok(win, FALSE); + *scroll = *scroll + n; + wrefresh(win); +} + +/* + * Display a menu for choosing among a number of options + */ +int dialog_menu(const char *title, const char *prompt, + const void *selected, int *s_scroll) +{ + int i, j, x, y, box_x, box_y; + int height, width, menu_height; + int key = 0, button = 0, scroll = 0, choice = 0; + int first_item = 0, max_choice; + WINDOW *dialog, *menu; + +do_resize: + height = getmaxy(stdscr); + width = getmaxx(stdscr); + if (height < MENUBOX_HEIGTH_MIN || width < MENUBOX_WIDTH_MIN) + return -ERRDISPLAYTOOSMALL; + + height -= 4; + width -= 5; + menu_height = height - 10; + + max_choice = MIN(menu_height, item_count()); + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + wbkgdset(dialog, dlg.dialog.atr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + menu_width = width - 6; + box_y = height - menu_height - 5; + box_x = (width - menu_width) / 2 - 1; + + /* create new window for the menu */ + menu = subwin(dialog, menu_height, menu_width, + y + box_y + 1, x + box_x + 1); + keypad(menu, TRUE); + + /* draw a box around the menu items */ + draw_box(dialog, box_y, box_x, menu_height + 2, menu_width + 2, + dlg.menubox_border.atr, dlg.menubox.atr); + + if (menu_width >= 80) + item_x = (menu_width - 70) / 2; + else + item_x = 4; + + /* Set choice to default item */ + item_foreach() + if (selected && (selected == item_data())) + choice = item_n(); + /* get the saved scroll info */ + scroll = *s_scroll; + if ((scroll <= choice) && (scroll + max_choice > choice) && + (scroll >= 0) && (scroll + max_choice <= item_count())) { + first_item = scroll; + choice = choice - scroll; + } else { + scroll = 0; + } + if ((choice >= max_choice)) { + if (choice >= item_count() - max_choice / 2) + scroll = first_item = item_count() - max_choice; + else + scroll = first_item = choice - max_choice / 2; + choice = choice - scroll; + } + + /* Print the menu */ + for (i = 0; i < max_choice; i++) { + print_item(first_item + i, i, i == choice); + } + + wnoutrefresh(menu); + + print_arrows(dialog, item_count(), scroll, + box_y, box_x + item_x + 1, menu_height); + + print_buttons(dialog, height, width, 0); + wmove(menu, choice, item_x + 1); + wrefresh(menu); + + while (key != KEY_ESC) { + key = wgetch(menu); + + if (key < 256 && isalpha(key)) + key = tolower(key); + + if (strchr("ynmh", key)) + i = max_choice; + else { + for (i = choice + 1; i < max_choice; i++) { + item_set(scroll + i); + j = first_alpha(item_str(), "YyNnMmHh"); + if (key == tolower(item_str()[j])) + break; + } + if (i == max_choice) + for (i = 0; i < max_choice; i++) { + item_set(scroll + i); + j = first_alpha(item_str(), "YyNnMmHh"); + if (key == tolower(item_str()[j])) + break; + } + } + + if (item_count() != 0 && + (i < max_choice || + key == KEY_UP || key == KEY_DOWN || + key == '-' || key == '+' || + key == KEY_PPAGE || key == KEY_NPAGE)) { + /* Remove highligt of current item */ + print_item(scroll + choice, choice, FALSE); + + if (key == KEY_UP || key == '-') { + if (choice < 2 && scroll) { + /* Scroll menu down */ + do_scroll(menu, &scroll, -1); + + print_item(scroll, 0, FALSE); + } else + choice = MAX(choice - 1, 0); + + } else if (key == KEY_DOWN || key == '+') { + print_item(scroll+choice, choice, FALSE); + + if ((choice > max_choice - 3) && + (scroll + max_choice < item_count())) { + /* Scroll menu up */ + do_scroll(menu, &scroll, 1); + + print_item(scroll+max_choice - 1, + max_choice - 1, FALSE); + } else + choice = MIN(choice + 1, max_choice - 1); + + } else if (key == KEY_PPAGE) { + scrollok(menu, TRUE); + for (i = 0; (i < max_choice); i++) { + if (scroll > 0) { + do_scroll(menu, &scroll, -1); + print_item(scroll, 0, FALSE); + } else { + if (choice > 0) + choice--; + } + } + + } else if (key == KEY_NPAGE) { + for (i = 0; (i < max_choice); i++) { + if (scroll + max_choice < item_count()) { + do_scroll(menu, &scroll, 1); + print_item(scroll+max_choice-1, + max_choice - 1, FALSE); + } else { + if (choice + 1 < max_choice) + choice++; + } + } + } else + choice = i; + + print_item(scroll + choice, choice, TRUE); + + print_arrows(dialog, item_count(), scroll, + box_y, box_x + item_x + 1, menu_height); + + wnoutrefresh(dialog); + wrefresh(menu); + + continue; /* wait for another key press */ + } + + switch (key) { + case KEY_LEFT: + case TAB: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 4 : (button > 4 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(menu); + break; + case ' ': + case 's': + case 'y': + case 'n': + case 'm': + case '/': + case 'h': + case '?': + case 'z': + case '\n': + /* save scroll info */ + *s_scroll = scroll; + delwin(menu); + delwin(dialog); + item_set(scroll + choice); + item_set_selected(1); + switch (key) { + case 'h': + case '?': + return 2; + case 's': + case 'y': + return 5; + case 'n': + return 6; + case 'm': + return 7; + case ' ': + return 8; + case '/': + return 9; + case 'z': + return 10; + case '\n': + return button; + } + return 0; + case 'e': + case 'x': + key = KEY_ESC; + break; + case KEY_ESC: + key = on_key_esc(menu); + break; + case KEY_RESIZE: + on_key_resize(); + delwin(menu); + delwin(dialog); + goto do_resize; + } + } + delwin(menu); + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/nemu/tools/kconfig/lxdialog/textbox.c b/nemu/tools/kconfig/lxdialog/textbox.c new file mode 100644 index 0000000..4e339b1 --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/textbox.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * textbox.c -- implements the text box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +static void back_lines(int n); +static void print_page(WINDOW *win, int height, int width, update_text_fn + update_text, void *data); +static void print_line(WINDOW *win, int row, int width); +static char *get_line(void); +static void print_position(WINDOW * win); + +static int hscroll; +static int begin_reached, end_reached, page_length; +static char *buf; +static char *page; + +/* + * refresh window content + */ +static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, + int cur_y, int cur_x, update_text_fn update_text, + void *data) +{ + print_page(box, boxh, boxw, update_text, data); + print_position(dialog); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); +} + + +/* + * Display text from a file in a dialog box. + * + * keys is a null-terminated array + * update_text() may not add or remove any '\n' or '\0' in tbuf + */ +int dialog_textbox(const char *title, char *tbuf, int initial_height, + int initial_width, int *keys, int *_vscroll, int *_hscroll, + update_text_fn update_text, void *data) +{ + int i, x, y, cur_x, cur_y, key = 0; + int height, width, boxh, boxw; + WINDOW *dialog, *box; + bool done = false; + + begin_reached = 1; + end_reached = 0; + page_length = 0; + hscroll = 0; + buf = tbuf; + page = buf; /* page is pointer to start of page to be displayed */ + + if (_vscroll && *_vscroll) { + begin_reached = 0; + + for (i = 0; i < *_vscroll; i++) + get_line(); + } + if (_hscroll) + hscroll = *_hscroll; + +do_resize: + getmaxyx(stdscr, height, width); + if (height < TEXTBOX_HEIGTH_MIN || width < TEXTBOX_WIDTH_MIN) + return -ERRDISPLAYTOOSMALL; + if (initial_height != 0) + height = initial_height; + else + if (height > 4) + height -= 4; + else + height = 0; + if (initial_width != 0) + width = initial_width; + else + if (width > 5) + width -= 5; + else + width = 0; + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + /* Create window for box region, used for scrolling text */ + boxh = height - 4; + boxw = width - 2; + box = subwin(dialog, boxh, boxw, y + 1, x + 1); + wattrset(box, dlg.dialog.atr); + wbkgdset(box, dlg.dialog.atr & A_COLOR); + + keypad(box, TRUE); + + /* register the new window, along with its borders */ + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + wbkgdset(dialog, dlg.dialog.atr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + print_button(dialog, " Exit ", height - 2, width / 2 - 4, TRUE); + wnoutrefresh(dialog); + getyx(dialog, cur_y, cur_x); /* Save cursor position */ + + /* Print first page of text */ + attr_clear(box, boxh, boxw, dlg.dialog.atr); + refresh_text_box(dialog, box, boxh, boxw, cur_y, cur_x, update_text, + data); + + while (!done) { + key = wgetch(dialog); + switch (key) { + case 'E': /* Exit */ + case 'e': + case 'X': + case 'x': + case 'q': + case '\n': + done = true; + break; + case 'g': /* First page */ + case KEY_HOME: + if (!begin_reached) { + begin_reached = 1; + page = buf; + refresh_text_box(dialog, box, boxh, boxw, + cur_y, cur_x, update_text, + data); + } + break; + case 'G': /* Last page */ + case KEY_END: + + end_reached = 1; + /* point to last char in buf */ + page = buf + strlen(buf); + back_lines(boxh); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'K': /* Previous line */ + case 'k': + case KEY_UP: + if (begin_reached) + break; + + back_lines(page_length + 1); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'B': /* Previous page */ + case 'b': + case 'u': + case KEY_PPAGE: + if (begin_reached) + break; + back_lines(page_length + boxh); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'J': /* Next line */ + case 'j': + case KEY_DOWN: + if (end_reached) + break; + + back_lines(page_length - 1); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case KEY_NPAGE: /* Next page */ + case ' ': + case 'd': + if (end_reached) + break; + + begin_reached = 0; + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case '0': /* Beginning of line */ + case 'H': /* Scroll left */ + case 'h': + case KEY_LEFT: + if (hscroll <= 0) + break; + + if (key == '0') + hscroll = 0; + else + hscroll--; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case 'L': /* Scroll right */ + case 'l': + case KEY_RIGHT: + if (hscroll >= MAX_LEN) + break; + hscroll++; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + refresh_text_box(dialog, box, boxh, boxw, cur_y, + cur_x, update_text, data); + break; + case KEY_ESC: + if (on_key_esc(dialog) == KEY_ESC) + done = true; + break; + case KEY_RESIZE: + back_lines(height); + delwin(box); + delwin(dialog); + on_key_resize(); + goto do_resize; + default: + for (i = 0; keys[i]; i++) { + if (key == keys[i]) { + done = true; + break; + } + } + } + } + delwin(box); + delwin(dialog); + if (_vscroll) { + const char *s; + + s = buf; + *_vscroll = 0; + back_lines(page_length); + while (s < page && (s = strchr(s, '\n'))) { + (*_vscroll)++; + s++; + } + } + if (_hscroll) + *_hscroll = hscroll; + return key; +} + +/* + * Go back 'n' lines in text. Called by dialog_textbox(). + * 'page' will be updated to point to the desired line in 'buf'. + */ +static void back_lines(int n) +{ + int i; + + begin_reached = 0; + /* Go back 'n' lines */ + for (i = 0; i < n; i++) { + if (*page == '\0') { + if (end_reached) { + end_reached = 0; + continue; + } + } + if (page == buf) { + begin_reached = 1; + return; + } + page--; + do { + if (page == buf) { + begin_reached = 1; + return; + } + page--; + } while (*page != '\n'); + page++; + } +} + +/* + * Print a new page of text. + */ +static void print_page(WINDOW *win, int height, int width, update_text_fn + update_text, void *data) +{ + int i, passed_end = 0; + + if (update_text) { + char *end; + + for (i = 0; i < height; i++) + get_line(); + end = page; + back_lines(height); + update_text(buf, page - buf, end - buf, data); + } + + page_length = 0; + for (i = 0; i < height; i++) { + print_line(win, i, width); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + wnoutrefresh(win); +} + +/* + * Print a new line of text. + */ +static void print_line(WINDOW * win, int row, int width) +{ + char *line; + + line = get_line(); + line += MIN(strlen(line), hscroll); /* Scroll horizontally */ + wmove(win, row, 0); /* move cursor to correct line */ + waddch(win, ' '); + waddnstr(win, line, MIN(strlen(line), width - 2)); + + /* Clear 'residue' of previous line */ +#if OLD_NCURSES + { + int x = getcurx(win); + int i; + for (i = 0; i < width - x; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif +} + +/* + * Return current line of text. Called by dialog_textbox() and print_line(). + * 'page' should point to start of current line before calling, and will be + * updated to point to start of next line. + */ +static char *get_line(void) +{ + int i = 0; + static char line[MAX_LEN + 1]; + + end_reached = 0; + while (*page != '\n') { + if (*page == '\0') { + end_reached = 1; + break; + } else if (i < MAX_LEN) + line[i++] = *(page++); + else { + /* Truncate lines longer than MAX_LEN characters */ + if (i == MAX_LEN) + line[i++] = '\0'; + page++; + } + } + if (i <= MAX_LEN) + line[i] = '\0'; + if (!end_reached) + page++; /* move past '\n' */ + + return line; +} + +/* + * Print current position + */ +static void print_position(WINDOW * win) +{ + int percent; + + wattrset(win, dlg.position_indicator.atr); + wbkgdset(win, dlg.position_indicator.atr & A_COLOR); + percent = (page - buf) * 100 / strlen(buf); + wmove(win, getmaxy(win) - 3, getmaxx(win) - 9); + wprintw(win, "(%3d%%)", percent); +} diff --git a/nemu/tools/kconfig/lxdialog/util.c b/nemu/tools/kconfig/lxdialog/util.c new file mode 100644 index 0000000..1b490d4 --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/util.c @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * util.c + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include + +#include "dialog.h" + +/* Needed in signal handler in mconf.c */ +int saved_x, saved_y; + +struct dialog_info dlg; + +static void set_mono_theme(void) +{ + dlg.screen.atr = A_NORMAL; + dlg.shadow.atr = A_NORMAL; + dlg.dialog.atr = A_NORMAL; + dlg.title.atr = A_BOLD; + dlg.border.atr = A_NORMAL; + dlg.button_active.atr = A_REVERSE; + dlg.button_inactive.atr = A_DIM; + dlg.button_key_active.atr = A_REVERSE; + dlg.button_key_inactive.atr = A_BOLD; + dlg.button_label_active.atr = A_REVERSE; + dlg.button_label_inactive.atr = A_NORMAL; + dlg.inputbox.atr = A_NORMAL; + dlg.inputbox_border.atr = A_NORMAL; + dlg.searchbox.atr = A_NORMAL; + dlg.searchbox_title.atr = A_BOLD; + dlg.searchbox_border.atr = A_NORMAL; + dlg.position_indicator.atr = A_BOLD; + dlg.menubox.atr = A_NORMAL; + dlg.menubox_border.atr = A_NORMAL; + dlg.item.atr = A_NORMAL; + dlg.item_selected.atr = A_REVERSE; + dlg.tag.atr = A_BOLD; + dlg.tag_selected.atr = A_REVERSE; + dlg.tag_key.atr = A_BOLD; + dlg.tag_key_selected.atr = A_REVERSE; + dlg.check.atr = A_BOLD; + dlg.check_selected.atr = A_REVERSE; + dlg.uarrow.atr = A_BOLD; + dlg.darrow.atr = A_BOLD; +} + +#define DLG_COLOR(dialog, f, b, h) \ +do { \ + dlg.dialog.fg = (f); \ + dlg.dialog.bg = (b); \ + dlg.dialog.hl = (h); \ +} while (0) + +static void set_classic_theme(void) +{ + DLG_COLOR(screen, COLOR_CYAN, COLOR_BLUE, true); + DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, true); + DLG_COLOR(dialog, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(title, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(button_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(button_inactive, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(button_key_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_WHITE, false); + DLG_COLOR(button_label_active, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(button_label_inactive, COLOR_BLACK, COLOR_WHITE, true); + DLG_COLOR(inputbox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(inputbox_border, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(searchbox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(searchbox_title, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(searchbox_border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(position_indicator, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(menubox, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(menubox_border, COLOR_WHITE, COLOR_WHITE, true); + DLG_COLOR(item, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(item_selected, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(tag, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(tag_key, COLOR_YELLOW, COLOR_WHITE, true); + DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(check, COLOR_BLACK, COLOR_WHITE, false); + DLG_COLOR(check_selected, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(uarrow, COLOR_GREEN, COLOR_WHITE, true); + DLG_COLOR(darrow, COLOR_GREEN, COLOR_WHITE, true); +} + +static void set_blackbg_theme(void) +{ + DLG_COLOR(screen, COLOR_RED, COLOR_BLACK, true); + DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, false); + DLG_COLOR(dialog, COLOR_WHITE, COLOR_BLACK, false); + DLG_COLOR(title, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(button_active, COLOR_YELLOW, COLOR_RED, false); + DLG_COLOR(button_inactive, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_RED, true); + DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_RED, false); + DLG_COLOR(button_label_inactive, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(inputbox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(inputbox_border, COLOR_YELLOW, COLOR_BLACK, false); + + DLG_COLOR(searchbox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(searchbox_title, COLOR_YELLOW, COLOR_BLACK, true); + DLG_COLOR(searchbox_border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(position_indicator, COLOR_RED, COLOR_BLACK, false); + + DLG_COLOR(menubox, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(menubox_border, COLOR_BLACK, COLOR_BLACK, true); + + DLG_COLOR(item, COLOR_WHITE, COLOR_BLACK, false); + DLG_COLOR(item_selected, COLOR_WHITE, COLOR_RED, false); + + DLG_COLOR(tag, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_RED, true); + DLG_COLOR(tag_key, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_RED, true); + + DLG_COLOR(check, COLOR_YELLOW, COLOR_BLACK, false); + DLG_COLOR(check_selected, COLOR_YELLOW, COLOR_RED, true); + + DLG_COLOR(uarrow, COLOR_RED, COLOR_BLACK, false); + DLG_COLOR(darrow, COLOR_RED, COLOR_BLACK, false); +} + +static void set_bluetitle_theme(void) +{ + set_classic_theme(); + DLG_COLOR(title, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_BLUE, true); + DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_BLUE, true); + DLG_COLOR(searchbox_title, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(position_indicator, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(tag, COLOR_BLUE, COLOR_WHITE, true); + DLG_COLOR(tag_key, COLOR_BLUE, COLOR_WHITE, true); + +} + +/* + * Select color theme + */ +static int set_theme(const char *theme) +{ + int use_color = 1; + if (!theme) + set_bluetitle_theme(); + else if (strcmp(theme, "classic") == 0) + set_classic_theme(); + else if (strcmp(theme, "bluetitle") == 0) + set_bluetitle_theme(); + else if (strcmp(theme, "blackbg") == 0) + set_blackbg_theme(); + else if (strcmp(theme, "mono") == 0) + use_color = 0; + + return use_color; +} + +static void init_one_color(struct dialog_color *color) +{ + static int pair = 0; + + pair++; + init_pair(pair, color->fg, color->bg); + if (color->hl) + color->atr = A_BOLD | COLOR_PAIR(pair); + else + color->atr = COLOR_PAIR(pair); +} + +static void init_dialog_colors(void) +{ + init_one_color(&dlg.screen); + init_one_color(&dlg.shadow); + init_one_color(&dlg.dialog); + init_one_color(&dlg.title); + init_one_color(&dlg.border); + init_one_color(&dlg.button_active); + init_one_color(&dlg.button_inactive); + init_one_color(&dlg.button_key_active); + init_one_color(&dlg.button_key_inactive); + init_one_color(&dlg.button_label_active); + init_one_color(&dlg.button_label_inactive); + init_one_color(&dlg.inputbox); + init_one_color(&dlg.inputbox_border); + init_one_color(&dlg.searchbox); + init_one_color(&dlg.searchbox_title); + init_one_color(&dlg.searchbox_border); + init_one_color(&dlg.position_indicator); + init_one_color(&dlg.menubox); + init_one_color(&dlg.menubox_border); + init_one_color(&dlg.item); + init_one_color(&dlg.item_selected); + init_one_color(&dlg.tag); + init_one_color(&dlg.tag_selected); + init_one_color(&dlg.tag_key); + init_one_color(&dlg.tag_key_selected); + init_one_color(&dlg.check); + init_one_color(&dlg.check_selected); + init_one_color(&dlg.uarrow); + init_one_color(&dlg.darrow); +} + +/* + * Setup for color display + */ +static void color_setup(const char *theme) +{ + int use_color; + + use_color = set_theme(theme); + if (use_color && has_colors()) { + start_color(); + init_dialog_colors(); + } else + set_mono_theme(); +} + +/* + * Set window to attribute 'attr' + */ +void attr_clear(WINDOW * win, int height, int width, chtype attr) +{ + int i, j; + + wattrset(win, attr); + for (i = 0; i < height; i++) { + wmove(win, i, 0); + for (j = 0; j < width; j++) + waddch(win, ' '); + } + touchwin(win); +} + +void dialog_clear(void) +{ + int lines, columns; + + lines = getmaxy(stdscr); + columns = getmaxx(stdscr); + + attr_clear(stdscr, lines, columns, dlg.screen.atr); + /* Display background title if it exists ... - SLH */ + if (dlg.backtitle != NULL) { + int i, len = 0, skip = 0; + struct subtitle_list *pos; + + wattrset(stdscr, dlg.screen.atr); + mvwaddstr(stdscr, 0, 1, (char *)dlg.backtitle); + + for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { + /* 3 is for the arrow and spaces */ + len += strlen(pos->text) + 3; + } + + wmove(stdscr, 1, 1); + if (len > columns - 2) { + const char *ellipsis = "[...] "; + waddstr(stdscr, ellipsis); + skip = len - (columns - 2 - strlen(ellipsis)); + } + + for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { + if (skip == 0) + waddch(stdscr, ACS_RARROW); + else + skip--; + + if (skip == 0) + waddch(stdscr, ' '); + else + skip--; + + if (skip < strlen(pos->text)) { + waddstr(stdscr, pos->text + skip); + skip = 0; + } else + skip -= strlen(pos->text); + + if (skip == 0) + waddch(stdscr, ' '); + else + skip--; + } + + for (i = len + 1; i < columns - 1; i++) + waddch(stdscr, ACS_HLINE); + } + wnoutrefresh(stdscr); +} + +/* + * Do some initialization for dialog + */ +int init_dialog(const char *backtitle) +{ + int height, width; + + initscr(); /* Init curses */ + + /* Get current cursor position for signal handler in mconf.c */ + getyx(stdscr, saved_y, saved_x); + + getmaxyx(stdscr, height, width); + if (height < WINDOW_HEIGTH_MIN || width < WINDOW_WIDTH_MIN) { + endwin(); + return -ERRDISPLAYTOOSMALL; + } + + dlg.backtitle = backtitle; + color_setup(getenv("MENUCONFIG_COLOR")); + + keypad(stdscr, TRUE); + cbreak(); + noecho(); + dialog_clear(); + + return 0; +} + +void set_dialog_backtitle(const char *backtitle) +{ + dlg.backtitle = backtitle; +} + +void set_dialog_subtitles(struct subtitle_list *subtitles) +{ + dlg.subtitles = subtitles; +} + +/* + * End using dialog functions. + */ +void end_dialog(int x, int y) +{ + /* move cursor back to original position */ + move(y, x); + refresh(); + endwin(); +} + +/* Print the title of the dialog. Center the title and truncate + * tile if wider than dialog (- 2 chars). + **/ +void print_title(WINDOW *dialog, const char *title, int width) +{ + if (title) { + int tlen = MIN(width - 2, strlen(title)); + wattrset(dialog, dlg.title.atr); + mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' '); + mvwaddnstr(dialog, 0, (width - tlen)/2, title, tlen); + waddch(dialog, ' '); + } +} + +/* + * Print a string of text in a window, automatically wrap around to the + * next line if the string is too long to fit on one line. Newline + * characters '\n' are propperly processed. We start on a new line + * if there is no room for at least 4 nonblanks following a double-space. + */ +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x) +{ + int newl, cur_x, cur_y; + int prompt_len, room, wlen; + char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0; + + strcpy(tempstr, prompt); + + prompt_len = strlen(tempstr); + + if (prompt_len <= width - x * 2) { /* If prompt is short */ + wmove(win, y, (width - prompt_len) / 2); + waddstr(win, tempstr); + } else { + cur_x = x; + cur_y = y; + newl = 1; + word = tempstr; + while (word && *word) { + sp = strpbrk(word, "\n "); + if (sp && *sp == '\n') + newline_separator = sp; + + if (sp) + *sp++ = 0; + + /* Wrap to next line if either the word does not fit, + or it is the first word of a new sentence, and it is + short, and the next word does not fit. */ + room = width - cur_x; + wlen = strlen(word); + if (wlen > room || + (newl && wlen < 4 && sp + && wlen + 1 + strlen(sp) > room + && (!(sp2 = strpbrk(sp, "\n ")) + || wlen + 1 + (sp2 - sp) > room))) { + cur_y++; + cur_x = x; + } + wmove(win, cur_y, cur_x); + waddstr(win, word); + getyx(win, cur_y, cur_x); + + /* Move to the next line if the word separator was a newline */ + if (newline_separator) { + cur_y++; + cur_x = x; + newline_separator = 0; + } else + cur_x++; + + if (sp && *sp == ' ') { + cur_x++; /* double space */ + while (*++sp == ' ') ; + newl = 1; + } else + newl = 0; + word = sp; + } + } +} + +/* + * Print a button + */ +void print_button(WINDOW * win, const char *label, int y, int x, int selected) +{ + int i, temp; + + wmove(win, y, x); + wattrset(win, selected ? dlg.button_active.atr + : dlg.button_inactive.atr); + waddstr(win, "<"); + temp = strspn(label, " "); + label += temp; + wattrset(win, selected ? dlg.button_label_active.atr + : dlg.button_label_inactive.atr); + for (i = 0; i < temp; i++) + waddch(win, ' '); + wattrset(win, selected ? dlg.button_key_active.atr + : dlg.button_key_inactive.atr); + waddch(win, label[0]); + wattrset(win, selected ? dlg.button_label_active.atr + : dlg.button_label_inactive.atr); + waddstr(win, (char *)label + 1); + wattrset(win, selected ? dlg.button_active.atr + : dlg.button_inactive.atr); + waddstr(win, ">"); + wmove(win, y, x + temp + 1); +} + +/* + * Draw a rectangular box with line drawing characters + */ +void +draw_box(WINDOW * win, int y, int x, int height, int width, + chtype box, chtype border) +{ + int i, j; + + wattrset(win, 0); + for (i = 0; i < height; i++) { + wmove(win, y + i, x); + for (j = 0; j < width; j++) + if (!i && !j) + waddch(win, border | ACS_ULCORNER); + else if (i == height - 1 && !j) + waddch(win, border | ACS_LLCORNER); + else if (!i && j == width - 1) + waddch(win, box | ACS_URCORNER); + else if (i == height - 1 && j == width - 1) + waddch(win, box | ACS_LRCORNER); + else if (!i) + waddch(win, border | ACS_HLINE); + else if (i == height - 1) + waddch(win, box | ACS_HLINE); + else if (!j) + waddch(win, border | ACS_VLINE); + else if (j == width - 1) + waddch(win, box | ACS_VLINE); + else + waddch(win, box | ' '); + } +} + +/* + * Draw shadows along the right and bottom edge to give a more 3D look + * to the boxes + */ +void draw_shadow(WINDOW * win, int y, int x, int height, int width) +{ + int i; + + if (has_colors()) { /* Whether terminal supports color? */ + wattrset(win, dlg.shadow.atr); + wmove(win, y + height, x + 2); + for (i = 0; i < width; i++) + waddch(win, winch(win) & A_CHARTEXT); + for (i = y + 1; i < y + height + 1; i++) { + wmove(win, i, x + width); + waddch(win, winch(win) & A_CHARTEXT); + waddch(win, winch(win) & A_CHARTEXT); + } + wnoutrefresh(win); + } +} + +/* + * Return the position of the first alphabetic character in a string. + */ +int first_alpha(const char *string, const char *exempt) +{ + int i, in_paren = 0, c; + + for (i = 0; i < strlen(string); i++) { + c = tolower(string[i]); + + if (strchr("<[(", c)) + ++in_paren; + if (strchr(">])", c) && in_paren > 0) + --in_paren; + + if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0) + return i; + } + + return 0; +} + +/* + * ncurses uses ESC to detect escaped char sequences. This resutl in + * a small timeout before ESC is actually delivered to the application. + * lxdialog suggest which is correctly translated to two + * times esc. But then we need to ignore the second esc to avoid stepping + * out one menu too much. Filter away all escaped key sequences since + * keypad(FALSE) turn off ncurses support for escape sequences - and thats + * needed to make notimeout() do as expected. + */ +int on_key_esc(WINDOW *win) +{ + int key; + int key2; + int key3; + + nodelay(win, TRUE); + keypad(win, FALSE); + key = wgetch(win); + key2 = wgetch(win); + do { + key3 = wgetch(win); + } while (key3 != ERR); + nodelay(win, FALSE); + keypad(win, TRUE); + if (key == KEY_ESC && key2 == ERR) + return KEY_ESC; + else if (key != ERR && key != KEY_ESC && key2 == ERR) + ungetch(key); + + return -1; +} + +/* redraw screen in new size */ +int on_key_resize(void) +{ + dialog_clear(); + return KEY_RESIZE; +} + +struct dialog_list *item_cur; +struct dialog_list item_nil; +struct dialog_list *item_head; + +void item_reset(void) +{ + struct dialog_list *p, *next; + + for (p = item_head; p; p = next) { + next = p->next; + free(p); + } + item_head = NULL; + item_cur = &item_nil; +} + +void item_make(const char *fmt, ...) +{ + va_list ap; + struct dialog_list *p = malloc(sizeof(*p)); + + if (item_head) + item_cur->next = p; + else + item_head = p; + item_cur = p; + memset(p, 0, sizeof(*p)); + + va_start(ap, fmt); + vsnprintf(item_cur->node.str, sizeof(item_cur->node.str), fmt, ap); + va_end(ap); +} + +void item_add_str(const char *fmt, ...) +{ + va_list ap; + size_t avail; + + avail = sizeof(item_cur->node.str) - strlen(item_cur->node.str); + + va_start(ap, fmt); + vsnprintf(item_cur->node.str + strlen(item_cur->node.str), + avail, fmt, ap); + item_cur->node.str[sizeof(item_cur->node.str) - 1] = '\0'; + va_end(ap); +} + +void item_set_tag(char tag) +{ + item_cur->node.tag = tag; +} +void item_set_data(void *ptr) +{ + item_cur->node.data = ptr; +} + +void item_set_selected(int val) +{ + item_cur->node.selected = val; +} + +int item_activate_selected(void) +{ + item_foreach() + if (item_is_selected()) + return 1; + return 0; +} + +void *item_data(void) +{ + return item_cur->node.data; +} + +char item_tag(void) +{ + return item_cur->node.tag; +} + +int item_count(void) +{ + int n = 0; + struct dialog_list *p; + + for (p = item_head; p; p = p->next) + n++; + return n; +} + +void item_set(int n) +{ + int i = 0; + item_foreach() + if (i++ == n) + return; +} + +int item_n(void) +{ + int n = 0; + struct dialog_list *p; + + for (p = item_head; p; p = p->next) { + if (p == item_cur) + return n; + n++; + } + return 0; +} + +const char *item_str(void) +{ + return item_cur->node.str; +} + +int item_is_selected(void) +{ + return (item_cur->node.selected != 0); +} + +int item_is_tag(char tag) +{ + return (item_cur->node.tag == tag); +} diff --git a/nemu/tools/kconfig/lxdialog/yesno.c b/nemu/tools/kconfig/lxdialog/yesno.c new file mode 100644 index 0000000..bcaac9b --- /dev/null +++ b/nemu/tools/kconfig/lxdialog/yesno.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * yesno.c -- implements the yes/no box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + */ + +#include "dialog.h" + +/* + * Display termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 10; + int y = height - 2; + + print_button(dialog, " Yes ", y, x, selected == 0); + print_button(dialog, " No ", y, x + 13, selected == 1); + + wmove(dialog, y, x + 1 + 13 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with two buttons - Yes and No + */ +int dialog_yesno(const char *title, const char *prompt, int height, int width) +{ + int i, x, y, key = 0, button = 0; + WINDOW *dialog; + +do_resize: + if (getmaxy(stdscr) < (height + YESNO_HEIGTH_MIN)) + return -ERRDISPLAYTOOSMALL; + if (getmaxx(stdscr) < (width + YESNO_WIDTH_MIN)) + return -ERRDISPLAYTOOSMALL; + + /* center dialog box on screen */ + x = (getmaxx(stdscr) - width) / 2; + y = (getmaxy(stdscr) - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, + dlg.dialog.atr, dlg.border.atr); + wattrset(dialog, dlg.border.atr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dlg.dialog.atr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dlg.dialog.atr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + print_buttons(dialog, height, width, 0); + + while (key != KEY_ESC) { + key = wgetch(dialog); + switch (key) { + case 'Y': + case 'y': + delwin(dialog); + return 0; + case 'N': + case 'n': + delwin(dialog); + return 1; + + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case ' ': + case '\n': + delwin(dialog); + return button; + case KEY_ESC: + key = on_key_esc(dialog); + break; + case KEY_RESIZE: + delwin(dialog); + on_key_resize(); + goto do_resize; + } + } + + delwin(dialog); + return key; /* ESC pressed */ +} diff --git a/nemu/tools/kconfig/mconf.c b/nemu/tools/kconfig/mconf.c new file mode 100644 index 0000000..4063dbc --- /dev/null +++ b/nemu/tools/kconfig/mconf.c @@ -0,0 +1,1040 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel + * + * Introduced single menu mode (show all sub-menus in one large tree). + * 2002-11-06 Petr Baudis + * + * i18n, 2005, Arnaldo Carvalho de Melo + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lkc.h" +#include "lxdialog/dialog.h" + +static const char mconf_readme[] = +"Overview\n" +"--------\n" +"This interface lets you select features and parameters for the build.\n" +"Features can either be built-in, modularized, or ignored. Parameters\n" +"must be entered in as decimal or hexadecimal numbers or text.\n" +"\n" +"Menu items beginning with following braces represent features that\n" +" [ ] can be built in or removed\n" +" < > can be built in, modularized or removed\n" +" { } can be built in or modularized (selected by other feature)\n" +" - - are selected by other feature,\n" +"while *, M or whitespace inside braces means to build in, build as\n" +"a module or to exclude the feature respectively.\n" +"\n" +"To change any of these features, highlight it with the cursor\n" +"keys and press to build it in, to make it a module or\n" +" to remove it. You may also press the to cycle\n" +"through the available options (i.e. Y->N->M->Y).\n" +"\n" +"Some additional keyboard hints:\n" +"\n" +"Menus\n" +"----------\n" +"o Use the Up/Down arrow keys (cursor keys) to highlight the item you\n" +" wish to change or the submenu you wish to select and press .\n" +" Submenus are designated by \"--->\", empty ones by \"----\".\n" +"\n" +" Shortcut: Press the option's highlighted letter (hotkey).\n" +" Pressing a hotkey more than once will sequence\n" +" through all visible items which use that hotkey.\n" +"\n" +" You may also use the and keys to scroll\n" +" unseen options into view.\n" +"\n" +"o To exit a menu use the cursor keys to highlight the button\n" +" and press .\n" +"\n" +" Shortcut: Press or or if there is no hotkey\n" +" using those letters. You may press a single , but\n" +" there is a delayed response which you may find annoying.\n" +"\n" +" Also, the and cursor keys will cycle between and\n" +" \n" +"\n" +"\n" +"Data Entry\n" +"-----------\n" +"o Enter the requested information and press \n" +" If you are entering hexadecimal values, it is not necessary to\n" +" add the '0x' prefix to the entry.\n" +"\n" +"o For help, use the or cursor keys to highlight the help option\n" +" and press . You can try as well.\n" +"\n" +"\n" +"Text Box (Help Window)\n" +"--------\n" +"o Use the cursor keys to scroll up/down/left/right. The VI editor\n" +" keys h,j,k,l function here as do , , and for\n" +" those who are familiar with less and lynx.\n" +"\n" +"o Press , , , or to exit.\n" +"\n" +"\n" +"Alternate Configuration Files\n" +"-----------------------------\n" +"Menuconfig supports the use of alternate configuration files for\n" +"those who, for various reasons, find it necessary to switch\n" +"between different configurations.\n" +"\n" +"The button will let you save the current configuration to\n" +"a file of your choosing. Use the button to load a previously\n" +"saved alternate configuration.\n" +"\n" +"Even if you don't use alternate configuration files, but you find\n" +"during a Menuconfig session that you have completely messed up your\n" +"settings, you may use the button to restore your previously\n" +"saved settings from \".config\" without restarting Menuconfig.\n" +"\n" +"Other information\n" +"-----------------\n" +"If you use Menuconfig in an XTERM window, make sure you have your\n" +"$TERM variable set to point to an xterm definition which supports\n" +"color. Otherwise, Menuconfig will look rather bad. Menuconfig will\n" +"not display correctly in an RXVT window because rxvt displays only one\n" +"intensity of color, bright.\n" +"\n" +"Menuconfig will display larger menus on screens or xterms which are\n" +"set to display more than the standard 25 row by 80 column geometry.\n" +"In order for this to work, the \"stty size\" command must be able to\n" +"display the screen's current row and column geometry. I STRONGLY\n" +"RECOMMEND that you make sure you do NOT have the shell variables\n" +"LINES and COLUMNS exported into your environment. Some distributions\n" +"export those variables via /etc/profile. Some ncurses programs can\n" +"become confused when those variables (LINES & COLUMNS) don't reflect\n" +"the true screen size.\n" +"\n" +"Optional personality available\n" +"------------------------------\n" +"If you prefer to have all of the options listed in a single menu,\n" +"rather than the default multimenu hierarchy, run the menuconfig with\n" +"MENUCONFIG_MODE environment variable set to single_menu. Example:\n" +"\n" +"make MENUCONFIG_MODE=single_menu menuconfig\n" +"\n" +" will then unroll the appropriate category, or enfold it if it\n" +"is already unrolled.\n" +"\n" +"Note that this mode can eventually be a little more CPU expensive\n" +"(especially with a larger number of unrolled categories) than the\n" +"default mode.\n" +"\n" +"Different color themes available\n" +"--------------------------------\n" +"It is possible to select different color themes using the variable\n" +"MENUCONFIG_COLOR. To select a theme use:\n" +"\n" +"make MENUCONFIG_COLOR= menuconfig\n" +"\n" +"Available themes are\n" +" mono => selects colors suitable for monochrome displays\n" +" blackbg => selects a color scheme with black background\n" +" classic => theme with blue background. The classic look\n" +" bluetitle => an LCD friendly version of classic. (default)\n" +"\n", +menu_instructions[] = + "Arrow keys navigate the menu. " + " selects submenus ---> (or empty submenus ----). " + "Highlighted letters are hotkeys. " + "Pressing includes, excludes, modularizes features. " + "Press to exit, for Help, for Search. " + "Legend: [*] built-in [ ] excluded module < > module capable", +radiolist_instructions[] = + "Use the arrow keys to navigate this window or " + "press the hotkey of the item you wish to select " + "followed by the . " + "Press for additional information about this option.", +inputbox_instructions_int[] = + "Please enter a decimal value. " + "Fractions will not be accepted. " + "Use the key to move from the input field to the buttons below it.", +inputbox_instructions_hex[] = + "Please enter a hexadecimal value. " + "Use the key to move from the input field to the buttons below it.", +inputbox_instructions_string[] = + "Please enter a string value. " + "Use the key to move from the input field to the buttons below it.", +setmod_text[] = + "This feature depends on another which has been configured as a module.\n" + "As a result, this feature will be built as a module.", +load_config_text[] = + "Enter the name of the configuration file you wish to load. " + "Accept the name shown to restore the configuration you " + "last retrieved. Leave blank to abort.", +load_config_help[] = + "\n" + "For various reasons, one may wish to keep several different\n" + "configurations available on a single machine.\n" + "\n" + "If you have saved a previous configuration in a file other than the\n" + "default one, entering its name here will allow you to modify that\n" + "configuration.\n" + "\n" + "If you are uncertain, then you have probably never used alternate\n" + "configuration files. You should therefore leave this blank to abort.\n", +save_config_text[] = + "Enter a filename to which this configuration should be saved " + "as an alternate. Leave blank to abort.", +save_config_help[] = + "\n" + "For various reasons, one may wish to keep different configurations\n" + "available on a single machine.\n" + "\n" + "Entering a file name here will allow you to later retrieve, modify\n" + "and use the current configuration as an alternate to whatever\n" + "configuration options you have selected at that time.\n" + "\n" + "If you are uncertain what all this means then you should probably\n" + "leave this blank.\n", +search_help[] = + "\n" + "Search for symbols and display their relations.\n" + "Regular expressions are allowed.\n" + "Example: search for \"^FOO\"\n" + "Result:\n" + "-----------------------------------------------------------------\n" + "Symbol: FOO [=m]\n" + "Type : tristate\n" + "Prompt: Foo bus is used to drive the bar HW\n" + " Location:\n" + " -> Bus options (PCI, PCMCIA, EISA, ISA)\n" + " -> PCI support (PCI [=y])\n" + "(1) -> PCI access mode ( [=y])\n" + " Defined at drivers/pci/Kconfig:47\n" + " Depends on: X86_LOCAL_APIC && X86_IO_APIC || IA64\n" + " Selects: LIBCRC32\n" + " Selected by: BAR [=n]\n" + "-----------------------------------------------------------------\n" + "o The line 'Type:' shows the type of the configuration option for\n" + " this symbol (bool, tristate, string, ...)\n" + "o The line 'Prompt:' shows the text used in the menu structure for\n" + " this symbol\n" + "o The 'Defined at' line tells at what file / line number the symbol\n" + " is defined\n" + "o The 'Depends on:' line tells what symbols need to be defined for\n" + " this symbol to be visible in the menu (selectable)\n" + "o The 'Location:' lines tells where in the menu structure this symbol\n" + " is located\n" + " A location followed by a [=y] indicates that this is a\n" + " selectable menu item - and the current value is displayed inside\n" + " brackets.\n" + " Press the key in the (#) prefix to jump directly to that\n" + " location. You will be returned to the current search results\n" + " after exiting this new menu.\n" + "o The 'Selects:' line tells what symbols will be automatically\n" + " selected if this symbol is selected (y or m)\n" + "o The 'Selected by' line tells what symbol has selected this symbol\n" + "\n" + "Only relevant lines are shown.\n" + "\n\n" + "Search examples:\n" + "Examples: USB => find all symbols containing USB\n" + " ^USB => find all symbols starting with USB\n" + " USB$ => find all symbols ending with USB\n" + "\n"; + +static int indent; +static struct menu *current_menu; +static int child_count; +static int single_menu_mode; +static int show_all_options; +static int save_and_exit; +static int silent; + +static void conf(struct menu *menu, struct menu *active_menu); +static void conf_choice(struct menu *menu); +static void conf_string(struct menu *menu); +static void conf_load(void); +static void conf_save(void); +static int show_textbox_ext(const char *title, char *text, int r, int c, + int *keys, int *vscroll, int *hscroll, + update_text_fn update_text, void *data); +static void show_textbox(const char *title, const char *text, int r, int c); +static void show_helptext(const char *title, const char *text); +static void show_help(struct menu *menu); + +static char filename[PATH_MAX+1]; +static void set_config_filename(const char *config_filename) +{ + static char menu_backtitle[PATH_MAX+128]; + int size; + + size = snprintf(menu_backtitle, sizeof(menu_backtitle), + "%s - %s", config_filename, rootmenu.prompt->text); + if (size >= sizeof(menu_backtitle)) + menu_backtitle[sizeof(menu_backtitle)-1] = '\0'; + set_dialog_backtitle(menu_backtitle); + + size = snprintf(filename, sizeof(filename), "%s", config_filename); + if (size >= sizeof(filename)) + filename[sizeof(filename)-1] = '\0'; +} + +struct subtitle_part { + struct list_head entries; + const char *text; +}; +static LIST_HEAD(trail); + +static struct subtitle_list *subtitles; +static void set_subtitle(void) +{ + struct subtitle_part *sp; + struct subtitle_list *pos, *tmp; + + for (pos = subtitles; pos != NULL; pos = tmp) { + tmp = pos->next; + free(pos); + } + + subtitles = NULL; + list_for_each_entry(sp, &trail, entries) { + if (sp->text) { + if (pos) { + pos->next = xcalloc(1, sizeof(*pos)); + pos = pos->next; + } else { + subtitles = pos = xcalloc(1, sizeof(*pos)); + } + pos->text = sp->text; + } + } + + set_dialog_subtitles(subtitles); +} + +static void reset_subtitle(void) +{ + struct subtitle_list *pos, *tmp; + + for (pos = subtitles; pos != NULL; pos = tmp) { + tmp = pos->next; + free(pos); + } + subtitles = NULL; + set_dialog_subtitles(subtitles); +} + +struct search_data { + struct list_head *head; + struct menu **targets; + int *keys; +}; + +static void update_text(char *buf, size_t start, size_t end, void *_data) +{ + struct search_data *data = _data; + struct jump_key *pos; + int k = 0; + + list_for_each_entry(pos, data->head, entries) { + if (pos->offset >= start && pos->offset < end) { + char header[4]; + + if (k < JUMP_NB) { + int key = '0' + (pos->index % JUMP_NB) + 1; + + sprintf(header, "(%c)", key); + data->keys[k] = key; + data->targets[k] = pos->target; + k++; + } else { + sprintf(header, " "); + } + + memcpy(buf + pos->offset, header, sizeof(header) - 1); + } + } + data->keys[k] = 0; +} + +static void search_conf(void) +{ + struct symbol **sym_arr; + struct gstr res; + struct gstr title; + char *dialog_input; + int dres, vscroll = 0, hscroll = 0; + bool again; + struct gstr sttext; + struct subtitle_part stpart; + + title = str_new(); + str_printf( &title, "Enter (sub)string or regexp to search for " + "(with or without \"%s\")", CONFIG_); + +again: + dialog_clear(); + dres = dialog_inputbox("Search Configuration Parameter", + str_get(&title), + 10, 75, ""); + switch (dres) { + case 0: + break; + case 1: + show_helptext("Search Configuration", search_help); + goto again; + default: + str_free(&title); + return; + } + + /* strip the prefix if necessary */ + dialog_input = dialog_input_result; + if (strncasecmp(dialog_input_result, CONFIG_, strlen(CONFIG_)) == 0) + dialog_input += strlen(CONFIG_); + + sttext = str_new(); + str_printf(&sttext, "Search (%s)", dialog_input_result); + stpart.text = str_get(&sttext); + list_add_tail(&stpart.entries, &trail); + + sym_arr = sym_re_search(dialog_input); + do { + LIST_HEAD(head); + struct menu *targets[JUMP_NB]; + int keys[JUMP_NB + 1], i; + struct search_data data = { + .head = &head, + .targets = targets, + .keys = keys, + }; + struct jump_key *pos, *tmp; + + res = get_relations_str(sym_arr, &head); + set_subtitle(); + dres = show_textbox_ext("Search Results", (char *) + str_get(&res), 0, 0, keys, &vscroll, + &hscroll, &update_text, (void *) + &data); + again = false; + for (i = 0; i < JUMP_NB && keys[i]; i++) + if (dres == keys[i]) { + conf(targets[i]->parent, targets[i]); + again = true; + } + str_free(&res); + list_for_each_entry_safe(pos, tmp, &head, entries) + free(pos); + } while (again); + free(sym_arr); + str_free(&title); + list_del(trail.prev); + str_free(&sttext); +} + +static void build_conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + int type, tmp, doint = 2; + tristate val; + char ch; + bool visible; + + /* + * note: menu_is_visible() has side effect that it will + * recalc the value of the symbol. + */ + visible = menu_is_visible(menu); + if (show_all_options && !menu_has_prompt(menu)) + return; + else if (!show_all_options && !visible) + return; + + sym = menu->sym; + prop = menu->prompt; + if (!sym) { + if (prop && menu != current_menu) { + const char *prompt = menu_get_prompt(menu); + switch (prop->type) { + case P_MENU: + child_count++; + if (single_menu_mode) { + item_make("%s%*c%s", + menu->data ? "-->" : "++>", + indent + 1, ' ', prompt); + } else + item_make(" %*c%s %s", + indent + 1, ' ', prompt, + menu_is_empty(menu) ? "----" : "--->"); + item_set_tag('m'); + item_set_data(menu); + if (single_menu_mode && menu->data) + goto conf_childs; + return; + case P_COMMENT: + if (prompt) { + child_count++; + item_make(" %*c*** %s ***", indent + 1, ' ', prompt); + item_set_tag(':'); + item_set_data(menu); + } + break; + default: + if (prompt) { + child_count++; + item_make("---%*c%s", indent + 1, ' ', prompt); + item_set_tag(':'); + item_set_data(menu); + } + } + } else + doint = 0; + goto conf_childs; + } + + type = sym_get_type(sym); + if (sym_is_choice(sym)) { + struct symbol *def_sym = sym_get_choice_value(sym); + struct menu *def_menu = NULL; + + child_count++; + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) && child->sym == def_sym) + def_menu = child; + } + + val = sym_get_tristate_value(sym); + if (sym_is_changeable(sym)) { + switch (type) { + case S_BOOLEAN: + item_make("[%c]", val == no ? ' ' : '*'); + break; + case S_TRISTATE: + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; + } + item_make("<%c>", ch); + break; + } + item_set_tag('t'); + item_set_data(menu); + } else { + item_make(" "); + item_set_tag(def_menu ? 't' : ':'); + item_set_data(menu); + } + + item_add_str("%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + if (val == yes) { + if (def_menu) { + item_add_str(" (%s)", menu_get_prompt(def_menu)); + item_add_str(" --->"); + if (def_menu->list) { + indent += 2; + build_conf(def_menu); + indent -= 2; + } + } + return; + } + } else { + if (menu == current_menu) { + item_make("---%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + item_set_tag(':'); + item_set_data(menu); + goto conf_childs; + } + child_count++; + val = sym_get_tristate_value(sym); + if (sym_is_choice_value(sym) && val == yes) { + item_make(" "); + item_set_tag(':'); + item_set_data(menu); + } else { + switch (type) { + case S_BOOLEAN: + if (sym_is_changeable(sym)) + item_make("[%c]", val == no ? ' ' : '*'); + else + item_make("-%c-", val == no ? ' ' : '*'); + item_set_tag('t'); + item_set_data(menu); + break; + case S_TRISTATE: + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; + } + if (sym_is_changeable(sym)) { + if (sym->rev_dep.tri == mod) + item_make("{%c}", ch); + else + item_make("<%c>", ch); + } else + item_make("-%c-", ch); + item_set_tag('t'); + item_set_data(menu); + break; + default: + tmp = 2 + strlen(sym_get_string_value(sym)); /* () = 2 */ + item_make("(%s)", sym_get_string_value(sym)); + tmp = indent - tmp + 4; + if (tmp < 0) + tmp = 0; + item_add_str("%*c%s%s", tmp, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changeable(sym)) ? + "" : " (NEW)"); + item_set_tag('s'); + item_set_data(menu); + goto conf_childs; + } + } + item_add_str("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changeable(sym)) ? + "" : " (NEW)"); + if (menu->prompt->type == P_MENU) { + item_add_str(" %s", menu_is_empty(menu) ? "----" : "--->"); + return; + } + } + +conf_childs: + indent += doint; + for (child = menu->list; child; child = child->next) + build_conf(child); + indent -= doint; +} + +static void conf(struct menu *menu, struct menu *active_menu) +{ + struct menu *submenu; + const char *prompt = menu_get_prompt(menu); + struct subtitle_part stpart; + struct symbol *sym; + int res; + int s_scroll = 0; + + if (menu != &rootmenu) + stpart.text = menu_get_prompt(menu); + else + stpart.text = NULL; + list_add_tail(&stpart.entries, &trail); + + while (1) { + item_reset(); + current_menu = menu; + build_conf(menu); + if (!child_count) + break; + set_subtitle(); + dialog_clear(); + res = dialog_menu(prompt ? prompt : "Main Menu", + menu_instructions, + active_menu, &s_scroll); + if (res == 1 || res == KEY_ESC || res == -ERRDISPLAYTOOSMALL) + break; + if (item_count() != 0) { + if (!item_activate_selected()) + continue; + if (!item_tag()) + continue; + } + submenu = item_data(); + active_menu = item_data(); + if (submenu) + sym = submenu->sym; + else + sym = NULL; + + switch (res) { + case 0: + switch (item_tag()) { + case 'm': + if (single_menu_mode) + submenu->data = (void *) (long) !submenu->data; + else + conf(submenu, NULL); + break; + case 't': + if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes) + conf_choice(submenu); + else if (submenu->prompt->type == P_MENU) + conf(submenu, NULL); + break; + case 's': + conf_string(submenu); + break; + } + break; + case 2: + if (sym) + show_help(submenu); + else { + reset_subtitle(); + show_helptext("README", mconf_readme); + } + break; + case 3: + reset_subtitle(); + conf_save(); + break; + case 4: + reset_subtitle(); + conf_load(); + break; + case 5: + if (item_is_tag('t')) { + if (sym_set_tristate_value(sym, yes)) + break; + if (sym_set_tristate_value(sym, mod)) + show_textbox(NULL, setmod_text, 6, 74); + } + break; + case 6: + if (item_is_tag('t')) + sym_set_tristate_value(sym, no); + break; + case 7: + if (item_is_tag('t')) + sym_set_tristate_value(sym, mod); + break; + case 8: + if (item_is_tag('t')) + sym_toggle_tristate_value(sym); + else if (item_is_tag('m')) + conf(submenu, NULL); + break; + case 9: + search_conf(); + break; + case 10: + show_all_options = !show_all_options; + break; + } + } + + list_del(trail.prev); +} + +static int show_textbox_ext(const char *title, char *text, int r, int c, int + *keys, int *vscroll, int *hscroll, update_text_fn + update_text, void *data) +{ + dialog_clear(); + return dialog_textbox(title, text, r, c, keys, vscroll, hscroll, + update_text, data); +} + +static void show_textbox(const char *title, const char *text, int r, int c) +{ + show_textbox_ext(title, (char *) text, r, c, (int []) {0}, NULL, NULL, + NULL, NULL); +} + +static void show_helptext(const char *title, const char *text) +{ + show_textbox(title, text, 0, 0); +} + +static void conf_message_callback(const char *s) +{ + if (save_and_exit) { + if (!silent) + printf("%s", s); + } else { + show_textbox(NULL, s, 6, 60); + } +} + +static void show_help(struct menu *menu) +{ + struct gstr help = str_new(); + + help.max_width = getmaxx(stdscr) - 10; + menu_get_ext_help(menu, &help); + + show_helptext(menu_get_prompt(menu), str_get(&help)); + str_free(&help); +} + +static void conf_choice(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + struct menu *child; + struct symbol *active; + + active = sym_get_choice_value(menu->sym); + while (1) { + int res; + int selected; + item_reset(); + + current_menu = menu; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + if (child->sym) + item_make("%s", menu_get_prompt(child)); + else { + item_make("*** %s ***", menu_get_prompt(child)); + item_set_tag(':'); + } + item_set_data(child); + if (child->sym == active) + item_set_selected(1); + if (child->sym == sym_get_choice_value(menu->sym)) + item_set_tag('X'); + } + dialog_clear(); + res = dialog_checklist(prompt ? prompt : "Main Menu", + radiolist_instructions, + MENUBOX_HEIGTH_MIN, + MENUBOX_WIDTH_MIN, + CHECKLIST_HEIGTH_MIN); + selected = item_activate_selected(); + switch (res) { + case 0: + if (selected) { + child = item_data(); + if (!child->sym) + break; + + sym_set_tristate_value(child->sym, yes); + } + return; + case 1: + if (selected) { + child = item_data(); + show_help(child); + active = child->sym; + } else + show_help(menu); + break; + case KEY_ESC: + return; + case -ERRDISPLAYTOOSMALL: + return; + } + } +} + +static void conf_string(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + + while (1) { + int res; + const char *heading; + + switch (sym_get_type(menu->sym)) { + case S_INT: + heading = inputbox_instructions_int; + break; + case S_HEX: + heading = inputbox_instructions_hex; + break; + case S_STRING: + heading = inputbox_instructions_string; + break; + default: + heading = "Internal mconf error!"; + } + dialog_clear(); + res = dialog_inputbox(prompt ? prompt : "Main Menu", + heading, 10, 75, + sym_get_string_value(menu->sym)); + switch (res) { + case 0: + if (sym_set_string_value(menu->sym, dialog_input_result)) + return; + show_textbox(NULL, "You have made an invalid entry.", 5, 43); + break; + case 1: + show_help(menu); + break; + case KEY_ESC: + return; + } + } +} + +static void conf_load(void) +{ + + while (1) { + int res; + dialog_clear(); + res = dialog_inputbox(NULL, load_config_text, + 11, 55, filename); + switch(res) { + case 0: + if (!dialog_input_result[0]) + return; + if (!conf_read(dialog_input_result)) { + set_config_filename(dialog_input_result); + sym_set_change_count(1); + return; + } + show_textbox(NULL, "File does not exist!", 5, 38); + break; + case 1: + show_helptext("Load Alternate Configuration", load_config_help); + break; + case KEY_ESC: + return; + } + } +} + +static void conf_save(void) +{ + while (1) { + int res; + dialog_clear(); + res = dialog_inputbox(NULL, save_config_text, + 11, 55, filename); + switch(res) { + case 0: + if (!dialog_input_result[0]) + return; + if (!conf_write(dialog_input_result)) { + set_config_filename(dialog_input_result); + return; + } + show_textbox(NULL, "Can't create file!", 5, 60); + break; + case 1: + show_helptext("Save Alternate Configuration", save_config_help); + break; + case KEY_ESC: + return; + } + } +} + +static int handle_exit(void) +{ + int res; + + save_and_exit = 1; + reset_subtitle(); + dialog_clear(); + if (conf_get_changed()) + res = dialog_yesno(NULL, + "Do you wish to save your new configuration?\n" + "(Press to continue kernel configuration.)", + 6, 60); + else + res = -1; + + end_dialog(saved_x, saved_y); + + switch (res) { + case 0: + if (conf_write(filename)) { + fprintf(stderr, "\n\n" + "Error while writing of the configuration.\n" + "Your configuration changes were NOT saved." + "\n\n"); + return 1; + } + conf_write_autoconf(0); + /* fall through */ + case -1: + if (!silent) + printf("\n\n" + "*** End of the configuration.\n" + "*** Execute 'make' to start the build or try 'make help'." + "\n\n"); + res = 0; + break; + default: + if (!silent) + fprintf(stderr, "\n\n" + "Your configuration changes were NOT saved." + "\n\n"); + if (res != KEY_ESC) + res = 0; + } + + return res; +} + +static void sig_handler(int signo) +{ + exit(handle_exit()); +} + +int main(int ac, char **av) +{ + char *mode; + int res; + + signal(SIGINT, sig_handler); + + if (ac > 1 && strcmp(av[1], "-s") == 0) { + silent = 1; + /* Silence conf_read() until the real callback is set up */ + conf_set_message_callback(NULL); + av++; + } + conf_parse(av[1]); + conf_read(NULL); + + mode = getenv("MENUCONFIG_MODE"); + if (mode) { + if (!strcasecmp(mode, "single_menu")) + single_menu_mode = 1; + } + + if (init_dialog(NULL)) { + fprintf(stderr, "Your display is too small to run Menuconfig!\n"); + fprintf(stderr, "It must be at least 19 lines by 80 columns.\n"); + return 1; + } + + set_config_filename(conf_get_configname()); + conf_set_message_callback(conf_message_callback); + do { + conf(&rootmenu, NULL); + res = handle_exit(); + } while (res == KEY_ESC); + + return res; +} diff --git a/nemu/tools/kconfig/menu.c b/nemu/tools/kconfig/menu.c new file mode 100644 index 0000000..a5fbd6c --- /dev/null +++ b/nemu/tools/kconfig/menu.c @@ -0,0 +1,899 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel + */ + +#include +#include +#include +#include + +#include "lkc.h" + +static const char nohelp_text[] = "There is no help available for this option."; + +struct menu rootmenu; +static struct menu **last_entry_ptr; + +struct file *file_list; +struct file *current_file; + +void menu_warn(struct menu *menu, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", menu->file->name, menu->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static void prop_warn(struct property *prop, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", prop->file->name, prop->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +void _menu_init(void) +{ + current_entry = current_menu = &rootmenu; + last_entry_ptr = &rootmenu.list; +} + +void menu_add_entry(struct symbol *sym) +{ + struct menu *menu; + + menu = xmalloc(sizeof(*menu)); + memset(menu, 0, sizeof(*menu)); + menu->sym = sym; + menu->parent = current_menu; + menu->file = current_file; + menu->lineno = zconf_lineno(); + + *last_entry_ptr = menu; + last_entry_ptr = &menu->next; + current_entry = menu; + if (sym) + menu_add_symbol(P_SYMBOL, sym, NULL); +} + +struct menu *menu_add_menu(void) +{ + last_entry_ptr = ¤t_entry->list; + current_menu = current_entry; + return current_menu; +} + +void menu_end_menu(void) +{ + last_entry_ptr = ¤t_menu->next; + current_menu = current_menu->parent; +} + +/* + * Rewrites 'm' to 'm' && MODULES, so that it evaluates to 'n' when running + * without modules + */ +static struct expr *rewrite_m(struct expr *e) +{ + if (!e) + return e; + + switch (e->type) { + case E_NOT: + e->left.expr = rewrite_m(e->left.expr); + break; + case E_OR: + case E_AND: + e->left.expr = rewrite_m(e->left.expr); + e->right.expr = rewrite_m(e->right.expr); + break; + case E_SYMBOL: + /* change 'm' into 'm' && MODULES */ + if (e->left.sym == &symbol_mod) + return expr_alloc_and(e, expr_alloc_symbol(modules_sym)); + break; + default: + break; + } + return e; +} + +void menu_add_dep(struct expr *dep) +{ + current_entry->dep = expr_alloc_and(current_entry->dep, dep); +} + +void menu_set_type(int type) +{ + struct symbol *sym = current_entry->sym; + + if (sym->type == type) + return; + if (sym->type == S_UNKNOWN) { + sym->type = type; + return; + } + menu_warn(current_entry, + "ignoring type redefinition of '%s' from '%s' to '%s'", + sym->name ? sym->name : "", + sym_type_name(sym->type), sym_type_name(type)); +} + +static struct property *menu_add_prop(enum prop_type type, struct expr *expr, + struct expr *dep) +{ + struct property *prop; + + prop = xmalloc(sizeof(*prop)); + memset(prop, 0, sizeof(*prop)); + prop->type = type; + prop->file = current_file; + prop->lineno = zconf_lineno(); + prop->menu = current_entry; + prop->expr = expr; + prop->visible.expr = dep; + + /* append property to the prop list of symbol */ + if (current_entry->sym) { + struct property **propp; + + for (propp = ¤t_entry->sym->prop; + *propp; + propp = &(*propp)->next) + ; + *propp = prop; + } + + return prop; +} + +struct property *menu_add_prompt(enum prop_type type, char *prompt, + struct expr *dep) +{ + struct property *prop = menu_add_prop(type, NULL, dep); + + if (isspace(*prompt)) { + prop_warn(prop, "leading whitespace ignored"); + while (isspace(*prompt)) + prompt++; + } + if (current_entry->prompt) + prop_warn(prop, "prompt redefined"); + + /* Apply all upper menus' visibilities to actual prompts. */ + if (type == P_PROMPT) { + struct menu *menu = current_entry; + + while ((menu = menu->parent) != NULL) { + struct expr *dup_expr; + + if (!menu->visibility) + continue; + /* + * Do not add a reference to the menu's visibility + * expression but use a copy of it. Otherwise the + * expression reduction functions will modify + * expressions that have multiple references which + * can cause unwanted side effects. + */ + dup_expr = expr_copy(menu->visibility); + + prop->visible.expr = expr_alloc_and(prop->visible.expr, + dup_expr); + } + } + + current_entry->prompt = prop; + prop->text = prompt; + + return prop; +} + +void menu_add_visibility(struct expr *expr) +{ + current_entry->visibility = expr_alloc_and(current_entry->visibility, + expr); +} + +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep) +{ + menu_add_prop(type, expr, dep); +} + +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) +{ + menu_add_prop(type, expr_alloc_symbol(sym), dep); +} + +void menu_add_option_modules(void) +{ + if (modules_sym) + zconf_error("symbol '%s' redefines option 'modules' already defined by symbol '%s'", + current_entry->sym->name, modules_sym->name); + modules_sym = current_entry->sym; +} + +void menu_add_option_defconfig_list(void) +{ + if (!sym_defconfig_list) + sym_defconfig_list = current_entry->sym; + else if (sym_defconfig_list != current_entry->sym) + zconf_error("trying to redefine defconfig symbol"); + sym_defconfig_list->flags |= SYMBOL_NO_WRITE; +} + +void menu_add_option_allnoconfig_y(void) +{ + current_entry->sym->flags |= SYMBOL_ALLNOCONFIG_Y; +} + +static int menu_validate_number(struct symbol *sym, struct symbol *sym2) +{ + return sym2->type == S_INT || sym2->type == S_HEX || + (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); +} + +static void sym_check_prop(struct symbol *sym) +{ + struct property *prop; + struct symbol *sym2; + char *use; + + for (prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_DEFAULT: + if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) && + prop->expr->type != E_SYMBOL) + prop_warn(prop, + "default for config symbol '%s'" + " must be a single symbol", sym->name); + if (prop->expr->type != E_SYMBOL) + break; + sym2 = prop_get_symbol(prop); + if (sym->type == S_HEX || sym->type == S_INT) { + if (!menu_validate_number(sym, sym2)) + prop_warn(prop, + "'%s': number is invalid", + sym->name); + } + if (sym_is_choice(sym)) { + struct property *choice_prop = + sym_get_choice_prop(sym2); + + if (!choice_prop || + prop_get_symbol(choice_prop) != sym) + prop_warn(prop, + "choice default symbol '%s' is not contained in the choice", + sym2->name); + } + break; + case P_SELECT: + case P_IMPLY: + use = prop->type == P_SELECT ? "select" : "imply"; + sym2 = prop_get_symbol(prop); + if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) + prop_warn(prop, + "config symbol '%s' uses %s, but is " + "not bool or tristate", sym->name, use); + else if (sym2->type != S_UNKNOWN && + sym2->type != S_BOOLEAN && + sym2->type != S_TRISTATE) + prop_warn(prop, + "'%s' has wrong type. '%s' only " + "accept arguments of bool and " + "tristate type", sym2->name, use); + break; + case P_RANGE: + if (sym->type != S_INT && sym->type != S_HEX) + prop_warn(prop, "range is only allowed " + "for int or hex symbols"); + if (!menu_validate_number(sym, prop->expr->left.sym) || + !menu_validate_number(sym, prop->expr->right.sym)) + prop_warn(prop, "range is invalid"); + break; + default: + ; + } + } +} + +void menu_finalize(struct menu *parent) +{ + struct menu *menu, *last_menu; + struct symbol *sym; + struct property *prop; + struct expr *parentdep, *basedep, *dep, *dep2, **ep; + + sym = parent->sym; + if (parent->list) { + /* + * This menu node has children. We (recursively) process them + * and propagate parent dependencies before moving on. + */ + + if (sym && sym_is_choice(sym)) { + if (sym->type == S_UNKNOWN) { + /* find the first choice value to find out choice type */ + current_entry = parent; + for (menu = parent->list; menu; menu = menu->next) { + if (menu->sym && menu->sym->type != S_UNKNOWN) { + menu_set_type(menu->sym->type); + break; + } + } + } + /* set the type of the remaining choice values */ + for (menu = parent->list; menu; menu = menu->next) { + current_entry = menu; + if (menu->sym && menu->sym->type == S_UNKNOWN) + menu_set_type(sym->type); + } + + /* + * Use the choice itself as the parent dependency of + * the contained items. This turns the mode of the + * choice into an upper bound on the visibility of the + * choice value symbols. + */ + parentdep = expr_alloc_symbol(sym); + } else { + /* Menu node for 'menu', 'if' */ + parentdep = parent->dep; + } + + /* For each child menu node... */ + for (menu = parent->list; menu; menu = menu->next) { + /* + * Propagate parent dependencies to the child menu + * node, also rewriting and simplifying expressions + */ + basedep = rewrite_m(menu->dep); + basedep = expr_transform(basedep); + basedep = expr_alloc_and(expr_copy(parentdep), basedep); + basedep = expr_eliminate_dups(basedep); + menu->dep = basedep; + + if (menu->sym) + /* + * Note: For symbols, all prompts are included + * too in the symbol's own property list + */ + prop = menu->sym->prop; + else + /* + * For non-symbol menu nodes, we just need to + * handle the prompt + */ + prop = menu->prompt; + + /* For each property... */ + for (; prop; prop = prop->next) { + if (prop->menu != menu) + /* + * Two possibilities: + * + * 1. The property lacks dependencies + * and so isn't location-specific, + * e.g. an 'option' + * + * 2. The property belongs to a symbol + * defined in multiple locations and + * is from some other location. It + * will be handled there in that + * case. + * + * Skip the property. + */ + continue; + + /* + * Propagate parent dependencies to the + * property's condition, rewriting and + * simplifying expressions at the same time + */ + dep = rewrite_m(prop->visible.expr); + dep = expr_transform(dep); + dep = expr_alloc_and(expr_copy(basedep), dep); + dep = expr_eliminate_dups(dep); + if (menu->sym && menu->sym->type != S_TRISTATE) + dep = expr_trans_bool(dep); + prop->visible.expr = dep; + + /* + * Handle selects and implies, which modify the + * dependencies of the selected/implied symbol + */ + if (prop->type == P_SELECT) { + struct symbol *es = prop_get_symbol(prop); + es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr, + expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep))); + } else if (prop->type == P_IMPLY) { + struct symbol *es = prop_get_symbol(prop); + es->implied.expr = expr_alloc_or(es->implied.expr, + expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep))); + } + } + } + + if (sym && sym_is_choice(sym)) + expr_free(parentdep); + + /* + * Recursively process children in the same fashion before + * moving on + */ + for (menu = parent->list; menu; menu = menu->next) + menu_finalize(menu); + } else if (sym) { + /* + * Automatic submenu creation. If sym is a symbol and A, B, C, + * ... are consecutive items (symbols, menus, ifs, etc.) that + * all depend on sym, then the following menu structure is + * created: + * + * sym + * +-A + * +-B + * +-C + * ... + * + * This also works recursively, giving the following structure + * if A is a symbol and B depends on A: + * + * sym + * +-A + * | +-B + * +-C + * ... + */ + + basedep = parent->prompt ? parent->prompt->visible.expr : NULL; + basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no); + basedep = expr_eliminate_dups(expr_transform(basedep)); + + /* Examine consecutive elements after sym */ + last_menu = NULL; + for (menu = parent->next; menu; menu = menu->next) { + dep = menu->prompt ? menu->prompt->visible.expr : menu->dep; + if (!expr_contains_symbol(dep, sym)) + /* No dependency, quit */ + break; + if (expr_depends_symbol(dep, sym)) + /* Absolute dependency, put in submenu */ + goto next; + + /* + * Also consider it a dependency on sym if our + * dependencies contain sym and are a "superset" of + * sym's dependencies, e.g. '(sym || Q) && R' when sym + * depends on R. + * + * Note that 'R' might be from an enclosing menu or if, + * making this a more common case than it might seem. + */ + dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no); + dep = expr_eliminate_dups(expr_transform(dep)); + dep2 = expr_copy(basedep); + expr_eliminate_eq(&dep, &dep2); + expr_free(dep); + if (!expr_is_yes(dep2)) { + /* Not superset, quit */ + expr_free(dep2); + break; + } + /* Superset, put in submenu */ + expr_free(dep2); + next: + menu_finalize(menu); + menu->parent = parent; + last_menu = menu; + } + expr_free(basedep); + if (last_menu) { + parent->list = parent->next; + parent->next = last_menu->next; + last_menu->next = NULL; + } + + sym->dir_dep.expr = expr_alloc_or(sym->dir_dep.expr, parent->dep); + } + for (menu = parent->list; menu; menu = menu->next) { + if (sym && sym_is_choice(sym) && + menu->sym && !sym_is_choice_value(menu->sym)) { + current_entry = menu; + menu->sym->flags |= SYMBOL_CHOICEVAL; + if (!menu->prompt) + menu_warn(menu, "choice value must have a prompt"); + for (prop = menu->sym->prop; prop; prop = prop->next) { + if (prop->type == P_DEFAULT) + prop_warn(prop, "defaults for choice " + "values not supported"); + if (prop->menu == menu) + continue; + if (prop->type == P_PROMPT && + prop->menu->parent->sym != sym) + prop_warn(prop, "choice value used outside its choice group"); + } + /* Non-tristate choice values of tristate choices must + * depend on the choice being set to Y. The choice + * values' dependencies were propagated to their + * properties above, so the change here must be re- + * propagated. + */ + if (sym->type == S_TRISTATE && menu->sym->type != S_TRISTATE) { + basedep = expr_alloc_comp(E_EQUAL, sym, &symbol_yes); + menu->dep = expr_alloc_and(basedep, menu->dep); + for (prop = menu->sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + prop->visible.expr = expr_alloc_and(expr_copy(basedep), + prop->visible.expr); + } + } + menu_add_symbol(P_CHOICE, sym, NULL); + prop = sym_get_choice_prop(sym); + for (ep = &prop->expr; *ep; ep = &(*ep)->left.expr) + ; + *ep = expr_alloc_one(E_LIST, NULL); + (*ep)->right.sym = menu->sym; + } + + /* + * This code serves two purposes: + * + * (1) Flattening 'if' blocks, which do not specify a submenu + * and only add dependencies. + * + * (Automatic submenu creation might still create a submenu + * from an 'if' before this code runs.) + * + * (2) "Undoing" any automatic submenus created earlier below + * promptless symbols. + * + * Before: + * + * A + * if ... (or promptless symbol) + * +-B + * +-C + * D + * + * After: + * + * A + * if ... (or promptless symbol) + * B + * C + * D + */ + if (menu->list && (!menu->prompt || !menu->prompt->text)) { + for (last_menu = menu->list; ; last_menu = last_menu->next) { + last_menu->parent = parent; + if (!last_menu->next) + break; + } + last_menu->next = menu->next; + menu->next = menu->list; + menu->list = NULL; + } + } + + if (sym && !(sym->flags & SYMBOL_WARNED)) { + if (sym->type == S_UNKNOWN) + menu_warn(parent, "config symbol defined without type"); + + if (sym_is_choice(sym) && !parent->prompt) + menu_warn(parent, "choice must have a prompt"); + + /* Check properties connected to this symbol */ + sym_check_prop(sym); + sym->flags |= SYMBOL_WARNED; + } + + /* + * For non-optional choices, add a reverse dependency (corresponding to + * a select) of ' && m'. This prevents the user from + * setting the choice mode to 'n' when the choice is visible. + * + * This would also work for non-choice symbols, but only non-optional + * choices clear SYMBOL_OPTIONAL as of writing. Choices are implemented + * as a type of symbol. + */ + if (sym && !sym_is_optional(sym) && parent->prompt) { + sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr, + expr_alloc_and(parent->prompt->visible.expr, + expr_alloc_symbol(&symbol_mod))); + } +} + +bool menu_has_prompt(struct menu *menu) +{ + if (!menu->prompt) + return false; + return true; +} + +/* + * Determine if a menu is empty. + * A menu is considered empty if it contains no or only + * invisible entries. + */ +bool menu_is_empty(struct menu *menu) +{ + struct menu *child; + + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child)) + return(false); + } + return(true); +} + +bool menu_is_visible(struct menu *menu) +{ + struct menu *child; + struct symbol *sym; + tristate visible; + + if (!menu->prompt) + return false; + + if (menu->visibility) { + if (expr_calc_value(menu->visibility) == no) + return false; + } + + sym = menu->sym; + if (sym) { + sym_calc_value(sym); + visible = menu->prompt->visible.tri; + } else + visible = menu->prompt->visible.tri = expr_calc_value(menu->prompt->visible.expr); + + if (visible != no) + return true; + + if (!sym || sym_get_tristate_value(menu->sym) == no) + return false; + + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child)) { + if (sym) + sym->flags |= SYMBOL_DEF_USER; + return true; + } + } + + return false; +} + +const char *menu_get_prompt(struct menu *menu) +{ + if (menu->prompt) + return menu->prompt->text; + else if (menu->sym) + return menu->sym->name; + return NULL; +} + +struct menu *menu_get_root_menu(struct menu *menu) +{ + return &rootmenu; +} + +struct menu *menu_get_parent_menu(struct menu *menu) +{ + enum prop_type type; + + for (; menu != &rootmenu; menu = menu->parent) { + type = menu->prompt ? menu->prompt->type : 0; + if (type == P_MENU) + break; + } + return menu; +} + +bool menu_has_help(struct menu *menu) +{ + return menu->help != NULL; +} + +const char *menu_get_help(struct menu *menu) +{ + if (menu->help) + return menu->help; + else + return ""; +} + +static void get_def_str(struct gstr *r, struct menu *menu) +{ + str_printf(r, "Defined at %s:%d\n", + menu->file->name, menu->lineno); +} + +static void get_dep_str(struct gstr *r, struct expr *expr, const char *prefix) +{ + if (!expr_is_yes(expr)) { + str_append(r, prefix); + expr_gstr_print(expr, r); + str_append(r, "\n"); + } +} + +static void get_prompt_str(struct gstr *r, struct property *prop, + struct list_head *head) +{ + int i, j; + struct menu *submenu[8], *menu, *location = NULL; + struct jump_key *jump = NULL; + + str_printf(r, " Prompt: %s\n", prop->text); + + get_dep_str(r, prop->menu->dep, " Depends on: "); + /* + * Most prompts in Linux have visibility that exactly matches their + * dependencies. For these, we print only the dependencies to improve + * readability. However, prompts with inline "if" expressions and + * prompts with a parent that has a "visible if" expression have + * differing dependencies and visibility. In these rare cases, we + * print both. + */ + if (!expr_eq(prop->menu->dep, prop->visible.expr)) + get_dep_str(r, prop->visible.expr, " Visible if: "); + + menu = prop->menu->parent; + for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent) { + bool accessible = menu_is_visible(menu); + + submenu[i++] = menu; + if (location == NULL && accessible) + location = menu; + } + if (head && location) { + jump = xmalloc(sizeof(struct jump_key)); + + if (menu_is_visible(prop->menu)) { + /* + * There is not enough room to put the hint at the + * beginning of the "Prompt" line. Put the hint on the + * last "Location" line even when it would belong on + * the former. + */ + jump->target = prop->menu; + } else + jump->target = location; + + if (list_empty(head)) + jump->index = 0; + else + jump->index = list_entry(head->prev, struct jump_key, + entries)->index + 1; + + list_add_tail(&jump->entries, head); + } + + if (i > 0) { + str_printf(r, " Location:\n"); + for (j = 4; --i >= 0; j += 2) { + menu = submenu[i]; + if (jump && menu == location) + jump->offset = strlen(r->s); + str_printf(r, "%*c-> %s", j, ' ', + menu_get_prompt(menu)); + if (menu->sym) { + str_printf(r, " (%s [=%s])", menu->sym->name ? + menu->sym->name : "", + sym_get_string_value(menu->sym)); + } + str_append(r, "\n"); + } + } +} + +static void get_symbol_props_str(struct gstr *r, struct symbol *sym, + enum prop_type tok, const char *prefix) +{ + bool hit = false; + struct property *prop; + + for_all_properties(sym, prop, tok) { + if (!hit) { + str_append(r, prefix); + hit = true; + } else + str_printf(r, " && "); + expr_gstr_print(prop->expr, r); + } + if (hit) + str_append(r, "\n"); +} + +/* + * head is optional and may be NULL + */ +static void get_symbol_str(struct gstr *r, struct symbol *sym, + struct list_head *head) +{ + struct property *prop; + + if (sym && sym->name) { + str_printf(r, "Symbol: %s [=%s]\n", sym->name, + sym_get_string_value(sym)); + str_printf(r, "Type : %s\n", sym_type_name(sym->type)); + if (sym->type == S_INT || sym->type == S_HEX) { + prop = sym_get_range_prop(sym); + if (prop) { + str_printf(r, "Range : "); + expr_gstr_print(prop->expr, r); + str_append(r, "\n"); + } + } + } + + /* Print the definitions with prompts before the ones without */ + for_all_properties(sym, prop, P_SYMBOL) { + if (prop->menu->prompt) { + get_def_str(r, prop->menu); + get_prompt_str(r, prop->menu->prompt, head); + } + } + + for_all_properties(sym, prop, P_SYMBOL) { + if (!prop->menu->prompt) { + get_def_str(r, prop->menu); + get_dep_str(r, prop->menu->dep, " Depends on: "); + } + } + + get_symbol_props_str(r, sym, P_SELECT, "Selects: "); + if (sym->rev_dep.expr) { + expr_gstr_print_revdep(sym->rev_dep.expr, r, yes, "Selected by [y]:\n"); + expr_gstr_print_revdep(sym->rev_dep.expr, r, mod, "Selected by [m]:\n"); + expr_gstr_print_revdep(sym->rev_dep.expr, r, no, "Selected by [n]:\n"); + } + + get_symbol_props_str(r, sym, P_IMPLY, "Implies: "); + if (sym->implied.expr) { + expr_gstr_print_revdep(sym->implied.expr, r, yes, "Implied by [y]:\n"); + expr_gstr_print_revdep(sym->implied.expr, r, mod, "Implied by [m]:\n"); + expr_gstr_print_revdep(sym->implied.expr, r, no, "Implied by [n]:\n"); + } + + str_append(r, "\n\n"); +} + +struct gstr get_relations_str(struct symbol **sym_arr, struct list_head *head) +{ + struct symbol *sym; + struct gstr res = str_new(); + int i; + + for (i = 0; sym_arr && (sym = sym_arr[i]); i++) + get_symbol_str(&res, sym, head); + if (!i) + str_append(&res, "No matches found.\n"); + return res; +} + + +void menu_get_ext_help(struct menu *menu, struct gstr *help) +{ + struct symbol *sym = menu->sym; + const char *help_text = nohelp_text; + + if (menu_has_help(menu)) { + if (sym->name) + str_printf(help, "%s%s:\n\n", CONFIG_, sym->name); + help_text = menu_get_help(menu); + } + str_printf(help, "%s\n", help_text); + if (sym) + get_symbol_str(help, sym, NULL); +} diff --git a/nemu/tools/kconfig/parser.y b/nemu/tools/kconfig/parser.y new file mode 100644 index 0000000..190f111 --- /dev/null +++ b/nemu/tools/kconfig/parser.y @@ -0,0 +1,727 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2002 Roman Zippel + */ +%{ + +#include +#include +#include +#include +#include +#include + +#include "lkc.h" + +#define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) + +#define PRINTD 0x0001 +#define DEBUG_PARSE 0x0002 + +int cdebug = PRINTD; + +static void yyerror(const char *err); +static void zconfprint(const char *err, ...); +static void zconf_error(const char *err, ...); +static bool zconf_endtoken(const char *tokenname, + const char *expected_tokenname); + +struct symbol *symbol_hash[SYMBOL_HASHSIZE]; + +static struct menu *current_menu, *current_entry; + +%} + +%union +{ + char *string; + struct symbol *symbol; + struct expr *expr; + struct menu *menu; + enum symbol_type type; + enum variable_flavor flavor; +} + +%token T_HELPTEXT +%token T_WORD +%token T_WORD_QUOTE +%token T_ALLNOCONFIG_Y +%token T_BOOL +%token T_CHOICE +%token T_CLOSE_PAREN +%token T_COLON_EQUAL +%token T_COMMENT +%token T_CONFIG +%token T_DEFAULT +%token T_DEFCONFIG_LIST +%token T_DEF_BOOL +%token T_DEF_TRISTATE +%token T_DEPENDS +%token T_ENDCHOICE +%token T_ENDIF +%token T_ENDMENU +%token T_HELP +%token T_HEX +%token T_IF +%token T_IMPLY +%token T_INT +%token T_MAINMENU +%token T_MENU +%token T_MENUCONFIG +%token T_MODULES +%token T_ON +%token T_OPEN_PAREN +%token T_OPTION +%token T_OPTIONAL +%token T_PLUS_EQUAL +%token T_PROMPT +%token T_RANGE +%token T_SELECT +%token T_SOURCE +%token T_STRING +%token T_TRISTATE +%token T_VISIBLE +%token T_EOL +%token T_ASSIGN_VAL + +%left T_OR +%left T_AND +%left T_EQUAL T_UNEQUAL +%left T_LESS T_LESS_EQUAL T_GREATER T_GREATER_EQUAL +%nonassoc T_NOT + +%type nonconst_symbol +%type symbol +%type type logic_type default +%type expr +%type if_expr +%type end +%type if_entry menu_entry choice_entry +%type word_opt assign_val +%type assign_op + +%destructor { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + $$->file->name, $$->lineno); + if (current_menu == $$) + menu_end_menu(); +} if_entry menu_entry choice_entry + +%% +input: mainmenu_stmt stmt_list | stmt_list; + +/* mainmenu entry */ + +mainmenu_stmt: T_MAINMENU T_WORD_QUOTE T_EOL +{ + menu_add_prompt(P_MENU, $2, NULL); +}; + +stmt_list: + /* empty */ + | stmt_list assignment_stmt + | stmt_list choice_stmt + | stmt_list comment_stmt + | stmt_list config_stmt + | stmt_list if_stmt + | stmt_list menu_stmt + | stmt_list menuconfig_stmt + | stmt_list source_stmt + | stmt_list T_WORD error T_EOL { zconf_error("unknown statement \"%s\"", $2); } + | stmt_list error T_EOL { zconf_error("invalid statement"); } +; + +stmt_list_in_choice: + /* empty */ + | stmt_list_in_choice comment_stmt + | stmt_list_in_choice config_stmt + | stmt_list_in_choice if_stmt_in_choice + | stmt_list_in_choice error T_EOL { zconf_error("invalid statement"); } +; + +/* config/menuconfig entry */ + +config_entry_start: T_CONFIG nonconst_symbol T_EOL +{ + $2->flags |= SYMBOL_OPTIONAL; + menu_add_entry($2); + printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2->name); +}; + +config_stmt: config_entry_start config_option_list +{ + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +}; + +menuconfig_entry_start: T_MENUCONFIG nonconst_symbol T_EOL +{ + $2->flags |= SYMBOL_OPTIONAL; + menu_add_entry($2); + printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2->name); +}; + +menuconfig_stmt: menuconfig_entry_start config_option_list +{ + if (current_entry->prompt) + current_entry->prompt->type = P_MENU; + else + zconfprint("warning: menuconfig statement without prompt"); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +}; + +config_option_list: + /* empty */ + | config_option_list config_option + | config_option_list depends + | config_option_list help +; + +config_option: type prompt_stmt_opt T_EOL +{ + menu_set_type($1); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + $1); +}; + +config_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +}; + +config_option: default expr if_expr T_EOL +{ + menu_add_expr(P_DEFAULT, $2, $3); + if ($1 != S_UNKNOWN) + menu_set_type($1); + printd(DEBUG_PARSE, "%s:%d:default(%u)\n", + zconf_curname(), zconf_lineno(), + $1); +}; + +config_option: T_SELECT nonconst_symbol if_expr T_EOL +{ + menu_add_symbol(P_SELECT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_IMPLY nonconst_symbol if_expr T_EOL +{ + menu_add_symbol(P_IMPLY, $2, $3); + printd(DEBUG_PARSE, "%s:%d:imply\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_RANGE symbol symbol if_expr T_EOL +{ + menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,$2, $3), $4); + printd(DEBUG_PARSE, "%s:%d:range\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_OPTION T_MODULES T_EOL +{ + menu_add_option_modules(); +}; + +config_option: T_OPTION T_DEFCONFIG_LIST T_EOL +{ + menu_add_option_defconfig_list(); +}; + +config_option: T_OPTION T_ALLNOCONFIG_Y T_EOL +{ + menu_add_option_allnoconfig_y(); +}; + +/* choice entry */ + +choice: T_CHOICE word_opt T_EOL +{ + struct symbol *sym = sym_lookup($2, SYMBOL_CHOICE); + sym->flags |= SYMBOL_NO_WRITE; + menu_add_entry(sym); + menu_add_expr(P_CHOICE, NULL, NULL); + free($2); + printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno()); +}; + +choice_entry: choice choice_option_list +{ + $$ = menu_add_menu(); +}; + +choice_end: end +{ + if (zconf_endtoken($1, "choice")) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endchoice\n", zconf_curname(), zconf_lineno()); + } +}; + +choice_stmt: choice_entry stmt_list_in_choice choice_end +; + +choice_option_list: + /* empty */ + | choice_option_list choice_option + | choice_option_list depends + | choice_option_list help +; + +choice_option: T_PROMPT T_WORD_QUOTE if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +}; + +choice_option: logic_type prompt_stmt_opt T_EOL +{ + menu_set_type($1); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), $1); +}; + +choice_option: T_OPTIONAL T_EOL +{ + current_entry->sym->flags |= SYMBOL_OPTIONAL; + printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno()); +}; + +choice_option: T_DEFAULT nonconst_symbol if_expr T_EOL +{ + menu_add_symbol(P_DEFAULT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:default\n", + zconf_curname(), zconf_lineno()); +}; + +type: + logic_type + | T_INT { $$ = S_INT; } + | T_HEX { $$ = S_HEX; } + | T_STRING { $$ = S_STRING; } + +logic_type: + T_BOOL { $$ = S_BOOLEAN; } + | T_TRISTATE { $$ = S_TRISTATE; } + +default: + T_DEFAULT { $$ = S_UNKNOWN; } + | T_DEF_BOOL { $$ = S_BOOLEAN; } + | T_DEF_TRISTATE { $$ = S_TRISTATE; } + +/* if entry */ + +if_entry: T_IF expr T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno()); + menu_add_entry(NULL); + menu_add_dep($2); + $$ = menu_add_menu(); +}; + +if_end: end +{ + if (zconf_endtoken($1, "if")) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endif\n", zconf_curname(), zconf_lineno()); + } +}; + +if_stmt: if_entry stmt_list if_end +; + +if_stmt_in_choice: if_entry stmt_list_in_choice if_end +; + +/* menu entry */ + +menu: T_MENU T_WORD_QUOTE T_EOL +{ + menu_add_entry(NULL); + menu_add_prompt(P_MENU, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:menu\n", zconf_curname(), zconf_lineno()); +}; + +menu_entry: menu menu_option_list +{ + $$ = menu_add_menu(); +}; + +menu_end: end +{ + if (zconf_endtoken($1, "menu")) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endmenu\n", zconf_curname(), zconf_lineno()); + } +}; + +menu_stmt: menu_entry stmt_list menu_end +; + +menu_option_list: + /* empty */ + | menu_option_list visible + | menu_option_list depends +; + +source_stmt: T_SOURCE T_WORD_QUOTE T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), $2); + zconf_nextfile($2); + free($2); +}; + +/* comment entry */ + +comment: T_COMMENT T_WORD_QUOTE T_EOL +{ + menu_add_entry(NULL); + menu_add_prompt(P_COMMENT, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:comment\n", zconf_curname(), zconf_lineno()); +}; + +comment_stmt: comment comment_option_list +; + +comment_option_list: + /* empty */ + | comment_option_list depends +; + +/* help option */ + +help_start: T_HELP T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:help\n", zconf_curname(), zconf_lineno()); + zconf_starthelp(); +}; + +help: help_start T_HELPTEXT +{ + if (current_entry->help) { + free(current_entry->help); + zconfprint("warning: '%s' defined with more than one help text -- only the last one will be used", + current_entry->sym->name ?: ""); + } + + /* Is the help text empty or all whitespace? */ + if ($2[strspn($2, " \f\n\r\t\v")] == '\0') + zconfprint("warning: '%s' defined with blank help text", + current_entry->sym->name ?: ""); + + current_entry->help = $2; +}; + +/* depends option */ + +depends: T_DEPENDS T_ON expr T_EOL +{ + menu_add_dep($3); + printd(DEBUG_PARSE, "%s:%d:depends on\n", zconf_curname(), zconf_lineno()); +}; + +/* visibility option */ +visible: T_VISIBLE if_expr T_EOL +{ + menu_add_visibility($2); +}; + +/* prompt statement */ + +prompt_stmt_opt: + /* empty */ + | T_WORD_QUOTE if_expr +{ + menu_add_prompt(P_PROMPT, $1, $2); +}; + +end: T_ENDMENU T_EOL { $$ = "menu"; } + | T_ENDCHOICE T_EOL { $$ = "choice"; } + | T_ENDIF T_EOL { $$ = "if"; } +; + +if_expr: /* empty */ { $$ = NULL; } + | T_IF expr { $$ = $2; } +; + +expr: symbol { $$ = expr_alloc_symbol($1); } + | symbol T_LESS symbol { $$ = expr_alloc_comp(E_LTH, $1, $3); } + | symbol T_LESS_EQUAL symbol { $$ = expr_alloc_comp(E_LEQ, $1, $3); } + | symbol T_GREATER symbol { $$ = expr_alloc_comp(E_GTH, $1, $3); } + | symbol T_GREATER_EQUAL symbol { $$ = expr_alloc_comp(E_GEQ, $1, $3); } + | symbol T_EQUAL symbol { $$ = expr_alloc_comp(E_EQUAL, $1, $3); } + | symbol T_UNEQUAL symbol { $$ = expr_alloc_comp(E_UNEQUAL, $1, $3); } + | T_OPEN_PAREN expr T_CLOSE_PAREN { $$ = $2; } + | T_NOT expr { $$ = expr_alloc_one(E_NOT, $2); } + | expr T_OR expr { $$ = expr_alloc_two(E_OR, $1, $3); } + | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); } +; + +/* For symbol definitions, selects, etc., where quotes are not accepted */ +nonconst_symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); }; + +symbol: nonconst_symbol + | T_WORD_QUOTE { $$ = sym_lookup($1, SYMBOL_CONST); free($1); } +; + +word_opt: /* empty */ { $$ = NULL; } + | T_WORD + +/* assignment statement */ + +assignment_stmt: T_WORD assign_op assign_val T_EOL { variable_add($1, $3, $2); free($1); free($3); } + +assign_op: + T_EQUAL { $$ = VAR_RECURSIVE; } + | T_COLON_EQUAL { $$ = VAR_SIMPLE; } + | T_PLUS_EQUAL { $$ = VAR_APPEND; } +; + +assign_val: + /* empty */ { $$ = xstrdup(""); }; + | T_ASSIGN_VAL +; + +%% + +void conf_parse(const char *name) +{ + struct symbol *sym; + int i; + + zconf_initscan(name); + + _menu_init(); + + if (getenv("ZCONF_DEBUG")) + yydebug = 1; + yyparse(); + + /* Variables are expanded in the parse phase. We can free them here. */ + variable_all_del(); + + if (yynerrs) + exit(1); + if (!modules_sym) + modules_sym = sym_find( "n" ); + + if (!menu_has_prompt(&rootmenu)) { + current_entry = &rootmenu; + menu_add_prompt(P_MENU, "Main menu", NULL); + } + + menu_finalize(&rootmenu); + for_all_symbols(i, sym) { + if (sym_check_deps(sym)) + yynerrs++; + } + if (yynerrs) + exit(1); + sym_set_change_count(1); +} + +static bool zconf_endtoken(const char *tokenname, + const char *expected_tokenname) +{ + if (strcmp(tokenname, expected_tokenname)) { + zconf_error("unexpected '%s' within %s block", + tokenname, expected_tokenname); + yynerrs++; + return false; + } + if (current_menu->file != current_file) { + zconf_error("'%s' in different file than '%s'", + tokenname, expected_tokenname); + fprintf(stderr, "%s:%d: location of the '%s'\n", + current_menu->file->name, current_menu->lineno, + expected_tokenname); + yynerrs++; + return false; + } + return true; +} + +static void zconfprint(const char *err, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconf_error(const char *err, ...) +{ + va_list ap; + + yynerrs++; + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void yyerror(const char *err) +{ + fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err); +} + +static void print_quoted_string(FILE *out, const char *str) +{ + const char *p; + int len; + + putc('"', out); + while ((p = strchr(str, '"'))) { + len = p - str; + if (len) + fprintf(out, "%.*s", len, str); + fputs("\\\"", out); + str = p + 1; + } + fputs(str, out); + putc('"', out); +} + +static void print_symbol(FILE *out, struct menu *menu) +{ + struct symbol *sym = menu->sym; + struct property *prop; + + if (sym_is_choice(sym)) + fprintf(out, "\nchoice\n"); + else + fprintf(out, "\nconfig %s\n", sym->name); + switch (sym->type) { + case S_BOOLEAN: + fputs(" bool\n", out); + break; + case S_TRISTATE: + fputs(" tristate\n", out); + break; + case S_STRING: + fputs(" string\n", out); + break; + case S_INT: + fputs(" integer\n", out); + break; + case S_HEX: + fputs(" hex\n", out); + break; + default: + fputs(" ???\n", out); + break; + } + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + switch (prop->type) { + case P_PROMPT: + fputs(" prompt ", out); + print_quoted_string(out, prop->text); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_DEFAULT: + fputs( " default ", out); + expr_fprint(prop->expr, out); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_CHOICE: + fputs(" #choice value\n", out); + break; + case P_SELECT: + fputs( " select ", out); + expr_fprint(prop->expr, out); + fputc('\n', out); + break; + case P_IMPLY: + fputs( " imply ", out); + expr_fprint(prop->expr, out); + fputc('\n', out); + break; + case P_RANGE: + fputs( " range ", out); + expr_fprint(prop->expr, out); + fputc('\n', out); + break; + case P_MENU: + fputs( " menu ", out); + print_quoted_string(out, prop->text); + fputc('\n', out); + break; + case P_SYMBOL: + fputs( " symbol ", out); + fprintf(out, "%s\n", prop->menu->sym->name); + break; + default: + fprintf(out, " unknown prop %d!\n", prop->type); + break; + } + } + if (menu->help) { + int len = strlen(menu->help); + while (menu->help[--len] == '\n') + menu->help[len] = 0; + fprintf(out, " help\n%s\n", menu->help); + } +} + +void zconfdump(FILE *out) +{ + struct property *prop; + struct symbol *sym; + struct menu *menu; + + menu = rootmenu.list; + while (menu) { + if ((sym = menu->sym)) + print_symbol(out, menu); + else if ((prop = menu->prompt)) { + switch (prop->type) { + case P_COMMENT: + fputs("\ncomment ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + case P_MENU: + fputs("\nmenu ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + default: + ; + } + if (!expr_is_yes(prop->visible.expr)) { + fputs(" depends ", out); + expr_fprint(prop->visible.expr, out); + fputc('\n', out); + } + } + + if (menu->list) + menu = menu->list; + else if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->prompt && menu->prompt->type == P_MENU) + fputs("\nendmenu\n", out); + if (menu->next) { + menu = menu->next; + break; + } + } + } +} + +#include "menu.c" diff --git a/nemu/tools/kconfig/preprocess.c b/nemu/tools/kconfig/preprocess.c new file mode 100644 index 0000000..0590f86 --- /dev/null +++ b/nemu/tools/kconfig/preprocess.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2018 Masahiro Yamada + +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "lkc.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static char *expand_string_with_args(const char *in, int argc, char *argv[]); +static char *expand_string(const char *in); + +static void __attribute__((noreturn)) pperror(const char *format, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", current_file->name, yylineno); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fprintf(stderr, "\n"); + + exit(1); +} + +/* + * Environment variables + */ +static LIST_HEAD(env_list); + +struct env { + char *name; + char *value; + struct list_head node; +}; + +static void env_add(const char *name, const char *value) +{ + struct env *e; + + e = xmalloc(sizeof(*e)); + e->name = xstrdup(name); + e->value = xstrdup(value); + + list_add_tail(&e->node, &env_list); +} + +static void env_del(struct env *e) +{ + list_del(&e->node); + free(e->name); + free(e->value); + free(e); +} + +/* The returned pointer must be freed when done */ +static char *env_expand(const char *name) +{ + struct env *e; + const char *value; + + if (!*name) + return NULL; + + list_for_each_entry(e, &env_list, node) { + if (!strcmp(name, e->name)) + return xstrdup(e->value); + } + + value = getenv(name); + if (!value) + return NULL; + + /* + * We need to remember all referenced environment variables. + * They will be written out to include/config/auto.conf.cmd + */ + env_add(name, value); + + return xstrdup(value); +} + +void env_write_dep(FILE *f, const char *autoconfig_name) +{ + struct env *e, *tmp; + + list_for_each_entry_safe(e, tmp, &env_list, node) { + fprintf(f, "ifneq \"$(%s)\" \"%s\"\n", e->name, e->value); + fprintf(f, "%s: FORCE\n", autoconfig_name); + fprintf(f, "endif\n"); + env_del(e); + } +} + +/* + * Built-in functions + */ +struct function { + const char *name; + unsigned int min_args; + unsigned int max_args; + char *(*func)(int argc, char *argv[]); +}; + +static char *do_error_if(int argc, char *argv[]) +{ + if (!strcmp(argv[0], "y")) + pperror("%s", argv[1]); + + return xstrdup(""); +} + +static char *do_filename(int argc, char *argv[]) +{ + return xstrdup(current_file->name); +} + +static char *do_info(int argc, char *argv[]) +{ + printf("%s\n", argv[0]); + + return xstrdup(""); +} + +static char *do_lineno(int argc, char *argv[]) +{ + char buf[16]; + + sprintf(buf, "%d", yylineno); + + return xstrdup(buf); +} + +static char *do_shell(int argc, char *argv[]) +{ + FILE *p; + char buf[256]; + char *cmd; + size_t nread; + int i; + + cmd = argv[0]; + + p = popen(cmd, "r"); + if (!p) { + perror(cmd); + exit(1); + } + + nread = fread(buf, 1, sizeof(buf), p); + if (nread == sizeof(buf)) + nread--; + + /* remove trailing new lines */ + while (nread > 0 && buf[nread - 1] == '\n') + nread--; + + buf[nread] = 0; + + /* replace a new line with a space */ + for (i = 0; i < nread; i++) { + if (buf[i] == '\n') + buf[i] = ' '; + } + + if (pclose(p) == -1) { + perror(cmd); + exit(1); + } + + return xstrdup(buf); +} + +static char *do_warning_if(int argc, char *argv[]) +{ + if (!strcmp(argv[0], "y")) + fprintf(stderr, "%s:%d: %s\n", + current_file->name, yylineno, argv[1]); + + return xstrdup(""); +} + +static const struct function function_table[] = { + /* Name MIN MAX Function */ + { "error-if", 2, 2, do_error_if }, + { "filename", 0, 0, do_filename }, + { "info", 1, 1, do_info }, + { "lineno", 0, 0, do_lineno }, + { "shell", 1, 1, do_shell }, + { "warning-if", 2, 2, do_warning_if }, +}; + +#define FUNCTION_MAX_ARGS 16 + +static char *function_expand(const char *name, int argc, char *argv[]) +{ + const struct function *f; + int i; + + for (i = 0; i < ARRAY_SIZE(function_table); i++) { + f = &function_table[i]; + if (strcmp(f->name, name)) + continue; + + if (argc < f->min_args) + pperror("too few function arguments passed to '%s'", + name); + + if (argc > f->max_args) + pperror("too many function arguments passed to '%s'", + name); + + return f->func(argc, argv); + } + + return NULL; +} + +/* + * Variables (and user-defined functions) + */ +static LIST_HEAD(variable_list); + +struct variable { + char *name; + char *value; + enum variable_flavor flavor; + int exp_count; + struct list_head node; +}; + +static struct variable *variable_lookup(const char *name) +{ + struct variable *v; + + list_for_each_entry(v, &variable_list, node) { + if (!strcmp(name, v->name)) + return v; + } + + return NULL; +} + +static char *variable_expand(const char *name, int argc, char *argv[]) +{ + struct variable *v; + char *res; + + v = variable_lookup(name); + if (!v) + return NULL; + + if (argc == 0 && v->exp_count) + pperror("Recursive variable '%s' references itself (eventually)", + name); + + if (v->exp_count > 1000) + pperror("Too deep recursive expansion"); + + v->exp_count++; + + if (v->flavor == VAR_RECURSIVE) + res = expand_string_with_args(v->value, argc, argv); + else + res = xstrdup(v->value); + + v->exp_count--; + + return res; +} + +void variable_add(const char *name, const char *value, + enum variable_flavor flavor) +{ + struct variable *v; + char *new_value; + bool append = false; + + v = variable_lookup(name); + if (v) { + /* For defined variables, += inherits the existing flavor */ + if (flavor == VAR_APPEND) { + flavor = v->flavor; + append = true; + } else { + free(v->value); + } + } else { + /* For undefined variables, += assumes the recursive flavor */ + if (flavor == VAR_APPEND) + flavor = VAR_RECURSIVE; + + v = xmalloc(sizeof(*v)); + v->name = xstrdup(name); + v->exp_count = 0; + list_add_tail(&v->node, &variable_list); + } + + v->flavor = flavor; + + if (flavor == VAR_SIMPLE) + new_value = expand_string(value); + else + new_value = xstrdup(value); + + if (append) { + v->value = xrealloc(v->value, + strlen(v->value) + strlen(new_value) + 2); + strcat(v->value, " "); + strcat(v->value, new_value); + free(new_value); + } else { + v->value = new_value; + } +} + +static void variable_del(struct variable *v) +{ + list_del(&v->node); + free(v->name); + free(v->value); + free(v); +} + +void variable_all_del(void) +{ + struct variable *v, *tmp; + + list_for_each_entry_safe(v, tmp, &variable_list, node) + variable_del(v); +} + +/* + * Evaluate a clause with arguments. argc/argv are arguments from the upper + * function call. + * + * Returned string must be freed when done + */ +static char *eval_clause(const char *str, size_t len, int argc, char *argv[]) +{ + char *tmp, *name, *res, *endptr, *prev, *p; + int new_argc = 0; + char *new_argv[FUNCTION_MAX_ARGS]; + int nest = 0; + int i; + unsigned long n; + + tmp = xstrndup(str, len); + + /* + * If variable name is '1', '2', etc. It is generally an argument + * from a user-function call (i.e. local-scope variable). If not + * available, then look-up global-scope variables. + */ + n = strtoul(tmp, &endptr, 10); + if (!*endptr && n > 0 && n <= argc) { + res = xstrdup(argv[n - 1]); + goto free_tmp; + } + + prev = p = tmp; + + /* + * Split into tokens + * The function name and arguments are separated by a comma. + * For example, if the function call is like this: + * $(foo,$(x),$(y)) + * + * The input string for this helper should be: + * foo,$(x),$(y) + * + * and split into: + * new_argv[0] = 'foo' + * new_argv[1] = '$(x)' + * new_argv[2] = '$(y)' + */ + while (*p) { + if (nest == 0 && *p == ',') { + *p = 0; + if (new_argc >= FUNCTION_MAX_ARGS) + pperror("too many function arguments"); + new_argv[new_argc++] = prev; + prev = p + 1; + } else if (*p == '(') { + nest++; + } else if (*p == ')') { + nest--; + } + + p++; + } + new_argv[new_argc++] = prev; + + /* + * Shift arguments + * new_argv[0] represents a function name or a variable name. Put it + * into 'name', then shift the rest of the arguments. This simplifies + * 'const' handling. + */ + name = expand_string_with_args(new_argv[0], argc, argv); + new_argc--; + for (i = 0; i < new_argc; i++) + new_argv[i] = expand_string_with_args(new_argv[i + 1], + argc, argv); + + /* Search for variables */ + res = variable_expand(name, new_argc, new_argv); + if (res) + goto free; + + /* Look for built-in functions */ + res = function_expand(name, new_argc, new_argv); + if (res) + goto free; + + /* Last, try environment variable */ + if (new_argc == 0) { + res = env_expand(name); + if (res) + goto free; + } + + res = xstrdup(""); +free: + for (i = 0; i < new_argc; i++) + free(new_argv[i]); + free(name); +free_tmp: + free(tmp); + + return res; +} + +/* + * Expand a string that follows '$' + * + * For example, if the input string is + * ($(FOO)$($(BAR)))$(BAZ) + * this helper evaluates + * $($(FOO)$($(BAR))) + * and returns a new string containing the expansion (note that the string is + * recursively expanded), also advancing 'str' to point to the next character + * after the corresponding closing parenthesis, in this case, *str will be + * $(BAR) + */ +static char *expand_dollar_with_args(const char **str, int argc, char *argv[]) +{ + const char *p = *str; + const char *q; + int nest = 0; + + /* + * In Kconfig, variable/function references always start with "$(". + * Neither single-letter variables as in $A nor curly braces as in ${CC} + * are supported. '$' not followed by '(' loses its special meaning. + */ + if (*p != '(') { + *str = p; + return xstrdup("$"); + } + + p++; + q = p; + while (*q) { + if (*q == '(') { + nest++; + } else if (*q == ')') { + if (nest-- == 0) + break; + } + q++; + } + + if (!*q) + pperror("unterminated reference to '%s': missing ')'", p); + + /* Advance 'str' to after the expanded initial portion of the string */ + *str = q + 1; + + return eval_clause(p, q - p, argc, argv); +} + +char *expand_dollar(const char **str) +{ + return expand_dollar_with_args(str, 0, NULL); +} + +static char *__expand_string(const char **str, bool (*is_end)(char c), + int argc, char *argv[]) +{ + const char *in, *p; + char *expansion, *out; + size_t in_len, out_len; + + out = xmalloc(1); + *out = 0; + out_len = 1; + + p = in = *str; + + while (1) { + if (*p == '$') { + in_len = p - in; + p++; + expansion = expand_dollar_with_args(&p, argc, argv); + out_len += in_len + strlen(expansion); + out = xrealloc(out, out_len); + strncat(out, in, in_len); + strcat(out, expansion); + free(expansion); + in = p; + continue; + } + + if (is_end(*p)) + break; + + p++; + } + + in_len = p - in; + out_len += in_len; + out = xrealloc(out, out_len); + strncat(out, in, in_len); + + /* Advance 'str' to the end character */ + *str = p; + + return out; +} + +static bool is_end_of_str(char c) +{ + return !c; +} + +/* + * Expand variables and functions in the given string. Undefined variables + * expand to an empty string. + * The returned string must be freed when done. + */ +static char *expand_string_with_args(const char *in, int argc, char *argv[]) +{ + return __expand_string(&in, is_end_of_str, argc, argv); +} + +static char *expand_string(const char *in) +{ + return expand_string_with_args(in, 0, NULL); +} + +static bool is_end_of_token(char c) +{ + return !(isalnum(c) || c == '_' || c == '-'); +} + +/* + * Expand variables in a token. The parsing stops when a token separater + * (in most cases, it is a whitespace) is encountered. 'str' is updated to + * point to the next character. + * + * The returned string must be freed when done. + */ +char *expand_one_token(const char **str) +{ + return __expand_string(str, is_end_of_token, 0, NULL); +} diff --git a/nemu/tools/kconfig/symbol.c b/nemu/tools/kconfig/symbol.c new file mode 100644 index 0000000..ffa3ec6 --- /dev/null +++ b/nemu/tools/kconfig/symbol.c @@ -0,0 +1,1314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002 Roman Zippel + */ + +#include +#include +#include +#include +#include + +#include "lkc.h" + +struct symbol symbol_yes = { + .name = "y", + .curr = { "y", yes }, + .flags = SYMBOL_CONST|SYMBOL_VALID, +}; + +struct symbol symbol_mod = { + .name = "m", + .curr = { "m", mod }, + .flags = SYMBOL_CONST|SYMBOL_VALID, +}; + +struct symbol symbol_no = { + .name = "n", + .curr = { "n", no }, + .flags = SYMBOL_CONST|SYMBOL_VALID, +}; + +static struct symbol symbol_empty = { + .name = "", + .curr = { "", no }, + .flags = SYMBOL_VALID, +}; + +struct symbol *sym_defconfig_list; +struct symbol *modules_sym; +static tristate modules_val; + +enum symbol_type sym_get_type(struct symbol *sym) +{ + enum symbol_type type = sym->type; + + if (type == S_TRISTATE) { + if (sym_is_choice_value(sym) && sym->visible == yes) + type = S_BOOLEAN; + else if (modules_val == no) + type = S_BOOLEAN; + } + return type; +} + +const char *sym_type_name(enum symbol_type type) +{ + switch (type) { + case S_BOOLEAN: + return "bool"; + case S_TRISTATE: + return "tristate"; + case S_INT: + return "integer"; + case S_HEX: + return "hex"; + case S_STRING: + return "string"; + case S_UNKNOWN: + return "unknown"; + } + return "???"; +} + +struct property *sym_get_choice_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_choices(sym, prop) + return prop; + return NULL; +} + +static struct property *sym_get_default_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +struct property *sym_get_range_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_properties(sym, prop, P_RANGE) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +static long long sym_get_range_val(struct symbol *sym, int base) +{ + sym_calc_value(sym); + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + break; + } + return strtoll(sym->curr.val, NULL, base); +} + +static void sym_validate_range(struct symbol *sym) +{ + struct property *prop; + int base; + long long val, val2; + char str[64]; + + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + return; + } + prop = sym_get_range_prop(sym); + if (!prop) + return; + val = strtoll(sym->curr.val, NULL, base); + val2 = sym_get_range_val(prop->expr->left.sym, base); + if (val >= val2) { + val2 = sym_get_range_val(prop->expr->right.sym, base); + if (val <= val2) + return; + } + if (sym->type == S_INT) + sprintf(str, "%lld", val2); + else + sprintf(str, "0x%llx", val2); + sym->curr.val = xstrdup(str); +} + +static void sym_set_changed(struct symbol *sym) +{ + struct property *prop; + + sym->flags |= SYMBOL_CHANGED; + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu) + prop->menu->flags |= MENU_CHANGED; + } +} + +static void sym_set_all_changed(void) +{ + struct symbol *sym; + int i; + + for_all_symbols(i, sym) + sym_set_changed(sym); +} + +static void sym_calc_visibility(struct symbol *sym) +{ + struct property *prop; + struct symbol *choice_sym = NULL; + tristate tri; + + /* any prompt visible? */ + tri = no; + + if (sym_is_choice_value(sym)) + choice_sym = prop_get_symbol(sym_get_choice_prop(sym)); + + for_all_prompts(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + /* + * Tristate choice_values with visibility 'mod' are + * not visible if the corresponding choice's value is + * 'yes'. + */ + if (choice_sym && sym->type == S_TRISTATE && + prop->visible.tri == mod && choice_sym->curr.tri == yes) + prop->visible.tri = no; + + tri = EXPR_OR(tri, prop->visible.tri); + } + if (tri == mod && (sym->type != S_TRISTATE || modules_val == no)) + tri = yes; + if (sym->visible != tri) { + sym->visible = tri; + sym_set_changed(sym); + } + if (sym_is_choice_value(sym)) + return; + /* defaulting to "yes" if no explicit "depends on" are given */ + tri = yes; + if (sym->dir_dep.expr) + tri = expr_calc_value(sym->dir_dep.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->dir_dep.tri != tri) { + sym->dir_dep.tri = tri; + sym_set_changed(sym); + } + tri = no; + if (sym->rev_dep.expr) + tri = expr_calc_value(sym->rev_dep.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->rev_dep.tri != tri) { + sym->rev_dep.tri = tri; + sym_set_changed(sym); + } + tri = no; + if (sym->implied.expr) + tri = expr_calc_value(sym->implied.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->implied.tri != tri) { + sym->implied.tri = tri; + sym_set_changed(sym); + } +} + +/* + * Find the default symbol for a choice. + * First try the default values for the choice symbol + * Next locate the first visible choice value + * Return NULL if none was found + */ +struct symbol *sym_choice_default(struct symbol *sym) +{ + struct symbol *def_sym; + struct property *prop; + struct expr *e; + + /* any of the defaults visible? */ + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri == no) + continue; + def_sym = prop_get_symbol(prop); + if (def_sym->visible != no) + return def_sym; + } + + /* just get the first visible value */ + prop = sym_get_choice_prop(sym); + expr_list_for_each_sym(prop->expr, e, def_sym) + if (def_sym->visible != no) + return def_sym; + + /* failed to locate any defaults */ + return NULL; +} + +static struct symbol *sym_calc_choice(struct symbol *sym) +{ + struct symbol *def_sym; + struct property *prop; + struct expr *e; + int flags; + + /* first calculate all choice values' visibilities */ + flags = sym->flags; + prop = sym_get_choice_prop(sym); + expr_list_for_each_sym(prop->expr, e, def_sym) { + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + flags &= def_sym->flags; + } + + sym->flags &= flags | ~SYMBOL_DEF_USER; + + /* is the user choice visible? */ + def_sym = sym->def[S_DEF_USER].val; + if (def_sym && def_sym->visible != no) + return def_sym; + + def_sym = sym_choice_default(sym); + + if (def_sym == NULL) + /* no choice? reset tristate value */ + sym->curr.tri = no; + + return def_sym; +} + +static void sym_warn_unmet_dep(struct symbol *sym) +{ + struct gstr gs = str_new(); + + str_printf(&gs, + "\nWARNING: unmet direct dependencies detected for %s\n", + sym->name); + str_printf(&gs, + " Depends on [%c]: ", + sym->dir_dep.tri == mod ? 'm' : 'n'); + expr_gstr_print(sym->dir_dep.expr, &gs); + str_printf(&gs, "\n"); + + expr_gstr_print_revdep(sym->rev_dep.expr, &gs, yes, + " Selected by [y]:\n"); + expr_gstr_print_revdep(sym->rev_dep.expr, &gs, mod, + " Selected by [m]:\n"); + + fputs(str_get(&gs), stderr); +} + +void sym_calc_value(struct symbol *sym) +{ + struct symbol_value newval, oldval; + struct property *prop; + struct expr *e; + + if (!sym) + return; + + if (sym->flags & SYMBOL_VALID) + return; + + if (sym_is_choice_value(sym) && + sym->flags & SYMBOL_NEED_SET_CHOICE_VALUES) { + sym->flags &= ~SYMBOL_NEED_SET_CHOICE_VALUES; + prop = sym_get_choice_prop(sym); + sym_calc_value(prop_get_symbol(prop)); + } + + sym->flags |= SYMBOL_VALID; + + oldval = sym->curr; + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + newval = symbol_empty.curr; + break; + case S_BOOLEAN: + case S_TRISTATE: + newval = symbol_no.curr; + break; + default: + sym->curr.val = sym->name; + sym->curr.tri = no; + return; + } + sym->flags &= ~SYMBOL_WRITE; + + sym_calc_visibility(sym); + + if (sym->visible != no) + sym->flags |= SYMBOL_WRITE; + + /* set default if recursively called */ + sym->curr = newval; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_is_choice_value(sym) && sym->visible == yes) { + prop = sym_get_choice_prop(sym); + newval.tri = (prop_get_symbol(prop)->curr.val == sym) ? yes : no; + } else { + if (sym->visible != no) { + /* if the symbol is visible use the user value + * if available, otherwise try the default value + */ + if (sym_has_value(sym)) { + newval.tri = EXPR_AND(sym->def[S_DEF_USER].tri, + sym->visible); + goto calc_newval; + } + } + if (sym->rev_dep.tri != no) + sym->flags |= SYMBOL_WRITE; + if (!sym_is_choice(sym)) { + prop = sym_get_default_prop(sym); + if (prop) { + newval.tri = EXPR_AND(expr_calc_value(prop->expr), + prop->visible.tri); + if (newval.tri != no) + sym->flags |= SYMBOL_WRITE; + } + if (sym->implied.tri != no) { + sym->flags |= SYMBOL_WRITE; + newval.tri = EXPR_OR(newval.tri, sym->implied.tri); + newval.tri = EXPR_AND(newval.tri, + sym->dir_dep.tri); + } + } + calc_newval: + if (sym->dir_dep.tri < sym->rev_dep.tri) + sym_warn_unmet_dep(sym); + newval.tri = EXPR_OR(newval.tri, sym->rev_dep.tri); + } + if (newval.tri == mod && sym_get_type(sym) == S_BOOLEAN) + newval.tri = yes; + break; + case S_STRING: + case S_HEX: + case S_INT: + if (sym->visible != no && sym_has_value(sym)) { + newval.val = sym->def[S_DEF_USER].val; + break; + } + prop = sym_get_default_prop(sym); + if (prop) { + struct symbol *ds = prop_get_symbol(prop); + if (ds) { + sym->flags |= SYMBOL_WRITE; + sym_calc_value(ds); + newval.val = ds->curr.val; + } + } + break; + default: + ; + } + + sym->curr = newval; + if (sym_is_choice(sym) && newval.tri == yes) + sym->curr.val = sym_calc_choice(sym); + sym_validate_range(sym); + + if (memcmp(&oldval, &sym->curr, sizeof(oldval))) { + sym_set_changed(sym); + if (modules_sym == sym) { + sym_set_all_changed(); + modules_val = modules_sym->curr.tri; + } + } + + if (sym_is_choice(sym)) { + struct symbol *choice_sym; + + prop = sym_get_choice_prop(sym); + expr_list_for_each_sym(prop->expr, e, choice_sym) { + if ((sym->flags & SYMBOL_WRITE) && + choice_sym->visible != no) + choice_sym->flags |= SYMBOL_WRITE; + if (sym->flags & SYMBOL_CHANGED) + sym_set_changed(choice_sym); + } + } + + if (sym->flags & SYMBOL_NO_WRITE) + sym->flags &= ~SYMBOL_WRITE; + + if (sym->flags & SYMBOL_NEED_SET_CHOICE_VALUES) + set_all_choice_values(sym); +} + +void sym_clear_all_valid(void) +{ + struct symbol *sym; + int i; + + for_all_symbols(i, sym) + sym->flags &= ~SYMBOL_VALID; + sym_add_change_count(1); + sym_calc_value(modules_sym); +} + +bool sym_tristate_within_range(struct symbol *sym, tristate val) +{ + int type = sym_get_type(sym); + + if (sym->visible == no) + return false; + + if (type != S_BOOLEAN && type != S_TRISTATE) + return false; + + if (type == S_BOOLEAN && val == mod) + return false; + if (sym->visible <= sym->rev_dep.tri) + return false; + if (sym_is_choice_value(sym) && sym->visible == yes) + return val == yes; + return val >= sym->rev_dep.tri && val <= sym->visible; +} + +bool sym_set_tristate_value(struct symbol *sym, tristate val) +{ + tristate oldval = sym_get_tristate_value(sym); + + if (oldval != val && !sym_tristate_within_range(sym, val)) + return false; + + if (!(sym->flags & SYMBOL_DEF_USER)) { + sym->flags |= SYMBOL_DEF_USER; + sym_set_changed(sym); + } + /* + * setting a choice value also resets the new flag of the choice + * symbol and all other choice values. + */ + if (sym_is_choice_value(sym) && val == yes) { + struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); + struct property *prop; + struct expr *e; + + cs->def[S_DEF_USER].val = sym; + cs->flags |= SYMBOL_DEF_USER; + prop = sym_get_choice_prop(cs); + for (e = prop->expr; e; e = e->left.expr) { + if (e->right.sym->visible != no) + e->right.sym->flags |= SYMBOL_DEF_USER; + } + } + + sym->def[S_DEF_USER].tri = val; + if (oldval != val) + sym_clear_all_valid(); + + return true; +} + +tristate sym_toggle_tristate_value(struct symbol *sym) +{ + tristate oldval, newval; + + oldval = newval = sym_get_tristate_value(sym); + do { + switch (newval) { + case no: + newval = mod; + break; + case mod: + newval = yes; + break; + case yes: + newval = no; + break; + } + if (sym_set_tristate_value(sym, newval)) + break; + } while (oldval != newval); + return newval; +} + +bool sym_string_valid(struct symbol *sym, const char *str) +{ + signed char ch; + + switch (sym->type) { + case S_STRING: + return true; + case S_INT: + ch = *str++; + if (ch == '-') + ch = *str++; + if (!isdigit(ch)) + return false; + if (ch == '0' && *str != 0) + return false; + while ((ch = *str++)) { + if (!isdigit(ch)) + return false; + } + return true; + case S_HEX: + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + str += 2; + ch = *str++; + do { + if (!isxdigit(ch)) + return false; + } while ((ch = *str++)); + return true; + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + case 'm': case 'M': + case 'n': case 'N': + return true; + } + return false; + default: + return false; + } +} + +bool sym_string_within_range(struct symbol *sym, const char *str) +{ + struct property *prop; + long long val; + + switch (sym->type) { + case S_STRING: + return sym_string_valid(sym, str); + case S_INT: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtoll(str, NULL, 10); + return val >= sym_get_range_val(prop->expr->left.sym, 10) && + val <= sym_get_range_val(prop->expr->right.sym, 10); + case S_HEX: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtoll(str, NULL, 16); + return val >= sym_get_range_val(prop->expr->left.sym, 16) && + val <= sym_get_range_val(prop->expr->right.sym, 16); + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + return sym_tristate_within_range(sym, yes); + case 'm': case 'M': + return sym_tristate_within_range(sym, mod); + case 'n': case 'N': + return sym_tristate_within_range(sym, no); + } + return false; + default: + return false; + } +} + +bool sym_set_string_value(struct symbol *sym, const char *newval) +{ + const char *oldval; + char *val; + int size; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (newval[0]) { + case 'y': case 'Y': + return sym_set_tristate_value(sym, yes); + case 'm': case 'M': + return sym_set_tristate_value(sym, mod); + case 'n': case 'N': + return sym_set_tristate_value(sym, no); + } + return false; + default: + ; + } + + if (!sym_string_within_range(sym, newval)) + return false; + + if (!(sym->flags & SYMBOL_DEF_USER)) { + sym->flags |= SYMBOL_DEF_USER; + sym_set_changed(sym); + } + + oldval = sym->def[S_DEF_USER].val; + size = strlen(newval) + 1; + if (sym->type == S_HEX && (newval[0] != '0' || (newval[1] != 'x' && newval[1] != 'X'))) { + size += 2; + sym->def[S_DEF_USER].val = val = xmalloc(size); + *val++ = '0'; + *val++ = 'x'; + } else if (!oldval || strcmp(oldval, newval)) + sym->def[S_DEF_USER].val = val = xmalloc(size); + else + return true; + + strcpy(val, newval); + free((void *)oldval); + sym_clear_all_valid(); + + return true; +} + +/* + * Find the default value associated to a symbol. + * For tristate symbol handle the modules=n case + * in which case "m" becomes "y". + * If the symbol does not have any default then fallback + * to the fixed default values. + */ +const char *sym_get_string_default(struct symbol *sym) +{ + struct property *prop; + struct symbol *ds; + const char *str; + tristate val; + + sym_calc_visibility(sym); + sym_calc_value(modules_sym); + val = symbol_no.curr.tri; + str = symbol_empty.curr.val; + + /* If symbol has a default value look it up */ + prop = sym_get_default_prop(sym); + if (prop != NULL) { + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + /* The visibility may limit the value from yes => mod */ + val = EXPR_AND(expr_calc_value(prop->expr), prop->visible.tri); + break; + default: + /* + * The following fails to handle the situation + * where a default value is further limited by + * the valid range. + */ + ds = prop_get_symbol(prop); + if (ds != NULL) { + sym_calc_value(ds); + str = (const char *)ds->curr.val; + } + } + } + + /* Handle select statements */ + val = EXPR_OR(val, sym->rev_dep.tri); + + /* transpose mod to yes if modules are not enabled */ + if (val == mod) + if (!sym_is_choice_value(sym) && modules_sym->curr.tri == no) + val = yes; + + /* transpose mod to yes if type is bool */ + if (sym->type == S_BOOLEAN && val == mod) + val = yes; + + /* adjust the default value if this symbol is implied by another */ + if (val < sym->implied.tri) + val = sym->implied.tri; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (val) { + case no: return "n"; + case mod: return "m"; + case yes: return "y"; + } + case S_INT: + case S_HEX: + return str; + case S_STRING: + return str; + case S_UNKNOWN: + break; + } + return ""; +} + +const char *sym_get_string_value(struct symbol *sym) +{ + tristate val; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + val = sym_get_tristate_value(sym); + switch (val) { + case no: + return "n"; + case mod: + sym_calc_value(modules_sym); + return (modules_sym->curr.tri == no) ? "n" : "m"; + case yes: + return "y"; + } + break; + default: + ; + } + return (const char *)sym->curr.val; +} + +bool sym_is_changeable(struct symbol *sym) +{ + return sym->visible > sym->rev_dep.tri; +} + +static unsigned strhash(const char *s) +{ + /* fnv32 hash */ + unsigned hash = 2166136261U; + for (; *s; s++) + hash = (hash ^ *s) * 0x01000193; + return hash; +} + +struct symbol *sym_lookup(const char *name, int flags) +{ + struct symbol *symbol; + char *new_name; + int hash; + + if (name) { + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + hash = strhash(name) % SYMBOL_HASHSIZE; + + for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) { + if (symbol->name && + !strcmp(symbol->name, name) && + (flags ? symbol->flags & flags + : !(symbol->flags & (SYMBOL_CONST|SYMBOL_CHOICE)))) + return symbol; + } + new_name = xstrdup(name); + } else { + new_name = NULL; + hash = 0; + } + + symbol = xmalloc(sizeof(*symbol)); + memset(symbol, 0, sizeof(*symbol)); + symbol->name = new_name; + symbol->type = S_UNKNOWN; + symbol->flags = flags; + + symbol->next = symbol_hash[hash]; + symbol_hash[hash] = symbol; + + return symbol; +} + +struct symbol *sym_find(const char *name) +{ + struct symbol *symbol = NULL; + int hash = 0; + + if (!name) + return NULL; + + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + hash = strhash(name) % SYMBOL_HASHSIZE; + + for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) { + if (symbol->name && + !strcmp(symbol->name, name) && + !(symbol->flags & SYMBOL_CONST)) + break; + } + + return symbol; +} + +const char *sym_escape_string_value(const char *in) +{ + const char *p; + size_t reslen; + char *res; + size_t l; + + reslen = strlen(in) + strlen("\"\"") + 1; + + p = in; + for (;;) { + l = strcspn(p, "\"\\"); + p += l; + + if (p[0] == '\0') + break; + + reslen++; + p++; + } + + res = xmalloc(reslen); + res[0] = '\0'; + + strcat(res, "\""); + + p = in; + for (;;) { + l = strcspn(p, "\"\\"); + strncat(res, p, l); + p += l; + + if (p[0] == '\0') + break; + + strcat(res, "\\"); + strncat(res, p++, 1); + } + + strcat(res, "\""); + return res; +} + +struct sym_match { + struct symbol *sym; + off_t so, eo; +}; + +/* Compare matched symbols as thus: + * - first, symbols that match exactly + * - then, alphabetical sort + */ +static int sym_rel_comp(const void *sym1, const void *sym2) +{ + const struct sym_match *s1 = sym1; + const struct sym_match *s2 = sym2; + int exact1, exact2; + + /* Exact match: + * - if matched length on symbol s1 is the length of that symbol, + * then this symbol should come first; + * - if matched length on symbol s2 is the length of that symbol, + * then this symbol should come first. + * Note: since the search can be a regexp, both symbols may match + * exactly; if this is the case, we can't decide which comes first, + * and we fallback to sorting alphabetically. + */ + exact1 = (s1->eo - s1->so) == strlen(s1->sym->name); + exact2 = (s2->eo - s2->so) == strlen(s2->sym->name); + if (exact1 && !exact2) + return -1; + if (!exact1 && exact2) + return 1; + + /* As a fallback, sort symbols alphabetically */ + return strcmp(s1->sym->name, s2->sym->name); +} + +struct symbol **sym_re_search(const char *pattern) +{ + struct symbol *sym, **sym_arr = NULL; + struct sym_match *sym_match_arr = NULL; + int i, cnt, size; + regex_t re; + regmatch_t match[1]; + + cnt = size = 0; + /* Skip if empty */ + if (strlen(pattern) == 0) + return NULL; + if (regcomp(&re, pattern, REG_EXTENDED|REG_ICASE)) + return NULL; + + for_all_symbols(i, sym) { + if (sym->flags & SYMBOL_CONST || !sym->name) + continue; + if (regexec(&re, sym->name, 1, match, 0)) + continue; + if (cnt >= size) { + void *tmp; + size += 16; + tmp = realloc(sym_match_arr, size * sizeof(struct sym_match)); + if (!tmp) + goto sym_re_search_free; + sym_match_arr = tmp; + } + sym_calc_value(sym); + /* As regexec returned 0, we know we have a match, so + * we can use match[0].rm_[se]o without further checks + */ + sym_match_arr[cnt].so = match[0].rm_so; + sym_match_arr[cnt].eo = match[0].rm_eo; + sym_match_arr[cnt++].sym = sym; + } + if (sym_match_arr) { + qsort(sym_match_arr, cnt, sizeof(struct sym_match), sym_rel_comp); + sym_arr = malloc((cnt+1) * sizeof(struct symbol *)); + if (!sym_arr) + goto sym_re_search_free; + for (i = 0; i < cnt; i++) + sym_arr[i] = sym_match_arr[i].sym; + sym_arr[cnt] = NULL; + } +sym_re_search_free: + /* sym_match_arr can be NULL if no match, but free(NULL) is OK */ + free(sym_match_arr); + regfree(&re); + + return sym_arr; +} + +/* + * When we check for recursive dependencies we use a stack to save + * current state so we can print out relevant info to user. + * The entries are located on the call stack so no need to free memory. + * Note insert() remove() must always match to properly clear the stack. + */ +static struct dep_stack { + struct dep_stack *prev, *next; + struct symbol *sym; + struct property *prop; + struct expr **expr; +} *check_top; + +static void dep_stack_insert(struct dep_stack *stack, struct symbol *sym) +{ + memset(stack, 0, sizeof(*stack)); + if (check_top) + check_top->next = stack; + stack->prev = check_top; + stack->sym = sym; + check_top = stack; +} + +static void dep_stack_remove(void) +{ + check_top = check_top->prev; + if (check_top) + check_top->next = NULL; +} + +/* + * Called when we have detected a recursive dependency. + * check_top point to the top of the stact so we use + * the ->prev pointer to locate the bottom of the stack. + */ +static void sym_check_print_recursive(struct symbol *last_sym) +{ + struct dep_stack *stack; + struct symbol *sym, *next_sym; + struct menu *menu = NULL; + struct property *prop; + struct dep_stack cv_stack; + + if (sym_is_choice_value(last_sym)) { + dep_stack_insert(&cv_stack, last_sym); + last_sym = prop_get_symbol(sym_get_choice_prop(last_sym)); + } + + for (stack = check_top; stack != NULL; stack = stack->prev) + if (stack->sym == last_sym) + break; + if (!stack) { + fprintf(stderr, "unexpected recursive dependency error\n"); + return; + } + + for (; stack; stack = stack->next) { + sym = stack->sym; + next_sym = stack->next ? stack->next->sym : last_sym; + prop = stack->prop; + if (prop == NULL) + prop = stack->sym->prop; + + /* for choice values find the menu entry (used below) */ + if (sym_is_choice(sym) || sym_is_choice_value(sym)) { + for (prop = sym->prop; prop; prop = prop->next) { + menu = prop->menu; + if (prop->menu) + break; + } + } + if (stack->sym == last_sym) + fprintf(stderr, "%s:%d:error: recursive dependency detected!\n", + prop->file->name, prop->lineno); + + if (sym_is_choice(sym)) { + fprintf(stderr, "%s:%d:\tchoice %s contains symbol %s\n", + menu->file->name, menu->lineno, + sym->name ? sym->name : "", + next_sym->name ? next_sym->name : ""); + } else if (sym_is_choice_value(sym)) { + fprintf(stderr, "%s:%d:\tsymbol %s is part of choice %s\n", + menu->file->name, menu->lineno, + sym->name ? sym->name : "", + next_sym->name ? next_sym->name : ""); + } else if (stack->expr == &sym->dir_dep.expr) { + fprintf(stderr, "%s:%d:\tsymbol %s depends on %s\n", + prop->file->name, prop->lineno, + sym->name ? sym->name : "", + next_sym->name ? next_sym->name : ""); + } else if (stack->expr == &sym->rev_dep.expr) { + fprintf(stderr, "%s:%d:\tsymbol %s is selected by %s\n", + prop->file->name, prop->lineno, + sym->name ? sym->name : "", + next_sym->name ? next_sym->name : ""); + } else if (stack->expr == &sym->implied.expr) { + fprintf(stderr, "%s:%d:\tsymbol %s is implied by %s\n", + prop->file->name, prop->lineno, + sym->name ? sym->name : "", + next_sym->name ? next_sym->name : ""); + } else if (stack->expr) { + fprintf(stderr, "%s:%d:\tsymbol %s %s value contains %s\n", + prop->file->name, prop->lineno, + sym->name ? sym->name : "", + prop_get_type_name(prop->type), + next_sym->name ? next_sym->name : ""); + } else { + fprintf(stderr, "%s:%d:\tsymbol %s %s is visible depending on %s\n", + prop->file->name, prop->lineno, + sym->name ? sym->name : "", + prop_get_type_name(prop->type), + next_sym->name ? next_sym->name : ""); + } + } + + fprintf(stderr, + "For a resolution refer to Documentation/kbuild/kconfig-language.rst\n" + "subsection \"Kconfig recursive dependency limitations\"\n" + "\n"); + + if (check_top == &cv_stack) + dep_stack_remove(); +} + +static struct symbol *sym_check_expr_deps(struct expr *e) +{ + struct symbol *sym; + + if (!e) + return NULL; + switch (e->type) { + case E_OR: + case E_AND: + sym = sym_check_expr_deps(e->left.expr); + if (sym) + return sym; + return sym_check_expr_deps(e->right.expr); + case E_NOT: + return sym_check_expr_deps(e->left.expr); + case E_EQUAL: + case E_GEQ: + case E_GTH: + case E_LEQ: + case E_LTH: + case E_UNEQUAL: + sym = sym_check_deps(e->left.sym); + if (sym) + return sym; + return sym_check_deps(e->right.sym); + case E_SYMBOL: + return sym_check_deps(e->left.sym); + default: + break; + } + fprintf(stderr, "Oops! How to check %d?\n", e->type); + return NULL; +} + +/* return NULL when dependencies are OK */ +static struct symbol *sym_check_sym_deps(struct symbol *sym) +{ + struct symbol *sym2; + struct property *prop; + struct dep_stack stack; + + dep_stack_insert(&stack, sym); + + stack.expr = &sym->dir_dep.expr; + sym2 = sym_check_expr_deps(sym->dir_dep.expr); + if (sym2) + goto out; + + stack.expr = &sym->rev_dep.expr; + sym2 = sym_check_expr_deps(sym->rev_dep.expr); + if (sym2) + goto out; + + stack.expr = &sym->implied.expr; + sym2 = sym_check_expr_deps(sym->implied.expr); + if (sym2) + goto out; + + stack.expr = NULL; + + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->type == P_CHOICE || prop->type == P_SELECT || + prop->type == P_IMPLY) + continue; + stack.prop = prop; + sym2 = sym_check_expr_deps(prop->visible.expr); + if (sym2) + break; + if (prop->type != P_DEFAULT || sym_is_choice(sym)) + continue; + stack.expr = &prop->expr; + sym2 = sym_check_expr_deps(prop->expr); + if (sym2) + break; + stack.expr = NULL; + } + +out: + dep_stack_remove(); + + return sym2; +} + +static struct symbol *sym_check_choice_deps(struct symbol *choice) +{ + struct symbol *sym, *sym2; + struct property *prop; + struct expr *e; + struct dep_stack stack; + + dep_stack_insert(&stack, choice); + + prop = sym_get_choice_prop(choice); + expr_list_for_each_sym(prop->expr, e, sym) + sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + + choice->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + sym2 = sym_check_sym_deps(choice); + choice->flags &= ~SYMBOL_CHECK; + if (sym2) + goto out; + + expr_list_for_each_sym(prop->expr, e, sym) { + sym2 = sym_check_sym_deps(sym); + if (sym2) + break; + } +out: + expr_list_for_each_sym(prop->expr, e, sym) + sym->flags &= ~SYMBOL_CHECK; + + if (sym2 && sym_is_choice_value(sym2) && + prop_get_symbol(sym_get_choice_prop(sym2)) == choice) + sym2 = choice; + + dep_stack_remove(); + + return sym2; +} + +struct symbol *sym_check_deps(struct symbol *sym) +{ + struct symbol *sym2; + struct property *prop; + + if (sym->flags & SYMBOL_CHECK) { + sym_check_print_recursive(sym); + return sym; + } + if (sym->flags & SYMBOL_CHECKED) + return NULL; + + if (sym_is_choice_value(sym)) { + struct dep_stack stack; + + /* for choice groups start the check with main choice symbol */ + dep_stack_insert(&stack, sym); + prop = sym_get_choice_prop(sym); + sym2 = sym_check_deps(prop_get_symbol(prop)); + dep_stack_remove(); + } else if (sym_is_choice(sym)) { + sym2 = sym_check_choice_deps(sym); + } else { + sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + sym2 = sym_check_sym_deps(sym); + sym->flags &= ~SYMBOL_CHECK; + } + + return sym2; +} + +struct symbol *prop_get_symbol(struct property *prop) +{ + if (prop->expr && (prop->expr->type == E_SYMBOL || + prop->expr->type == E_LIST)) + return prop->expr->left.sym; + return NULL; +} + +const char *prop_get_type_name(enum prop_type type) +{ + switch (type) { + case P_PROMPT: + return "prompt"; + case P_COMMENT: + return "comment"; + case P_MENU: + return "menu"; + case P_DEFAULT: + return "default"; + case P_CHOICE: + return "choice"; + case P_SELECT: + return "select"; + case P_IMPLY: + return "imply"; + case P_RANGE: + return "range"; + case P_SYMBOL: + return "symbol"; + case P_UNKNOWN: + break; + } + return "unknown"; +} diff --git a/nemu/tools/kconfig/util.c b/nemu/tools/kconfig/util.c new file mode 100644 index 0000000..2958539 --- /dev/null +++ b/nemu/tools/kconfig/util.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2002-2005 Roman Zippel + * Copyright (C) 2002-2005 Sam Ravnborg + */ + +#include +#include +#include +#include "lkc.h" + +/* file already present in list? If not add it */ +struct file *file_lookup(const char *name) +{ + struct file *file; + + for (file = file_list; file; file = file->next) { + if (!strcmp(name, file->name)) { + return file; + } + } + + file = xmalloc(sizeof(*file)); + memset(file, 0, sizeof(*file)); + file->name = xstrdup(name); + file->next = file_list; + file_list = file; + return file; +} + +/* Allocate initial growable string */ +struct gstr str_new(void) +{ + struct gstr gs; + gs.s = xmalloc(sizeof(char) * 64); + gs.len = 64; + gs.max_width = 0; + strcpy(gs.s, "\0"); + return gs; +} + +/* Free storage for growable string */ +void str_free(struct gstr *gs) +{ + if (gs->s) + free(gs->s); + gs->s = NULL; + gs->len = 0; +} + +/* Append to growable string */ +void str_append(struct gstr *gs, const char *s) +{ + size_t l; + if (s) { + l = strlen(gs->s) + strlen(s) + 1; + if (l > gs->len) { + gs->s = xrealloc(gs->s, l); + gs->len = l; + } + strcat(gs->s, s); + } +} + +/* Append printf formatted string to growable string */ +void str_printf(struct gstr *gs, const char *fmt, ...) +{ + va_list ap; + char s[10000]; /* big enough... */ + va_start(ap, fmt); + vsnprintf(s, sizeof(s), fmt, ap); + str_append(gs, s); + va_end(ap); +} + +/* Retrieve value of growable string */ +const char *str_get(struct gstr *gs) +{ + return gs->s; +} + +void *xmalloc(size_t size) +{ + void *p = malloc(size); + if (p) + return p; + fprintf(stderr, "Out of memory.\n"); + exit(1); +} + +void *xcalloc(size_t nmemb, size_t size) +{ + void *p = calloc(nmemb, size); + if (p) + return p; + fprintf(stderr, "Out of memory.\n"); + exit(1); +} + +void *xrealloc(void *p, size_t size) +{ + p = realloc(p, size); + if (p) + return p; + fprintf(stderr, "Out of memory.\n"); + exit(1); +} + +char *xstrdup(const char *s) +{ + char *p; + + p = strdup(s); + if (p) + return p; + fprintf(stderr, "Out of memory.\n"); + exit(1); +} + +char *xstrndup(const char *s, size_t n) +{ + char *p; + + p = strndup(s, n); + if (p) + return p; + fprintf(stderr, "Out of memory.\n"); + exit(1); +} diff --git a/nemu/tools/kvm-diff/Makefile b/nemu/tools/kvm-diff/Makefile new file mode 100644 index 0000000..508e1e9 --- /dev/null +++ b/nemu/tools/kvm-diff/Makefile @@ -0,0 +1,23 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +NAME = x86-kvm +SRCS = $(shell find src/ -name "*.c") + +SHARE = 1 +INC_PATH += $(NEMU_HOME)/include $(NEMU_HOME)/src/isa/x86/include +GUEST_ISA = x86 + +include $(NEMU_HOME)/scripts/build.mk diff --git a/nemu/tools/kvm-diff/include/paddr.h b/nemu/tools/kvm-diff/include/paddr.h new file mode 100644 index 0000000..04a174b --- /dev/null +++ b/nemu/tools/kvm-diff/include/paddr.h @@ -0,0 +1,16 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +// this is an empty file to avoid compile error diff --git a/nemu/tools/kvm-diff/src/kvm.c b/nemu/tools/kvm-diff/src/kvm.c new file mode 100644 index 0000000..0eb73e3 --- /dev/null +++ b/nemu/tools/kvm-diff/src/kvm.c @@ -0,0 +1,408 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +// from NEMU +#include +#include +#include + +#include +#include +#include +#include +#include + +/* CR0 bits */ +#define CR0_PE 1u +#define CR0_PG (1u << 31) + +#define RFLAGS_ID (1u << 21) +#define RFLAGS_AC (1u << 18) +#define RFLAGS_RF (1u << 16) +#define RFLAGS_TF (1u << 8) +#define RFLAGS_AF (1u << 4) +#define RFLAGS_FIX_MASK (RFLAGS_ID | RFLAGS_AC | RFLAGS_RF | RFLAGS_TF | RFLAGS_AF) + +struct vm { + int sys_fd; + int fd; + uint8_t *mem; + uint8_t *mmio; +}; + +struct vcpu { + int fd; + struct kvm_run *kvm_run; + int int_wp_state; + int has_error_code; + uint32_t entry; +}; + +enum { + STATE_IDLE, // if encounter an int instruction, then set watchpoint + STATE_INT_INST, // if hit the watchpoint, then delete the watchpoint + STATE_IRET_INST,// if hit the watchpoint, then delete the watchpoint +}; + +static struct vm vm; +static struct vcpu vcpu; + +// This should be called everytime after KVM_SET_REGS. +// It seems that KVM_SET_REGS will clean the state of single step. +static void kvm_set_step_mode(bool watch, uint32_t watch_addr) { + struct kvm_guest_debug debug = {}; + debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP | KVM_GUESTDBG_USE_HW_BP; + debug.arch.debugreg[0] = watch_addr; + debug.arch.debugreg[7] = (watch ? 0x1 : 0x0); // watch instruction fetch at `watch_addr` + if (ioctl(vcpu.fd, KVM_SET_GUEST_DEBUG, &debug) < 0) { + perror("KVM_SET_GUEST_DEBUG"); + assert(0); + } +} + +static void kvm_setregs(const struct kvm_regs *r) { + if (ioctl(vcpu.fd, KVM_SET_REGS, r) < 0) { + perror("KVM_SET_REGS"); + assert(0); + } + kvm_set_step_mode(false, 0); +} + +static void kvm_getsregs(struct kvm_sregs *r) { + if (ioctl(vcpu.fd, KVM_GET_SREGS, r) < 0) { + perror("KVM_GET_SREGS"); + assert(0); + } +} + +static void kvm_setsregs(const struct kvm_sregs *r) { + if (ioctl(vcpu.fd, KVM_SET_SREGS, r) < 0) { + perror("KVM_SET_SREGS"); + assert(0); + } +} + +static void* create_mem(int slot, uintptr_t base, size_t mem_size) { + void *mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (mem == MAP_FAILED) { + perror("mmap mem"); + assert(0); + } + + madvise(mem, mem_size, MADV_MERGEABLE); + + struct kvm_userspace_memory_region memreg; + memreg.slot = slot; + memreg.flags = 0; + memreg.guest_phys_addr = base; + memreg.memory_size = mem_size; + memreg.userspace_addr = (unsigned long)mem; + if (ioctl(vm.fd, KVM_SET_USER_MEMORY_REGION, &memreg) < 0) { + perror("KVM_SET_USER_MEMORY_REGION"); + assert(0); + } + return mem; +} + +static void vm_init(size_t mem_size) { + int api_ver; + + vm.sys_fd = open("/dev/kvm", O_RDWR); + if (vm.sys_fd < 0) { + perror("open /dev/kvm"); + assert(0); + } + + api_ver = ioctl(vm.sys_fd, KVM_GET_API_VERSION, 0); + if (api_ver < 0) { + perror("KVM_GET_API_VERSION"); + assert(0); + } + + if (api_ver != KVM_API_VERSION) { + fprintf(stderr, "Got KVM api version %d, expected %d\n", + api_ver, KVM_API_VERSION); + assert(0); + } + + vm.fd = ioctl(vm.sys_fd, KVM_CREATE_VM, 0); + if (vm.fd < 0) { + perror("KVM_CREATE_VM"); + assert(0); + } + + if (ioctl(vm.fd, KVM_SET_TSS_ADDR, 0xfffbd000) < 0) { + perror("KVM_SET_TSS_ADDR"); + assert(0); + } + + vm.mem = create_mem(0, 0, mem_size); + vm.mmio = create_mem(1, 0xa1000000, 0x1000); +} + +static void vcpu_init() { + int vcpu_mmap_size; + + vcpu.fd = ioctl(vm.fd, KVM_CREATE_VCPU, 0); + if (vcpu.fd < 0) { + perror("KVM_CREATE_VCPU"); + assert(0); + } + + vcpu_mmap_size = ioctl(vm.sys_fd, KVM_GET_VCPU_MMAP_SIZE, 0); + if (vcpu_mmap_size <= 0) { + perror("KVM_GET_VCPU_MMAP_SIZE"); + assert(0); + } + + vcpu.kvm_run = mmap(NULL, vcpu_mmap_size, PROT_READ | PROT_WRITE, + MAP_SHARED, vcpu.fd, 0); + if (vcpu.kvm_run == MAP_FAILED) { + perror("mmap kvm_run"); + assert(0); + } + + vcpu.kvm_run->kvm_valid_regs = KVM_SYNC_X86_REGS | KVM_SYNC_X86_SREGS; + vcpu.int_wp_state = STATE_IDLE; +} + +static const uint8_t mbr[] = { + // start32: + 0x0f, 0x01, 0x15, 0x28, 0x7c, 0x00, 0x00, // lgdtl 0x7c28 + 0xea, 0x0e, 0x7c, 0x00, 0x00, 0x08, 0x00, // ljmp $0x8, 0x7c0e + + // here: + 0xeb, 0xfe, // jmp here + + // GDT + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, + + // GDT descriptor + 0x17, 0x00, 0x10, 0x7c, 0x00, 0x00 +}; + +static void setup_protected_mode(struct kvm_sregs *sregs) { + struct kvm_segment seg = { + .base = 0, + .limit = 0xffffffff, + .selector = 1 << 3, + .present = 1, + .type = 11, /* Code: execute, read, accessed */ + .dpl = 0, + .db = 1, + .s = 1, /* Code/data */ + .l = 0, + .g = 1, /* 4KB granularity */ + }; + + sregs->cr0 |= CR0_PE; /* enter protected mode */ + + sregs->cs = seg; + + seg.type = 3; /* Data: read/write, accessed */ + seg.selector = 2 << 3; + sregs->ds = sregs->es = sregs->fs = sregs->gs = sregs->ss = seg; +} + +static uint64_t va2pa(uint64_t va) { + if (vcpu.kvm_run->s.regs.sregs.cr0 & CR0_PG) { + struct kvm_translation t = { .linear_address = va }; + int ret = ioctl(vcpu.fd, KVM_TRANSLATE, &t); + assert(ret == 0); + return t.valid ? t.physical_address : -1ull; + } + return va; +} + +static int patching() { + // patching for special instructions + uint32_t pc = va2pa(vcpu.kvm_run->s.regs.regs.rip); + if (pc == 0xffffffff) return 0; + if (vm.mem[pc] == 0x9c) { // pushf + if (vcpu.int_wp_state == STATE_INT_INST) return 0; + vcpu.kvm_run->s.regs.regs.rsp -= 4; + uint32_t esp = va2pa(vcpu.kvm_run->s.regs.regs.rsp); + *(uint32_t *)(vm.mem + esp) = vcpu.kvm_run->s.regs.regs.rflags & ~RFLAGS_FIX_MASK; + vcpu.kvm_run->s.regs.regs.rflags |= RFLAGS_TF; + vcpu.kvm_run->s.regs.regs.rip ++; + vcpu.kvm_run->kvm_dirty_regs = KVM_SYNC_X86_REGS; + return 1; + } + else if (vm.mem[pc] == 0x9d) { // popf + if (vcpu.int_wp_state == STATE_INT_INST) return 0; + uint32_t esp = va2pa(vcpu.kvm_run->s.regs.regs.rsp); + vcpu.kvm_run->s.regs.regs.rflags = *(uint32_t *)(vm.mem + esp) | RFLAGS_TF | 2; + vcpu.kvm_run->s.regs.regs.rsp += 4; + vcpu.kvm_run->s.regs.regs.rip ++; + vcpu.kvm_run->kvm_dirty_regs = KVM_SYNC_X86_REGS; + return 1; + } + else if (vm.mem[pc] == 0xcf) { // iret + uint32_t ret_addr = va2pa(vcpu.kvm_run->s.regs.regs.rsp); + uint32_t eip = *(uint32_t *)(vm.mem + ret_addr); + vcpu.entry = eip; + kvm_set_step_mode(true, eip); + vcpu.int_wp_state = STATE_IRET_INST; + return 0; + } + return 0; +} + +static void fix_push_sreg() { + uint32_t esp = va2pa(vcpu.kvm_run->s.regs.regs.rsp); + *(uint32_t *)(vm.mem + esp) &= 0x0000ffff; +} + +static void patching_after(uint64_t last_pc) { + uint32_t pc = va2pa(last_pc); + if (pc == 0xffffffff) return; + uint8_t opcode = vm.mem[pc]; + if (opcode == 0x1e || opcode == 0x06) { // push %ds/%es + fix_push_sreg(); + assert(vcpu.kvm_run->s.regs.regs.rip == last_pc + 1); + } + else if (opcode == 0x0f) { + uint8_t opcode2 = vm.mem[pc + 1]; + if (opcode2 == 0xa0) { // push %fs + fix_push_sreg(); + assert(vcpu.kvm_run->s.regs.regs.rip == last_pc + 2); + } + } +} + +static void kvm_exec(uint64_t n) { + for (; n > 0; n --) { + if (patching()) continue; + + uint64_t pc = vcpu.kvm_run->s.regs.regs.rip; + if (ioctl(vcpu.fd, KVM_RUN, 0) < 0) { + if (errno == EINTR) { + n ++; + continue; + } + perror("KVM_RUN"); + assert(0); + } + + if (vcpu.kvm_run->exit_reason != KVM_EXIT_DEBUG) { + if (vcpu.kvm_run->exit_reason == KVM_EXIT_HLT) return; + fprintf(stderr, "Got exit_reason %d at pc = 0x%llx, expected KVM_EXIT_DEBUG (%d)\n", + vcpu.kvm_run->exit_reason, vcpu.kvm_run->s.regs.regs.rip, KVM_EXIT_DEBUG); + assert(0); + } else { + patching_after(pc); + if (vcpu.int_wp_state == STATE_INT_INST) { + uint32_t eflag_offset = 8 + (vcpu.has_error_code ? 4 : 0); + uint32_t eflag_addr = va2pa(vcpu.kvm_run->s.regs.regs.rsp + eflag_offset); + *(uint32_t *)(vm.mem + eflag_addr) &= ~RFLAGS_FIX_MASK; + + Assert(vcpu.entry == vcpu.kvm_run->debug.arch.pc, + "entry not match, right = 0x%llx, wrong = 0x%x", vcpu.kvm_run->debug.arch.pc, vcpu.entry); + kvm_set_step_mode(false, 0); + vcpu.int_wp_state = STATE_IDLE; + //Log("exception = %d, pc = %llx, dr6 = %llx, dr7 = %llx", vcpu.kvm_run->debug.arch.exception, + // vcpu.kvm_run->debug.arch.pc, vcpu.kvm_run->debug.arch.dr6, vcpu.kvm_run->debug.arch.dr7); + } else if (vcpu.int_wp_state == STATE_IRET_INST) { + Assert(vcpu.entry == vcpu.kvm_run->debug.arch.pc, + "entry not match, right = 0x%llx, wrong = 0x%x", vcpu.kvm_run->debug.arch.pc, vcpu.entry); + kvm_set_step_mode(false, 0); + vcpu.int_wp_state = STATE_IDLE; + } + } + } +} + +static void run_protected_mode() { + struct kvm_sregs sregs; + kvm_getsregs(&sregs); + setup_protected_mode(&sregs); + kvm_setsregs(&sregs); + + struct kvm_regs regs; + memset(®s, 0, sizeof(regs)); + regs.rflags = 2; + regs.rip = 0x7c00; + // this will also set KVM_GUESTDBG_ENABLE + kvm_setregs(®s); + + memcpy(vm.mem + 0x7c00, mbr, sizeof(mbr)); + // run enough instructions to load GDT + kvm_exec(10); +} + +__EXPORT void difftest_memcpy(paddr_t addr, void *buf, size_t n, bool direction) { + if (direction == DIFFTEST_TO_REF) memcpy(vm.mem + addr, buf, n); + else memcpy(buf, vm.mem + addr, n); +} + +__EXPORT void difftest_regcpy(void *r, bool direction) { + struct kvm_regs *ref = &(vcpu.kvm_run->s.regs.regs); + x86_CPU_state *x86 = r; + if (direction == DIFFTEST_TO_REF) { + ref->rax = x86->eax; + ref->rbx = x86->ebx; + ref->rcx = x86->ecx; + ref->rdx = x86->edx; + ref->rsp = x86->esp; + ref->rbp = x86->ebp; + ref->rsi = x86->esi; + ref->rdi = x86->edi; + ref->rip = x86->pc; + ref->rflags |= RFLAGS_TF; + vcpu.kvm_run->kvm_dirty_regs = KVM_SYNC_X86_REGS; + } else { + x86->eax = ref->rax; + x86->ebx = ref->rbx; + x86->ecx = ref->rcx; + x86->edx = ref->rdx; + x86->esp = ref->rsp; + x86->ebp = ref->rbp; + x86->esi = ref->rsi; + x86->edi = ref->rdi; + x86->pc = ref->rip; + } +} + +__EXPORT void difftest_exec(uint64_t n) { + kvm_exec(n); +} + +__EXPORT void difftest_raise_intr(word_t NO) { + uint32_t pgate_vaddr = vcpu.kvm_run->s.regs.sregs.idt.base + NO * 8; + uint32_t pgate = va2pa(pgate_vaddr); + // assume code.base = 0 + uint32_t entry = vm.mem[pgate] | (vm.mem[pgate + 1] << 8) | + (vm.mem[pgate + 6] << 16) | (vm.mem[pgate + 7] << 24); + kvm_set_step_mode(true, entry); + vcpu.int_wp_state = STATE_INT_INST; + vcpu.has_error_code = (NO == 14); + vcpu.entry = entry; + + if (NO == 48) { + // inject timer interrupt + struct kvm_interrupt intr = { .irq = NO }; + int ret = ioctl(vcpu.fd, KVM_INTERRUPT, &intr); + assert(ret == 0); + } +} + +__EXPORT void difftest_init(int port) { + vm_init(CONFIG_MSIZE); + vcpu_init(); + run_protected_mode(); +} diff --git a/nemu/tools/qemu-diff/Makefile b/nemu/tools/qemu-diff/Makefile new file mode 100644 index 0000000..0ce26cd --- /dev/null +++ b/nemu/tools/qemu-diff/Makefile @@ -0,0 +1,23 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +NAME = $(GUEST_ISA)-qemu +SRCS = $(shell find src/ -name "*.c") + +SHARE = 1 +CFLAGS += -DNEMU_HOME=\"$(NEMU_HOME)\" -DCONFIG_ISA_$(GUEST_ISA) +INC_PATH += $(NEMU_HOME)/include + +include $(NEMU_HOME)/scripts/build.mk diff --git a/nemu/tools/qemu-diff/include/common.h b/nemu/tools/qemu-diff/include/common.h new file mode 100644 index 0000000..2e3fc8e --- /dev/null +++ b/nemu/tools/qemu-diff/include/common.h @@ -0,0 +1,32 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include +#include +#include +#include +#include +#include +#include + +typedef uint32_t paddr_t; + +#include "isa.h" +#include "protocol.h" + +#endif diff --git a/nemu/tools/qemu-diff/include/isa.h b/nemu/tools/qemu-diff/include/isa.h new file mode 100644 index 0000000..3637206 --- /dev/null +++ b/nemu/tools/qemu-diff/include/isa.h @@ -0,0 +1,59 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#ifndef __ISA_H__ +#define __ISA_H__ + +#if defined(CONFIG_ISA_mips32) +#define ISA_QEMU_BIN "qemu-system-mipsel" +#define ISA_QEMU_ARGS "-machine", "mipssim",\ + "-kernel", NEMU_HOME "/resource/mips-elf/mips.dummy", +#elif defined(CONFIG_ISA_riscv) && !defined(CONFIG_RV64) +#define ISA_QEMU_BIN "qemu-system-riscv32" +#define ISA_QEMU_ARGS "-bios", "none", +#elif defined(CONFIG_ISA_riscv) && defined(CONFIG_RV64) +#define ISA_QEMU_BIN "qemu-system-riscv64" +#define ISA_QEMU_ARGS +#elif defined(CONFIG_ISA_x86) +#define ISA_QEMU_BIN "qemu-system-i386" +#define ISA_QEMU_ARGS +#else +#error Unsupport ISA +#endif + +union isa_gdb_regs { + struct { +#if defined(CONFIG_ISA_mips32) + uint32_t gpr[32]; + uint32_t status, lo, hi, badvaddr, cause, pc; +#elif defined(CONFIG_ISA_riscv) && !defined(CONFIG_RV64) + uint32_t gpr[32]; + uint32_t pc; +#elif defined(CONFIG_ISA_riscv) && defined(CONFIG_RV64) + uint64_t gpr[32]; + uint64_t fpr[32]; + uint64_t pc; +#elif defined(CONFIG_ISA_x86) + uint32_t eax, ecx, edx, ebx, esp, ebp, esi, edi; + uint32_t eip, eflags; + uint32_t cs, ss, ds, es, fs, gs; +#endif + }; + struct { + uint32_t array[77]; + }; +}; + +#endif diff --git a/nemu/tools/qemu-diff/include/protocol.h b/nemu/tools/qemu-diff/include/protocol.h new file mode 100644 index 0000000..2c74a2c --- /dev/null +++ b/nemu/tools/qemu-diff/include/protocol.h @@ -0,0 +1,37 @@ +/* Simple interface of a GDB remote protocol client. + * Copyright (C) 2015 Red Hat Inc. + * + * This file is part of gdb-toys. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include + +struct gdb_conn; + +uint16_t gdb_decode_hex(uint8_t msb, uint8_t lsb); +uint64_t gdb_decode_hex_str(uint8_t *bytes); + +uint8_t hex_encode(uint8_t digit); + +struct gdb_conn *gdb_begin_inet(const char *addr, uint16_t port); + +void gdb_end(struct gdb_conn *conn); + +void gdb_send(struct gdb_conn *conn, const uint8_t *command, size_t size); + +uint8_t *gdb_recv(struct gdb_conn *conn, size_t *size); + +const char * gdb_start_noack(struct gdb_conn *conn); diff --git a/nemu/tools/qemu-diff/src/diff-test.c b/nemu/tools/qemu-diff/src/diff-test.c new file mode 100644 index 0000000..e5cd9fd --- /dev/null +++ b/nemu/tools/qemu-diff/src/diff-test.c @@ -0,0 +1,99 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "common.h" +#include +#include +#include + +bool gdb_connect_qemu(int); +bool gdb_memcpy_to_qemu(uint32_t, void *, int); +bool gdb_getregs(union isa_gdb_regs *); +bool gdb_setregs(union isa_gdb_regs *); +bool gdb_si(); +void gdb_exit(); + +void init_isa(); + +__EXPORT void difftest_memcpy(paddr_t addr, void *buf, size_t n, bool direction) { + assert(direction == DIFFTEST_TO_REF); + if (direction == DIFFTEST_TO_REF) { + bool ok = gdb_memcpy_to_qemu(addr, buf, n); + assert(ok == 1); + } +} + +__EXPORT void difftest_regcpy(void *dut, bool direction) { + union isa_gdb_regs qemu_r; + gdb_getregs(&qemu_r); + if (direction == DIFFTEST_TO_REF) { + memcpy(&qemu_r, dut, DIFFTEST_REG_SIZE); + gdb_setregs(&qemu_r); + } else { + memcpy(dut, &qemu_r, DIFFTEST_REG_SIZE); + } +} + +__EXPORT void difftest_exec(uint64_t n) { + while (n --) gdb_si(); +} + +__EXPORT void difftest_init(int port) { + char buf[32]; + sprintf(buf, "tcp::%d", port); + + int ppid_before_fork = getpid(); + int pid = fork(); + if (pid == -1) { + perror("fork"); + assert(0); + } + else if (pid == 0) { + // child + + // install a parent death signal in the chlid + int r = prctl(PR_SET_PDEATHSIG, SIGTERM); + if (r == -1) { + perror("prctl error"); + assert(0); + } + + if (getppid() != ppid_before_fork) { + printf("parent has died!\n"); + assert(0); + } + + close(STDIN_FILENO); + execlp(ISA_QEMU_BIN, ISA_QEMU_BIN, ISA_QEMU_ARGS "-S", "-gdb", buf, "-nographic", + "-serial", "none", "-monitor", "none", NULL); + perror("exec"); + assert(0); + } + else { + // father + + gdb_connect_qemu(port); + printf("Connect to QEMU with %s successfully\n", buf); + + atexit(gdb_exit); + + init_isa(); + } +} + +__EXPORT void difftest_raise_intr(uint64_t NO) { + printf("raise_intr is not supported\n"); + assert(0); +} diff --git a/nemu/tools/qemu-diff/src/gdb-host.c b/nemu/tools/qemu-diff/src/gdb-host.c new file mode 100644 index 0000000..422f637 --- /dev/null +++ b/nemu/tools/qemu-diff/src/gdb-host.c @@ -0,0 +1,118 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "common.h" + +static struct gdb_conn *conn; + +bool gdb_connect_qemu(int port) { + // connect to gdbserver on localhost port 1234 + while ((conn = gdb_begin_inet("127.0.0.1", port)) == NULL) { + usleep(1); + } + + return true; +} + +static bool gdb_memcpy_to_qemu_small(uint32_t dest, void *src, int len) { + char *buf = malloc(len * 2 + 128); + assert(buf != NULL); + int p = sprintf(buf, "M0x%x,%x:", dest, len); + int i; + for (i = 0; i < len; i ++) { + p += sprintf(buf + p, "%c%c", hex_encode(((uint8_t *)src)[i] >> 4), hex_encode(((uint8_t *)src)[i] & 0xf)); + } + + gdb_send(conn, (const uint8_t *)buf, strlen(buf)); + free(buf); + + size_t size; + uint8_t *reply = gdb_recv(conn, &size); + bool ok = !strcmp((const char*)reply, "OK"); + free(reply); + + return ok; +} + +bool gdb_memcpy_to_qemu(uint32_t dest, void *src, int len) { + const int mtu = 1500; + bool ok = true; + while (len > mtu) { + ok &= gdb_memcpy_to_qemu_small(dest, src, mtu); + dest += mtu; + src += mtu; + len -= mtu; + } + ok &= gdb_memcpy_to_qemu_small(dest, src, len); + return ok; +} + +bool gdb_getregs(union isa_gdb_regs *r) { + gdb_send(conn, (const uint8_t *)"g", 1); + size_t size; + uint8_t *reply = gdb_recv(conn, &size); + + int i; + uint8_t *p = reply; + uint8_t c; + for (i = 0; i < sizeof(union isa_gdb_regs) / sizeof(uint32_t); i ++) { + c = p[8]; + p[8] = '\0'; + r->array[i] = gdb_decode_hex_str(p); + p[8] = c; + p += 8; + } + + free(reply); + + return true; +} + +bool gdb_setregs(union isa_gdb_regs *r) { + int len = sizeof(union isa_gdb_regs); + char *buf = malloc(len * 2 + 128); + assert(buf != NULL); + buf[0] = 'G'; + + void *src = r; + int p = 1; + int i; + for (i = 0; i < len; i ++) { + p += sprintf(buf + p, "%c%c", hex_encode(((uint8_t *)src)[i] >> 4), hex_encode(((uint8_t *)src)[i] & 0xf)); + } + + gdb_send(conn, (const uint8_t *)buf, strlen(buf)); + free(buf); + + size_t size; + uint8_t *reply = gdb_recv(conn, &size); + bool ok = !strcmp((const char*)reply, "OK"); + free(reply); + + return ok; +} + +bool gdb_si() { + char buf[] = "vCont;s:1"; + gdb_send(conn, (const uint8_t *)buf, strlen(buf)); + size_t size; + uint8_t *reply = gdb_recv(conn, &size); + free(reply); + return true; +} + +void gdb_exit() { + gdb_end(conn); +} diff --git a/nemu/tools/qemu-diff/src/isa.c b/nemu/tools/qemu-diff/src/isa.c new file mode 100644 index 0000000..d783c64 --- /dev/null +++ b/nemu/tools/qemu-diff/src/isa.c @@ -0,0 +1,78 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "common.h" + +#if defined(CONFIG_ISA_x86) + +bool gdb_memcpy_to_qemu(uint32_t, void *, int); +bool gdb_getregs(union isa_gdb_regs *); +bool gdb_setregs(union isa_gdb_regs *); +void difftest_exec(uint64_t n); + +static uint8_t mbr[] = { + // start16: + 0xfa, // cli + 0x31, 0xc0, // xorw %ax,%ax + 0x8e, 0xd8, // movw %ax,%ds + 0x8e, 0xc0, // movw %ax,%es + 0x8e, 0xd0, // movw %ax,%ss + 0x0f, 0x01, 0x16, 0x44, 0x7c, // lgdt gdtdesc + 0x0f, 0x20, 0xc0, // movl %cr0,%eax + 0x66, 0x83, 0xc8, 0x01, // orl $CR0_PE,%eax + 0x0f, 0x22, 0xc0, // movl %eax,%cr0 + 0xea, 0x1d, 0x7c, 0x08, 0x00, // ljmp $GDT_ENTRY(1),$start32 + + // start32: + 0x66, 0xb8, 0x10, 0x00, // movw $0x10,%ax + 0x8e, 0xd8, // movw %ax, %ds + 0x8e, 0xc0, // movw %ax, %es + 0x8e, 0xd0, // movw %ax, %ss + 0xeb, 0xfe, // jmp 7c27 + 0x8d, 0x76, 0x00, // lea 0x0(%esi),%esi + + // GDT + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, + + // GDT descriptor + 0x17, 0x00, 0x2c, 0x7c, 0x00, 0x00 +}; + +void init_isa() { + // put the MBR code to QEMU to enable protected mode + bool ok = gdb_memcpy_to_qemu(0x7c00, mbr, sizeof(mbr)); + assert(ok == 1); + + union isa_gdb_regs r; + gdb_getregs(&r); + + // set cs:eip to 0000:7c00 + r.eip = 0x7c00; + r.cs = 0x0000; + ok = gdb_setregs(&r); + assert(ok == 1); + + // execute enough instructions to enter protected mode + difftest_exec(20); +} + +#else + +void init_isa() { +} + +#endif diff --git a/nemu/tools/qemu-diff/src/protocol.c b/nemu/tools/qemu-diff/src/protocol.c new file mode 100644 index 0000000..9130095 --- /dev/null +++ b/nemu/tools/qemu-diff/src/protocol.c @@ -0,0 +1,305 @@ +/* Simple implementation of a GDB remote protocol client. + * Copyright (C) 2015 Red Hat Inc. + * + * This file is part of gdb-toys. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "common.h" +#include +#include + +#include + +#include +#include + +#include +#include + +struct gdb_conn { + FILE *in; + FILE *out; + bool ack; +}; + + +static uint8_t +hex_nibble(uint8_t hex) { + return isdigit(hex) ? hex - '0' : tolower(hex) - 'a' + 10; +} + +uint8_t hex_encode(uint8_t digit) { + return digit > 9 ? 'a' + digit - 10 : '0' + digit; +} + +uint16_t gdb_decode_hex(uint8_t msb, uint8_t lsb) { + if (!isxdigit(msb) || !isxdigit(lsb)) + return UINT16_MAX; + return 16 * hex_nibble(msb) + hex_nibble(lsb); +} + +uint64_t gdb_decode_hex_str(uint8_t *bytes) { + uint64_t value = 0; + uint64_t weight = 1; + while (isxdigit(bytes[0]) && isxdigit(bytes[1])) { + value += weight * gdb_decode_hex(bytes[0], bytes[1]); + bytes += 2; + weight *= 16 * 16; + } + return value; +} + + +static struct gdb_conn* gdb_begin(int fd) { + struct gdb_conn *conn = calloc(1, sizeof(struct gdb_conn)); + if (conn == NULL) + err(1, "calloc"); + + conn->ack = true; + + // duplicate the handle to separate read/write state + int fd2 = dup(fd); + if (fd2 < 0) + err(1, "dup"); + + // open a FILE* for reading + conn->in = fdopen(fd, "rb"); + if (conn->in == NULL) + err(1, "fdopen"); + + // open a FILE* for writing + conn->out = fdopen(fd2, "wb"); + if (conn->out == NULL) + err(1, "fdopen"); + + // reset line state by acking any earlier input + fputc('+', conn->out); + fflush(conn->out); + + return conn; +} + +struct gdb_conn* gdb_begin_inet(const char *addr, uint16_t port) { + // fill the socket information + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(port), + }; + if (inet_aton(addr, &sa.sin_addr) == 0) + errx(1, "Invalid address: %s", addr); + + // open the socket and start the tcp connection + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) + err(1, "socket"); + if (connect(fd, (const struct sockaddr *)&sa, sizeof(sa)) != 0) { + close(fd); + return NULL; + } + + socklen_t tmp; + tmp = 1; + int r = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&tmp, sizeof(tmp)); + if (r) { + perror("setsockopt"); + assert(0); + } + tmp = 1; + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&tmp, sizeof(tmp)); + if (r) { + perror("setsockopt"); + assert(0); + } + + // initialize the rest of gdb on this handle + return gdb_begin(fd); +} + + +void gdb_end(struct gdb_conn *conn) { + fclose(conn->in); + fclose(conn->out); + free(conn); +} + +static void send_packet(FILE *out, const uint8_t *command, size_t size) { + // compute the checksum -- simple mod256 addition + uint8_t sum = 0; + size_t i; + for (i = 0; i < size; ++i) + sum += command[i]; + + // NB: seems neither escaping nor RLE is generally expected by + // gdbserver. e.g. giving "invalid hex digit" on an RLE'd address. + // So just write raw here, and maybe let higher levels escape/RLE. + + fputc('$', out); // packet start + fwrite(command, 1, size, out); // payload + fprintf(out, "#%02X", sum); // packet end, checksum + fflush(out); + + if (ferror(out)) + err(1, "send"); + else if (feof(out)) + errx(0, "send: Connection closed"); +} + +void gdb_send(struct gdb_conn *conn, const uint8_t *command, size_t size) { + bool acked = false; + do { + send_packet(conn->out, command, size); + + if (!conn->ack) + break; + + // look for '+' ACK or '-' NACK/resend + acked = fgetc(conn->in) == '+'; + } while (!acked); +} + +static uint8_t* recv_packet(FILE *in, size_t *ret_size, bool* ret_sum_ok) { + size_t i = 0; + size_t size = 4096; + uint8_t *reply = malloc(size); + if (reply == NULL) + err(1, "malloc"); + + int c; + uint8_t sum = 0; + bool escape = false; + + // fast-forward to the first start of packet + while ((c = fgetc(in)) != EOF && c != '$'); + + while ((c = fgetc(in)) != EOF) { + sum += c; + switch (c) { + case '$': // new packet? start over... + i = 0; + sum = 0; + escape = false; + continue; + + case '#': // end of packet + sum -= c; // not part of the checksum + { + uint8_t msb = fgetc(in); + uint8_t lsb = fgetc(in); + *ret_sum_ok = sum == gdb_decode_hex(msb, lsb); + } + *ret_size = i; + + // terminate it for good measure + if (i == size) { + reply = realloc(reply, size + 1); + if (reply == NULL) + err(1, "realloc"); + } + reply[i] = '\0'; + + return reply; + + case '}': // escape: next char is XOR 0x20 + escape = true; + continue; + + case '*': // run-length-encoding + // The next character tells how many times to repeat the last + // character we saw. The count is added to 29, so that the + // minimum-beneficial RLE 3 is the first printable character ' '. + // The count character can't be >126 or '$'/'#' packet markers. + + if (i > 0) { // need something to repeat! + int c2 = fgetc(in); + if (c2 < 29 || c2 > 126 || c2 == '$' || c2 == '#') { + // invalid count character! + ungetc(c2, in); + } else { + int count = c2 - 29; + + // get a bigger buffer if needed + if (i + count > size) { + size *= 2; + reply = realloc(reply, size); + if (reply == NULL) + err(1, "realloc"); + } + + // fill the repeated character + memset(&reply[i], reply[i - 1], count); + i += count; + sum += c2; + continue; + } + } + } + + // XOR an escaped character + if (escape) { + c ^= 0x20; + escape = false; + } + + // get a bigger buffer if needed + if (i == size) { + size *= 2; + reply = realloc(reply, size); + if (reply == NULL) + err(1, "realloc"); + } + + // add one character + reply[i++] = c; + } + + if (ferror(in)) + err(1, "recv"); + else if (feof(in)) + errx(0, "recv: Connection closed"); + else + errx(1, "recv: Unknown connection error"); +} + +uint8_t* gdb_recv(struct gdb_conn *conn, size_t *size) { + uint8_t *reply; + bool acked = false; + do { + reply = recv_packet(conn->in, size, &acked); + + if (!conn->ack) + break; + + // send +/- depending on checksum result, retry if needed + fputc(acked ? '+' : '-', conn->out); + fflush(conn->out); + } while (!acked); + + return reply; +} + +const char* gdb_start_noack(struct gdb_conn *conn) { + static const char cmd[] = "QStartNoAckMode"; + gdb_send(conn, (const uint8_t *)cmd, sizeof(cmd) - 1); + + size_t size; + uint8_t *reply = gdb_recv(conn, &size); + bool ok = size == 2 && !strcmp((const char*)reply, "OK"); + free(reply); + + if (ok) + conn->ack = false; + return ok ? "OK" : ""; +} diff --git a/nemu/tools/spike-diff/.gitignore b/nemu/tools/spike-diff/.gitignore new file mode 100644 index 0000000..e226ef3 --- /dev/null +++ b/nemu/tools/spike-diff/.gitignore @@ -0,0 +1,2 @@ +repo/ +!difftest.cc diff --git a/nemu/tools/spike-diff/Makefile b/nemu/tools/spike-diff/Makefile new file mode 100644 index 0000000..168b41d --- /dev/null +++ b/nemu/tools/spike-diff/Makefile @@ -0,0 +1,54 @@ +#*************************************************************************************** +# Copyright (c) 2014-2022 Zihao Yu, Nanjing University +# +# NEMU is licensed under Mulan PSL v2. +# You can use this software according to the terms and conditions of the Mulan PSL v2. +# You may obtain a copy of Mulan PSL v2 at: +# http://license.coscl.org.cn/MulanPSL2 +# +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +# +# See the Mulan PSL v2 for more details. +#**************************************************************************************/ + +REPO_PATH = repo +ifeq ($(wildcard repo/spike_main),) + $(shell git clone --depth=1 git@github.com:NJU-ProjectN/riscv-isa-sim $(REPO_PATH)) +endif + +REPO_BUILD_PATH = $(REPO_PATH)/build +REPO_MAKEFILE = $(REPO_BUILD_PATH)/Makefile +$(REPO_MAKEFILE): + @mkdir -p $(@D) + cd $(@D) && $(abspath $(REPO_PATH))/configure + sed -i -e 's/-g -O2/-O2/' $@ + +SPIKE = $(REPO_BUILD_PATH)/spike +$(SPIKE): $(REPO_MAKEFILE) + CFLAGS="-fvisibility=hidden" CXXFLAGS="-fvisibility=hidden" $(MAKE) -C $(^D) + +BUILD_DIR = ./build +$(shell mkdir -p $(BUILD_DIR)) + +inc_dependencies = fesvr riscv disasm customext fdt softfloat spike_main spike_dasm build +INC_PATH = -I$(REPO_PATH) $(addprefix -I$(REPO_PATH)/, $(inc_dependencies)) +INC_PATH += -I$(NEMU_HOME)/include +lib_dependencies = libspike_main.a libriscv.a libdisasm.a libsoftfloat.a libfesvr.a libfdt.a +INC_LIBS = $(addprefix $(REPO_PATH)/build/, $(lib_dependencies)) + +NAME = $(GUEST_ISA)-spike-so +BINARY = $(BUILD_DIR)/$(NAME) +SRCS = difftest.cc + +$(BINARY): $(SPIKE) $(SRCS) + g++ -std=c++17 -O2 -shared -fPIC -fvisibility=hidden $(INC_PATH) $(SRCS) $(INC_LIBS) -o $@ + +clean: + rm -rf $(BUILD_DIR) + +all: $(BINARY) +.DEFAULT_GOAL = all + +.PHONY: all clean $(SPIKE) diff --git a/nemu/tools/spike-diff/difftest.cc b/nemu/tools/spike-diff/difftest.cc new file mode 100644 index 0000000..580fb31 --- /dev/null +++ b/nemu/tools/spike-diff/difftest.cc @@ -0,0 +1,131 @@ +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ + +#include "mmu.h" +#include "sim.h" +#include "../../include/common.h" +#include + +#define NR_GPR MUXDEF(CONFIG_RVE, 16, 32) + +static std::vector> difftest_plugin_devices; +static std::vector difftest_htif_args; +static std::vector> difftest_mem( + 1, std::make_pair(reg_t(DRAM_BASE), new mem_t(CONFIG_MSIZE))); +static debug_module_config_t difftest_dm_config = { + .progbufsize = 2, + .max_sba_data_width = 0, + .require_authentication = false, + .abstract_rti = 0, + .support_hasel = true, + .support_abstract_csr_access = true, + .support_abstract_fpr_access = true, + .support_haltgroups = true, + .support_impebreak = true +}; + +struct diff_context_t { + word_t gpr[MUXDEF(CONFIG_RVE, 16, 32)]; + word_t pc; +}; + +static sim_t* s = NULL; +static processor_t *p = NULL; +static state_t *state = NULL; + +void sim_t::diff_init(int port) { + p = get_core("0"); + state = p->get_state(); +} + +void sim_t::diff_step(uint64_t n) { + step(n); +} + +void sim_t::diff_get_regs(void* diff_context) { + struct diff_context_t* ctx = (struct diff_context_t*)diff_context; + for (int i = 0; i < NR_GPR; i++) { + ctx->gpr[i] = state->XPR[i]; + } + ctx->pc = state->pc; +} + +void sim_t::diff_set_regs(void* diff_context) { + struct diff_context_t* ctx = (struct diff_context_t*)diff_context; + for (int i = 0; i < NR_GPR; i++) { + state->XPR.write(i, (sword_t)ctx->gpr[i]); + } + state->pc = ctx->pc; +} + +void sim_t::diff_memcpy(reg_t dest, void* src, size_t n) { + mmu_t* mmu = p->get_mmu(); + for (size_t i = 0; i < n; i++) { + mmu->store(dest+i, *((uint8_t*)src+i)); + } +} + +extern "C" { + +__EXPORT void difftest_memcpy(paddr_t addr, void *buf, size_t n, bool direction) { + if (direction == DIFFTEST_TO_REF) { + s->diff_memcpy(addr, buf, n); + } else { + assert(0); + } +} + +__EXPORT void difftest_regcpy(void* dut, bool direction) { + if (direction == DIFFTEST_TO_REF) { + s->diff_set_regs(dut); + } else { + s->diff_get_regs(dut); + } +} + +__EXPORT void difftest_exec(uint64_t n) { + s->diff_step(n); +} + +__EXPORT void difftest_init(int port) { + difftest_htif_args.push_back(""); + const char *isa = "RV" MUXDEF(CONFIG_RV64, "64", "32") MUXDEF(CONFIG_RVE, "E", "I") "MAFDC"; + cfg_t cfg(/*default_initrd_bounds=*/std::make_pair((reg_t)0, (reg_t)0), + /*default_bootargs=*/nullptr, + /*default_isa=*/isa, + /*default_priv=*/DEFAULT_PRIV, + /*default_varch=*/DEFAULT_VARCH, + /*default_misaligned=*/false, + /*default_endianness*/endianness_little, + /*default_pmpregions=*/16, + /*default_mem_layout=*/std::vector(), + /*default_hartids=*/std::vector(1), + /*default_real_time_clint=*/false, + /*default_trigger_count=*/4); + s = new sim_t(&cfg, false, + difftest_mem, difftest_plugin_devices, difftest_htif_args, + difftest_dm_config, nullptr, false, NULL, + false, + NULL, + true); + s->diff_init(port); +} + +__EXPORT void difftest_raise_intr(uint64_t NO) { + trap_t t(NO); + p->take_trap_public(t, state->pc); +} + +}