From 6bb84fe005fc151df1a67259242da9c0eaf3fe93 Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Mon, 29 Jun 2026 03:00:12 -0500 Subject: begin work on pc side --- .clangd | 13 -- Makefile | 138 -------------- fire-pit/test.c | 49 ----- fire-pit/test.h | 77 -------- linux/.clangd | 0 linux/main | Bin 0 -> 16784 bytes linux/main.c | 132 +++++++++++++ ppc/.clangd | 13 ++ ppc/Makefile | 138 ++++++++++++++ ppc/fire-pit/test.c | 49 +++++ ppc/fire-pit/test.h | 77 ++++++++ ppc/source/macros.h | 14 ++ ppc/source/state.h | 57 ++++++ ppc/source/states/menu.c | 98 ++++++++++ ppc/source/template.c | 303 ++++++++++++++++++++++++++++++ ppc/source/term.h | 13 ++ ppc/source/test-impl.inc.h | 53 ++++++ ppc/source/test.c | 453 +++++++++++++++++++++++++++++++++++++++++++++ ppc/source/test.h | 122 ++++++++++++ ppc/source/tests/basic.c | 79 ++++++++ ppc/source/tests/led.c | 38 ++++ source/macros.h | 14 -- source/state.h | 57 ------ source/states/menu.c | 98 ---------- source/template.c | 303 ------------------------------ source/term.h | 13 -- source/test-impl.inc.h | 53 ------ source/test.c | 453 --------------------------------------------- source/test.h | 122 ------------ source/tests/basic.c | 79 -------- source/tests/led.c | 38 ---- 31 files changed, 1639 insertions(+), 1507 deletions(-) delete mode 100644 .clangd delete mode 100644 Makefile delete mode 100644 fire-pit/test.c delete mode 100644 fire-pit/test.h create mode 100644 linux/.clangd create mode 100755 linux/main create mode 100644 linux/main.c create mode 100644 ppc/.clangd create mode 100644 ppc/Makefile create mode 100644 ppc/fire-pit/test.c create mode 100644 ppc/fire-pit/test.h create mode 100644 ppc/source/macros.h create mode 100644 ppc/source/state.h create mode 100644 ppc/source/states/menu.c create mode 100644 ppc/source/template.c create mode 100644 ppc/source/term.h create mode 100644 ppc/source/test-impl.inc.h create mode 100644 ppc/source/test.c create mode 100644 ppc/source/test.h create mode 100644 ppc/source/tests/basic.c create mode 100644 ppc/source/tests/led.c delete mode 100644 source/macros.h delete mode 100644 source/state.h delete mode 100644 source/states/menu.c delete mode 100644 source/template.c delete mode 100644 source/term.h delete mode 100644 source/test-impl.inc.h delete mode 100644 source/test.c delete mode 100644 source/test.h delete mode 100644 source/tests/basic.c delete mode 100644 source/tests/led.c diff --git a/.clangd b/.clangd deleted file mode 100644 index ed00e13..0000000 --- a/.clangd +++ /dev/null @@ -1,13 +0,0 @@ -CompileFlags: - Add: - - --target=powerpc-eabi - - -mcpu=750 - - -DGEKKO - - -DHW_RVL - - -D__PPC__ - - -isystem - - /opt/devkitpro/devkitPPC/powerpc-eabi/include - - -isystem - - /opt/devkitpro/devkitPPC/lib/gcc/powerpc-eabi/16.1.0/include - - -D_CLANGD - diff --git a/Makefile b/Makefile deleted file mode 100644 index f896f14..0000000 --- a/Makefile +++ /dev/null @@ -1,138 +0,0 @@ -#--------------------------------------------------------------------------------- -# Clear the implicit built in rules -#--------------------------------------------------------------------------------- -.SUFFIXES: -#--------------------------------------------------------------------------------- -ifeq ($(strip $(DEVKITPPC)),) -$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") -endif - -include $(DEVKITPPC)/wii_rules - -#--------------------------------------------------------------------------------- -# TARGET is the name of the output -# BUILD is the directory where object files & intermediate files will be placed -# SOURCES is a list of directories containing source code -# INCLUDES is a list of directories containing extra header files -#--------------------------------------------------------------------------------- -TARGET := $(notdir $(CURDIR)) -BUILD := build -SOURCES := source source/states source/tests -DATA := data -INCLUDES := - -#--------------------------------------------------------------------------------- -# options for code generation -#--------------------------------------------------------------------------------- - -CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE) -CXXFLAGS = $(CFLAGS) - -LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map - -#--------------------------------------------------------------------------------- -# any extra libraries we wish to link with the project -#--------------------------------------------------------------------------------- -LIBS := -lwiiuse -lbte -logc -lm - -#--------------------------------------------------------------------------------- -# list of directories containing libraries, this must be the top level containing -# include and lib -#--------------------------------------------------------------------------------- -LIBDIRS := - -#--------------------------------------------------------------------------------- -# no real need to edit anything past this point unless you need to add additional -# rules for different file extensions -#--------------------------------------------------------------------------------- -ifneq ($(BUILD),$(notdir $(CURDIR))) -#--------------------------------------------------------------------------------- - -export OUTPUT := $(CURDIR)/$(TARGET) - -export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ - $(foreach dir,$(DATA),$(CURDIR)/$(dir)) - -export DEPSDIR := $(CURDIR)/$(BUILD) - -#--------------------------------------------------------------------------------- -# automatically build a list of object files for our project -#--------------------------------------------------------------------------------- -CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) -sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) -SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) -BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) - -#--------------------------------------------------------------------------------- -# use CXX for linking C++ projects, CC for standard C -#--------------------------------------------------------------------------------- -ifeq ($(strip $(CPPFILES)),) - export LD := $(CC) -else - export LD := $(CXX) -endif - -export OFILES_BIN := $(addsuffix .o,$(BINFILES)) -export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(sFILES:.s=.o) $(SFILES:.S=.o) -export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) - -export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) - -#--------------------------------------------------------------------------------- -# build a list of include paths -#--------------------------------------------------------------------------------- -export INCLUDE := $(foreach dir,$(INCLUDES), -iquote $(CURDIR)/$(dir)) \ - $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ - -I$(CURDIR)/$(BUILD) \ - -I$(LIBOGC_INC) - -#--------------------------------------------------------------------------------- -# build a list of library paths -#--------------------------------------------------------------------------------- -export LIBPATHS := -L$(LIBOGC_LIB) $(foreach dir,$(LIBDIRS),-L$(dir)/lib) - -export OUTPUT := $(CURDIR)/$(TARGET) -.PHONY: $(BUILD) clean - -#--------------------------------------------------------------------------------- -$(BUILD): - @[ -d $@ ] || mkdir -p $@ - @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile - -#--------------------------------------------------------------------------------- -clean: - @echo clean ... - @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol - -#--------------------------------------------------------------------------------- -run: - wiiload $(TARGET).dol - - -#--------------------------------------------------------------------------------- -else - -DEPENDS := $(OFILES:.o=.d) - -#--------------------------------------------------------------------------------- -# main targets -#--------------------------------------------------------------------------------- -$(OUTPUT).dol: $(OUTPUT).elf -$(OUTPUT).elf: $(OFILES) - -$(OFILES_SOURCES) : $(HFILES) - -#--------------------------------------------------------------------------------- -# This rule links in binary data with the .jpg extension -#--------------------------------------------------------------------------------- -%.jpg.o %_jpg.h : %.jpg -#--------------------------------------------------------------------------------- - @echo $(notdir $<) - $(bin2o) - --include $(DEPENDS) - -#--------------------------------------------------------------------------------- -endif -#--------------------------------------------------------------------------------- diff --git a/fire-pit/test.c b/fire-pit/test.c deleted file mode 100644 index 822db4f..0000000 --- a/fire-pit/test.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "test.h" - -static int identify_setup(struct et_test *test, void *work, int exi_channel) -{ - int *pexi_channel = work; - *pexi_channel = exi_channel; - return 1; -} - -static int identify_tick(struct et_test *test, void *work) -{ - int exi_channel = *((int *)work); - - EXI_Lock(exi_channel, EXI_DEVICE_0, NULL); -} - -static et_status identify_finish(struct et_test *test, void *work) -{ - -} - -static const char *identify_translate(struct et_test *test, char *out, size_t outbuf_sz, et_status status) -{ - -} - -#define DEF_TEST(_flags, _name, _work_t, _setup, _tick, _finish, ...) { \ - .flags = _flags, \ - .name = _name, \ - .work_size = sizeof(_work_t), \ - .setup_f = _setup, \ - .tick_f = _tick, \ - .finish_f = _finish, \ - __VA_ARGS__ \ -} - -#define msg_table priv_data[0] -#define TEST_MSGS(_msg_table) .msg_table = _msg_table - -#define TEST_TRANSLATE_DEFAULT .trans_f = &et_translate_default, -#define TEST_TRANSLATE(_t) .trans_f = _t, - -const struct et_test et_tests[] = { - DEF_TEST(ET_TEST_ESSENTIAL, "Identify_Gecko", int, identify_setup, identify_tick, identify_finish, TEST_TRANSLATE(identify_translate)), -}; - - -const size_t et_ntests = sizeof(et_tests) / sizeof(et_tests[0]); - diff --git a/fire-pit/test.h b/fire-pit/test.h deleted file mode 100644 index c907530..0000000 --- a/fire-pit/test.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef EXITEST_TEST_H_INCLUDED -#define EXITEST_TEST_H_INCLUDED - -#include - -struct et_test; - -typedef u32 et_status; - -/* Test passed. */ -#define ET_STATUS_PASS (et_status)0x00 - -/* Test failed. */ -#define ET_STATUS_FAIL (et_status)0x01 - -/* Test skipped or not applicable. */ -#define ET_STATUS_SKIP (et_status)0x02 - -/* Internal error during testing. */ -#define ET_STATUS_ERROR (et_status)0x03 - -#define ET_STATUS_MASK (et_status)0x03 - -#define ET_STATUS_GET_BASE(_status) ((et_status)(_status) & ~((1u << ET_STATUS_ERRCODE_SHIFT) - 1)) - -#define ET_STATUS_ERRCODE_SHIFT 2u -#define ET_STATUS_ERRCODE_MASK (((1u << ET_STATUS_PRIVATE_SHIFT) - 1) & ~ET_STATUS_MASK) -#define ET_STATUS_MAKE_ERRCODE(_code) (((et_status)(_code) << ET_STATUS_ERRCODE_SHIFT) & ET_STATUS_ERRCODE_MASK) -#define ET_STATUS_GET_ERRCODE(_status) (((et_status)(_status) & ET_STATUS_ERRCODE_MASK) >> ET_STATUS_ERRCODE_SHIFT) - -#define ET_STATUS_PRIVATE_SHIFT 8u -#define ET_STATUS_GET_PRIVATE(_status) ((et_status)(_status) >> ET_STATUS_PRIVATE_SHIFT) -#define ET_STATUS_MAKE_PRIVATE(_exp) ((et_status)(_exp) << ET_STATUS_PRIVATE_SHIFT) -#define ET_STATUS_PRIVATE_MASK (ET_STATUS_MAKE_PRIVATE(~0u)) - -typedef enum { - ET_TEST_INTERACTIVE = 0x0001u, - ET_TEST_ESSENTIAL = 0x0002u -} et_test_flags; - -typedef enum { - ET_ERRCODE_NONE = ET_STATUS_MAKE_ERRCODE(0), - ET_ERRCODE_BAD_ALLOC = ET_STATUS_MAKE_ERRCODE(0x01u), - - /* Error code is test-specific */ - ET_ERRCODE_PRIVATE = ET_STATUS_MAKE_ERRCODE(~0u), -} et_status_errcode; - -/* returns 1 to continue testing, 0 when test is complete (call finish for status) */ -typedef int (et_test_setup_proc)(struct et_test *test, void *work, int exi_channel); -typedef int (et_test_tick_proc)(struct et_test *test, void *work); -typedef et_status (et_test_finish_proc)(struct et_test *test, void *work); - -/* translates a status code into a readable message. The returned pointer is a static string, and may not have all the information - * as if a writeable buffer is passed into the function. - * - * Tests: do not write into out if outbuf_sz is too small. Try to keep the max message length within 256 bytes. */ -typedef const char *(et_test_translate_proc)(struct et_test *test, char *out, size_t outbuf_sz, et_status status); - -struct et_test { - et_test_flags flags; - const char *name; - size_t work_size; - - et_test_setup_proc *setup_f; - et_test_tick_proc *tick_f; - et_test_finish_proc *finish_f; - et_test_translate_proc *trans_f; - - const void *priv_data[4]; -}; - -/* Array of tests. */ -extern const struct et_test et_tests[]; -extern const size_t et_ntests; - -#endif /* include guard */ diff --git a/linux/.clangd b/linux/.clangd new file mode 100644 index 0000000..e69de29 diff --git a/linux/main b/linux/main new file mode 100755 index 0000000..e84e0b9 Binary files /dev/null and b/linux/main differ diff --git a/linux/main.c b/linux/main.c new file mode 100644 index 0000000..bd208f2 --- /dev/null +++ b/linux/main.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG_MESSAGE +#define DEBUG(_s, ...) fprintf(stderr, _s, ## __VA_ARGS__) +#else +#define DEBUG(_s, ...) +#endif + +#define elt0(_exp) ((_exp) < 0) +#define check(_x, _cond, ...) do { if (_cond(_x)) { fatal_error(__VA_ARGS__); } } while (0) +#define fatal_error(...) do { fprintf(stderr, __VA_ARGS__); exit(1); } while (0) + +#define GECKO_BAUD B115200 + +static int setup_term(const char *name) { + int fd; + int flags; + struct termios tio; + +#define O(_ex, _m) check(_ex, elt0, "error " _m " %s: %s\n", name, strerror(errno)) + + O(fd = openat(AT_FDCWD, name, O_RDWR | O_NOCTTY), "opening"); + O(tcgetattr(fd, &tio), "getting tio attrs for"); + O(cfsetispeed(&tio, GECKO_BAUD), "setting ispeed of"); + O(cfsetospeed(&tio, GECKO_BAUD), "setting ospeed of"); + + tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + tio.c_oflag &= ~OPOST; + tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tio.c_cflag &= ~(CSIZE | PARENB); + tio.c_cflag |= CS8; + + O(tcsetattr(fd, TCSANOW, &tio), "setting tio attrs for"); + + O(flags = fcntl(fd, F_GETFL), "getting stream status for"); + flags |= O_NONBLOCK; + O(fcntl(fd, F_SETFL, flags), "setting stream status for"); + +#undef O + + return fd; +} + +static void handle_command(const char *cmd) { + +} + +int main(int argc, char **argv) { + struct termios tio; + int tfd; + unsigned char buf[1024]; + char cmdbuf[1024]; + struct pollfd fds[2]; + int infd; + int flags; + int can_write = 0; + size_t partialcmd_sz = 0; + + int test_mode = 0; + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + tfd = setup_term(argv[1]); + + check(infd = dup(STDIN_FILENO), elt0, "error duplicating stdin: %s\n", strerror(errno)); + check(flags = fcntl(infd, F_GETFL), elt0, "error getting stream status for stdin: %s\n", strerror(errno)); + flags |= O_NONBLOCK; + check(fcntl(infd, F_SETFL, flags), elt0, "setting stream status for stdin: %s\n", strerror(errno)); + + fds[0].fd = tfd; + fds[0].events = POLLIN | POLLOUT; + + fds[1].fd = STDIN_FILENO; + fds[1].events = POLLIN; + + while (1) { + int nready; + check(nready = poll(fds, 2, -1), elt0, "poll()ing: %s\n", strerror(errno)); + + if (nready == 0) continue; + + for (nfds_t i = 0; i < 2; ++i) { + if (fds[i].revents & (POLLERR | POLLHUP)) { + fatal_error("stream error or hangup condition on %d\n", fds[i].fd); + } + + if (fds[i].revents & POLLIN) { + ssize_t nread; + while ((nread = read(fds[i].fd, buf, 1024)) > 0) { + if (fds[i].fd == infd) { + /* command */ + for (ssize_t cidx = 0; cidx < nread; ++i) { + /* TODO: split command by lines */ + } + } else { + /* gecko */ + + } + check(write(STDOUT_FILENO, buf, nread), elt0, "write(%d) failed: %s\n", STDOUT_FILENO, strerror(errno)); + } + + if (nread == 0 && fds[i].fd == tfd) { + fatal_error("EOF reached on terminal.\n"); + } + + if (nread < 0 && errno != EAGAIN) { + fatal_error("io error on fd %d: %s\n", fds[i].fd, strerror(errno)); + } + } + + if (fds[i].revents & POLLOUT) { + fds[i].events &= ~POLLOUT; + can_write = 1; + } + } + } + + close(tfd); + close(infd); + + return 0; +} diff --git a/ppc/.clangd b/ppc/.clangd new file mode 100644 index 0000000..ed00e13 --- /dev/null +++ b/ppc/.clangd @@ -0,0 +1,13 @@ +CompileFlags: + Add: + - --target=powerpc-eabi + - -mcpu=750 + - -DGEKKO + - -DHW_RVL + - -D__PPC__ + - -isystem + - /opt/devkitpro/devkitPPC/powerpc-eabi/include + - -isystem + - /opt/devkitpro/devkitPPC/lib/gcc/powerpc-eabi/16.1.0/include + - -D_CLANGD + diff --git a/ppc/Makefile b/ppc/Makefile new file mode 100644 index 0000000..f896f14 --- /dev/null +++ b/ppc/Makefile @@ -0,0 +1,138 @@ +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif + +include $(DEVKITPPC)/wii_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source source/states source/tests +DATA := data +INCLUDES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- + +CFLAGS = -g -O2 -Wall $(MACHDEP) $(INCLUDE) +CXXFLAGS = $(CFLAGS) + +LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(notdir $@).map + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -lwiiuse -lbte -logc -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD := $(CC) +else + export LD := $(CXX) +endif + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(sFILES:.s=.o) $(SFILES:.S=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) + +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE := $(foreach dir,$(INCLUDES), -iquote $(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) \ + -I$(LIBOGC_INC) + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS := -L$(LIBOGC_LIB) $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol + +#--------------------------------------------------------------------------------- +run: + wiiload $(TARGET).dol + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).dol: $(OUTPUT).elf +$(OUTPUT).elf: $(OFILES) + +$(OFILES_SOURCES) : $(HFILES) + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .jpg extension +#--------------------------------------------------------------------------------- +%.jpg.o %_jpg.h : %.jpg +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + $(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- diff --git a/ppc/fire-pit/test.c b/ppc/fire-pit/test.c new file mode 100644 index 0000000..822db4f --- /dev/null +++ b/ppc/fire-pit/test.c @@ -0,0 +1,49 @@ +#include "test.h" + +static int identify_setup(struct et_test *test, void *work, int exi_channel) +{ + int *pexi_channel = work; + *pexi_channel = exi_channel; + return 1; +} + +static int identify_tick(struct et_test *test, void *work) +{ + int exi_channel = *((int *)work); + + EXI_Lock(exi_channel, EXI_DEVICE_0, NULL); +} + +static et_status identify_finish(struct et_test *test, void *work) +{ + +} + +static const char *identify_translate(struct et_test *test, char *out, size_t outbuf_sz, et_status status) +{ + +} + +#define DEF_TEST(_flags, _name, _work_t, _setup, _tick, _finish, ...) { \ + .flags = _flags, \ + .name = _name, \ + .work_size = sizeof(_work_t), \ + .setup_f = _setup, \ + .tick_f = _tick, \ + .finish_f = _finish, \ + __VA_ARGS__ \ +} + +#define msg_table priv_data[0] +#define TEST_MSGS(_msg_table) .msg_table = _msg_table + +#define TEST_TRANSLATE_DEFAULT .trans_f = &et_translate_default, +#define TEST_TRANSLATE(_t) .trans_f = _t, + +const struct et_test et_tests[] = { + DEF_TEST(ET_TEST_ESSENTIAL, "Identify_Gecko", int, identify_setup, identify_tick, identify_finish, TEST_TRANSLATE(identify_translate)), +}; + + +const size_t et_ntests = sizeof(et_tests) / sizeof(et_tests[0]); + diff --git a/ppc/fire-pit/test.h b/ppc/fire-pit/test.h new file mode 100644 index 0000000..c907530 --- /dev/null +++ b/ppc/fire-pit/test.h @@ -0,0 +1,77 @@ +#ifndef EXITEST_TEST_H_INCLUDED +#define EXITEST_TEST_H_INCLUDED + +#include + +struct et_test; + +typedef u32 et_status; + +/* Test passed. */ +#define ET_STATUS_PASS (et_status)0x00 + +/* Test failed. */ +#define ET_STATUS_FAIL (et_status)0x01 + +/* Test skipped or not applicable. */ +#define ET_STATUS_SKIP (et_status)0x02 + +/* Internal error during testing. */ +#define ET_STATUS_ERROR (et_status)0x03 + +#define ET_STATUS_MASK (et_status)0x03 + +#define ET_STATUS_GET_BASE(_status) ((et_status)(_status) & ~((1u << ET_STATUS_ERRCODE_SHIFT) - 1)) + +#define ET_STATUS_ERRCODE_SHIFT 2u +#define ET_STATUS_ERRCODE_MASK (((1u << ET_STATUS_PRIVATE_SHIFT) - 1) & ~ET_STATUS_MASK) +#define ET_STATUS_MAKE_ERRCODE(_code) (((et_status)(_code) << ET_STATUS_ERRCODE_SHIFT) & ET_STATUS_ERRCODE_MASK) +#define ET_STATUS_GET_ERRCODE(_status) (((et_status)(_status) & ET_STATUS_ERRCODE_MASK) >> ET_STATUS_ERRCODE_SHIFT) + +#define ET_STATUS_PRIVATE_SHIFT 8u +#define ET_STATUS_GET_PRIVATE(_status) ((et_status)(_status) >> ET_STATUS_PRIVATE_SHIFT) +#define ET_STATUS_MAKE_PRIVATE(_exp) ((et_status)(_exp) << ET_STATUS_PRIVATE_SHIFT) +#define ET_STATUS_PRIVATE_MASK (ET_STATUS_MAKE_PRIVATE(~0u)) + +typedef enum { + ET_TEST_INTERACTIVE = 0x0001u, + ET_TEST_ESSENTIAL = 0x0002u +} et_test_flags; + +typedef enum { + ET_ERRCODE_NONE = ET_STATUS_MAKE_ERRCODE(0), + ET_ERRCODE_BAD_ALLOC = ET_STATUS_MAKE_ERRCODE(0x01u), + + /* Error code is test-specific */ + ET_ERRCODE_PRIVATE = ET_STATUS_MAKE_ERRCODE(~0u), +} et_status_errcode; + +/* returns 1 to continue testing, 0 when test is complete (call finish for status) */ +typedef int (et_test_setup_proc)(struct et_test *test, void *work, int exi_channel); +typedef int (et_test_tick_proc)(struct et_test *test, void *work); +typedef et_status (et_test_finish_proc)(struct et_test *test, void *work); + +/* translates a status code into a readable message. The returned pointer is a static string, and may not have all the information + * as if a writeable buffer is passed into the function. + * + * Tests: do not write into out if outbuf_sz is too small. Try to keep the max message length within 256 bytes. */ +typedef const char *(et_test_translate_proc)(struct et_test *test, char *out, size_t outbuf_sz, et_status status); + +struct et_test { + et_test_flags flags; + const char *name; + size_t work_size; + + et_test_setup_proc *setup_f; + et_test_tick_proc *tick_f; + et_test_finish_proc *finish_f; + et_test_translate_proc *trans_f; + + const void *priv_data[4]; +}; + +/* Array of tests. */ +extern const struct et_test et_tests[]; +extern const size_t et_ntests; + +#endif /* include guard */ diff --git a/ppc/source/macros.h b/ppc/source/macros.h new file mode 100644 index 0000000..9a9db83 --- /dev/null +++ b/ppc/source/macros.h @@ -0,0 +1,14 @@ +#ifndef EXITEST_MACROS_H_INCLUDED +#define EXITEST_MACROS_H_INCLUDED + +#define STR(_s) STR2(_s) +#define STR2(_s) #_s + +#define PASTE(_x, _y) PASTE2(_x, _y) +#define PASTE2(_x, _y) _x ## _y + +#define COMMA , + +#define ARRLEN(_a) sizeof(_a) / sizeof(*(_a)) + +#endif /* include guard */ diff --git a/ppc/source/state.h b/ppc/source/state.h new file mode 100644 index 0000000..2f70527 --- /dev/null +++ b/ppc/source/state.h @@ -0,0 +1,57 @@ +#ifndef EXITEST_STATE_H_INCLUDED +#define EXITEST_STATE_H_INCLUDED + +#include +#include + +struct et_state; + +typedef union { + uintptr_t i; + void *p; +} et_state_init_data; + +extern const et_state_init_data et_init_data_null; + +struct et_next_state { + const struct et_state *state; + et_state_init_data init_data; +}; + +/* Sets up the state. The second argument (void *) should point to at least state->private_size bytes + * of data. The state assumes responsibility for clearing/initializing this buffer before use. + * + * Returns < 0 for error, >= 0 for success. + * This function conceptually takes ownership of init_data. The implications of this depend + * on what init_data actually contains -- sometimes it's globally shared data. + * + * State authors: Free any data filled into init_data regardless of error states. + * The caller will not touch init_data again. */ +typedef int (et_state_init_f)(const struct et_state *, void *, et_state_init_data); + +/* Ticks the state. + * + * Returns < 0 for error (still call cleanup or exit(...)), 0 for "continue", >0 for "advance state". + * If a state requests to advance to the next state, the info will be in *next_state (which should be non-NULL). + * Note that next_state->init_data may have malloc()'d data which must be handed off by passing to the init function + * of the appropriate state. */ +typedef int (et_state_tick_f)(const struct et_state *, void *, struct et_next_state *); +typedef void (et_state_cleanup_f)(const struct et_state *, void *); + +struct et_state { + et_state_init_f *init_f; + et_state_tick_f *tick_f; + et_state_cleanup_f *cleanup_f; + size_t private_size; +}; + +/* convenience macros because these existed as functions at one point + * (note: state expression is evaluated twice. make sure it doesn't have side effects) */ + +#define et_state_init(_st, _data, _init_data) ((_st)->init_f(_st, _data, _init_data)) +#define et_state_tick(_st, _data, _next) ((_st)->tick_f(_st, _data, _next)) +#define et_state_cleanup(_st, _data) ((_st)->cleanup_f(_st, _data)) + +extern const struct et_state *et_state_setup; + +#endif /* include guard */ diff --git a/ppc/source/states/menu.c b/ppc/source/states/menu.c new file mode 100644 index 0000000..ea5134b --- /dev/null +++ b/ppc/source/states/menu.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +#define EXITEST_STATE_INTERNAL +#include "../state.h" +#include "../term.h" +#include "../test.h" + +static void print_setup_menu(int selected_slot) { + fputs(TERM_CUR_UP(2), stdout); + printf(TERM_CLEARLINE "- [DL/DR] EXI Slot %c\n", selected_slot == 0 ? 'A' : 'B'); + printf(TERM_CLEARLINE "Press A to begin testing.\n"); +} + +static int st_setup_init(const struct et_state *state, void *state_data, et_state_init_data init_data) { + u32 *setup_data = state_data; + + *setup_data = 0; + + printf("\nTest setup menu:\n\n\n\n"); + print_setup_menu(*setup_data); + + return 0; +} + +static int begin_testing(u32 menu_state, struct et_next_state *next_state) { + struct et_test_plan *plan = calloc(1, sizeof(struct et_test_plan)); + if (!plan) { + puts("Failed to allocate new test plan!"); + return -1; + } + + size_t num_tests; + const struct et_test *const *all_tests = et_get_tests(&num_tests); + + struct et_test_plan_entry *plan_entries = calloc(num_tests, sizeof(struct et_test_plan_entry)); + if (!plan_entries) { + puts("Failed to allocate new test plan entries!"); + free(plan); + return -1; + } + + for (size_t i = 0; i < num_tests; ++i) { + plan_entries[i].test = all_tests[i]; + plan_entries[i].status = TEST_PRIMORDIAL; + plan_entries[i].ext_status = NULL; + plan_entries[i].ext_status_free = NULL; + } + + plan->exi_channel = (int)menu_state; + plan->next_state = et_state_summarize_test_plan; + plan->next_state_init.p = plan; + plan->num_tests = num_tests; + plan->current_test = 0; + plan->tests = plan_entries; + + next_state->state = et_state_begin_testing; + next_state->init_data.p = plan; + + putchar('\n'); + + return 1; +} + +static int st_setup_tick(const struct et_state *state, void *state_data, struct et_next_state *next_state) { + u32 *setup_data = state_data; + u32 wpad_pressed = WPAD_ButtonsDown(0); + u32 pad_pressed = PAD_ButtonsDown(0); + + if (wpad_pressed & WPAD_BUTTON_LEFT || wpad_pressed & WPAD_BUTTON_RIGHT || pad_pressed & PAD_BUTTON_LEFT || pad_pressed & PAD_BUTTON_RIGHT) { + *setup_data = !*setup_data; + print_setup_menu(*setup_data); + } + + if (wpad_pressed & WPAD_BUTTON_A || pad_pressed & PAD_BUTTON_A) { + /* set up test plan */ + return begin_testing(*setup_data, next_state); + } + + return 0; +} + +static void st_setup_cleanup(const struct et_state *state, void *state_data) { + /* do nothing */ +} + +const et_state_init_data et_init_data_null = { .p = NULL }; + +static struct et_state et__state_setup = { + .init_f = &st_setup_init, + .tick_f = &st_setup_tick, + .cleanup_f = &st_setup_cleanup, + .private_size = sizeof(u32) +}; + +const struct et_state *et_state_setup = &et__state_setup; diff --git a/ppc/source/template.c b/ppc/source/template.c new file mode 100644 index 0000000..8665774 --- /dev/null +++ b/ppc/source/template.c @@ -0,0 +1,303 @@ +#include +#include +#include +#include +#include +#include + +#include "term.h" +#include "state.h" + +static void *xfb = NULL; +static GXRModeObj *rmode = NULL; + +#define TARGET_IOS 58 + +static void initialize(int again) { + VIDEO_Init(); + + rmode = VIDEO_GetPreferredMode(NULL); + xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); + + console_init(xfb, 20, 20, rmode->fbWidth - 20, rmode->xfbHeight - 20, rmode->fbWidth * VI_DISPLAY_PIX_SZ); + + VIDEO_Configure(rmode); + VIDEO_SetNextFramebuffer(xfb); + VIDEO_ClearFrameBuffer(rmode, xfb, COLOR_BLACK); + VIDEO_SetBlack(FALSE); + VIDEO_Flush(); + VIDEO_WaitVSync(); + if (rmode->viTVMode & VI_NON_INTERLACE) { + VIDEO_WaitVSync(); + } + + fputs(TERM_CLEAR TERM_CUR_POS(2, 5), stdout); + printf("USB Gecko (newnewexi) test tool by figboot\n\n"); + + s32 iosver = IOS_GetVersion(); + s32 iosrev = IOS_GetRevision(); + printf("You're running IOS%" PRId32 " rev %" PRId32 ".\n", iosver, iosrev); + + if (iosver != TARGET_IOS) { + if (again) { + printf(TERM_CSI("93m") "Failed to set up IOS" STR(TARGET_IOS) "! Bailing out.\n"); + exit(1); + } + + printf("Trying to load IOS" STR(TARGET_IOS) "...\n"); + + s32 res; + if ((res = IOS_ReloadIOS(TARGET_IOS)) < 0) { + printf(TERM_CSI("93m") "Failed to set up IOS" STR(TARGET_IOS) "! IOS_ReloadIOS returned %" PRId32 "\n", res); + exit(1); + } + + free(MEM_K1_TO_K0(xfb)); + initialize(1); + } +} + +static void __attribute__((noreturn)) fatal_error(void) { + printf("The program will now abort due to the fatal error.\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + void *state_data = NULL; + size_t state_data_size = 0; + struct et_next_state next; + const struct et_state *cur_state = NULL; + int stateres = 0; + + u32 wpad_pressed; + u32 pad_pressed; + + initialize(0); + + WPAD_Init(); + PAD_Init(); + + printf("Done initializing.\n"); + + /* Set up initial state */ + cur_state = et_state_setup; + + if (cur_state->private_size > 0) { + state_data = malloc(cur_state->private_size); + state_data_size = cur_state->private_size; + if (!state_data) { + printf("Failed to allocate private space for setup state %p!", cur_state); + fatal_error(); + } + } + + if ((stateres = et_state_init(cur_state, state_data, et_init_data_null)) < 0) { + printf("Failed to init setup state %p: %d\n", cur_state, stateres); + fatal_error(); + } + + while (SYS_MainLoop()) { + WPAD_ScanPads(); + PAD_ScanPads(); + + wpad_pressed = WPAD_ButtonsDown(0); + pad_pressed = PAD_ButtonsDown(0); + + if (wpad_pressed & WPAD_BUTTON_HOME || pad_pressed & PAD_BUTTON_START) { + printf("Exit button pressed. Goodbye!\n"); + exit(0); + } + + stateres = et_state_tick(cur_state, state_data, &next); + if (stateres < 0) { + printf("Failed to tick state %p: %d\n", cur_state, stateres); + fatal_error(); + } + + if (stateres > 0) { + /* tear down the old state first... */ + et_state_cleanup(cur_state, state_data); + + cur_state = next.state; + + if (state_data_size < cur_state->private_size) { + void *new_data = realloc(state_data, cur_state->private_size); + if (!new_data) { + printf("Failed to allocate %zu bytes for new state %p\n", cur_state->private_size, cur_state); + fatal_error(); + } + + state_data = new_data; + state_data_size = cur_state->private_size; + } + + if ((stateres = et_state_init(cur_state, state_data, next.init_data)) < 0) { + printf("Failed to init new state %p: %d\n", cur_state, stateres); + fatal_error(); + } + } + + VIDEO_WaitVSync(); + } + + return 0; +} + +#if 0 + +//--------------------------------------------------------------------------------- +int main(int argc, char **argv) { +//--------------------------------------------------------------------------------- + + // Initialise the video system + VIDEO_Init(); + + // This function initialises the attached controllers + WPAD_Init(); + + // Obtain the preferred video mode from the system + // This will correspond to the settings in the Wii menu + rmode = VIDEO_GetPreferredMode(NULL); + + // Allocate memory for the display in the uncached region + xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); + + // Initialise the console, required for printf + console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ); + + // Set up the video registers with the chosen mode + VIDEO_Configure(rmode); + + // Tell the video hardware where our display memory is + VIDEO_SetNextFramebuffer(xfb); + + // Make the display visible + VIDEO_SetBlack(false); + + // Flush the video register changes to the hardware + VIDEO_Flush(); + + // Wait for Video setup to complete + VIDEO_WaitVSync(); + if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync(); + + + // The console understands VT terminal escape codes + // This positions the cursor on row 2, column 0 + // we can use variables for this with format codes too + // e.g. printf ("\x1b[%d;%dH", row, column ); + printf("\x1b[2;0H"); + + + printf("Hello World!\n"); + +#define UGCHAN EXI_CHANNEL_1 /* slot B */ +#define UGDEV EXI_DEVICE_0 /* memory card */ + + if (EXI_Lock(UGCHAN, UGDEV, NULL) == 1) { + EXI_Select(UGCHAN, UGDEV, EXI_SPEED32MHZ); + uint32_t cmd = 0x90000000; + int res = 0; + if (EXI_Imm(UGCHAN, &cmd, sizeof(cmd), EXI_READWRITE, NULL) == 0) res |= 0x01; + if (EXI_Sync(UGCHAN) == 0) res |= 0x02; + + if (res == 0) { + printf("Got: 0x%08x\n", cmd); + if (cmd != 0x04700000) { + printf("This is probably not a USB gecko...\n"); + } + } else { + printf("EXI read/write error :( %#04x\n", res); + } + + EXI_Deselect(UGCHAN); + EXI_Unlock(UGCHAN); + } else { + printf("Failed to lock EXI channel %d %d\n", UGCHAN, UGDEV); + goto waitexit; + } + + for (int i = 0; i < 10; ++i) { + if (EXI_Lock(UGCHAN, UGDEV, NULL) == 0) { + printf("Failed to lock EXI for LED\n"); + break; + } + + int res = 0; + + if (EXI_Select(UGCHAN, UGDEV, EXI_SPEED32MHZ) == 0) res |= 0x01; + uint32_t cmd = (i & 1) ? 0x80000000 : 0x70000000; + if (EXI_Imm(UGCHAN, &cmd, sizeof(cmd), EXI_WRITE, NULL) == 0) res |= 0x02; + if (EXI_Sync(UGCHAN) == 0) res |= 0x04; + if (EXI_Deselect(UGCHAN) == 0) res |= 0x08; + + if (EXI_Unlock(UGCHAN) == 0) res |= 0x10; + + printf("Error for LED: 0x%02x\n", res); + + for (int j = 0; j < 60; ++j) { + VIDEO_WaitVSync(); + } + } + + + for (int j = 0; j < 10; ++j) { + if (EXI_Lock(UGCHAN, UGDEV, NULL) == 1) { + printf("Sending a zillion bytes to your so-called \"usb gecko\"...\n"); + + for (int i = 0; i < 4096; ++i) { + EXI_Select(UGCHAN, UGDEV, EXI_SPEED32MHZ); + + uint16_t cmd = 0xB000 | (uint16_t)(((i & 63) + 32) << 4); + if (EXI_Imm(UGCHAN, &cmd, sizeof(cmd), EXI_READWRITE, NULL) == 0) { + printf("Failed when writing %d\n", i); + goto error; + } + + if (EXI_Sync(UGCHAN) == 0) { + printf("Failed sync when writing %d\n", i); + goto error; + } + + if ((cmd & 0x0400) == 0) { + printf("Out of FIFO space at %d\n", i); + goto error; + } + + EXI_Deselect(UGCHAN); + continue; +error: + EXI_Deselect(UGCHAN); + break; + } + + EXI_Unlock(UGCHAN); + } else { + printf("Failed to lock EXI channel %d %d\n", UGCHAN, UGDEV); + goto waitexit; + } + } + +waitexit: + printf("That's all! Press HOME to exit.\n"); + + while(SYS_MainLoop()) { + + // Call WPAD_ScanPads each loop, this reads the latest controller states + WPAD_ScanPads(); + + // WPAD_ButtonsDown tells us which buttons were pressed in this loop + // this is a "one shot" state which will not fire again until the button has been released + u32 pressed = WPAD_ButtonsDown(0); + + // We return to the launcher application via exit + if ( pressed & WPAD_BUTTON_HOME ) exit(0); + + // Wait for the next frame + VIDEO_WaitVSync(); + } + + return 0; +} +#endif diff --git a/ppc/source/term.h b/ppc/source/term.h new file mode 100644 index 0000000..cc65615 --- /dev/null +++ b/ppc/source/term.h @@ -0,0 +1,13 @@ +#ifndef EXITEST_TERM_H_INCLUDED +#define EXITEST_TERM_H_INCLUDED + +#include "macros.h" + +#define TERM_ESC "\x1b" +#define TERM_CSI(_s) TERM_ESC "[" _s +#define TERM_CLEAR TERM_CSI("2J") +#define TERM_CLEARLINE TERM_CSI("2K") +#define TERM_CUR_POS(_row, _col) TERM_CSI(STR(_row) ";" STR(_col) "f") +#define TERM_CUR_UP(_rows) TERM_CSI(STR(_rows) "A") + +#endif /* include guard */ diff --git a/ppc/source/test-impl.inc.h b/ppc/source/test-impl.inc.h new file mode 100644 index 0000000..8e1559f --- /dev/null +++ b/ppc/source/test-impl.inc.h @@ -0,0 +1,53 @@ +#ifndef ET__ALLOW_TEST_IMPL_INC +#error "Do not include this file directly! Include test.h instead." +#endif + +#undef ET__ALLOW_TEST_IMPL_INC + +/* This file will be included in .c files which implement tests. */ +#ifdef _CLANGD +#include +#include "test.h" +/* include stuff this file uses here to make clangd happy (will not be visible to test.h) */ +#endif + +struct et_test_state_private_base { + struct et_test_plan *plan; +}; + +/* function prototypes for test-internal stuff */ +int test_next(struct et_test_plan *plan, struct et_next_state *next_state); + +int test_state_init(const struct et_state *state, void *data, et_state_init_data init_data); +void test_state_cleanup(const struct et_state *state, void *data); + +void test_plan_entry_set_error(struct et_test_plan_entry *entry, const char *fmt, ...) __attribute__((format(printf, 2, 3))); +void test_plan_entry_set_error_static(struct et_test_plan_entry *entry, const char *str); + +et_test_status test_exi_readwrite(struct et_test_plan *plan, void *data, size_t data_sz, et_test_error base); + +/* Expands to a struct initializer for a struct et_test */ +#define TEST_STRUCT_INIT_FULL(_namestr, _flags, _init_f, _tick_f, _cleanup_f, _private_type) \ +{ \ + .parent = { \ + .init_f = test_state_init, \ + .tick_f = _tick_f, \ + .cleanup_f = test_state_cleanup, \ + .private_size = sizeof(_private_type), \ + }, \ + .test_init_f = _init_f, \ + .test_cleanup_f = _cleanup_f, \ + .name = _namestr, \ + .flags = _flags \ +} + +#define TEST__STRUCT_NAME(_name) PASTE(TEST_SYM(_name), __struct) +#define TEST__STRUCT_DEF(_name, _ini) static const struct et_test TEST__STRUCT_NAME(_name) = _ini +#define TEST__STRUCT_SANITY_CHECK(_ty) static_assert(sizeof(_ty) >= sizeof(struct et_test_state_private_base), "private data type too small (does it include base?)") +#define TEST__SYM_DEF(_name) const struct et_test *const TEST_SYM(_name) = &TEST__STRUCT_NAME(_name) +#define TEST_DEF(_name, _namestr, _flags, _init_f, _tick_f, _cleanup_f, _private_type) \ + TEST__STRUCT_SANITY_CHECK(_private_type); \ + TEST__STRUCT_DEF(_name, TEST_STRUCT_INIT_FULL(_namestr, _flags, _init_f, _tick_f, _cleanup_f, _private_type)); \ + TEST__SYM_DEF(_name) + +#define test_current(_plan) ((_plan)->tests + (_plan)->current_test) diff --git a/ppc/source/test.c b/ppc/source/test.c new file mode 100644 index 0000000..c59deaf --- /dev/null +++ b/ppc/source/test.c @@ -0,0 +1,453 @@ +#include +#include +#include +#include +#include +#include +#include + +#define TEST_INTERNAL +#include "test.h" +#include "term.h" + +typedef u32 et_test_status; + +#define test_current(_plan) ((_plan)->tests + (_plan)->current_test) + +static int crit_test_fail_init(const struct et_state *state, void *data, et_state_init_data init_data) { + struct et_test_plan *plan = init_data.p; + struct et_test_plan_entry *last_ent; + + *(void **)data = plan; + + assert(plan->current_test > 0); /* NOTE: current_test has been incremented by this point */ + + last_ent = plan->tests + (plan->current_test - 1); + + printf("\nCritical test failed: [%4s] %s\n", et_test_get_status_mnemonic(last_ent->status), last_ent->test->name); + if (last_ent->ext_status) { + printf("Test says:\n%s\n", last_ent->ext_status); + } else { + printf("No extended status.\n"); + } + + putchar('\n'); + + printf("Press A to continue.\n"); + printf("Press B to finish testing.\n"); + printf("Press HOME/START to exit.\n"); + + return 0; +} + +static int crit_test_fail_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { + struct et_test_plan *plan = *(void **)data; + u32 wpad_pressed = WPAD_ButtonsDown(0); + u32 pad_pressed = PAD_ButtonsDown(0); + + if (wpad_pressed & WPAD_BUTTON_A || pad_pressed & PAD_BUTTON_A) { + next_state->state = et_state_begin_testing; /* also works to resume testing */ + next_state->init_data.p = plan; /* test plan */ + return 1; + } + + if (wpad_pressed & WPAD_BUTTON_B || pad_pressed & PAD_BUTTON_B) { + next_state->state = plan->next_state; + next_state->init_data = plan->next_state_init; + return 1; + } + + if (wpad_pressed & WPAD_BUTTON_HOME || pad_pressed & PAD_BUTTON_START) { + printf("Goodbye!\n"); + exit(0); + } + + return 0; +} + +static void crit_test_fail_cleanup(const struct et_state *state, void *data) { + /* do nothing */ +} + +static const struct et_state state_crit_test_fail = { + .private_size = sizeof(struct et_test_plan *), + .init_f = crit_test_fail_init, + .tick_f = crit_test_fail_tick, + .cleanup_f = crit_test_fail_cleanup +}; + +int test_next(struct et_test_plan *plan, struct et_next_state *next_state) { + /* NOTE: this function doesn't increment current_test because the incremented value + * would be visible in the cleanup function of the test state. */ + + /* NOTE: this condition will not trigger for states that leak their TEST_PRIMORDIAL status. + * I don't care about this situation so much, since that should only happen for bugged tests. */ + const struct et_test_plan_entry *ent = test_current(plan); + switch (TEST_STATUS_GET_TYPE(ent->status)) { + case TEST_SERROR: + case TEST_SFAIL: + if (ent->test->flags & TEST_PAUSE_ON_ERROR) { + next_state->state = &state_crit_test_fail; + next_state->init_data.p = plan; + return 1; + } + break; + default: + /* do nothing */ + (void)0; + } + + if (plan->current_test == plan->num_tests - 1) { + /* this is the last state... we're done! */ + next_state->state = plan->next_state; + next_state->init_data = plan->next_state_init; + } else { + next_state->state = &plan->tests[plan->current_test + 1].test->parent; + next_state->init_data.p = plan; + } + + return 1; /* always return 1: advance to next state */ +} + +int test_state_init(const struct et_state *state, void *data, et_state_init_data init_data) { + struct et_test_plan *plan = init_data.p; + struct et_test_state_private_base *testdata = data; + struct et_test_plan_entry *cur = test_current(plan); + const struct et_test *test = (const struct et_test *)state; + + testdata->plan = plan; + + assert(cur->test == test); /* ensure correct test was loaded */ + assert(TEST_STATUS_IS_CLEAN(cur->status)); + + if (test->test_init_f) { + return test->test_init_f(state, data, init_data); + } + + return 0; +} + +void test_state_cleanup(const struct et_state *state, void *data) { + struct et_test_state_private_base *testdata = data; + struct et_test_plan_entry *cur = test_current(testdata->plan); + const struct et_test *test = (const struct et_test *)state; + + if (test->test_cleanup_f) { + test->test_cleanup_f(state, data); + } + + if (TEST_STATUS_IS_CLEAN(cur->status)) { + /* test didn't set status :( */ + test_plan_entry_set_error_static(cur, "test aborted"); + cur->status = TEST_ERROR(TEST_EABORT); + } + + printf("[%4s] %s\n", et_test_get_status_mnemonic(cur->status), test->name); + + ++testdata->plan->current_test; +} + +static int begin_testing_init(const struct et_state *state, void *state_data, et_state_init_data init_data) { + struct et_test_plan *test_plan = init_data.p; + struct et_test_plan **pst_test_plan = state_data; + *pst_test_plan = test_plan; + return 0; +} + +static int begin_testing_tick(const struct et_state *state, void *state_data, struct et_next_state *next_state) { + struct et_test_plan **ptest_plan = state_data; + struct et_test_plan *test_plan = *ptest_plan; + + assert(test_plan->current_test <= test_plan->num_tests); + + if (test_plan->current_test < test_plan->num_tests) { + const struct et_test_plan_entry *next_test = test_plan->tests + test_plan->current_test; + assert(test_plan->tests); + assert(next_test->status == TEST_PRIMORDIAL); + + next_state->state = &next_test->test->parent; + next_state->init_data.p = test_plan; + } else { + /* no tests to run in this plan */ + next_state->state = test_plan->next_state; + next_state->init_data = test_plan->next_state_init; + } + + return 1; + +} + +static void begin_testing_cleanup(const struct et_state *state, void *state_data) { + /* do nothing */ +} + +static const struct et_state state_begin_testing_struct = { + .private_size = sizeof(const struct et_test_plan *), + .init_f = begin_testing_init, + .tick_f = begin_testing_tick, + .cleanup_f = begin_testing_cleanup +}; + +const struct et_state *et_state_begin_testing = &state_begin_testing_struct; + +struct summ_test_plan_private { + struct et_test_plan *plan; + size_t selected_test; +}; + +#define SUMM_MENU_ROWS 3 + +static void summ_test_print_menu(struct summ_test_plan_private *priv) { + const struct et_test *test = priv->plan->tests[priv->selected_test].test; + + fputs(TERM_CUR_UP(SUMM_MENU_ROWS), stdout); + puts(TERM_CLEARLINE "Press A to test again. Press HOME/START to exit."); + puts(TERM_CLEARLINE "Press +/X to show output for [DL/DR]:"); + printf(TERM_CLEARLINE "%s\n", test->name); +} + +static int summ_test_plan_init(const struct et_state *state, void *state_data, et_state_init_data init_data) { + struct summ_test_plan_private *priv = state_data; + struct et_test_plan *ptest_plan = init_data.p; + size_t passed = 0; + size_t failed = 0; + size_t errored = 0; + size_t skipped = 0; + size_t unknown = 0; + + priv->plan = ptest_plan; + priv->selected_test = 0; + + for (size_t i = 0; i < ptest_plan->num_tests; ++i) { + switch (TEST_STATUS_GET_TYPE(ptest_plan->tests[i].status)) { + case TEST_SPASS: ++passed; break; + case TEST_SFAIL: ++failed; break; + case TEST_SERROR: ++errored; break; + case TEST_SSKIPPED: ++skipped; break; + default: ++unknown; break; + } + } + + printf("\nTesting complete.\nPASS: %zu, FAIL: %zu, ERROR: %zu, SKIP: %zu, UNK: %zu\n\n", passed, failed, errored, skipped, unknown); + + for (int i = 0; i < SUMM_MENU_ROWS; ++i) putchar('\n'); + summ_test_print_menu(priv); + + return 0; +} + +static int summ_test_plan_tick(const struct et_state *state, void *state_data, struct et_next_state *next_state) { + struct summ_test_plan_private *priv = state_data; + + u32 wpad_buttons = WPAD_ButtonsDown(0); + u32 pad_buttons = PAD_ButtonsDown(0); + + if (wpad_buttons & WPAD_BUTTON_A || pad_buttons & PAD_BUTTON_A) { + next_state->state = et_state_setup; + next_state->init_data = et_init_data_null; + return 1; + } + + if (wpad_buttons & WPAD_BUTTON_HOME || pad_buttons & PAD_BUTTON_START) { + puts("Goodbye!"); + exit(0); + } + + if (wpad_buttons & WPAD_BUTTON_LEFT || pad_buttons & PAD_BUTTON_LEFT) { + if (priv->selected_test > 0) { + --priv->selected_test; + } else { + priv->selected_test = priv->plan->num_tests - 1; + } + + summ_test_print_menu(priv); + } + + if (wpad_buttons & WPAD_BUTTON_RIGHT || pad_buttons & PAD_BUTTON_RIGHT) { + if (priv->selected_test < priv->plan->num_tests - 1) { + ++priv->selected_test; + } else { + priv->selected_test = 0; + } + + summ_test_print_menu(priv); + } + + if (wpad_buttons & WPAD_BUTTON_1 || pad_buttons & PAD_BUTTON_X) { + struct et_test_plan_entry *entry = priv->plan->tests + priv->selected_test; + printf("\nExtended status for %s:\n", entry->test->name); + if (entry->ext_status) { + puts(entry->ext_status); + } else { + puts("(no status)"); + } + + for (int i = 0; i < SUMM_MENU_ROWS + 1; ++i) putchar('\n'); + summ_test_print_menu(priv); + } + + return 0; +} + +static void summ_test_plan_cleanup(const struct et_state *state, void *state_data) { + struct summ_test_plan_private *priv = state_data; + struct et_test_plan *ptest_plan = priv->plan; + + for (size_t i = 0; i < ptest_plan->num_tests; ++i) { + et_test_plan_entry_cleanup(ptest_plan->tests + i); + } + + free(ptest_plan->tests); + free(ptest_plan); +} + +static const struct et_state summ_test_plan_struct = { + .private_size = sizeof(struct summ_test_plan_private), + .init_f = summ_test_plan_init, + .tick_f = summ_test_plan_tick, + .cleanup_f = summ_test_plan_cleanup +}; + +const struct et_state *et_state_summarize_test_plan = &summ_test_plan_struct; + +#define COUNT_TESTS(_x) 1 +#define NUM_TESTS FOREACH_TEST(COUNT_TESTS, +) +static const struct et_test *all_tests[NUM_TESTS + 1]; +static bool tests_array_initialized = false; + +const struct et_test *const *et_get_tests(size_t *ntests) { + size_t idx = 0; + + if (ntests) *ntests = NUM_TESTS; + if (tests_array_initialized) return all_tests; + +#define A(_name) all_tests[idx++] = TEST_SYM(_name) + FOREACH_TEST(A, ;); +#undef A + + all_tests[idx] = NULL; + + tests_array_initialized = true; + return all_tests; +} + +static const char *error_oom = "(failed to allocate error message)"; +static const char *error_bad_fmt = "(bad extended error format)"; + +void et_test_plan_entry_cleanup(struct et_test_plan_entry *entry) { + if (entry->ext_status_free) { + /* okay to cast away const here. we're careful about making sure this pointer is non-NULL iff this pointer + * was malloc'd */ + entry->ext_status_free((void *)entry->ext_status); + } + + entry->ext_status = NULL; + entry->ext_status_free = NULL; +} + +void test_plan_entry_set_error(struct et_test_plan_entry *entry, const char *fmt, ...) { + int res; + va_list vp; + char *dest; + size_t dest_sz; + + et_test_plan_entry_cleanup(entry); + + va_start(vp, fmt); + res = vsnprintf(NULL, 0, fmt, vp); + va_end(vp); + + if (res < 0) { + entry->ext_status = error_bad_fmt; + entry->ext_status_free = NULL; + return; + } + + dest_sz = MIN((size_t)res + 1, (size_t)1024); + dest = malloc(dest_sz); + dest[0] = '\0'; + + if (!dest) { + entry->ext_status = error_oom; + entry->ext_status_free = NULL; + return; + } + + va_start(vp, fmt); + res = vsnprintf(dest, dest_sz, fmt, vp); + va_end(vp); + + dest[dest_sz - 1] = '\0'; + + assert(res >= 0); + + entry->ext_status = dest; + entry->ext_status_free = free; +} + +void test_plan_entry_set_error_static(struct et_test_plan_entry *entry, const char *str) { + et_test_plan_entry_cleanup(entry); + entry->ext_status = str; + entry->ext_status_free = NULL; +} + +const char *et_test_get_status_mnemonic(et_test_status status) { + switch (TEST_STATUS_GET_TYPE(status)) { + case TEST_SPASS: return "PASS"; + case TEST_SFAIL: return "FAIL"; + case TEST_SSKIPPED: + if (TEST_STATUS_IS_CLEAN(status)) { + return "SKI!"; + } else { + return "SKIP"; + } + case TEST_SERROR: + switch (TEST_STATUS_GET_ERROR(status)) { + case TEST_ENONE: return "ERR0"; + case TEST_EABORT: return "ERRA"; + default: return "ERRP"; + } + default: + return "UNKS"; + } +} + +et_test_status test_exi_readwrite(struct et_test_plan *plan, void *data, size_t data_sz, et_test_error base) { + struct et_test_plan_entry *cur = test_current(plan); + int chan = plan->exi_channel; + et_test_status status = TEST_PASS; + s32 exi_res; + + if ((exi_res = EXI_Lock(chan, EXI_DEVICE_0, NULL)) <= 0) { + status = TEST_ERROR(base); + test_plan_entry_set_error(cur, "failed to lock EXI channel: %" PRId32, exi_res); + goto fail_lock; + } + + if ((exi_res = EXI_Select(chan, EXI_DEVICE_0, EXI_SPEED32MHZ)) <= 0) { + status = TEST_ERROR(base + 1); + test_plan_entry_set_error(cur, "failed to select EXI channel: %" PRId32, exi_res); + goto fail_select; + } + + if ((exi_res = EXI_Imm(chan, data, data_sz, EXI_READWRITE, NULL)) <= 0) { + status = TEST_ERROR(base + 2); + test_plan_entry_set_error(cur, "failed to send EXI data: %" PRId32, exi_res); + goto fail_data; + } + + if ((exi_res = EXI_Sync(chan)) <= 0) { + status = TEST_ERROR(base + 3); + test_plan_entry_set_error(cur, "failed to complete transfer: %" PRId32, exi_res); + goto fail_data; + } + +fail_data: + EXI_Deselect(chan); + +fail_select: + EXI_Unlock(chan); + +fail_lock: + return status; +} diff --git a/ppc/source/test.h b/ppc/source/test.h new file mode 100644 index 0000000..81a107a --- /dev/null +++ b/ppc/source/test.h @@ -0,0 +1,122 @@ +#ifndef EXITEST_TEST_H_INCLUDED +#define EXITEST_TEST_H_INCLUDED + +#include +#include +#include "state.h" +#include "macros.h" + +typedef enum { + TEST_INTERACTIVE = 0x01u, + TEST_PAUSE_ON_ERROR = 0x02u /* for "essential" tests that should pass */ +} et_test_flags; + +struct et_test; +struct et_test_plan; + +struct et_test { + struct et_state parent; + const char *name; + et_test_flags flags; + + et_state_init_f *test_init_f; + et_state_cleanup_f *test_cleanup_f; +}; + +/* 0000eeee eeeeeeee eeeeeeee x00000ss */ +/* e = error code bits, + * x = 1 if this test is still in the "clean" (not executed) state + * ss = status type (pass/fail/etc.) */ +typedef u32 et_test_status; + +typedef enum { + TEST_ENONE = (u32)0, + + /* test was aborted (i.e., requested to go to next test without setting a status code) */ + TEST_EABORT, + + /* minimum private test error (define test-private error codes as TEST_EPRIV_BASE+n) */ + TEST_EPRIV_BASE = 32, + + /* largest error code allowed */ + TEST_EMAX = ((u32)1 << 20) - 1 +} et_test_error; + +typedef enum { + TEST_SSKIPPED = 0u, + TEST_SERROR, + TEST_SFAIL, + TEST_SPASS, + + TEST_SMAX = ((u32)1 << 2) - 1 +} et_test_status_type; + +#define TEST_STATUS_GET_ERROR(_status) (et_test_error)(((u32)(_status) >> 8) & TEST_EMAX) +#define TEST_STATUS_GET_TYPE(_status) (et_test_status_type)((u32)(_status) & TEST_SMAX) +#define TEST_STATUS_IS_CLEAN(_status) (!!((u32)(_status) & 0x80u)) + +#define TEST_MAKE_STATUS(_type, _error, _clean) (et_test_status) \ + (((u32)(_type) & TEST_SMAX) | \ + (((u32)(_clean) & 1) << 7) | \ + (((u32)(_error) & TEST_EMAX) << 8)) + +#define TEST_SKIPPED TEST_MAKE_STATUS(TEST_SSKIPPED, TEST_ENONE, 0) +#define TEST_FAIL TEST_MAKE_STATUS(TEST_SFAIL, TEST_ENONE, 0) +#define TEST_PASS TEST_MAKE_STATUS(TEST_SPASS, TEST_ENONE, 0) +#define TEST_ERROR(_e) TEST_MAKE_STATUS(TEST_SERROR, _e, 0) + +#define TEST_PRIMORDIAL TEST_MAKE_STATUS(TEST_SSKIPPED, TEST_ENONE, 1) + +/* Expected setup date: pointer to test plan */ +extern const struct et_state *et_state_begin_testing; +extern const struct et_state *et_state_summarize_test_plan; + +const char *et_test_get_status_mnemonic(et_test_status status); + +struct et_test_plan_entry { + const struct et_test *test; + et_test_status status; + const char *ext_status; + void (*ext_status_free)(void *); +}; + +/* a properly initialized test plan will have all the tests' statuses set to TEST_PRIMORDIAL */ +struct et_test_plan { + int exi_channel; + + /* this state will be set after testing is complete. + * NOTE: next_state_init should somehow point to this test plan, + * since it's probably dynamically allocated and will be leaked if not + * freed. */ + const struct et_state *next_state; + et_state_init_data next_state_init; + + size_t num_tests; + size_t current_test; + struct et_test_plan_entry *tests; +}; + +void et_test_plan_entry_cleanup(struct et_test_plan_entry *entry); + +#define FOREACH_TEST(O, _sep) \ + O(check_id) _sep \ + O(bad_commands) _sep \ + O(flash_led) + +#define TEST_SYM(_name) PASTE(et_test_, _name) +#define DECL_TEST(_name) extern const struct et_test *const TEST_SYM(_name) + +FOREACH_TEST(DECL_TEST, ;); +#undef DECL_TEST + +const struct et_test *const *et_get_tests(size_t *ntests); + +#if defined(ET_TEST_IMPL) || defined(TEST_INTERNAL) +#define ET__ALLOW_TEST_IMPL_INC +#include "test-impl.inc.h" +#else +#undef FOREACH_TEST +#undef TEST_SYM +#endif + +#endif /* include guard */ diff --git a/ppc/source/tests/basic.c b/ppc/source/tests/basic.c new file mode 100644 index 0000000..4f34ba4 --- /dev/null +++ b/ppc/source/tests/basic.c @@ -0,0 +1,79 @@ +#define ET_TEST_IMPL +#include "../test.h" +#include +#include + +#define TEST_ITERATIONS 16 + +struct test_with_iterations { + struct et_test_state_private_base base; + int iteration; +}; + +static int init_test_with_iterations(const struct et_state *state, void *data, et_state_init_data init_data) { + struct test_with_iterations *priv = data; + priv->iteration = 0; + return 0; +} + +static int check_id_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { + struct test_with_iterations *priv = data; + struct et_test_plan *test_plan = priv->base.plan; + struct et_test_plan_entry *cur = test_current(test_plan); + et_test_status status = TEST_PASS; + u16 command = 0x9000; + + if ((status = test_exi_readwrite(test_plan, &command, sizeof(command), TEST_EPRIV_BASE)) != TEST_PASS) { + cur->status = status; + return test_next(test_plan, next_state); + } + + if (command != 0x0470) { + test_plan_entry_set_error(cur, "bad identifier from gecko: expected 0x0470, got 0x%04" PRIx16, command); + status = TEST_FAIL; + } + + cur->status = status; + + if (status != TEST_PASS || ++priv->iteration >= TEST_ITERATIONS) { + return test_next(test_plan, next_state); + } + + return 0; +} + +TEST_DEF(check_id, "Identify Gecko (0x09)", TEST_PAUSE_ON_ERROR, init_test_with_iterations, check_id_tick, NULL, struct test_with_iterations); + +static int bad_commands_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { + struct test_with_iterations *priv = data; + struct et_test_plan *test_plan = priv->base.plan; + struct et_test_plan_entry *cur = test_current(test_plan); + et_test_status status = TEST_PASS; + + static const u16 bad_commands[] = { 0x0000, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0xe000, 0xf000 }; + + for (int i = 0; i < ARRLEN(bad_commands); ++i) { + u16 orig_cmd = bad_commands[i]; + u16 cmd = orig_cmd; + if ((status = test_exi_readwrite(test_plan, &cmd, sizeof(cmd), TEST_EPRIV_BASE)) != TEST_PASS) { + goto test_fail; + } + + if (cmd != 0x0000) { + test_plan_entry_set_error(cur, "got bogus response from invalid command 0x%04" PRIx16 ": 0x%04" PRIx16, orig_cmd, cmd); + status = TEST_FAIL; + break; + } + } + +test_fail: + cur->status = status; + + if (status != TEST_PASS || ++priv->iteration >= TEST_ITERATIONS) { + return test_next(test_plan, next_state); + } + + return 0; +} + +TEST_DEF(bad_commands, "Bad commands", 0, init_test_with_iterations, bad_commands_tick, NULL, struct test_with_iterations); diff --git a/ppc/source/tests/led.c b/ppc/source/tests/led.c new file mode 100644 index 0000000..82913ae --- /dev/null +++ b/ppc/source/tests/led.c @@ -0,0 +1,38 @@ +#define ET_TEST_IMPL +#include "../test.h" +#include + +static int flash_led_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { + struct et_test_state_private_base *priv = data; + struct et_test_plan *plan = priv->plan; + struct et_test_plan_entry *cur = test_current(plan); + et_test_status status = TEST_PASS; + + for (int i = 0; i < 1024; ++i) { + u16 cmd_orig, cmd; + + if (i & 1) { + /* LED off */ + cmd_orig = 0x7000; + } else { + /* LED on */ + cmd_orig = 0x8000; + } + + cmd = cmd_orig; + if ((status = test_exi_readwrite(plan, &cmd, sizeof(cmd), TEST_EPRIV_BASE)) != TEST_PASS) { + break; + } + + if (cmd != 0x0000) { + status = TEST_FAIL; + test_plan_entry_set_error(cur, "spurious response to 0x%04" PRIx16 ": 0x%04" PRIx16 " (should be 0x0000)", cmd_orig, cmd); + break; + } + } + + cur->status = status; + return test_next(plan, next_state); +} + +TEST_DEF(flash_led, "Flash LED", 0, NULL, flash_led_tick, NULL, struct et_test_state_private_base); diff --git a/source/macros.h b/source/macros.h deleted file mode 100644 index 9a9db83..0000000 --- a/source/macros.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef EXITEST_MACROS_H_INCLUDED -#define EXITEST_MACROS_H_INCLUDED - -#define STR(_s) STR2(_s) -#define STR2(_s) #_s - -#define PASTE(_x, _y) PASTE2(_x, _y) -#define PASTE2(_x, _y) _x ## _y - -#define COMMA , - -#define ARRLEN(_a) sizeof(_a) / sizeof(*(_a)) - -#endif /* include guard */ diff --git a/source/state.h b/source/state.h deleted file mode 100644 index 2f70527..0000000 --- a/source/state.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef EXITEST_STATE_H_INCLUDED -#define EXITEST_STATE_H_INCLUDED - -#include -#include - -struct et_state; - -typedef union { - uintptr_t i; - void *p; -} et_state_init_data; - -extern const et_state_init_data et_init_data_null; - -struct et_next_state { - const struct et_state *state; - et_state_init_data init_data; -}; - -/* Sets up the state. The second argument (void *) should point to at least state->private_size bytes - * of data. The state assumes responsibility for clearing/initializing this buffer before use. - * - * Returns < 0 for error, >= 0 for success. - * This function conceptually takes ownership of init_data. The implications of this depend - * on what init_data actually contains -- sometimes it's globally shared data. - * - * State authors: Free any data filled into init_data regardless of error states. - * The caller will not touch init_data again. */ -typedef int (et_state_init_f)(const struct et_state *, void *, et_state_init_data); - -/* Ticks the state. - * - * Returns < 0 for error (still call cleanup or exit(...)), 0 for "continue", >0 for "advance state". - * If a state requests to advance to the next state, the info will be in *next_state (which should be non-NULL). - * Note that next_state->init_data may have malloc()'d data which must be handed off by passing to the init function - * of the appropriate state. */ -typedef int (et_state_tick_f)(const struct et_state *, void *, struct et_next_state *); -typedef void (et_state_cleanup_f)(const struct et_state *, void *); - -struct et_state { - et_state_init_f *init_f; - et_state_tick_f *tick_f; - et_state_cleanup_f *cleanup_f; - size_t private_size; -}; - -/* convenience macros because these existed as functions at one point - * (note: state expression is evaluated twice. make sure it doesn't have side effects) */ - -#define et_state_init(_st, _data, _init_data) ((_st)->init_f(_st, _data, _init_data)) -#define et_state_tick(_st, _data, _next) ((_st)->tick_f(_st, _data, _next)) -#define et_state_cleanup(_st, _data) ((_st)->cleanup_f(_st, _data)) - -extern const struct et_state *et_state_setup; - -#endif /* include guard */ diff --git a/source/states/menu.c b/source/states/menu.c deleted file mode 100644 index ea5134b..0000000 --- a/source/states/menu.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include -#include - -#define EXITEST_STATE_INTERNAL -#include "../state.h" -#include "../term.h" -#include "../test.h" - -static void print_setup_menu(int selected_slot) { - fputs(TERM_CUR_UP(2), stdout); - printf(TERM_CLEARLINE "- [DL/DR] EXI Slot %c\n", selected_slot == 0 ? 'A' : 'B'); - printf(TERM_CLEARLINE "Press A to begin testing.\n"); -} - -static int st_setup_init(const struct et_state *state, void *state_data, et_state_init_data init_data) { - u32 *setup_data = state_data; - - *setup_data = 0; - - printf("\nTest setup menu:\n\n\n\n"); - print_setup_menu(*setup_data); - - return 0; -} - -static int begin_testing(u32 menu_state, struct et_next_state *next_state) { - struct et_test_plan *plan = calloc(1, sizeof(struct et_test_plan)); - if (!plan) { - puts("Failed to allocate new test plan!"); - return -1; - } - - size_t num_tests; - const struct et_test *const *all_tests = et_get_tests(&num_tests); - - struct et_test_plan_entry *plan_entries = calloc(num_tests, sizeof(struct et_test_plan_entry)); - if (!plan_entries) { - puts("Failed to allocate new test plan entries!"); - free(plan); - return -1; - } - - for (size_t i = 0; i < num_tests; ++i) { - plan_entries[i].test = all_tests[i]; - plan_entries[i].status = TEST_PRIMORDIAL; - plan_entries[i].ext_status = NULL; - plan_entries[i].ext_status_free = NULL; - } - - plan->exi_channel = (int)menu_state; - plan->next_state = et_state_summarize_test_plan; - plan->next_state_init.p = plan; - plan->num_tests = num_tests; - plan->current_test = 0; - plan->tests = plan_entries; - - next_state->state = et_state_begin_testing; - next_state->init_data.p = plan; - - putchar('\n'); - - return 1; -} - -static int st_setup_tick(const struct et_state *state, void *state_data, struct et_next_state *next_state) { - u32 *setup_data = state_data; - u32 wpad_pressed = WPAD_ButtonsDown(0); - u32 pad_pressed = PAD_ButtonsDown(0); - - if (wpad_pressed & WPAD_BUTTON_LEFT || wpad_pressed & WPAD_BUTTON_RIGHT || pad_pressed & PAD_BUTTON_LEFT || pad_pressed & PAD_BUTTON_RIGHT) { - *setup_data = !*setup_data; - print_setup_menu(*setup_data); - } - - if (wpad_pressed & WPAD_BUTTON_A || pad_pressed & PAD_BUTTON_A) { - /* set up test plan */ - return begin_testing(*setup_data, next_state); - } - - return 0; -} - -static void st_setup_cleanup(const struct et_state *state, void *state_data) { - /* do nothing */ -} - -const et_state_init_data et_init_data_null = { .p = NULL }; - -static struct et_state et__state_setup = { - .init_f = &st_setup_init, - .tick_f = &st_setup_tick, - .cleanup_f = &st_setup_cleanup, - .private_size = sizeof(u32) -}; - -const struct et_state *et_state_setup = &et__state_setup; diff --git a/source/template.c b/source/template.c deleted file mode 100644 index 8665774..0000000 --- a/source/template.c +++ /dev/null @@ -1,303 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "term.h" -#include "state.h" - -static void *xfb = NULL; -static GXRModeObj *rmode = NULL; - -#define TARGET_IOS 58 - -static void initialize(int again) { - VIDEO_Init(); - - rmode = VIDEO_GetPreferredMode(NULL); - xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); - - console_init(xfb, 20, 20, rmode->fbWidth - 20, rmode->xfbHeight - 20, rmode->fbWidth * VI_DISPLAY_PIX_SZ); - - VIDEO_Configure(rmode); - VIDEO_SetNextFramebuffer(xfb); - VIDEO_ClearFrameBuffer(rmode, xfb, COLOR_BLACK); - VIDEO_SetBlack(FALSE); - VIDEO_Flush(); - VIDEO_WaitVSync(); - if (rmode->viTVMode & VI_NON_INTERLACE) { - VIDEO_WaitVSync(); - } - - fputs(TERM_CLEAR TERM_CUR_POS(2, 5), stdout); - printf("USB Gecko (newnewexi) test tool by figboot\n\n"); - - s32 iosver = IOS_GetVersion(); - s32 iosrev = IOS_GetRevision(); - printf("You're running IOS%" PRId32 " rev %" PRId32 ".\n", iosver, iosrev); - - if (iosver != TARGET_IOS) { - if (again) { - printf(TERM_CSI("93m") "Failed to set up IOS" STR(TARGET_IOS) "! Bailing out.\n"); - exit(1); - } - - printf("Trying to load IOS" STR(TARGET_IOS) "...\n"); - - s32 res; - if ((res = IOS_ReloadIOS(TARGET_IOS)) < 0) { - printf(TERM_CSI("93m") "Failed to set up IOS" STR(TARGET_IOS) "! IOS_ReloadIOS returned %" PRId32 "\n", res); - exit(1); - } - - free(MEM_K1_TO_K0(xfb)); - initialize(1); - } -} - -static void __attribute__((noreturn)) fatal_error(void) { - printf("The program will now abort due to the fatal error.\n"); - exit(1); -} - -int main(int argc, char **argv) -{ - void *state_data = NULL; - size_t state_data_size = 0; - struct et_next_state next; - const struct et_state *cur_state = NULL; - int stateres = 0; - - u32 wpad_pressed; - u32 pad_pressed; - - initialize(0); - - WPAD_Init(); - PAD_Init(); - - printf("Done initializing.\n"); - - /* Set up initial state */ - cur_state = et_state_setup; - - if (cur_state->private_size > 0) { - state_data = malloc(cur_state->private_size); - state_data_size = cur_state->private_size; - if (!state_data) { - printf("Failed to allocate private space for setup state %p!", cur_state); - fatal_error(); - } - } - - if ((stateres = et_state_init(cur_state, state_data, et_init_data_null)) < 0) { - printf("Failed to init setup state %p: %d\n", cur_state, stateres); - fatal_error(); - } - - while (SYS_MainLoop()) { - WPAD_ScanPads(); - PAD_ScanPads(); - - wpad_pressed = WPAD_ButtonsDown(0); - pad_pressed = PAD_ButtonsDown(0); - - if (wpad_pressed & WPAD_BUTTON_HOME || pad_pressed & PAD_BUTTON_START) { - printf("Exit button pressed. Goodbye!\n"); - exit(0); - } - - stateres = et_state_tick(cur_state, state_data, &next); - if (stateres < 0) { - printf("Failed to tick state %p: %d\n", cur_state, stateres); - fatal_error(); - } - - if (stateres > 0) { - /* tear down the old state first... */ - et_state_cleanup(cur_state, state_data); - - cur_state = next.state; - - if (state_data_size < cur_state->private_size) { - void *new_data = realloc(state_data, cur_state->private_size); - if (!new_data) { - printf("Failed to allocate %zu bytes for new state %p\n", cur_state->private_size, cur_state); - fatal_error(); - } - - state_data = new_data; - state_data_size = cur_state->private_size; - } - - if ((stateres = et_state_init(cur_state, state_data, next.init_data)) < 0) { - printf("Failed to init new state %p: %d\n", cur_state, stateres); - fatal_error(); - } - } - - VIDEO_WaitVSync(); - } - - return 0; -} - -#if 0 - -//--------------------------------------------------------------------------------- -int main(int argc, char **argv) { -//--------------------------------------------------------------------------------- - - // Initialise the video system - VIDEO_Init(); - - // This function initialises the attached controllers - WPAD_Init(); - - // Obtain the preferred video mode from the system - // This will correspond to the settings in the Wii menu - rmode = VIDEO_GetPreferredMode(NULL); - - // Allocate memory for the display in the uncached region - xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); - - // Initialise the console, required for printf - console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ); - - // Set up the video registers with the chosen mode - VIDEO_Configure(rmode); - - // Tell the video hardware where our display memory is - VIDEO_SetNextFramebuffer(xfb); - - // Make the display visible - VIDEO_SetBlack(false); - - // Flush the video register changes to the hardware - VIDEO_Flush(); - - // Wait for Video setup to complete - VIDEO_WaitVSync(); - if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync(); - - - // The console understands VT terminal escape codes - // This positions the cursor on row 2, column 0 - // we can use variables for this with format codes too - // e.g. printf ("\x1b[%d;%dH", row, column ); - printf("\x1b[2;0H"); - - - printf("Hello World!\n"); - -#define UGCHAN EXI_CHANNEL_1 /* slot B */ -#define UGDEV EXI_DEVICE_0 /* memory card */ - - if (EXI_Lock(UGCHAN, UGDEV, NULL) == 1) { - EXI_Select(UGCHAN, UGDEV, EXI_SPEED32MHZ); - uint32_t cmd = 0x90000000; - int res = 0; - if (EXI_Imm(UGCHAN, &cmd, sizeof(cmd), EXI_READWRITE, NULL) == 0) res |= 0x01; - if (EXI_Sync(UGCHAN) == 0) res |= 0x02; - - if (res == 0) { - printf("Got: 0x%08x\n", cmd); - if (cmd != 0x04700000) { - printf("This is probably not a USB gecko...\n"); - } - } else { - printf("EXI read/write error :( %#04x\n", res); - } - - EXI_Deselect(UGCHAN); - EXI_Unlock(UGCHAN); - } else { - printf("Failed to lock EXI channel %d %d\n", UGCHAN, UGDEV); - goto waitexit; - } - - for (int i = 0; i < 10; ++i) { - if (EXI_Lock(UGCHAN, UGDEV, NULL) == 0) { - printf("Failed to lock EXI for LED\n"); - break; - } - - int res = 0; - - if (EXI_Select(UGCHAN, UGDEV, EXI_SPEED32MHZ) == 0) res |= 0x01; - uint32_t cmd = (i & 1) ? 0x80000000 : 0x70000000; - if (EXI_Imm(UGCHAN, &cmd, sizeof(cmd), EXI_WRITE, NULL) == 0) res |= 0x02; - if (EXI_Sync(UGCHAN) == 0) res |= 0x04; - if (EXI_Deselect(UGCHAN) == 0) res |= 0x08; - - if (EXI_Unlock(UGCHAN) == 0) res |= 0x10; - - printf("Error for LED: 0x%02x\n", res); - - for (int j = 0; j < 60; ++j) { - VIDEO_WaitVSync(); - } - } - - - for (int j = 0; j < 10; ++j) { - if (EXI_Lock(UGCHAN, UGDEV, NULL) == 1) { - printf("Sending a zillion bytes to your so-called \"usb gecko\"...\n"); - - for (int i = 0; i < 4096; ++i) { - EXI_Select(UGCHAN, UGDEV, EXI_SPEED32MHZ); - - uint16_t cmd = 0xB000 | (uint16_t)(((i & 63) + 32) << 4); - if (EXI_Imm(UGCHAN, &cmd, sizeof(cmd), EXI_READWRITE, NULL) == 0) { - printf("Failed when writing %d\n", i); - goto error; - } - - if (EXI_Sync(UGCHAN) == 0) { - printf("Failed sync when writing %d\n", i); - goto error; - } - - if ((cmd & 0x0400) == 0) { - printf("Out of FIFO space at %d\n", i); - goto error; - } - - EXI_Deselect(UGCHAN); - continue; -error: - EXI_Deselect(UGCHAN); - break; - } - - EXI_Unlock(UGCHAN); - } else { - printf("Failed to lock EXI channel %d %d\n", UGCHAN, UGDEV); - goto waitexit; - } - } - -waitexit: - printf("That's all! Press HOME to exit.\n"); - - while(SYS_MainLoop()) { - - // Call WPAD_ScanPads each loop, this reads the latest controller states - WPAD_ScanPads(); - - // WPAD_ButtonsDown tells us which buttons were pressed in this loop - // this is a "one shot" state which will not fire again until the button has been released - u32 pressed = WPAD_ButtonsDown(0); - - // We return to the launcher application via exit - if ( pressed & WPAD_BUTTON_HOME ) exit(0); - - // Wait for the next frame - VIDEO_WaitVSync(); - } - - return 0; -} -#endif diff --git a/source/term.h b/source/term.h deleted file mode 100644 index cc65615..0000000 --- a/source/term.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef EXITEST_TERM_H_INCLUDED -#define EXITEST_TERM_H_INCLUDED - -#include "macros.h" - -#define TERM_ESC "\x1b" -#define TERM_CSI(_s) TERM_ESC "[" _s -#define TERM_CLEAR TERM_CSI("2J") -#define TERM_CLEARLINE TERM_CSI("2K") -#define TERM_CUR_POS(_row, _col) TERM_CSI(STR(_row) ";" STR(_col) "f") -#define TERM_CUR_UP(_rows) TERM_CSI(STR(_rows) "A") - -#endif /* include guard */ diff --git a/source/test-impl.inc.h b/source/test-impl.inc.h deleted file mode 100644 index 8e1559f..0000000 --- a/source/test-impl.inc.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef ET__ALLOW_TEST_IMPL_INC -#error "Do not include this file directly! Include test.h instead." -#endif - -#undef ET__ALLOW_TEST_IMPL_INC - -/* This file will be included in .c files which implement tests. */ -#ifdef _CLANGD -#include -#include "test.h" -/* include stuff this file uses here to make clangd happy (will not be visible to test.h) */ -#endif - -struct et_test_state_private_base { - struct et_test_plan *plan; -}; - -/* function prototypes for test-internal stuff */ -int test_next(struct et_test_plan *plan, struct et_next_state *next_state); - -int test_state_init(const struct et_state *state, void *data, et_state_init_data init_data); -void test_state_cleanup(const struct et_state *state, void *data); - -void test_plan_entry_set_error(struct et_test_plan_entry *entry, const char *fmt, ...) __attribute__((format(printf, 2, 3))); -void test_plan_entry_set_error_static(struct et_test_plan_entry *entry, const char *str); - -et_test_status test_exi_readwrite(struct et_test_plan *plan, void *data, size_t data_sz, et_test_error base); - -/* Expands to a struct initializer for a struct et_test */ -#define TEST_STRUCT_INIT_FULL(_namestr, _flags, _init_f, _tick_f, _cleanup_f, _private_type) \ -{ \ - .parent = { \ - .init_f = test_state_init, \ - .tick_f = _tick_f, \ - .cleanup_f = test_state_cleanup, \ - .private_size = sizeof(_private_type), \ - }, \ - .test_init_f = _init_f, \ - .test_cleanup_f = _cleanup_f, \ - .name = _namestr, \ - .flags = _flags \ -} - -#define TEST__STRUCT_NAME(_name) PASTE(TEST_SYM(_name), __struct) -#define TEST__STRUCT_DEF(_name, _ini) static const struct et_test TEST__STRUCT_NAME(_name) = _ini -#define TEST__STRUCT_SANITY_CHECK(_ty) static_assert(sizeof(_ty) >= sizeof(struct et_test_state_private_base), "private data type too small (does it include base?)") -#define TEST__SYM_DEF(_name) const struct et_test *const TEST_SYM(_name) = &TEST__STRUCT_NAME(_name) -#define TEST_DEF(_name, _namestr, _flags, _init_f, _tick_f, _cleanup_f, _private_type) \ - TEST__STRUCT_SANITY_CHECK(_private_type); \ - TEST__STRUCT_DEF(_name, TEST_STRUCT_INIT_FULL(_namestr, _flags, _init_f, _tick_f, _cleanup_f, _private_type)); \ - TEST__SYM_DEF(_name) - -#define test_current(_plan) ((_plan)->tests + (_plan)->current_test) diff --git a/source/test.c b/source/test.c deleted file mode 100644 index c59deaf..0000000 --- a/source/test.c +++ /dev/null @@ -1,453 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#define TEST_INTERNAL -#include "test.h" -#include "term.h" - -typedef u32 et_test_status; - -#define test_current(_plan) ((_plan)->tests + (_plan)->current_test) - -static int crit_test_fail_init(const struct et_state *state, void *data, et_state_init_data init_data) { - struct et_test_plan *plan = init_data.p; - struct et_test_plan_entry *last_ent; - - *(void **)data = plan; - - assert(plan->current_test > 0); /* NOTE: current_test has been incremented by this point */ - - last_ent = plan->tests + (plan->current_test - 1); - - printf("\nCritical test failed: [%4s] %s\n", et_test_get_status_mnemonic(last_ent->status), last_ent->test->name); - if (last_ent->ext_status) { - printf("Test says:\n%s\n", last_ent->ext_status); - } else { - printf("No extended status.\n"); - } - - putchar('\n'); - - printf("Press A to continue.\n"); - printf("Press B to finish testing.\n"); - printf("Press HOME/START to exit.\n"); - - return 0; -} - -static int crit_test_fail_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { - struct et_test_plan *plan = *(void **)data; - u32 wpad_pressed = WPAD_ButtonsDown(0); - u32 pad_pressed = PAD_ButtonsDown(0); - - if (wpad_pressed & WPAD_BUTTON_A || pad_pressed & PAD_BUTTON_A) { - next_state->state = et_state_begin_testing; /* also works to resume testing */ - next_state->init_data.p = plan; /* test plan */ - return 1; - } - - if (wpad_pressed & WPAD_BUTTON_B || pad_pressed & PAD_BUTTON_B) { - next_state->state = plan->next_state; - next_state->init_data = plan->next_state_init; - return 1; - } - - if (wpad_pressed & WPAD_BUTTON_HOME || pad_pressed & PAD_BUTTON_START) { - printf("Goodbye!\n"); - exit(0); - } - - return 0; -} - -static void crit_test_fail_cleanup(const struct et_state *state, void *data) { - /* do nothing */ -} - -static const struct et_state state_crit_test_fail = { - .private_size = sizeof(struct et_test_plan *), - .init_f = crit_test_fail_init, - .tick_f = crit_test_fail_tick, - .cleanup_f = crit_test_fail_cleanup -}; - -int test_next(struct et_test_plan *plan, struct et_next_state *next_state) { - /* NOTE: this function doesn't increment current_test because the incremented value - * would be visible in the cleanup function of the test state. */ - - /* NOTE: this condition will not trigger for states that leak their TEST_PRIMORDIAL status. - * I don't care about this situation so much, since that should only happen for bugged tests. */ - const struct et_test_plan_entry *ent = test_current(plan); - switch (TEST_STATUS_GET_TYPE(ent->status)) { - case TEST_SERROR: - case TEST_SFAIL: - if (ent->test->flags & TEST_PAUSE_ON_ERROR) { - next_state->state = &state_crit_test_fail; - next_state->init_data.p = plan; - return 1; - } - break; - default: - /* do nothing */ - (void)0; - } - - if (plan->current_test == plan->num_tests - 1) { - /* this is the last state... we're done! */ - next_state->state = plan->next_state; - next_state->init_data = plan->next_state_init; - } else { - next_state->state = &plan->tests[plan->current_test + 1].test->parent; - next_state->init_data.p = plan; - } - - return 1; /* always return 1: advance to next state */ -} - -int test_state_init(const struct et_state *state, void *data, et_state_init_data init_data) { - struct et_test_plan *plan = init_data.p; - struct et_test_state_private_base *testdata = data; - struct et_test_plan_entry *cur = test_current(plan); - const struct et_test *test = (const struct et_test *)state; - - testdata->plan = plan; - - assert(cur->test == test); /* ensure correct test was loaded */ - assert(TEST_STATUS_IS_CLEAN(cur->status)); - - if (test->test_init_f) { - return test->test_init_f(state, data, init_data); - } - - return 0; -} - -void test_state_cleanup(const struct et_state *state, void *data) { - struct et_test_state_private_base *testdata = data; - struct et_test_plan_entry *cur = test_current(testdata->plan); - const struct et_test *test = (const struct et_test *)state; - - if (test->test_cleanup_f) { - test->test_cleanup_f(state, data); - } - - if (TEST_STATUS_IS_CLEAN(cur->status)) { - /* test didn't set status :( */ - test_plan_entry_set_error_static(cur, "test aborted"); - cur->status = TEST_ERROR(TEST_EABORT); - } - - printf("[%4s] %s\n", et_test_get_status_mnemonic(cur->status), test->name); - - ++testdata->plan->current_test; -} - -static int begin_testing_init(const struct et_state *state, void *state_data, et_state_init_data init_data) { - struct et_test_plan *test_plan = init_data.p; - struct et_test_plan **pst_test_plan = state_data; - *pst_test_plan = test_plan; - return 0; -} - -static int begin_testing_tick(const struct et_state *state, void *state_data, struct et_next_state *next_state) { - struct et_test_plan **ptest_plan = state_data; - struct et_test_plan *test_plan = *ptest_plan; - - assert(test_plan->current_test <= test_plan->num_tests); - - if (test_plan->current_test < test_plan->num_tests) { - const struct et_test_plan_entry *next_test = test_plan->tests + test_plan->current_test; - assert(test_plan->tests); - assert(next_test->status == TEST_PRIMORDIAL); - - next_state->state = &next_test->test->parent; - next_state->init_data.p = test_plan; - } else { - /* no tests to run in this plan */ - next_state->state = test_plan->next_state; - next_state->init_data = test_plan->next_state_init; - } - - return 1; - -} - -static void begin_testing_cleanup(const struct et_state *state, void *state_data) { - /* do nothing */ -} - -static const struct et_state state_begin_testing_struct = { - .private_size = sizeof(const struct et_test_plan *), - .init_f = begin_testing_init, - .tick_f = begin_testing_tick, - .cleanup_f = begin_testing_cleanup -}; - -const struct et_state *et_state_begin_testing = &state_begin_testing_struct; - -struct summ_test_plan_private { - struct et_test_plan *plan; - size_t selected_test; -}; - -#define SUMM_MENU_ROWS 3 - -static void summ_test_print_menu(struct summ_test_plan_private *priv) { - const struct et_test *test = priv->plan->tests[priv->selected_test].test; - - fputs(TERM_CUR_UP(SUMM_MENU_ROWS), stdout); - puts(TERM_CLEARLINE "Press A to test again. Press HOME/START to exit."); - puts(TERM_CLEARLINE "Press +/X to show output for [DL/DR]:"); - printf(TERM_CLEARLINE "%s\n", test->name); -} - -static int summ_test_plan_init(const struct et_state *state, void *state_data, et_state_init_data init_data) { - struct summ_test_plan_private *priv = state_data; - struct et_test_plan *ptest_plan = init_data.p; - size_t passed = 0; - size_t failed = 0; - size_t errored = 0; - size_t skipped = 0; - size_t unknown = 0; - - priv->plan = ptest_plan; - priv->selected_test = 0; - - for (size_t i = 0; i < ptest_plan->num_tests; ++i) { - switch (TEST_STATUS_GET_TYPE(ptest_plan->tests[i].status)) { - case TEST_SPASS: ++passed; break; - case TEST_SFAIL: ++failed; break; - case TEST_SERROR: ++errored; break; - case TEST_SSKIPPED: ++skipped; break; - default: ++unknown; break; - } - } - - printf("\nTesting complete.\nPASS: %zu, FAIL: %zu, ERROR: %zu, SKIP: %zu, UNK: %zu\n\n", passed, failed, errored, skipped, unknown); - - for (int i = 0; i < SUMM_MENU_ROWS; ++i) putchar('\n'); - summ_test_print_menu(priv); - - return 0; -} - -static int summ_test_plan_tick(const struct et_state *state, void *state_data, struct et_next_state *next_state) { - struct summ_test_plan_private *priv = state_data; - - u32 wpad_buttons = WPAD_ButtonsDown(0); - u32 pad_buttons = PAD_ButtonsDown(0); - - if (wpad_buttons & WPAD_BUTTON_A || pad_buttons & PAD_BUTTON_A) { - next_state->state = et_state_setup; - next_state->init_data = et_init_data_null; - return 1; - } - - if (wpad_buttons & WPAD_BUTTON_HOME || pad_buttons & PAD_BUTTON_START) { - puts("Goodbye!"); - exit(0); - } - - if (wpad_buttons & WPAD_BUTTON_LEFT || pad_buttons & PAD_BUTTON_LEFT) { - if (priv->selected_test > 0) { - --priv->selected_test; - } else { - priv->selected_test = priv->plan->num_tests - 1; - } - - summ_test_print_menu(priv); - } - - if (wpad_buttons & WPAD_BUTTON_RIGHT || pad_buttons & PAD_BUTTON_RIGHT) { - if (priv->selected_test < priv->plan->num_tests - 1) { - ++priv->selected_test; - } else { - priv->selected_test = 0; - } - - summ_test_print_menu(priv); - } - - if (wpad_buttons & WPAD_BUTTON_1 || pad_buttons & PAD_BUTTON_X) { - struct et_test_plan_entry *entry = priv->plan->tests + priv->selected_test; - printf("\nExtended status for %s:\n", entry->test->name); - if (entry->ext_status) { - puts(entry->ext_status); - } else { - puts("(no status)"); - } - - for (int i = 0; i < SUMM_MENU_ROWS + 1; ++i) putchar('\n'); - summ_test_print_menu(priv); - } - - return 0; -} - -static void summ_test_plan_cleanup(const struct et_state *state, void *state_data) { - struct summ_test_plan_private *priv = state_data; - struct et_test_plan *ptest_plan = priv->plan; - - for (size_t i = 0; i < ptest_plan->num_tests; ++i) { - et_test_plan_entry_cleanup(ptest_plan->tests + i); - } - - free(ptest_plan->tests); - free(ptest_plan); -} - -static const struct et_state summ_test_plan_struct = { - .private_size = sizeof(struct summ_test_plan_private), - .init_f = summ_test_plan_init, - .tick_f = summ_test_plan_tick, - .cleanup_f = summ_test_plan_cleanup -}; - -const struct et_state *et_state_summarize_test_plan = &summ_test_plan_struct; - -#define COUNT_TESTS(_x) 1 -#define NUM_TESTS FOREACH_TEST(COUNT_TESTS, +) -static const struct et_test *all_tests[NUM_TESTS + 1]; -static bool tests_array_initialized = false; - -const struct et_test *const *et_get_tests(size_t *ntests) { - size_t idx = 0; - - if (ntests) *ntests = NUM_TESTS; - if (tests_array_initialized) return all_tests; - -#define A(_name) all_tests[idx++] = TEST_SYM(_name) - FOREACH_TEST(A, ;); -#undef A - - all_tests[idx] = NULL; - - tests_array_initialized = true; - return all_tests; -} - -static const char *error_oom = "(failed to allocate error message)"; -static const char *error_bad_fmt = "(bad extended error format)"; - -void et_test_plan_entry_cleanup(struct et_test_plan_entry *entry) { - if (entry->ext_status_free) { - /* okay to cast away const here. we're careful about making sure this pointer is non-NULL iff this pointer - * was malloc'd */ - entry->ext_status_free((void *)entry->ext_status); - } - - entry->ext_status = NULL; - entry->ext_status_free = NULL; -} - -void test_plan_entry_set_error(struct et_test_plan_entry *entry, const char *fmt, ...) { - int res; - va_list vp; - char *dest; - size_t dest_sz; - - et_test_plan_entry_cleanup(entry); - - va_start(vp, fmt); - res = vsnprintf(NULL, 0, fmt, vp); - va_end(vp); - - if (res < 0) { - entry->ext_status = error_bad_fmt; - entry->ext_status_free = NULL; - return; - } - - dest_sz = MIN((size_t)res + 1, (size_t)1024); - dest = malloc(dest_sz); - dest[0] = '\0'; - - if (!dest) { - entry->ext_status = error_oom; - entry->ext_status_free = NULL; - return; - } - - va_start(vp, fmt); - res = vsnprintf(dest, dest_sz, fmt, vp); - va_end(vp); - - dest[dest_sz - 1] = '\0'; - - assert(res >= 0); - - entry->ext_status = dest; - entry->ext_status_free = free; -} - -void test_plan_entry_set_error_static(struct et_test_plan_entry *entry, const char *str) { - et_test_plan_entry_cleanup(entry); - entry->ext_status = str; - entry->ext_status_free = NULL; -} - -const char *et_test_get_status_mnemonic(et_test_status status) { - switch (TEST_STATUS_GET_TYPE(status)) { - case TEST_SPASS: return "PASS"; - case TEST_SFAIL: return "FAIL"; - case TEST_SSKIPPED: - if (TEST_STATUS_IS_CLEAN(status)) { - return "SKI!"; - } else { - return "SKIP"; - } - case TEST_SERROR: - switch (TEST_STATUS_GET_ERROR(status)) { - case TEST_ENONE: return "ERR0"; - case TEST_EABORT: return "ERRA"; - default: return "ERRP"; - } - default: - return "UNKS"; - } -} - -et_test_status test_exi_readwrite(struct et_test_plan *plan, void *data, size_t data_sz, et_test_error base) { - struct et_test_plan_entry *cur = test_current(plan); - int chan = plan->exi_channel; - et_test_status status = TEST_PASS; - s32 exi_res; - - if ((exi_res = EXI_Lock(chan, EXI_DEVICE_0, NULL)) <= 0) { - status = TEST_ERROR(base); - test_plan_entry_set_error(cur, "failed to lock EXI channel: %" PRId32, exi_res); - goto fail_lock; - } - - if ((exi_res = EXI_Select(chan, EXI_DEVICE_0, EXI_SPEED32MHZ)) <= 0) { - status = TEST_ERROR(base + 1); - test_plan_entry_set_error(cur, "failed to select EXI channel: %" PRId32, exi_res); - goto fail_select; - } - - if ((exi_res = EXI_Imm(chan, data, data_sz, EXI_READWRITE, NULL)) <= 0) { - status = TEST_ERROR(base + 2); - test_plan_entry_set_error(cur, "failed to send EXI data: %" PRId32, exi_res); - goto fail_data; - } - - if ((exi_res = EXI_Sync(chan)) <= 0) { - status = TEST_ERROR(base + 3); - test_plan_entry_set_error(cur, "failed to complete transfer: %" PRId32, exi_res); - goto fail_data; - } - -fail_data: - EXI_Deselect(chan); - -fail_select: - EXI_Unlock(chan); - -fail_lock: - return status; -} diff --git a/source/test.h b/source/test.h deleted file mode 100644 index 81a107a..0000000 --- a/source/test.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef EXITEST_TEST_H_INCLUDED -#define EXITEST_TEST_H_INCLUDED - -#include -#include -#include "state.h" -#include "macros.h" - -typedef enum { - TEST_INTERACTIVE = 0x01u, - TEST_PAUSE_ON_ERROR = 0x02u /* for "essential" tests that should pass */ -} et_test_flags; - -struct et_test; -struct et_test_plan; - -struct et_test { - struct et_state parent; - const char *name; - et_test_flags flags; - - et_state_init_f *test_init_f; - et_state_cleanup_f *test_cleanup_f; -}; - -/* 0000eeee eeeeeeee eeeeeeee x00000ss */ -/* e = error code bits, - * x = 1 if this test is still in the "clean" (not executed) state - * ss = status type (pass/fail/etc.) */ -typedef u32 et_test_status; - -typedef enum { - TEST_ENONE = (u32)0, - - /* test was aborted (i.e., requested to go to next test without setting a status code) */ - TEST_EABORT, - - /* minimum private test error (define test-private error codes as TEST_EPRIV_BASE+n) */ - TEST_EPRIV_BASE = 32, - - /* largest error code allowed */ - TEST_EMAX = ((u32)1 << 20) - 1 -} et_test_error; - -typedef enum { - TEST_SSKIPPED = 0u, - TEST_SERROR, - TEST_SFAIL, - TEST_SPASS, - - TEST_SMAX = ((u32)1 << 2) - 1 -} et_test_status_type; - -#define TEST_STATUS_GET_ERROR(_status) (et_test_error)(((u32)(_status) >> 8) & TEST_EMAX) -#define TEST_STATUS_GET_TYPE(_status) (et_test_status_type)((u32)(_status) & TEST_SMAX) -#define TEST_STATUS_IS_CLEAN(_status) (!!((u32)(_status) & 0x80u)) - -#define TEST_MAKE_STATUS(_type, _error, _clean) (et_test_status) \ - (((u32)(_type) & TEST_SMAX) | \ - (((u32)(_clean) & 1) << 7) | \ - (((u32)(_error) & TEST_EMAX) << 8)) - -#define TEST_SKIPPED TEST_MAKE_STATUS(TEST_SSKIPPED, TEST_ENONE, 0) -#define TEST_FAIL TEST_MAKE_STATUS(TEST_SFAIL, TEST_ENONE, 0) -#define TEST_PASS TEST_MAKE_STATUS(TEST_SPASS, TEST_ENONE, 0) -#define TEST_ERROR(_e) TEST_MAKE_STATUS(TEST_SERROR, _e, 0) - -#define TEST_PRIMORDIAL TEST_MAKE_STATUS(TEST_SSKIPPED, TEST_ENONE, 1) - -/* Expected setup date: pointer to test plan */ -extern const struct et_state *et_state_begin_testing; -extern const struct et_state *et_state_summarize_test_plan; - -const char *et_test_get_status_mnemonic(et_test_status status); - -struct et_test_plan_entry { - const struct et_test *test; - et_test_status status; - const char *ext_status; - void (*ext_status_free)(void *); -}; - -/* a properly initialized test plan will have all the tests' statuses set to TEST_PRIMORDIAL */ -struct et_test_plan { - int exi_channel; - - /* this state will be set after testing is complete. - * NOTE: next_state_init should somehow point to this test plan, - * since it's probably dynamically allocated and will be leaked if not - * freed. */ - const struct et_state *next_state; - et_state_init_data next_state_init; - - size_t num_tests; - size_t current_test; - struct et_test_plan_entry *tests; -}; - -void et_test_plan_entry_cleanup(struct et_test_plan_entry *entry); - -#define FOREACH_TEST(O, _sep) \ - O(check_id) _sep \ - O(bad_commands) _sep \ - O(flash_led) - -#define TEST_SYM(_name) PASTE(et_test_, _name) -#define DECL_TEST(_name) extern const struct et_test *const TEST_SYM(_name) - -FOREACH_TEST(DECL_TEST, ;); -#undef DECL_TEST - -const struct et_test *const *et_get_tests(size_t *ntests); - -#if defined(ET_TEST_IMPL) || defined(TEST_INTERNAL) -#define ET__ALLOW_TEST_IMPL_INC -#include "test-impl.inc.h" -#else -#undef FOREACH_TEST -#undef TEST_SYM -#endif - -#endif /* include guard */ diff --git a/source/tests/basic.c b/source/tests/basic.c deleted file mode 100644 index 4f34ba4..0000000 --- a/source/tests/basic.c +++ /dev/null @@ -1,79 +0,0 @@ -#define ET_TEST_IMPL -#include "../test.h" -#include -#include - -#define TEST_ITERATIONS 16 - -struct test_with_iterations { - struct et_test_state_private_base base; - int iteration; -}; - -static int init_test_with_iterations(const struct et_state *state, void *data, et_state_init_data init_data) { - struct test_with_iterations *priv = data; - priv->iteration = 0; - return 0; -} - -static int check_id_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { - struct test_with_iterations *priv = data; - struct et_test_plan *test_plan = priv->base.plan; - struct et_test_plan_entry *cur = test_current(test_plan); - et_test_status status = TEST_PASS; - u16 command = 0x9000; - - if ((status = test_exi_readwrite(test_plan, &command, sizeof(command), TEST_EPRIV_BASE)) != TEST_PASS) { - cur->status = status; - return test_next(test_plan, next_state); - } - - if (command != 0x0470) { - test_plan_entry_set_error(cur, "bad identifier from gecko: expected 0x0470, got 0x%04" PRIx16, command); - status = TEST_FAIL; - } - - cur->status = status; - - if (status != TEST_PASS || ++priv->iteration >= TEST_ITERATIONS) { - return test_next(test_plan, next_state); - } - - return 0; -} - -TEST_DEF(check_id, "Identify Gecko (0x09)", TEST_PAUSE_ON_ERROR, init_test_with_iterations, check_id_tick, NULL, struct test_with_iterations); - -static int bad_commands_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { - struct test_with_iterations *priv = data; - struct et_test_plan *test_plan = priv->base.plan; - struct et_test_plan_entry *cur = test_current(test_plan); - et_test_status status = TEST_PASS; - - static const u16 bad_commands[] = { 0x0000, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0xe000, 0xf000 }; - - for (int i = 0; i < ARRLEN(bad_commands); ++i) { - u16 orig_cmd = bad_commands[i]; - u16 cmd = orig_cmd; - if ((status = test_exi_readwrite(test_plan, &cmd, sizeof(cmd), TEST_EPRIV_BASE)) != TEST_PASS) { - goto test_fail; - } - - if (cmd != 0x0000) { - test_plan_entry_set_error(cur, "got bogus response from invalid command 0x%04" PRIx16 ": 0x%04" PRIx16, orig_cmd, cmd); - status = TEST_FAIL; - break; - } - } - -test_fail: - cur->status = status; - - if (status != TEST_PASS || ++priv->iteration >= TEST_ITERATIONS) { - return test_next(test_plan, next_state); - } - - return 0; -} - -TEST_DEF(bad_commands, "Bad commands", 0, init_test_with_iterations, bad_commands_tick, NULL, struct test_with_iterations); diff --git a/source/tests/led.c b/source/tests/led.c deleted file mode 100644 index 82913ae..0000000 --- a/source/tests/led.c +++ /dev/null @@ -1,38 +0,0 @@ -#define ET_TEST_IMPL -#include "../test.h" -#include - -static int flash_led_tick(const struct et_state *state, void *data, struct et_next_state *next_state) { - struct et_test_state_private_base *priv = data; - struct et_test_plan *plan = priv->plan; - struct et_test_plan_entry *cur = test_current(plan); - et_test_status status = TEST_PASS; - - for (int i = 0; i < 1024; ++i) { - u16 cmd_orig, cmd; - - if (i & 1) { - /* LED off */ - cmd_orig = 0x7000; - } else { - /* LED on */ - cmd_orig = 0x8000; - } - - cmd = cmd_orig; - if ((status = test_exi_readwrite(plan, &cmd, sizeof(cmd), TEST_EPRIV_BASE)) != TEST_PASS) { - break; - } - - if (cmd != 0x0000) { - status = TEST_FAIL; - test_plan_entry_set_error(cur, "spurious response to 0x%04" PRIx16 ": 0x%04" PRIx16 " (should be 0x0000)", cmd_orig, cmd); - break; - } - } - - cur->status = status; - return test_next(plan, next_state); -} - -TEST_DEF(flash_led, "Flash LED", 0, NULL, flash_led_tick, NULL, struct et_test_state_private_base); -- cgit v1.2.3-70-g09d2