From ec5f86da86cb60c071c42c3d9b3f4a3488140b7e Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Thu, 31 Aug 2017 21:25:49 +0200 Subject: Initial commit Signed-off-by: Michael Buesch --- CMakeLists.txt | 5 + client.c | 136 +++ cmake.global | 13 + emulator/CMakeLists.txt | 54 + emulator/avr8emu.c | 407 +++++++ emulator/avr8emu.h | 38 + emulator/avr8emu_private.h | 60 + emulator/cpu.c | 2667 ++++++++++++++++++++++++++++++++++++++++++++ emulator/cpu.h | 177 +++ emulator/devices/ports.c | 259 +++++ emulator/devices/ports.h | 7 + emulator/devices/timers.c | 28 + emulator/devices/timers.h | 7 + emulator/devprocess.c | 156 +++ emulator/devprocess.h | 9 + emulator/hardware.c | 270 +++++ emulator/hardware.h | 57 + emulator/install.py | 50 + emulator/ipc.c | 219 ++++ emulator/ipc.h | 62 + emulator/list.h | 296 +++++ emulator/memory.c | 118 ++ emulator/memory.h | 225 ++++ emulator/pinout.c | 129 +++ emulator/pinout.h | 142 +++ emulator/python.c | 206 ++++ emulator/subprocess.c | 167 +++ emulator/subprocess.h | 116 ++ emulator/util.c | 35 + emulator/util.h | 74 ++ gui/avr8emu | 156 +++ 31 files changed, 6345 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 client.c create mode 100644 cmake.global create mode 100644 emulator/CMakeLists.txt create mode 100644 emulator/avr8emu.c create mode 100644 emulator/avr8emu.h create mode 100644 emulator/avr8emu_private.h create mode 100644 emulator/cpu.c create mode 100644 emulator/cpu.h create mode 100644 emulator/devices/ports.c create mode 100644 emulator/devices/ports.h create mode 100644 emulator/devices/timers.c create mode 100644 emulator/devices/timers.h create mode 100644 emulator/devprocess.c create mode 100644 emulator/devprocess.h create mode 100644 emulator/hardware.c create mode 100644 emulator/hardware.h create mode 100755 emulator/install.py create mode 100644 emulator/ipc.c create mode 100644 emulator/ipc.h create mode 100644 emulator/list.h create mode 100644 emulator/memory.c create mode 100644 emulator/memory.h create mode 100644 emulator/pinout.c create mode 100644 emulator/pinout.h create mode 100644 emulator/python.c create mode 100644 emulator/subprocess.c create mode 100644 emulator/subprocess.h create mode 100644 emulator/util.c create mode 100644 emulator/util.h create mode 100755 gui/avr8emu diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9021fa5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +project(avremu C) + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake.global) + +add_subdirectory(emulator) diff --git a/client.c b/client.c new file mode 100644 index 0000000..af9e8ba --- /dev/null +++ b/client.c @@ -0,0 +1,136 @@ +/* + * AVR8 emulator + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "emulator/avr8emu.h" + +#include +#include +#include +#include +#include + + +static struct avr8emu *emulator; + + +static void cb_status_message(struct avr8emu *emu, const char *msg) +{ + printf("MM %s", msg); +} + +static const struct avr8emu_callbacks callbacks = { + .status_message = cb_status_message, +}; + +static void usage(char **argv) +{ + printf("Usage: %s BINARY_AVR_CODE\n", argv[0]); +} + +static void signal_handler(int signr) +{ + int err; + + if (emulator) { + do { + err = avr8emu_poll_status(emulator); + } while (!err); + avr8emu_remove(emulator); + } + exit(1); +} + +int main(int argc, char **argv) +{ + struct sigaction sa; + const char *filename; + struct avr8emu *emu; + FILE *fd; + long int filesize; + char *bin; + int err; + size_t res; + + if (argc != 2) { + usage(argv); + return 1; + } + filename = argv[1]; + + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + fd = fopen(filename, "r"); + if (!fd) { + fprintf(stderr, "Could not open file %s\n", filename); + return 1; + } + err = fseek(fd, 0, SEEK_END); + if (err) { + fprintf(stderr, "Failed to seek in file %s\n", filename); + fclose(fd); + } + filesize = ftell(fd); + err = fseek(fd, 0, SEEK_SET); + if (err) { + fprintf(stderr, "Failed to seek in file %s\n", filename); + fclose(fd); + return 1; + } + bin = malloc(filesize); + if (!bin) { + fprintf(stderr, "Out of memory\n"); + fclose(fd); + return 1; + } + res = fread(bin, filesize, 1, fd); + fclose(fd); + if (res != 1) { + fprintf(stderr, "Could not read file (%u)\n", res); + free(bin); + return 1; + } + + emu = avr8emu_create(AVR_ATMEGA168, &callbacks);//FIXME + if (!emu) { + free(bin); + return 1; + } + err = avr8emu_initialize(emu, bin, filesize); + if (err) { + fprintf(stderr, "emulator init failed\n"); + goto out; + } + err = avr8emu_reset(emu); + if (err) { + fprintf(stderr, "emulator reset failed\n"); + goto out; + } + printf("emulator running...\n"); + emulator = emu; + while (1) { + avr8emu_poll_messages(emu); + while (!avr8emu_poll_status(emu)) + ; + } +out: + avr8emu_remove(emu); + free(bin); +} diff --git a/cmake.global b/cmake.global new file mode 100644 index 0000000..929a1ec --- /dev/null +++ b/cmake.global @@ -0,0 +1,13 @@ +include(TestBigEndian) + +test_big_endian(BIGENDIAN) +if (BIGENDIAN) + add_definitions(-DBIG_ENDIAN_HOST) +else (BIGENDIAN) + add_definitions(-DLITTLE_ENDIAN_HOST) +endif (BIGENDIAN) + +add_definitions("\"-Dinline=inline __attribute__((__always_inline__))\"") +add_definitions("-D_GNU_SOURCE -D_BSD_SOURCE") + +set(AVREMU_CFLAGS "-std=gnu99 -O2 -fomit-frame-pointer -Wall -pipe") diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt new file mode 100644 index 0000000..2f44837 --- /dev/null +++ b/emulator/CMakeLists.txt @@ -0,0 +1,54 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake.global) + +include(FindPythonLibs) +include(FindPythonInterp) + +include_directories(${PYTHON_INCLUDE_PATH}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../librazer) + +macro(ADD_PYTHON_MODULE _NAME _STATIC_SRCS) + if (NOT PYTHONINTERP_FOUND) + message(FATAL_ERROR "Python scripting language not found.") + endif (NOT PYTHONINTERP_FOUND) + + add_library(${_NAME} MODULE ${ARGN}) + + set_target_properties(${_NAME} PROPERTIES + PREFIX "" # Remove the "lib" prefix + ) + + install(CODE "exec_program(${PYTHON_EXECUTABLE} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/install.py ${CMAKE_CURRENT_BINARY_DIR} + RETURN_VALUE err) + if (NOT err EQUAL 0) + message(FATAL_ERROR \"Failed to install python module ${_NAME}\") + endif (NOT err EQUAL 0)") +endmacro (ADD_PYTHON_MODULE) + + +option(RUNTIME_SANITY_CHECKS "Enable runtime sanity checks" ON) +if (RUNTIME_SANITY_CHECKS) + add_definitions(-DRUNTIME_SANITY_CHECKS) +endif (RUNTIME_SANITY_CHECKS) + +# Built-in devices +set(SRC_DEVICES devices/ports.c + devices/timers.c) + +# The emulator core +set(SRC_CORE cpu.c + devprocess.c + hardware.c + ipc.c + memory.c + pinout.c + python.c + subprocess.c + util.c) + +add_python_module(pyavr8emu SHARED ${SRC_CORE} ${SRC_DEVICES}) + +set_target_properties(pyavr8emu PROPERTIES + COMPILE_FLAGS "${AVREMU_CFLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(pyavr8emu pthread rt) + diff --git a/emulator/avr8emu.c b/emulator/avr8emu.c new file mode 100644 index 0000000..cbc808b --- /dev/null +++ b/emulator/avr8emu.c @@ -0,0 +1,407 @@ +/* + * AVR8 emulator + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "avr8emu.h" +#include "avr8emu_private.h" +#include "util.h" +#include "cpu.h" +#include "hardware.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static void __attribute__((noreturn)) worker_shutdown(int error) +{ + cpu_exit(); + avr_do_cleanup(); + exit(error); +} + +static void worker_signal_handler(int signr) +{ + switch (signr) { + case SIGSEGV: + fprintf(stderr, "Segmentation fault\n"); + if (CHECK(1)) + fprintf(stderr, "This is probably a bug in the emulator.\n"); + else + fprintf(stderr, "This is either a bug in the running AVR " + "program or in the emulator software.\n"); + dump_cpu(stderr); + worker_shutdown(1); + break; + case SIGTERM: + case SIGINT: + printf("Emulation worker shutting down...\n"); + trap_cpu(CPU_TRAP_EXIT, 1); + dump_cpu(stdout); + worker_shutdown(0); + break; + default: + fprintf(stderr, "Worker caught unknown signal\n"); + worker_shutdown(1); + } +} + +static void worker_send_result(struct avr8emu_worker *wrk, int result) +{ + ssize_t res; + + res = write(wrk->result_to_host, &result, sizeof(result)); + if (res != sizeof(result)) + fprintf(stderr, "Failed to send result to host.\n"); +} + +static char * receive_payload(struct avr8emu_worker *wrk, uint32_t payload_len) +{ + char *buf; + ssize_t res; + + buf = malloc(payload_len); + if (!buf) { + fprintf(stderr, "Out of memory\n"); + return NULL; + } + res = read(wrk->message_from_host, buf, payload_len); + if (res != payload_len) { + fprintf(stderr, "Payload receive error\n"); + free(buf); + return NULL; + } + + return buf; +} + +static void free_payload(char *buf, uint32_t payload_len) +{ + free(buf); +} + +static void worker_handle_message(struct avr8emu_worker *wrk, + enum avr8emu_worker_message id, + uint32_t payload_len) +{ + char *buf; + int res; + + switch (id) { + case WORKERMSG_INIT: + buf = receive_payload(wrk, payload_len); + if (!buf) + return; + res = cpu_initialize(buf, payload_len); + free_payload(buf, payload_len); + break; + case WORKERMSG_RESET: + res = cpu_reset(); + break; + default: + res = -EINVAL; + fprintf(stderr, "Worker received unknown message.\n"); + } + worker_send_result(wrk, res); +} + +/* This is an emulator worker process. */ +static int worker_process(struct avr8emu_worker *wrk) +{ + struct sigaction sa; + fd_set fdset; + int err; + ssize_t len; + enum avr8emu_worker_message id; + uint32_t payload_len; + + sa.sa_handler = worker_signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + err = avr_do_setup(wrk->type); + if (err) + return err; + + FD_ZERO(&fdset); + FD_SET(wrk->message_from_host, &fdset); + + while (1) { + err = select(FD_SETSIZE, &fdset, NULL, NULL, NULL); + if (err <= 0) { + if (errno == EINTR) + continue; + perror("Worker pipe select"); + return 1; + } + + len = read(wrk->message_from_host, &id, sizeof(id)); + if (len != sizeof(id)) { + fprintf(stderr, "Worker ID receive error\n"); + return 1; + } + len = read(wrk->message_from_host, &payload_len, sizeof(payload_len)); + if (len != sizeof(payload_len)) { + fprintf(stderr, "Worker payload size receive error\n"); + return 1; + } + worker_handle_message(wrk, id, payload_len); + } + + return 1; +} + +void send_host_message(enum avr8emu_host_message msg, + const void *payload, uint32_t payload_len) +{//TODO +} + +static int send_worker_message(struct avr8emu *emu, + enum avr8emu_worker_message msg, + const void *payload, uint32_t payload_len) +{ + ssize_t res; + int result_code; + + res = write(emu->message_to_worker, &msg, sizeof(msg)); + if (res != sizeof(msg)) { + fprintf(stderr, "Failed to send message ID\n"); + return 1; + } + res = write(emu->message_to_worker, &payload_len, sizeof(payload_len)); + if (res != sizeof(payload_len)) { + fprintf(stderr, "Failed to send payload size\n"); + return 1; + } + if (payload_len) { + res = write(emu->message_to_worker, payload, payload_len); + if (res != payload_len) { + fprintf(stderr, "Failed to send payload\n"); + return 1; + } + } + /* Read the result code. */ + res = read(emu->result_from_worker, &result_code, sizeof(result_code)); + if (res != sizeof(result_code)) { + fprintf(stderr, "Failed to read message result code\n"); + return 1; + } + if (result_code) + fprintf(stderr, "Message %d failed with %d\n", msg, result_code); + + return result_code; +} + +static void close_two(int fds[2]) +{ + close(fds[0]); + close(fds[1]); +} + +static int fork_worker(struct avr8emu *emu) +{ + struct avr8emu_worker *wrk; + int err; + pid_t pid; + int host_pipes[2]; + int worker_pipes[2]; + int result_pipes[2]; + int error_pipes[2]; + + err = pipe(host_pipes); + if (err) { + perror("Host communication pipes"); + return err; + } + err = pipe(worker_pipes); + if (err) { + perror("Worker communication pipes"); + close_two(host_pipes); + return err; + } + err = pipe(result_pipes); + if (err) { + perror("Result communication pipes"); + close_two(host_pipes); + close_two(worker_pipes); + return err; + } + err = pipe(error_pipes); + if (err) { + perror("Error communication pipes"); + close_two(host_pipes); + close_two(worker_pipes); + close_two(result_pipes); + } + + pid = fork(); + if (pid == (pid_t)0) { + /* This is the worker process. */ + close(host_pipes[0]); + close(worker_pipes[1]); + close(result_pipes[0]); + close(error_pipes[0]); + wrk = avr_malloc(sizeof(*wrk)); + if (!wrk) + exit(1); + wrk->type = emu->type; + wrk->message_to_host = host_pipes[1]; + wrk->message_from_host = worker_pipes[0]; + wrk->result_to_host = result_pipes[1]; + wrk->error_pipe = error_pipes[1]; + /* Map stderr and stdout to the error_pipe. Enable line-buffering. */ + dup2(wrk->error_pipe, STDERR_FILENO); + dup2(wrk->error_pipe, STDOUT_FILENO); + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + /* Finally run the worker. */ + err = worker_process(wrk); + exit(err); + } else if (pid < (pid_t)0) { + perror("Forking worker process"); + close_two(host_pipes); + close_two(worker_pipes); + close_two(result_pipes); + close_two(error_pipes); + return -ENOMEM; + } else { + /* This is the host process. */ + close(host_pipes[1]); + close(worker_pipes[0]); + close(result_pipes[1]); + close(error_pipes[1]); + emu->message_to_worker = worker_pipes[1]; + emu->message_from_worker = host_pipes[0]; + emu->result_from_worker = result_pipes[0]; + emu->error_pipe = error_pipes[0]; + emu->worker_pid = pid; + } + + return 0; +} + +static void kill_worker(struct avr8emu *emu) +{ + int err; + + err = kill(emu->worker_pid, SIGTERM); + if (err) { + perror("Kill worker child process"); + return; + } + waitpid(emu->worker_pid, NULL, 0); +} + +struct avr8emu * avr8emu_create(enum avr_setup_type type, + const struct avr8emu_callbacks *cb) +{ + struct avr8emu *emu; + int err; + + if (!cb) + return NULL; + emu = avr_malloc(sizeof(*emu)); + if (!emu) + return NULL; + emu->type = type; + emu->callback = cb; + err = fork_worker(emu); + if (err) { + free(emu); + return NULL; + } + + return emu; +} + +void avr8emu_remove(struct avr8emu *emu) +{ + kill_worker(emu); + free(emu); +} + +int avr8emu_initialize(struct avr8emu *emu, const char *code, + size_t code_size) +{ + return send_worker_message(emu, WORKERMSG_INIT, code, code_size); +} + +int avr8emu_reset(struct avr8emu *emu) +{ + return send_worker_message(emu, WORKERMSG_RESET, NULL, 0); +} + +void avr8emu_poll_messages(struct avr8emu *emu) +{ + fd_set fdset; + struct timeval tv; + int err; + + FD_ZERO(&fdset); + FD_SET(emu->message_from_worker, &fdset); + tv.tv_sec = 0; + tv.tv_usec = 5000; + + err = select(FD_SETSIZE, &fdset, NULL, NULL, &tv); + if (err > 0) { + //TODO handle. + } +} + +int avr8emu_poll_status(struct avr8emu *emu) +{ + char *buf = NULL; + size_t bufsize = 0; + ssize_t res; + FILE *file; + fd_set fdset; + int err = -1; + struct timeval tv; + + FD_ZERO(&fdset); + FD_SET(emu->error_pipe, &fdset); + tv.tv_sec = 0; + tv.tv_usec = 5000; + + res = select(FD_SETSIZE, &fdset, NULL, NULL, &tv); + if (res > 0) { + file = fdopen(emu->error_pipe, "r"); + if (!file) { + fprintf(stderr, "Failed to open error pipe\n"); + return -1; + } + while (1) { + res = getline(&buf, &bufsize, file); + if (res <= 1) + break; + if (emu->callback) + emu->callback->status_message(emu, buf); + err = 0; + } + free(buf); + fclose(file); + } + + return err; +} diff --git a/emulator/avr8emu.h b/emulator/avr8emu.h new file mode 100644 index 0000000..104c9e3 --- /dev/null +++ b/emulator/avr8emu.h @@ -0,0 +1,38 @@ +#ifndef AVR8EMU_LIBRARY_H_ +#define AVR8EMU_LIBRARY_H_ + +#include + + +/* Type of an emulator instance. */ +struct avr8emu; + + + +struct avr8emu_callbacks { + void (*status_message)(struct avr8emu *emu, const char *msg); +}; + +/* Create a new emulator instance. */ +struct avr8emu * avr8emu_create(enum avr_setup_type type, + const struct avr8emu_callbacks *cb); +/* Delete an emulator instance. */ +void avr8emu_remove(struct avr8emu *emu); + +/* Initialize the microcontroller. */ +int avr8emu_initialize(struct avr8emu *emu, const char *code, + size_t code_size); + +/* Reset the microcontroller. This will start execution of + * the code if invoked for the first time. */ +int avr8emu_reset(struct avr8emu *emu); + +/* Call this on a regular basis to poll any messages from the + * worker process. */ +void avr8emu_poll_messages(struct avr8emu *emu); +/* Call this on a regular basis to poll any status messages + * from the worker process. */ +int avr8emu_poll_status(struct avr8emu *emu); + + +#endif /* AVR8EMU_LIBRARY_H_ */ diff --git a/emulator/avr8emu_private.h b/emulator/avr8emu_private.h new file mode 100644 index 0000000..98ca901 --- /dev/null +++ b/emulator/avr8emu_private.h @@ -0,0 +1,60 @@ +#ifndef AVR8EMU_LIBRARY_PRIVATE_H_ +#define AVR8EMU_LIBRARY_PRIVATE_H_ + +#include "util.h" +#include "avr8emu.h" + +#include +#include +#include +#include + + +struct avr8emu { + /* Type of microcontroller we are emulating. */ + enum avr_setup_type type; + /* Pipes for communication with the worker. */ + int message_to_worker; /* Result is received in result_from_worker */ + int result_from_worker; /* Result for message_to_worker */ + int message_from_worker; /* Has no result pipe */ + /* Pipe for receiving error and status messages from the worker. */ + int error_pipe; + /* The worker process PID. */ + pid_t worker_pid; + /* Callbacks to the emulator controller. */ + const struct avr8emu_callbacks *callback; +}; + +struct avr8emu_worker { + /* Type of microcontroller we are emulating. */ + enum avr_setup_type type; + /* Pipes for communication with the host. */ + int message_to_host; /* Has no result pipe */ + int message_from_host; /* Result is send through result_to_host */ + int result_to_host; /* Result for message_from_host */ + /* Pipe for sending error and status messages to the host. */ + int error_pipe; + /* True, if the microcontroller is initialized. */ + bool initialized; +}; + +#define AVR8EMU_MAX_MSG_SIZE (1024 * 8) + +/* Message from host to worker. */ +enum avr8emu_worker_message { + WORKERMSG_INIT, + WORKERMSG_RESET, +}; + + +/* Messaging to the host. (From worker). This is reentrant safe, so it + * can be called from multiple worker threads. */ +enum avr8emu_host_message { + HOSTMSG_PORTIO, /* I/O access to an I/O port. */ +}; + +void send_host_message(enum avr8emu_host_message msg, + const void *payload, uint32_t payload_len); + + +#endif /* AVR8EMU_LIBRARY_PRIVATE_H_ */ diff --git a/emulator/cpu.c b/emulator/cpu.c new file mode 100644 index 0000000..01512a5 --- /dev/null +++ b/emulator/cpu.c @@ -0,0 +1,2667 @@ +/* + * AVR emulator + * CPU emulation + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "cpu.h" +#include "util.h" +#include "hardware.h" + +#include +#include +#include +#include +#include + + +/* The CPU */ +struct avr_cpu cpu; +/* The flash memory */ +static char *code; + +/* The POSIX CPU thread. */ +static pthread_t cpu_thread; + + +static uint8_t cpu_get_sreg(void); + + +static inline bool in_cpu_thread(void) +{ + return !!pthread_equal(pthread_self(), cpu_thread); +} + +/* Get the ID of the instruction that modified this lazy flag. */ +static inline uint8_t lazy_flag_get_insn(const struct lazy_flag *f) +{ + return (uint8_t)(f - cpu.lazy_flag_cache); +} + +/* Get the stack pointer. */ +static inline uint16_t getSP(void) +{ + return le16_to_cpu(*((le16_t *)&memory.io[memory.sp_offset])); +} + +/* Write the stack pointer. */ +static inline void setSP(uint16_t sp) +{ + *((le16_t *)&memory.io[memory.sp_offset]) = cpu_to_le16(sp); +} + +/* Get the X pointer */ +static inline uint16_t getX(void) +{ + const uint8_t *x = &(cpu.r[X_LO]); + return le16_to_cpu(*(le16_t *)x); +} + +/* Set the X pointer */ +static inline void setX(uint16_t val) +{ + le16_t *x = (le16_t *)(&(cpu.r[X_LO])); + *x = cpu_to_le16(val); +} + +/* Get the Y pointer */ +static inline uint16_t getY(void) +{ + const uint8_t *y = &(cpu.r[Y_LO]); + return le16_to_cpu(*(le16_t *)y); +} + +/* Set the Y pointer */ +static inline void setY(uint16_t val) +{ + le16_t *y = (le16_t *)(&(cpu.r[Y_LO])); + *y = cpu_to_le16(val); +} + +/* Get the Z pointer */ +static inline uint16_t getZ(void) +{ + const uint8_t *z = &(cpu.r[Z_LO]); + return le16_to_cpu(*(le16_t *)z); +} + +/* Set the Z pointer */ +static inline void setZ(uint16_t val) +{ + le16_t *z = (le16_t *)(&(cpu.r[Z_LO])); + *z = cpu_to_le16(val); +} + +static void dump_stack(FILE *fd) +{ + uint16_t sp; + int i = 0, count = 40; + + sp = getSP(); + if (sp >= memory.size) { + fprintf(fd, "Invalid stackpointer. Not dumping.\n"); + return; + } + fprintf(fd, "Stackdump (%d bytes):\n", count); + while (i < count && sp && sp < memory.size) { + if ((i != 0) && (i % 4 == 0)) + fprintf(fd, "\n"); + fprintf(fd, "<0x%04X>: 0x%02X ", sp, memory.ram[sp]); + sp++; + i++; + } + fprintf(fd, "\n"); +} + +#if 0 +static void dump_lazy_flag(FILE *fd, const struct lazy_flag *f) +{ + fprintf(fd, "insn: 0x%02X op[0]: 0x%02X op[1]: 0x%02X res: 0x%02X " + "op16[0]: 0x%04X op16[1]: 0x%04X res16: 0x%04X\n", + lazy_flag_get_insn(f), f->op[0], f->op[1], f->res, + f->op16[0], f->op16[1], f->res16); +} + +static void dump_lazy_flags(FILE *fd) +{ + struct lazy_flag *f; + int i; + + for (i = 0; i < ARRAY_SIZE(cpu.sreg_lazy); i++) { + f = cpu.sreg_lazy[i]; + if (f) { + fprintf(fd, "LF%d -> ", i); + dump_lazy_flag(fd, cpu.sreg_lazy[i]); + } + } +} +#endif + +void dump_cpu(FILE *fd) +{ + int i; + uint8_t sreg; + + fprintf(fd, "=== CPU dump ===\n"); + fprintf(fd, "R: %u PC: 0x%08X (0x%08X) SP: 0x%04X Cycles: %llu\n", + (unsigned)cpu.running, cpu.pc, cpu.pc * 2, getSP(), + (unsigned long long)cpu.cycles_count); + sreg = cpu_get_sreg(); + fprintf(fd, "SREG: 0x%02X IRQs: %llu TRAP: %u\n", + sreg, (unsigned long long)cpu.irq_count, + reload_var(cpu.trap)); + if (cpu.r) { + for (i = 0; i < AVR_NR_REGS; i++) { + if ((i != 0) && (i % 4 == 0)) + fprintf(fd, "\n"); + fprintf(fd, "r%02u: 0x%02X ", i, cpu.r[i]); + } + fprintf(fd, "\n"); + fprintf(fd, "X: 0x%04X Y: 0x%04X Z: 0x%04X\n", + getX(), getY(), getZ()); + } else + fprintf(fd, "Invalid register file. Not dumping.\n"); + dump_stack(fd); +} + +static void __attribute__((__noreturn__)) fault(const char *fmt, ...) +{ + va_list va; + + fprintf(stderr, "\n---=== FAULT ===---\n"); + va_start(va, fmt); + vfprintf(stderr, fmt, va); + va_end(va); + fprintf(stderr, "\n"); + dump_cpu(stderr); + if (in_cpu_thread()) { + pthread_exit(NULL); + } else { + fprintf(stderr, "\n>>> BUG: Faulting from outside of the CPU! <<<\n"); + exit(1); + } + while (1) ; +} + +/* Get the SREG Carry flag. */ +static bool sreg_get_C(void) +{ + const struct lazy_flag *f = cpu.sreg_lazy[SREG_C_BIT]; + bool set; + + if (cpu.sreg_valid & SREG_C) + return !!(cpu.sreg & SREG_C); + + switch (lazy_flag_get_insn(f)) { + case INSN_SBCI: + case INSN_SUBI: + case INSN_CPI: + case INSN_CPC: + case INSN_SBC: + case INSN_CP: + case INSN_SUB: + set = !!(((~(f->op[0]) & f->op[1]) | + (f->op[1] & f->res) | + (f->res & ~(f->op[0]))) & (1 << 7)); + break; + case INSN_ADD: + case INSN_ADC: + set = !!(((f->op[0] & f->op[1]) | + (f->op[1] & ~(f->res)) | + (~(f->res) & f->op[0])) & (1 << 7)); + break; + case INSN_COM: + set = 1; + break; + case INSN_NEG: + set = !!(f->res); + break; + case INSN_LSR: + case INSN_ROR: + case INSN_ASR: + set = !!(f->op[0] & 0x01); + break; + case INSN_ADIW: + set = (!!(~(f->res16) & 0x8000) & !!(f->op16[0] & 0x8000)); + break; + case INSN_SBIW: + set = (!!(f->res16 & 0x8000) & !!(~(f->op16[0]) & 0x8000)); + break; + default: + if (!in_cpu_thread()) + return 0; + fault("Lazy C flag: Unhandled instruction %u", + lazy_flag_get_insn(f)); + } + cpu.sreg &= ~SREG_C; + cpu.sreg |= (set << SREG_C_BIT); + cpu.sreg_valid |= SREG_C; + + return set; +} + +/* Set the SREG Carry flag. */ +static inline void sreg_set_C(bool C) +{ + cpu.sreg &= ~SREG_C; + cpu.sreg |= (C << SREG_C_BIT); + cpu.sreg_valid |= SREG_C; +} + +/* Set the SREG Carry flag. LAZY */ +static inline void sreg_set_C_lazy(uint8_t insn) +{ + cpu.sreg_lazy[SREG_C_BIT] = &(cpu.lazy_flag_cache[insn]); +} + +/* Get the SREG Zero flag. */ +static bool sreg_get_Z(void) +{ + const struct lazy_flag *f = cpu.sreg_lazy[SREG_Z_BIT]; + bool set; + + if (cpu.sreg_valid & SREG_Z) + return !!(cpu.sreg & SREG_Z); + + switch (lazy_flag_get_insn(f)) { + case INSN_SBCI: + case INSN_CPC: + case INSN_SBC: + if (f->res == 0) { + /* Take the previously set Z flag. */ + set = !!(cpu.sreg & SREG_Z); + } else + set = 0; + break; + case INSN_SUBI: + case INSN_ORI: + case INSN_ANDI: + case INSN_CPI: + case INSN_COM: + case INSN_NEG: + case INSN_DEC: + case INSN_INC: + case INSN_LSR: + case INSN_ROR: + case INSN_ASR: + case INSN_ADD: + case INSN_CP: + case INSN_SUB: + case INSN_ADC: + case INSN_AND: + case INSN_EOR: + case INSN_OR: + set = (f->res == 0); + break; + case INSN_ADIW: + case INSN_SBIW: + set = (f->res16 == 0); + break; + default: + if (!in_cpu_thread()) + return 0; + fault("Lazy Z flag: Unhandled instruction %u", + lazy_flag_get_insn(f)); + } + cpu.sreg &= ~SREG_Z; + cpu.sreg |= (set << SREG_Z_BIT); + cpu.sreg_valid |= SREG_Z; + + return set; +} + +/* Set the SREG Zero flag. */ +static inline void sreg_set_Z(bool Z) +{ + cpu.sreg &= ~SREG_Z; + cpu.sreg |= (Z << SREG_Z_BIT); + cpu.sreg_valid |= SREG_Z; +} + +/* Set the SREG Zero flag. LAZY */ +static inline void sreg_set_Z_lazy(uint8_t insn) +{ + cpu.sreg_lazy[SREG_Z_BIT] = &(cpu.lazy_flag_cache[insn]); +} + +/* Get the SREG Negative flag. */ +static bool sreg_get_N(void) +{ + const struct lazy_flag *f = cpu.sreg_lazy[SREG_N_BIT]; + bool set; + + if (cpu.sreg_valid & SREG_N) + return !!(cpu.sreg & SREG_N); + + switch (lazy_flag_get_insn(f)) { + case INSN_SBCI: + case INSN_SUBI: + case INSN_ORI: + case INSN_ANDI: + case INSN_CPI: + case INSN_COM: + case INSN_NEG: + case INSN_DEC: + case INSN_INC: + case INSN_ROR: + case INSN_ASR: + case INSN_CPC: + case INSN_SBC: + case INSN_ADD: + case INSN_CP: + case INSN_SUB: + case INSN_ADC: + case INSN_AND: + case INSN_EOR: + case INSN_OR: + set = !!(f->res & 0x80); + break; + case INSN_LSR: + set = 0; + break; + case INSN_ADIW: + case INSN_SBIW: + set = !!(f->res16 & 0x8000); + break; + default: + if (!in_cpu_thread()) + return 0; + fault("Lazy N flag: Unhandled instruction %u", + lazy_flag_get_insn(f)); + } + cpu.sreg &= ~SREG_N; + cpu.sreg |= (set << SREG_N_BIT); + cpu.sreg_valid |= SREG_N; + + return set; +} + +/* Set the SREG Negative flag. */ +static inline void sreg_set_N(bool N) +{ + cpu.sreg &= ~SREG_N; + cpu.sreg |= (N << SREG_N_BIT); + cpu.sreg_valid |= SREG_N; +} + +/* Set the SREG Negative flag. LAZY */ +static inline void sreg_set_N_lazy(uint8_t insn) +{ + cpu.sreg_lazy[SREG_N_BIT] = &(cpu.lazy_flag_cache[insn]); +} + +/* Get the SREG two's complement oVerflow flag. */ +static bool sreg_get_V(void) +{ + const struct lazy_flag *f = cpu.sreg_lazy[SREG_V_BIT]; + bool set; + + if (cpu.sreg_valid & SREG_V) + return !!(cpu.sreg & SREG_V); + + switch (lazy_flag_get_insn(f)) { + case INSN_SBCI: + case INSN_SUBI: + case INSN_CPI: + case INSN_CPC: + case INSN_SBC: + case INSN_CP: + case INSN_SUB: + set = !!(((f->op[0] & ~(f->op[1]) & ~(f->res)) | + (~(f->op[0]) & f->op[1] & f->res)) & (1 << 7)); + break; + case INSN_ADD: + case INSN_ADC: + set = !!(((f->op[0] & f->op[1] & ~(f->res)) | + (~(f->op[0]) & ~(f->op[1]) & f->res)) & (1 << 7)); + break; + case INSN_ORI: + case INSN_ANDI: + case INSN_COM: + case INSN_AND: + case INSN_EOR: + case INSN_OR: + set = 0; + break; + case INSN_NEG: + case INSN_INC: + set = (f->res == 0x80); + break; + case INSN_DEC: + set = (f->op[0] == 0x80); + break; + case INSN_LSR: + set = !!(0 ^ (f->op[0] & 0x01)); + break; + case INSN_ROR: + case INSN_ASR: + set = (!!(f->res & 0x80)) ^ (!!(f->op[0] & 0x01)); + break; + case INSN_ADIW: + set = (!!(~(f->op16[0]) & 0x8000) & !!(f->res16 & 0x8000)); + break; + case INSN_SBIW: + set = (!!(f->op16[0] & 0x8000) & !!(~(f->res16) & 0x8000)); + break; + default: + if (!in_cpu_thread()) + return 0; + fault("Lazy V flag: Unhandled instruction %u", + lazy_flag_get_insn(f)); + } + cpu.sreg &= ~SREG_V; + cpu.sreg |= (set << SREG_V_BIT); + cpu.sreg_valid |= SREG_V; + + return set; +} + +/* Set the SREG two's complement oVerflow flag. */ +static inline void sreg_set_V(bool V) +{ + cpu.sreg &= ~SREG_V; + cpu.sreg |= (V << SREG_V_BIT); + cpu.sreg_valid |= SREG_V; +} + +/* Set the SREG two's complement oVerflow flag. LAZY */ +static inline void sreg_set_V_lazy(uint8_t insn) +{ + cpu.sreg_lazy[SREG_V_BIT] = &(cpu.lazy_flag_cache[insn]); +} + +/* Get the SREG Signed flag. */ +static bool sreg_get_S(void) +{ + bool set; + + if (cpu.sreg_valid & SREG_S) + return !!(cpu.sreg & SREG_S); + + /* S is always N XOR V. */ + set = sreg_get_N() ^ sreg_get_V(); + + cpu.sreg &= ~SREG_S; + cpu.sreg |= (set << SREG_S_BIT); + cpu.sreg_valid |= SREG_S; + + return set; +} + +static inline void sreg_set_S(bool S) +{ + cpu.sreg &= ~SREG_S; + cpu.sreg |= (S << SREG_S_BIT); + cpu.sreg_valid |= SREG_S; +} + +static inline void sreg_set_S_lazy(uint8_t insn) +{ + /* We don't need to set the pointer here, as S is calculated + * from the N and V flags. */ + /* + cpu.sreg_lazy[SREG_S_BIT] = &(cpu.lazy_flag_cache[insn]); + */ +} + +/* Get the SREG Halfcarry flag. */ +static bool sreg_get_H(void) +{ + const struct lazy_flag *f = cpu.sreg_lazy[SREG_H_BIT]; + bool set; + + if (cpu.sreg_valid & SREG_H) + return !!(cpu.sreg & SREG_H); + + switch (lazy_flag_get_insn(f)) { + case INSN_SBCI: + case INSN_SUBI: + case INSN_CPI: + case INSN_CPC: + case INSN_SBC: + case INSN_CP: + case INSN_SUB: + set = !!(((~(f->op[0]) & f->op[1]) | + (f->op[1] & f->res) | + (f->res & ~(f->op[0]))) & (1 << 3)); + break; + case INSN_ADD: + case INSN_ADC: + set = !!(((f->op[0] & f->op[1]) | + (f->op[1] & ~(f->res)) | + (~(f->res) & f->op[0])) & (1 << 3)); + break; + case INSN_NEG: + set = !!((f->res | f->op[0]) & (1 << 3)); + break; + default: + if (!in_cpu_thread()) + return 0; + fault("Lazy H flag: Unhandled instruction %u", + lazy_flag_get_insn(f)); + } + cpu.sreg &= ~SREG_H; + cpu.sreg |= (set << SREG_H_BIT); + cpu.sreg_valid |= SREG_H; + + return set; + +} + +/* Set the SREG Halfcarry flag. */ +static inline void sreg_set_H(bool H) +{ + cpu.sreg &= ~SREG_H; + cpu.sreg |= (H << SREG_H_BIT); + cpu.sreg_valid |= SREG_H; +} + +/* Set the SREG Halfcarry flag. LAZY */ +static inline void sreg_set_H_lazy(uint8_t insn) +{ + cpu.sreg_lazy[SREG_H_BIT] = &(cpu.lazy_flag_cache[insn]); +} + +/* Get the SREG Transfer bit flag. */ +static inline bool sreg_get_T(void) +{ + /* T is not lazy, so always valid. */ + return !!(cpu.sreg & SREG_T); +} + +/* Set the SREG Transfer bit flag. */ +static inline void sreg_set_T(bool T) +{ + cpu.sreg &= ~SREG_T; + cpu.sreg |= (T << SREG_T_BIT); +} + +/* Get the SREG global Interrupt enable flag. */ +static inline bool sreg_get_I(void) +{ + /* I is not lazy, so always valid. */ + return !!(cpu.sreg & SREG_I); +} + +/* Set the SREG global Interrupt enable flag. */ +static inline void sreg_set_I(bool I) +{ + cpu.sreg &= ~SREG_I; + cpu.sreg |= (I << SREG_I_BIT); +} + +#define LAZY_NONE 0xFFFFFFFF +/* Setup lazy flags for an instruction that works on 8-bit operands. + * This whole function will optimize to only a few Host-CPU instructions + * without any branches. */ +static inline void lazy_flags_setup(const uint8_t insn, + const uint32_t oper0, const uint32_t oper1, + const uint32_t res, + const uint8_t sreg_invalidate_mask) +{ + struct lazy_flag *f = &(cpu.lazy_flag_cache[insn]); + + break_linkage_on(!is_const_value(insn), + lazy_flags_setup_insn_not_const); + break_linkage_on(!is_const_value(sreg_invalidate_mask), + lazy_flags_setup_sreg_mask_not_const); + + /* The if-branches are optimized away at compiletime, as this + * is an inline function. The conditionals always evaluate into + * constant expressions, so the compiler removes the entire branch + * instruction. */ + if (insn >= FIRST_16BIT_INSN) { + if (!is_const_value(oper0) || oper0 != LAZY_NONE) + f->op16[0] = oper0; + if (!is_const_value(oper1) || oper1 != LAZY_NONE) + f->op16[1] = oper1; + if (!is_const_value(res) || res != LAZY_NONE) + f->res16 = res; + } else { + if (!is_const_value(oper0) || oper0 != LAZY_NONE) + f->op[0] = oper0; + if (!is_const_value(oper1) || oper1 != LAZY_NONE) + f->op[1] = oper1; + if (!is_const_value(res) || res != LAZY_NONE) + f->res = res; + } + /* The invalidation mask is constant. So the following if-branches + * will optimize away. */ + if (sreg_invalidate_mask & SREG_C) + sreg_set_C_lazy(insn); + if (sreg_invalidate_mask & SREG_Z) + sreg_set_Z_lazy(insn); + if (sreg_invalidate_mask & SREG_N) + sreg_set_N_lazy(insn); + if (sreg_invalidate_mask & SREG_V) + sreg_set_V_lazy(insn); + if (sreg_invalidate_mask & SREG_S) + sreg_set_S_lazy(insn); + if (sreg_invalidate_mask & SREG_H) + sreg_set_H_lazy(insn); + /* Invalidate all flags that are changed by this instruction. */ + cpu.sreg_valid &= ~sreg_invalidate_mask; +} + +/* Get the whole sreg. Note that this function is expensive. + * Use the single bit fetch functions, if you only need a single + * bit or a subset of the sreg bits. */ +static uint8_t cpu_get_sreg(void) +{ + uint8_t sreg = 0; + + sreg |= (uint8_t)sreg_get_C() << SREG_C_BIT; + sreg |= (uint8_t)sreg_get_Z() << SREG_Z_BIT; + sreg |= (uint8_t)sreg_get_N() << SREG_N_BIT; + sreg |= (uint8_t)sreg_get_V() << SREG_V_BIT; + sreg |= (uint8_t)sreg_get_S() << SREG_S_BIT; + sreg |= (uint8_t)sreg_get_H() << SREG_H_BIT; + sreg |= (uint8_t)sreg_get_T() << SREG_T_BIT; + sreg |= (uint8_t)sreg_get_I() << SREG_I_BIT; + + return sreg; +} + +/* Trigger the sideeffects of an I/O store (if registered any) */ +static inline void memory_store_sideeffect_trigger(uint16_t offset, uint8_t old_val, + uint8_t new_val) +{ + io_write_handler_t handler; + + handler = memory.io_write_handlers[offset - IO_MEM_OFFSET]; + if (handler) + handler(offset, old_val, new_val); +} + +/* Trigger the sideeffects of an I/O load (if registered any) */ +static inline void memory_load_sideeffect_trigger(uint16_t offset) +{ + io_read_handler_t handler; + + handler = memory.io_read_handlers[offset - IO_MEM_OFFSET]; + if (handler) + handler(offset); +} + +/* Store a value to memory and trigger sideeffects if needed. */ +static inline void memory_store(uint16_t offset, uint8_t value) +{ + uint8_t old, is_io_memory; + + memory_lock(offset, &is_io_memory); + old = memory.ram[offset]; + memory.ram[offset] = value; + if (is_io_memory) + memory_store_sideeffect_trigger(offset, old, value); + memory_unlock(is_io_memory); +} + +/* Memory store to non-IO memory. */ +static inline void memory_store_nonio(uint16_t offset, uint8_t value) +{ + if (CHECK(mem_offset_in_io_region(offset))) { + fault("Attempt to store to I/O memory by an instruction " + "that should only access non-I/O memory."); + } + memory.ram[offset] = value; +} + +/* Load a value from memory. Take care of locking. */ +static inline uint8_t memory_load(uint16_t offset) +{ + uint8_t ret, is_io_memory; + + memory_lock(offset, &is_io_memory); + if (is_io_memory) + memory_load_sideeffect_trigger(offset); + ret = memory.ram[offset]; + memory_unlock(is_io_memory); + + return ret; +} + +/* Memory load from non-IO memory. */ +static inline uint8_t memory_load_nonio(uint16_t offset) +{ + if (CHECK(mem_offset_in_io_region(offset))) { + fault("Attempt to load from I/O memory by an instruction " + "that should only access non-I/O memory."); + } + return memory.ram[offset]; +} + +static inline void insn_ret(void) +{ + uint32_t a, b, c; + uint16_t sp; + + sp = getSP(); + if (active_setup.pc_22bit) { + if (CHECK(sp >= memory.size - 3)) + fault("RET instruction stack overflow"); + c = memory_load_nonio(++sp); + b = memory_load_nonio(++sp); + a = memory_load_nonio(++sp); + setSP(sp); + cpu.pc = a | (b << 8) | (c << 16); + } else { + if (CHECK(sp >= memory.size - 2)) + fault("RET instruction stack overflow"); + b = memory_load_nonio(++sp); + a = memory_load_nonio(++sp); + setSP(sp); + cpu.pc = a | (b << 8); + } +} + +static inline void insn_store_pc_on_stack(bool pc_22bit, uint8_t pc_inc) +{ + uint32_t ret_pc; + uint16_t sp; + + ret_pc = cpu.pc + pc_inc; + sp = getSP(); + if (pc_22bit) { + if (CHECK(sp < 3)) + fault("PC -> STACK operation stack underflow"); + memory_store_nonio(sp--, ret_pc); + memory_store_nonio(sp--, ret_pc >> 8); + memory_store_nonio(sp--, ret_pc >> 16); + setSP(sp); + } else { + if (CHECK(sp < 2)) + fault("PC -> STACK operation stack underflow"); + memory_store_nonio(sp--, ret_pc); + memory_store_nonio(sp--, ret_pc >> 8); + setSP(sp); + } +} + +/* Get the low 16bits of a 16bit or 32bit instruction at PC. */ +static inline uint16_t get_lo16_insn(void) +{ + uint32_t byteoffset; + + byteoffset = cpu.pc * 2; + if (CHECK(byteoffset >= active_setup.flash_size)) + fault("CPU Program Counter >= flash size"); + return le16_to_cpu(*(le16_t *)(code + byteoffset)); +} + +/* Get the high 16bits of a 32bit instruction at PC. */ +static inline uint16_t get_hi16_insn(void) +{ + uint32_t byteoffset; + + byteoffset = (cpu.pc + 1) * 2; + if (CHECK(byteoffset >= active_setup.flash_size)) + fault("HI16-fetch: CPU Program Counter >= flash size"); + return le16_to_cpu(*(le16_t *)(code + byteoffset)); +} + +static inline bool is_two_word_instruction(uint16_t insn) +{ + switch (insn & 0xFE0F) { + case 0x9000: /* lds */ + case 0x9200: /* sts */ + return 1; + } + + switch (insn & 0xFE0E) { + case 0x940c: /* jmp */ + case 0x940e: /* call */ + return 1; + } + + return 0; +} + +/* Trap the CPU. This is called from outside of the CPU thread. */ +int trap_cpu(uint8_t id, bool synchronize) +{ + pthread_mutex_lock(&cpu.trap_mutex); + if (cpu.trap == id) { + pthread_mutex_unlock(&cpu.trap_mutex); + return 0; + } + cpu.trap = id; + if (synchronize) + pthread_cond_wait(&cpu.trapped_cond, &cpu.trap_mutex); + pthread_mutex_unlock(&cpu.trap_mutex); + + return 0; +} + +static int handle_trap(void) +{ + uint8_t id; + int stay_trapped; + + pthread_mutex_lock(&cpu.trap_mutex); + + id = cpu.trap; + if (unlikely(id == CPU_TRAP_CONTINUE)) { + stay_trapped = 0; + goto out_unlock; + } + pthread_cond_broadcast(&cpu.trapped_cond);//FIXME only broadcast after the first trap cycle? + + switch (id) { + case CPU_TRAP_WAIT: + stay_trapped = 1; + break; + case CPU_TRAP_SINGLESTEP: + /* Continue execution. We will get trapped again + * after the next instruction. */ + stay_trapped = 0; + break; + case CPU_TRAP_CONTINUE: + /* Continue execution. */ + stay_trapped = 0; + break; + case CPU_TRAP_EXIT: + cpu.running = 0; + pthread_mutex_unlock(&cpu.trap_mutex); + pthread_exit(NULL); + return 1; + default: + pthread_mutex_unlock(&cpu.trap_mutex); + fault("Unknown CPU trap %u triggered\n", id); + return 1; + } +out_unlock: + pthread_mutex_unlock(&cpu.trap_mutex); + + if (stay_trapped) + pthread_yield(); + + return stay_trapped; +} + +static void handle_breakpoint(bool programmed_bp) +{ + //FIXME: We don't really want to fault here. + // But do this for now. + fault("Breakpoint"); +} + +/* Trigger an IRQ. This is called from outside of the CPU thread. */ +int cpu_trigger_interrupt(uint8_t vector) +{ + struct avr_irq_descriptor *desc; + + pthread_spin_lock(&cpu.irq_lock); + if (unlikely(cpu.irqs_pending == MAX_IRQS_PENDING)) { + pthread_spin_unlock(&cpu.irq_lock); + return -EBUSY; + } + + /* Get a free IRQ descriptor. */ + desc = list_entry(cpu.irqdesc_cache.next, + struct avr_irq_descriptor, list); + list_move_tail(&desc->list, &cpu.irqdesc_pending); + desc->vector = vector; + cpu.irqs_pending++; + + pthread_spin_unlock(&cpu.irq_lock); + + return 0; +} + +static void handle_irqs(void) +{ + struct avr_irq_descriptor *desc; + uint8_t vector; + + if (!sreg_get_I()) { + /* Global interrupts are disabled. Handle the IRQ later. */ + return; + } + + pthread_spin_lock(&cpu.irq_lock); + if (unlikely(!cpu.irqs_pending)) { + pthread_spin_unlock(&cpu.irq_lock); + return; + } + cpu.irqs_pending--; + /* Read the IRQ descriptor and return it to the cache. */ + desc = list_entry(cpu.irqdesc_pending.next, + struct avr_irq_descriptor, list); + vector = desc->vector; + list_move(&desc->list, &cpu.irqdesc_cache); + pthread_spin_unlock(&cpu.irq_lock); + + /* IRQs are entered with the I flag disabled. */ + sreg_set_I(0); + /* Store PC on the stack, so we can later return from the + * IRQ with a reti instruction. */ + insn_store_pc_on_stack(active_setup.pc_22bit, 0); + /* Jump to the IRQ vector. */ + cpu.pc = vector; + + cpu.irq_count++; +} + +/* This is the function where the code gets executed. */ +static void * cpu_thread_function(void *unused) +{ + uint16_t insn; + bool enable_irqs = 0; + int res; + +//TODO implement memory RAMPD stuff. + cpu.cycles_count = 0; + while (1) { + if (unlikely(enable_irqs)) { + sreg_set_I(1); + enable_irqs = 0; + } +next_insn_skip_irqcheck: + if (unlikely(reload_var(cpu.trap))) { + while (1) { + res = handle_trap(); + if (res == 0) + break; /* Continue execution */ + } + } + if (unlikely(cpu.breakpoint == cpu.pc)) + handle_breakpoint(0); + if (unlikely(reload_var(cpu.irqs_pending))) + handle_irqs(); + + /* Get the instruction at PC and handle it. */ + insn = get_lo16_insn(); + +//printf("PC: 0x%08X\n", cpu.pc * 2); + switch (insn) { + case 0x0000: /* nop */ + cpu.pc++; + cpu.cycles_count++; + continue; + case 0x9409: /* ijmp */ + cpu.pc = getZ(); + cpu.cycles_count += 2; + continue; + case 0x9419: /* eijmp */ + cpu.pc = getZ(); + //TODO load EIND into PC.high + cpu.cycles_count += 2; + continue; + case 0x9508: /* ret */ + insn_ret(); + cpu.cycles_count += 4; + continue; + case 0x9509: /* icall */ + insn_store_pc_on_stack(active_setup.pc_22bit, 1); + cpu.pc = getZ(); + cpu.cycles_count += 3; + continue; + case 0x9518: /* reti */ + insn_ret(); + /* XXX Also execute the next insn with IRQs still disabled here? */ + sreg_set_I(1); + cpu.cycles_count += 4; + continue; + case 0x9519: /* eicall */ + insn_store_pc_on_stack(1, 1); + cpu.pc = getZ(); + //TODO load EIND into PC.high + cpu.cycles_count += 4; + continue; + case 0x9588: /* sleep */ + //TODO + cpu.pc++; + cpu.cycles_count++; + continue; + case 0x95a8: /* wdr */ + //TODO + cpu.pc++; + cpu.cycles_count++; + continue; + case 0x95c8: { /* lpm */ + uint16_t z = getZ(); + + if (CHECK(z >= active_setup.flash_size)) + fault("LPM with Z >= flash size"); + cpu.r[0] = code[z]; + cpu.pc++; + cpu.cycles_count += 3; + continue; + } + case 0x95d8: { /* elpm */ + uint32_t offset; + + fault("ELPM is not implemented"); + offset = getZ(); + //TODO fetch RAMPZ + //TODO offset sanity check + cpu.r[0] = code[offset]; + cpu.pc++; + cpu.cycles_count += 3; + continue; + } + case 0x95e8: { /* spm */ + uint32_t offset; + + fault("SPM is not implemented"); + offset = getZ(); + //TODO fetch RAMPZ + code[offset] = 0xFF; + code[offset + 1] = 0xFF; + //TODO write the stuff. + cpu.pc++; + //TODO set cpu.cycles_count + continue; + } + case 0x9598: /* break */ + handle_breakpoint(1); + cpu.pc++; + cpu.cycles_count++; + continue; + } + + switch (insn & 0xFF8F) { + case 0x9408: { /* bset */ + uint8_t bitnr; + + cpu.pc++; + cpu.cycles_count++; + bitnr = (insn & 0x70) >> 4; + switch (bitnr) { + case SREG_C_BIT: + sreg_set_C(1); + break; + case SREG_Z_BIT: + sreg_set_Z(1); + break; + case SREG_N_BIT: + sreg_set_N(1); + break; + case SREG_V_BIT: + sreg_set_V(1); + break; + case SREG_S_BIT: + sreg_set_S(1); + break; + case SREG_H_BIT: + sreg_set_H(1); + break; + case SREG_T_BIT: + sreg_set_T(1); + break; + case SREG_I_BIT: + /* Enable IRQs after the _next_ instruction has finished. */ + enable_irqs = 1; + goto next_insn_skip_irqcheck; + } + continue; + } + case 0x9488: { /* bclr */ + uint8_t bitnr; + + bitnr = (insn & 0x70) >> 4; + switch (bitnr) { + case SREG_C_BIT: + sreg_set_C(0); + break; + case SREG_Z_BIT: + sreg_set_Z(0); + break; + case SREG_N_BIT: + sreg_set_N(0); + break; + case SREG_V_BIT: + sreg_set_V(0); + break; + case SREG_S_BIT: + sreg_set_S(0); + break; + case SREG_H_BIT: + sreg_set_H(0); + break; + case SREG_T_BIT: + sreg_set_T(0); + break; + case SREG_I_BIT: + sreg_set_I(0); + break; + } + cpu.pc++; + cpu.cycles_count++; + continue; + } } + + switch (insn & 0xF000) { + case 0x4000: { /* sbci */ + uint8_t reg, orig, val, res; + + reg = ((insn & 0x00F0) >> 4) + 16; + val = ((insn & 0x0F00) >> 4) | (insn & 0x000F); + orig = cpu.r[reg]; + res = orig - val - (uint8_t)sreg_get_C(); + cpu.r[reg] = res; + + lazy_flags_setup(INSN_SBCI, orig, val, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x5000: { /* subi */ + uint8_t reg, orig, val, res; + + reg = ((insn & 0x00F0) >> 4) + 16; + val = ((insn & 0x0F00) >> 4) | (insn & 0x000F); + orig = cpu.r[reg]; + res = orig - val; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_SUBI, orig, val, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x6000: { /* ori */ + uint8_t reg, orig, val, res; + + reg = ((insn & 0x00F0) >> 4) + 16; + val = ((insn & 0x0F00) >> 4) | (insn & 0x000F); + orig = cpu.r[reg]; + res = orig | val; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_ORI, LAZY_NONE, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x7000: { /* andi */ + uint8_t reg, orig, val, res; + + reg = ((insn & 0x00F0) >> 4) + 16; + val = ((insn & 0x0F00) >> 4) | (insn & 0x000F); + orig = cpu.r[reg]; + res = orig & val; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_ANDI, LAZY_NONE, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0xe000: { /* ldi */ + uint8_t reg, val; + + reg = ((insn & 0x00F0) >> 4) + 16; + val = ((insn & 0x0F00) >> 4) | (insn & 0x000F); + cpu.r[reg] = val; + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0xc000: { /* rjmp */ + int32_t offset = 0; + + if (insn & 0x0800) + offset = ~0x0FFF; + offset |= insn & 0x0FFF; + cpu.pc = cpu.pc + offset + 1; + if (CHECK(cpu.pc >= active_setup.flash_size / 2)) + fault("RJMP destination PC >= flash length"); + cpu.cycles_count += 2; + + continue; + } + case 0xd000: { /* rcall */ + int32_t offset = 0; + + if (insn & 0x0800) + offset = ~0x0FFF; + offset |= insn & 0x0FFF; + insn_store_pc_on_stack(active_setup.pc_22bit, 1); + cpu.pc = cpu.pc + offset + 1; + if (CHECK(cpu.pc >= active_setup.flash_size / 2)) + fault("RCALL destination PC >= flash length"); + cpu.cycles_count += 3; + + continue; + } + case 0x3000: { /* cpi */ + uint8_t reg, orig, val, res; + + reg = ((insn & 0x00F0) >> 4) + 16; + val = ((insn & 0x0F00) >> 4) | (insn & 0x000F); + orig = cpu.r[reg]; + res = orig - val; + + lazy_flags_setup(INSN_CPI, orig, val, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } } + + switch (insn & 0xFE0F) { + case 0x9400: { /* com */ + uint8_t reg, res; + + reg = (insn & 0x01F0) >> 4; + res = ~(cpu.r[reg]); + cpu.r[reg] = res; + + lazy_flags_setup(INSN_COM, LAZY_NONE, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9401: { /* neg */ + uint8_t reg, orig, res; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + res = (uint8_t)0x00 - orig; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_NEG, orig, LAZY_NONE, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x940a: { /* dec */ + uint8_t reg, orig, res; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + res = orig - 1; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_DEC, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9403: { /* inc */ + uint8_t reg, orig, res; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + res = orig + 1; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_INC, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9402: { /* swap */ + uint8_t reg, orig; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + cpu.r[reg] = (orig >> 4) | (orig << 4); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9406: { /* lsr */ + uint8_t reg, orig, res; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + res = orig >> 1; + cpu.r[reg] = res; + + lazy_flags_setup(INSN_LSR, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9407: { /* ror */ + uint8_t reg, orig, res; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + res = (orig >> 1) | ((uint8_t)sreg_get_C() << 7); + cpu.r[reg] = res; + + lazy_flags_setup(INSN_ROR, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9006: /* elpm Z */ + fault("ELPM Z is not implemented");//TODO + continue; + case 0x9007: /* elpm Z+ */ + fault("ELPM Z+ is not implemented");//TODO + continue; + case 0x900c: { /* ld X */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t x; + + x = cpu.r[X_LO]; + if (CHECK(x >= memory.size)) + fault("LD X pointer outside of memory region"); + cpu.r[reg] = memory_load(x); + } else { + uint16_t x; + + x = getX(); + if (CHECK(x >= memory.size)) + fault("LD X pointer outside of memory region"); + cpu.r[reg] = memory_load(x); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x900d: { /* ld X+ */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t x; + + x = cpu.r[X_LO]; + if (CHECK(x >= memory.size)) + fault("LD X+ pointer outside of memory region"); + cpu.r[reg] = memory_load(x); + cpu.r[X_LO]++; + } else { + uint16_t x; + + x = getX(); + if (CHECK(x >= memory.size)) + fault("LD X+ pointer outside of memory region"); + cpu.r[reg] = memory_load(x); + setX(x + 1); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x900e: { /* ld -X */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t x; + + x = cpu.r[X_LO] - 1; + if (CHECK(x >= memory.size)) + fault("LD -X pointer outside of memory region"); + cpu.r[reg] = memory_load(x); + cpu.r[X_LO] = x; + } else { + uint16_t x; + + x = getX() - 1; + if (CHECK(x >= memory.size)) + fault("LD -X pointer outside of memory region"); + cpu.r[reg] = memory_load(x); + setX(x); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9009: { /* ld Y+ */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t y; + + y = cpu.r[Y_LO]; + if (CHECK(y >= memory.size)) + fault("LD Y+ pointer outside of memory region"); + cpu.r[reg] = memory_load(y); + cpu.r[Y_LO]++; + } else { + uint16_t y; + + y = getY(); + if (CHECK(y >= memory.size)) + fault("LD Y+ pointer outside of memory region"); + cpu.r[reg] = memory_load(y); + setY(y + 1); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x900a: { /* ld -Y */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t y; + + y = cpu.r[Y_LO] - 1; + if (CHECK(y >= memory.size)) + fault("LD -Y pointer outside of memory region"); + cpu.r[reg] = memory_load(y); + cpu.r[Y_LO] = y; + } else { + uint16_t y; + + y = getY() - 1; + if (CHECK(y >= memory.size)) + fault("LD -Y pointer outside of memory region"); + cpu.r[reg] = memory_load(y); + setY(y); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9001: { /* ld Z+ */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t z; + + z = cpu.r[Z_LO]; + if (CHECK(z >= memory.size)) + fault("LD Z+ pointer outside of memory region"); + cpu.r[reg] = memory_load(z); + cpu.r[Z_LO]++; + } else { + uint16_t z; + + z = getZ(); + if (CHECK(z >= memory.size)) + fault("LD Z+ pointer outside of memory region"); + cpu.r[reg] = memory_load(z); + setZ(z + 1); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9002: { /* ld -Z */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t z; + + z = cpu.r[Z_LO] - 1; + if (CHECK(z >= memory.size)) + fault("LD -Z pointer outside of memory region"); + cpu.r[reg] = memory_load(z); + cpu.r[Z_LO] = z; + } else { + uint16_t z; + + z = getZ() - 1; + if (CHECK(z >= memory.size)) + fault("LD -Z pointer outside of memory region"); + cpu.r[reg] = memory_load(z); + setZ(z); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9004: { /* lpm Z */ + uint8_t reg; + uint16_t z; + + reg = (insn & 0x01F0) >> 4; + z = getZ(); + if (CHECK(z >= active_setup.flash_size)) + fault("LPM Z pointer outside of flash region"); + cpu.r[reg] = code[z]; + + cpu.pc++; + cpu.cycles_count += 3; + continue; + } + case 0x9005: { /* lpm Z+ */ + uint8_t reg; + uint16_t z; + + reg = (insn & 0x01F0) >> 4; + z = getZ(); + if (CHECK(z >= active_setup.flash_size)) + fault("LPM Z+ pointer outside of flash region"); + cpu.r[reg] = code[z]; + setZ(z + 1); + + cpu.pc++; + cpu.cycles_count += 3; + continue; + } + case 0x920c: { /* st X */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t x; + + x = cpu.r[X_LO]; + if (CHECK(x >= memory.size)) + fault("ST X pointer outside of memory region"); + memory_store(x, cpu.r[reg]); + } else { + uint16_t x; + + x = getX(); + if (CHECK(x >= memory.size)) + fault("ST X pointer outside of memory region"); + memory_store(x, cpu.r[reg]); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x920d: { /* st X+ */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t x; + + x = cpu.r[X_LO]; + if (CHECK(x >= memory.size)) + fault("ST X+ pointer outside of memory region"); + memory_store(x, cpu.r[reg]); + cpu.r[X_LO]++; + } else { + uint16_t x; + + x = getX(); + if (CHECK(x >= memory.size)) + fault("ST X+ pointer outside of memory region"); + memory_store(x, cpu.r[reg]); + setX(x + 1); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x920e: { /* st -X */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t x; + + x = cpu.r[X_LO] - 1; + if (CHECK(x >= memory.size)) + fault("ST -X pointer outside of memory region"); + memory_store(x, cpu.r[reg]); + cpu.r[X_LO] = x; + } else { + uint16_t x; + + x = getX() - 1; + if (CHECK(x >= memory.size)) + fault("ST -X pointer outside of memory region"); + memory_store(x, cpu.r[reg]); + setX(x); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9209: { /* st Y+ */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t y; + + y = cpu.r[Y_LO]; + if (CHECK(y >= memory.size)) + fault("ST Y+ pointer outside of memory region"); + memory_store(y, cpu.r[reg]); + cpu.r[Y_LO]++; + } else { + uint16_t y; + + y = getY(); + if (CHECK(y >= memory.size)) + fault("ST Y+ pointer outside of memory region"); + memory_store(y, cpu.r[reg]); + setY(y + 1); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x920a: { /* st -Y */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t y; + + y = cpu.r[Y_LO] - 1; + if (CHECK(y >= memory.size)) + fault("ST -Y pointer outside of memory region"); + memory_store(y, cpu.r[reg]); + cpu.r[Y_LO] = y; + } else { + uint16_t y; + + y = getY() - 1; + if (CHECK(y >= memory.size)) + fault("ST -Y pointer outside of memory region"); + memory_store(y, cpu.r[reg]); + setY(y); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9201: { /* st Z+ */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t z; + + z = cpu.r[Z_LO]; + if (CHECK(z >= memory.size)) + fault("ST Z+ pointer outside of memory region"); + memory_store(z, cpu.r[reg]); + cpu.r[Z_LO]++; + } else { + uint16_t z; + + z = getZ(); + if (CHECK(z >= memory.size)) + fault("ST Z+ pointer outside of memory region"); + memory_store(z, cpu.r[reg]); + setZ(z + 1); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9202: { /* st -Z */ + uint8_t reg; + + reg = (insn & 0x01F0) >> 4; + if (memory.size <= 256) { + uint8_t z; + + z = cpu.r[Z_LO] - 1; + if (CHECK(z >= memory.size)) + fault("ST -Z pointer outside of memory region"); + memory_store(z, cpu.r[reg]); + cpu.r[Z_LO] = z; + } else { + uint16_t z; + + z = getZ() - 1; + if (CHECK(z >= memory.size)) + fault("ST -Z pointer outside of memory region"); + memory_store(z, cpu.r[reg]); + setZ(z); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x900f: { /* pop */ + uint8_t reg; + uint16_t sp; + + reg = (insn & 0x01F0) >> 4; + sp = getSP() + 1; + setSP(sp); + if (CHECK(sp >= memory.size)) + fault("POP stack pointer out of memory region"); + cpu.r[reg] = memory_load_nonio(sp); + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x920f: { /* push */ + uint8_t reg; + uint16_t sp; + + reg = (insn & 0x01F0) >> 4; + sp = getSP(); + if (CHECK(sp >= memory.size)) + fault("PUSH stack pointer out of memory region"); + memory_store_nonio(sp, cpu.r[reg]); + setSP(sp - 1); + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9405: { /* asr */ + uint8_t reg, orig, res; + + reg = (insn & 0x01F0) >> 4; + orig = cpu.r[reg]; + res = (orig >> 1) | (orig & 0x80); + cpu.r[reg] = res; + + lazy_flags_setup(INSN_ASR, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9000: { /* lds (32-bit instruction) */ + uint8_t reg; + uint16_t offset; + + reg = (insn & 0x01F0) >> 4; + offset = get_hi16_insn(); + if (CHECK(offset >= memory.size)) + fault("LDS offset >= memory size"); + cpu.r[reg] = memory_load(offset); + + cpu.pc += 2; + cpu.cycles_count += 2; + continue; + } + case 0x9200: { /* sts (32-bit instruction) */ + uint8_t reg; + uint16_t offset; + + reg = (insn & 0x01F0) >> 4; + offset = get_hi16_insn(); + if (CHECK(offset >= memory.size)) + fault("STS offset >= memory size"); + memory_store(offset, cpu.r[reg]); + + cpu.pc += 2; + cpu.cycles_count += 2; + continue; + } } + + switch (insn & 0xFE0E) { + case 0x940c: { /* jmp (32-bit instruction) */ + uint16_t hi16_insn; + uint32_t target; + + hi16_insn = get_hi16_insn(); + target = hi16_insn | ((insn & 0x0001) << 16) | + ((insn & 0x01F0) << (17 - 4)); + + cpu.pc = target; + cpu.cycles_count += 3; + continue; + } + case 0x940e: { /* call (32-bit instruction) */ + uint16_t hi16_insn; + uint32_t target; + + hi16_insn = get_hi16_insn(); + target = hi16_insn | ((insn & 0x0001) << 16) | + ((insn & 0x01F0) << (17 - 4)); + insn_store_pc_on_stack(active_setup.pc_22bit, 2); + + cpu.pc = target; + cpu.cycles_count += 4; + continue; + } } + + switch (insn & 0xD208) { + case 0x8200: { /* std Z */ + uint8_t reg, displace; + + reg = (insn & 0x01F0) >> 4; + displace = (insn & 0x0007) | ((insn & 0x0C00) >> 7) | + ((insn & 0x2000) >> 8); + if (memory.size <= 256) { + uint8_t z; + + z = cpu.r[Z_LO] + displace; + if (CHECK(z >= memory.size)) + fault("STD(ST) Z pointer outside of memory region"); + memory_store(z, cpu.r[reg]); + } else { + uint16_t z; + + z = getZ() + displace; + if (CHECK(z >= memory.size)) + fault("STD(ST) Z pointer outside of memory region"); + memory_store(z, cpu.r[reg]); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x8208: { /* std Y */ + uint8_t reg, displace; + + reg = (insn & 0x01F0) >> 4; + displace = (insn & 0x0007) | ((insn & 0x0C00) >> 7) | + ((insn & 0x2000) >> 8); + if (memory.size <= 256) { + uint8_t y; + + y = cpu.r[Y_LO] + displace; + if (CHECK(y >= memory.size)) + fault("STD(ST) Y pointer outside of memory region"); + memory_store(y, cpu.r[reg]); + } else { + uint16_t y; + + y = getY() + displace; + if (CHECK(y >= memory.size)) + fault("STD(ST) Y pointer outside of memory region"); + memory_store(y, cpu.r[reg]); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x8000: { /* ldd Z */ + uint8_t reg, displace; + + reg = (insn & 0x01F0) >> 4; + displace = (insn & 0x0007) | ((insn & 0x0C00) >> 7) | + ((insn & 0x2000) >> 8); + if (memory.size <= 256) { + uint8_t z; + + z = cpu.r[Z_LO] + displace; + if (CHECK(z >= memory.size)) + fault("LDD(LD) Z pointer outside of memory region"); + cpu.r[reg] = memory_load(z); + } else { + uint16_t z; + + z = getZ() + displace; + if (CHECK(z >= memory.size)) + fault("LDD(LD) Z pointer outside of memory region"); + cpu.r[reg] = memory_load(z); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x8008: { /* ldd Y */ + uint8_t reg, displace; + + reg = (insn & 0x01F0) >> 4; + displace = (insn & 0x0007) | ((insn & 0x0C00) >> 7) | + ((insn & 0x2000) >> 8); + if (memory.size <= 256) { + uint8_t y; + + y = cpu.r[Y_LO] + displace; + if (CHECK(y >= memory.size)) + fault("LDD(LD) Y pointer outside of memory region"); + cpu.r[reg] = memory_load(y); + } else { + uint16_t y; + + y = getY() + displace; + if (CHECK(y >= memory.size)) + fault("LDD(LD) Y pointer outside of memory region"); + cpu.r[reg] = memory_load(y); + } + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } } + + switch (insn & 0xFF00) { + case 0x0200: /* muls */ + fault("MULS is not implemented");//TODO + continue; + case 0x0100: { /* movw */ + uint8_t src, dst; + + src = ((insn & 0x000F) * 2); + dst = ((insn & 0x00F0) >> 4) * 2; + cpu.r[dst] = cpu.r[src]; + cpu.r[dst + 1] = cpu.r[src + 1]; + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9600: { /* adiw */ + uint8_t reg; + uint16_t orig, val, res; + + reg = (((insn & 0x0030) >> 4) * 2) + 24; + val = ((insn & 0x000F) | ((insn & 0x00C0) >> 2)); + orig = le16_to_cpu(*((le16_t *)&(cpu.r[reg]))); + res = orig + val; + *((le16_t *)&(cpu.r[reg])) = cpu_to_le16(res); + + lazy_flags_setup(INSN_ADIW, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9800: { /* cbi */ + uint8_t io, bit, old_val, new_val; + + bit = (insn & 0x0007); + io = (insn & 0x00F8) >> 3; + + pthread_spin_lock(&memory.io_lock); + old_val = memory.io[io]; + new_val = old_val & ~(1 << bit); + memory.io[io] = new_val; + memory_store_sideeffect_trigger(io + IO_MEM_OFFSET, + old_val, new_val); + pthread_spin_unlock(&memory.io_lock); + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9a00: { /* sbi */ + uint8_t io, bit, old_val, new_val; + + bit = (insn & 0x0007); + io = (insn & 0x00F8) >> 3; + + pthread_spin_lock(&memory.io_lock); + old_val = memory.io[io]; + new_val = old_val | (1 << bit); + memory.io[io] = new_val; + memory_store_sideeffect_trigger(io + IO_MEM_OFFSET, + old_val, new_val); + pthread_spin_unlock(&memory.io_lock); + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } + case 0x9900: { /* sbic */ + uint8_t io, bit; + + bit = (insn & 0x0007); + io = (insn & 0x00F8) >> 3; + if (memory.io[io] & (1 << bit)) { + cpu.pc++; + cpu.cycles_count++; + } else { + uint16_t next_insn = get_hi16_insn(); + + /* If the next insn is 32bit we skip two words. */ + if (is_two_word_instruction(next_insn)) { + cpu.pc += 3; + cpu.cycles_count += 3; + } else { + cpu.pc += 2; + cpu.cycles_count += 2; + } + } + + continue; + } + case 0x9b00: { /* sbis */ + uint8_t io, bit; + + bit = (insn & 0x0007); + io = (insn & 0x00F8) >> 3; + if (!(memory.io[io] & (1 << bit))) { + cpu.pc++; + cpu.cycles_count++; + } else { + uint16_t next_insn = get_hi16_insn(); + + /* If the next insn is 32bit we skip two words. */ + if (is_two_word_instruction(next_insn)) { + cpu.pc += 3; + cpu.cycles_count += 3; + } else { + cpu.pc += 2; + cpu.cycles_count += 2; + } + } + + continue; + } + case 0x9700: { /* sbiw */ + uint8_t reg; + uint16_t orig, val, res; + + reg = (((insn & 0x0030) >> 4) * 2) + 24; + val = ((insn & 0x000F) | ((insn & 0x00C0) >> 2)); + orig = le16_to_cpu(*((le16_t *)&(cpu.r[reg]))); + res = orig - val; + *((le16_t *)&(cpu.r[reg])) = cpu_to_le16(res); + + lazy_flags_setup(INSN_SBIW, orig, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count += 2; + continue; + } } + + switch (insn & 0xFC00) { + case 0x0400: { /* cpc */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd - rr - (uint8_t)sreg_get_C(); + + lazy_flags_setup(INSN_CPC, rd, rr, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x0800: { /* sbc */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd - rr - (uint8_t)sreg_get_C(); + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_SBC, rd, rr, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x0c00: { /* add */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd + rr; + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_ADD, rd, rr, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x1000: { /* cpse */ + uint8_t reg_d, reg_r, rd, rr; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + if (rd != rr) { + cpu.pc++; + cpu.cycles_count++; + } else { + uint16_t next_insn = get_hi16_insn(); + + /* If the next insn is 32bit we skip two words. */ + if (is_two_word_instruction(next_insn)) { + cpu.pc += 3; + cpu.cycles_count += 3; + } else { + cpu.pc += 2; + cpu.cycles_count += 2; + } + } + + continue; + } + case 0x1400: { /* cp */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd - rr; + + lazy_flags_setup(INSN_CP, rd, rr, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x1800: { /* sub */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd - rr; + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_SUB, rd, rr, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x1c00: { /* adc */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd + rr + (uint8_t)sreg_get_C(); + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_ADC, rd, rr, res, + SREG_H | SREG_S | SREG_V | SREG_N | SREG_Z | SREG_C); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x2000: { /* and */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd & rr; + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_AND, LAZY_NONE, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x2400: { /* eor */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd ^ rr; + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_EOR, LAZY_NONE, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x2800: { /* or */ + uint8_t reg_d, reg_r, rd, rr, res; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + rd = cpu.r[reg_d]; + rr = cpu.r[reg_r]; + res = rd | rr; + cpu.r[reg_d] = res; + + lazy_flags_setup(INSN_OR, LAZY_NONE, LAZY_NONE, res, + SREG_S | SREG_V | SREG_N | SREG_Z); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x2c00: { /* mov */ + uint8_t reg_d, reg_r; + + reg_d = (insn & 0x01F0) >> 4; + reg_r = (insn & 0x000F) | ((insn & 0x0200) >> 5); + cpu.r[reg_d] = cpu.r[reg_r]; + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0x9c00: /* mul */ + fault("MUL is not implemented");//TODO + continue; + case 0xf400: { /* brbc */ + uint8_t bit; + int8_t offset = 0; + bool set; + + bit = (insn & 0x0007); + if (insn & 0x0200) + offset = ~0x007F; + offset |= (insn & 0x03F8) >> 3; + + switch (bit) { + case SREG_C_BIT: + set = sreg_get_C(); + break; + case SREG_Z_BIT: + set = sreg_get_Z(); + break; + case SREG_N_BIT: + set = sreg_get_N(); + break; + case SREG_V_BIT: + set = sreg_get_V(); + break; + case SREG_S_BIT: + set = sreg_get_S(); + break; + case SREG_H_BIT: + set = sreg_get_H(); + break; + case SREG_T_BIT: + set = sreg_get_T(); + break; + case SREG_I_BIT: + set = sreg_get_I(); + break; + default: + fault("BRBC internal emulator bug"); + } + if (set) { + cpu.pc++; + cpu.cycles_count++; + } else { + cpu.pc += offset + 1; + cpu.cycles_count += 2; + } + + continue; + } + case 0xf000: { /* brbs */ + uint8_t bit; + int8_t offset = 0; + bool set; + + bit = (insn & 0x0007); + if (insn & 0x0200) + offset = ~0x007F; + offset |= (insn & 0x03F8) >> 3; + + switch (bit) { + case SREG_C_BIT: + set = sreg_get_C(); + break; + case SREG_Z_BIT: + set = sreg_get_Z(); + break; + case SREG_N_BIT: + set = sreg_get_N(); + break; + case SREG_V_BIT: + set = sreg_get_V(); + break; + case SREG_S_BIT: + set = sreg_get_S(); + break; + case SREG_H_BIT: + set = sreg_get_H(); + break; + case SREG_T_BIT: + set = sreg_get_T(); + break; + case SREG_I_BIT: + set = sreg_get_I(); + break; + default: + fault("BRBS internal emulator bug"); + } + if (set) { + cpu.pc += offset + 1; + cpu.cycles_count += 2; + } else { + cpu.pc++; + cpu.cycles_count++; + } + + continue; + } } + + switch (insn & 0xFE08) { + case 0xf800: { /* bld */ + uint8_t reg, bit, val; + + reg = (insn & 0x01F0) >> 4; + bit = (insn & 0x0007); + val = (uint8_t)sreg_get_T() << bit; + cpu.r[reg] &= ~(1 << bit); + cpu.r[reg] |= val; + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0xfa00: { /* bst */ + uint8_t reg, bit; + + reg = (insn & 0x01F0) >> 4; + bit = (insn & 0x0007); + sreg_set_T(!!(cpu.r[reg] & (1 << bit))); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0xfc00: { /* sbrc */ + uint8_t reg, bit; + + reg = (insn & 0x01F0) >> 4; + bit = (insn & 0x0007); + if (cpu.r[reg] & (1 << bit)) { + cpu.pc++; + cpu.cycles_count++; + } else { + uint16_t next_insn = get_hi16_insn(); + + /* If the next insn is 32bit we skip two words. */ + if (is_two_word_instruction(next_insn)) { + cpu.pc += 3; + cpu.cycles_count += 3; + } else { + cpu.pc += 2; + cpu.cycles_count += 2; + } + } + + continue; + } + case 0xfe00: { /* sbrs */ + uint8_t reg, bit; + + reg = (insn & 0x01F0) >> 4; + bit = (insn & 0x0007); + if (!(cpu.r[reg] & (1 << bit))) { + cpu.pc++; + cpu.cycles_count++; + } else { + uint16_t next_insn = get_hi16_insn(); + + /* If the next insn is 32bit we skip two words. */ + if (is_two_word_instruction(next_insn)) { + cpu.pc += 3; + cpu.cycles_count += 3; + } else { + cpu.pc += 2; + cpu.cycles_count += 2; + } + } + + continue; + } } + + switch (insn & 0xF800) { + case 0xb000: { /* in */ + uint8_t reg, io; + + reg = (insn & 0x01F0) >> 4; + io = (insn & 0x000F) | ((insn & 0x0600) >> 5); + + pthread_spin_lock(&memory.io_lock); + memory_load_sideeffect_trigger(io + IO_MEM_OFFSET); + cpu.r[reg] = memory.io[io]; + pthread_spin_unlock(&memory.io_lock); + + cpu.pc++; + cpu.cycles_count++; + continue; + } + case 0xb800: { /* out */ + uint8_t reg, io, old_val, new_val; + + reg = (insn & 0x01F0) >> 4; + io = (insn & 0x000F) | ((insn & 0x0600) >> 5); + + pthread_spin_lock(&memory.io_lock); + old_val = memory.io[io]; + new_val = cpu.r[reg]; + memory.io[io] = new_val; + memory_store_sideeffect_trigger(io + IO_MEM_OFFSET, + old_val, new_val); + pthread_spin_unlock(&memory.io_lock); + + cpu.pc++; + cpu.cycles_count++; + continue; + } } + + switch (insn & 0xFF88) { + case 0x0300: /* mulsu */ + fault("MULSU is not implemented");//TODO + continue; + case 0x0388: /* fmulsu */ + fault("FMULSU is not implemented");//TODO + continue; + case 0x0380: /* fmuls */ + fault("FMULS is not implemented");//TODO + continue; + case 0x0308: /* fmul */ + fault("FMUL is not implemented");//TODO + continue; + } + + fault("Unknown instruction 0x%04X\n", insn); + }; + + return NULL; +} + +static void cpu_io_write_sreg(uint16_t offset, uint8_t old_val, uint8_t new_val) +{ + cpu.sreg = new_val; + cpu.sreg_valid = 0xFF; +} + +static void cpu_io_read_sreg(uint16_t offset) +{ + memory.ram[offset] = cpu_get_sreg(); +} + +static int register_cpu_io_handlers(void) +{ + int err = 0; + + /* Register I/O handlers for CPU related I/O registers. */ + err |= register_io_write_handler(cpu_io_write_sreg, IO_SREG); + err |= register_io_read_handler(cpu_io_read_sreg, IO_SREG); + + if (err) + return -1; + return 0; +} + +static int cpu_interrupts_init(void) +{ + int i, err; + struct avr_irq_descriptor *desc, *cache; + + INIT_LIST_HEAD(&cpu.irqdesc_pending); + INIT_LIST_HEAD(&cpu.irqdesc_cache); + err = pthread_spin_init(&cpu.irq_lock, 0); + if (err) + return err; + + err = -ENOMEM; + cache = avr_malloc(sizeof(*cache) * MAX_IRQS_PENDING); + if (!cache) + goto err_spin_destroy; + cpu.irq_cache_p = cache; + for (i = 0; i < MAX_IRQS_PENDING; i++) { + desc = &(cache[i]); + + INIT_LIST_HEAD(&desc->list); + + list_add(&desc->list, &cpu.irqdesc_cache); + } + + return 0; +err_spin_destroy: + pthread_spin_destroy(&cpu.irq_lock); + return err; +} + +static void cpu_interrupts_exit(void) +{ + free(cpu.irq_cache_p); + pthread_spin_destroy(&cpu.irq_lock); +} + +int cpu_initialize(const char *bin, size_t bin_size) +{ + if (bin_size > active_setup.flash_size) { + fprintf(stderr, "Code is bigger than the " + "microcontroller flash size.\n"); + return -EINVAL; + } + code = avr_malloc(active_setup.flash_size); + if (!code) + return -ENOMEM; + memset(code, 0xFF, active_setup.flash_size); + memcpy(code, bin, bin_size); + cpu.initialized = 1; + + return 0; +} + +static void cpu_cleanup(void) +{ + if (cpu.running) + trap_cpu(CPU_TRAP_EXIT, 1); + if (cpu.initialized) { + cpu.initialized = 0; + cpu_interrupts_exit(); + pthread_cond_destroy(&cpu.trapped_cond); + pthread_mutex_destroy(&cpu.trap_mutex); + } +} + +int cpu_reset(void) +{ + int err; + + cpu_cleanup(); + + memset(&cpu, 0, sizeof(cpu)); + /* The application program will initialize the stack pointer. + * So we leave it zero. */ + + /* Registers are mapped to the start of MMIO space. */ + cpu.r = memory.ram; + /* We start at the reset vector. */ + cpu.pc = AVR_VECTOR_RESET; + /* Set the breakpoint so that it never triggers. */ + cpu.breakpoint = ~0; + /* Init lazy flags. Set all to valid zeros. */ + cpu.sreg = 0; + cpu.sreg_valid = 0xFF; + memset(&cpu.lazy_flag_cache, 0, sizeof(cpu.lazy_flag_cache)); + /* Initialize the CPU trap. */ + cpu.trap = CPU_TRAP_CONTINUE; + err = pthread_mutex_init(&cpu.trap_mutex, NULL); + if (err) + goto out_error; + err = pthread_cond_init(&cpu.trapped_cond, NULL); + if (err) + goto err_destr_trap; + err = register_cpu_io_handlers(); + if (err) + goto err_destr_cond; + err = cpu_interrupts_init(); + if (err) + goto err_destr_cond; + + /* Fire up the CPU! */ + cpu.running = 1; + err = pthread_create(&cpu_thread, NULL, cpu_thread_function, NULL); + if (err) { + cpu.running = 0; + perror("Failed to start CPU thread"); + goto err_irq_exit; + } + + return 0; + +err_irq_exit: + cpu_interrupts_exit(); +err_destr_cond: + pthread_cond_destroy(&cpu.trapped_cond); +err_destr_trap: + pthread_mutex_destroy(&cpu.trap_mutex); +out_error: + fprintf(stderr, "Failed to initialize the CPU data structure.\n"); + return -ENOMEM; +} + +void cpu_exit(void) +{ + cpu_cleanup(); + free(code); + code = NULL; +} diff --git a/emulator/cpu.h b/emulator/cpu.h new file mode 100644 index 0000000..6674ad6 --- /dev/null +++ b/emulator/cpu.h @@ -0,0 +1,177 @@ +#ifndef AVREMU_CPU_H_ +#define AVREMU_CPU_H_ + +#include "util.h" +#include "memory.h" + +#include +#include +#include +#include + + +#define AVR_VECTOR_RESET 0x000 + +/* Status register bits. */ +#define SREG_C_BIT 0 +#define SREG_C (1 << SREG_C_BIT) /* Carry */ +#define SREG_Z_BIT 1 +#define SREG_Z (1 << SREG_Z_BIT) /* Zero */ +#define SREG_N_BIT 2 +#define SREG_N (1 << SREG_N_BIT) /* Negative */ +#define SREG_V_BIT 3 +#define SREG_V (1 << SREG_V_BIT) /* Two's complement overflow */ +#define SREG_S_BIT 4 +#define SREG_S (1 << SREG_S_BIT) /* Sign */ +#define SREG_H_BIT 5 +#define SREG_H (1 << SREG_H_BIT) /* Half carry */ +#define SREG_T_BIT 6 +#define SREG_T (1 << SREG_T_BIT) /* Bit copy storage */ +#define SREG_I_BIT 7 +#define SREG_I (1 << SREG_I_BIT) /* Global IRQ enable */ + + +/* Instruction identifiers for lazy SREG flags evaluation. */ +enum lazy_flag_insn { + /* Instructions with 8-bit operands. */ + INSN_SBCI, + INSN_SUBI, + INSN_ORI, + INSN_ANDI, + INSN_CPI, + INSN_COM, + INSN_NEG, + INSN_DEC, + INSN_INC, + INSN_LSR, + INSN_ROR, + INSN_ASR, + INSN_CPC, + INSN_SBC, + INSN_ADD, + INSN_CP, + INSN_SUB, + INSN_ADC, + INSN_AND, + INSN_EOR, + INSN_OR, + + /* Instructions with 16-bit operands */ + INSN_ADIW, + INSN_SBIW, + + /* The total number of lazy flag instructions. */ + NR_LAZYFLAG_INSNS, + /* The first instruction with 16-bit operands. */ + FIRST_16BIT_INSN = INSN_ADIW, +}; + +/* Context data for fetching a lazy sreg flag. */ +struct lazy_flag { + union { + /* 8-bit operations */ + struct { + /* The operands to the instruction. */ + uint8_t op[2]; + /* The operation result. */ + uint8_t res; + }; + /* 16-bit operations */ + struct { + /* The operands to the instruction. */ + uint16_t op16[2]; + /* The operation result. */ + uint16_t res16; + }; + }; +}; + +struct avr_irq_descriptor { + struct list_head list; + /* The vector to jump to. */ + uint8_t vector; +}; +#define MAX_IRQS_PENDING 0xFF + +#define AVR_NR_REGS 32 + +/* The AVR CPU */ +struct avr_cpu { + /* Program counter. + * The PC addresses 16-bit words (not bytes). */ + uint32_t pc; + /* The PC where to break execution. */ + uint32_t breakpoint; + /* Array of general purpose registers */ + uint8_t *r; + + /* SREG flags bitmap. + * Look at sreg_valid bitmap to check whether a bit in "sreg" is valid. */ + uint8_t sreg; + /* Bitmap of the valid flags in the "sreg" bitmap. If a flag + * is invalid but needed it must be fetched lazyly. */ + uint8_t sreg_valid; + /* Per-SREG-flag array of pointers to lazy_flag data structures. */ + struct lazy_flag *sreg_lazy[8]; + + /* Per-instruction lazy_flag operand cache. */ + struct lazy_flag lazy_flag_cache[NR_LAZYFLAG_INSNS]; + + /* Used to trap the CPU at a safe point from another thread. */ + uint8_t trap; + pthread_mutex_t trap_mutex; + pthread_cond_t trapped_cond; + + /* Interrupts */ + uint8_t irqs_pending; + struct list_head irqdesc_pending; + struct list_head irqdesc_cache; + pthread_spinlock_t irq_lock; + void *irq_cache_p; /* Only used for freeing the data. */ + + bool initialized; + bool running; + + /* Statistics */ + + /* CPU cycle counter. */ + uint64_t cycles_count; + /* Interrupt counter. */ + uint64_t irq_count; +}; + +/* Pointer registers */ +#define X_LO 26 +#define X_HI 27 +#define Y_LO 28 +#define Y_HI 29 +#define Z_LO 30 +#define Z_HI 31 + + +/* Initialize and reset the CPU data structures. */ +int cpu_initialize(const char *bin, size_t bin_size); +void cpu_exit(void); + +int cpu_reset(void); + +void dump_cpu(FILE *fd); + +/* API for trapping and liberating the CPU from another thread. + * The CPU will be trapped before it fetches the next instruction. */ +enum cpu_trap_id { + CPU_TRAP_CONTINUE = 0, + CPU_TRAP_WAIT, + CPU_TRAP_SINGLESTEP, + CPU_TRAP_EXIT, /* Shutdown the CPU. */ +}; +int trap_cpu(uint8_t id, bool synchronize); + +/* Trigger an interrupt on the CPU. + * This will return -EBUSY if there are already + * MAX_IRQS_PENDING irqs pending. */ +int cpu_trigger_interrupt(uint8_t vector); + +extern struct avr_cpu cpu; + +#endif /* AVREMU_CPU_H_ */ diff --git a/emulator/devices/ports.c b/emulator/devices/ports.c new file mode 100644 index 0000000..7d278ff --- /dev/null +++ b/emulator/devices/ports.c @@ -0,0 +1,259 @@ +/* + * AVR emulator + * Ports + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ports.h" +#include "memory.h" +#include "util.h" + +#include +#include +#include +#include + + +enum port_voltage_level { + PORT_VOLT_TRISTATE = 0, + PORT_VOLT_HIGH, + PORT_VOLT_LOW, +}; + +struct port { + /* Spinlock for this port. */ + pthread_spinlock_t lock; + /* Data direction of the individual port bits. + * Bit-set means output. */ + uint8_t ddr; + /* PORTX register status. */ + uint8_t port; + /* Voltage level status on the physical pin. */ + enum port_voltage_level levels[8]; +}; + + +static struct port portb; +static struct port portc; +static struct port portd; + + +static void port_read(struct port *port, uint16_t mem_offset) +{ + pthread_spin_lock(&port->lock); + memory.ram[mem_offset] = port->port; + pthread_spin_lock(&port->lock); +} + +static void port_write(struct port *port, uint8_t new_val) +{ + pthread_spin_lock(&port->lock); + port->port = new_val; + //TODO + pthread_spin_unlock(&port->lock); +} + +static void ddr_read(struct port *port, uint16_t mem_offset) +{ + pthread_spin_lock(&port->lock); + memory.ram[mem_offset] = port->ddr; + pthread_spin_unlock(&port->lock); +} + +static void ddr_write(struct port *port, uint8_t new_val) +{ + pthread_spin_lock(&port->lock); + port->ddr = new_val; + pthread_spin_unlock(&port->lock); +} + +static void pin_read(struct port *port, uint16_t mem_offset) +{ + int i; + uint8_t mask, data = 0; + + pthread_spin_lock(&port->lock); + for (i = 0; i < 8; i++) { + mask = (1 << i); + if (unlikely(port->ddr & mask)) { + /* This is an output pin. */ + if (port->port & mask) + data |= mask; + } else { + /* This is an input pin. */ + switch (port->levels[i]) { + case PORT_VOLT_TRISTATE: + if (port->port & mask) + data |= mask; + break; + case PORT_VOLT_HIGH: + data |= mask; + break; + case PORT_VOLT_LOW: + break; + } + } + } + memory.ram[mem_offset] = data; + pthread_spin_lock(&port->lock); +} + +static void io_portb_write(uint16_t mem_offset, uint8_t old_val, uint8_t new_val) +{ + port_write(&portb, new_val); +} + +static void io_portc_write(uint16_t mem_offset, uint8_t old_val, uint8_t new_val) +{ + port_write(&portc, new_val); +} + +static void io_portd_write(uint16_t mem_offset, uint8_t old_val, uint8_t new_val) +{ + port_write(&portd, new_val); +} + +static void io_ddrb_write(uint16_t mem_offset, uint8_t old_val, uint8_t new_val) +{ + ddr_write(&portb, new_val); +} + +static void io_ddrc_write(uint16_t mem_offset, uint8_t old_val, uint8_t new_val) +{ + ddr_write(&portc, new_val); +} + +static void io_ddrd_write(uint16_t mem_offset, uint8_t old_val, uint8_t new_val) +{ + ddr_write(&portd, new_val); +} + +static void io_portb_read(uint16_t mem_offset) +{ + port_read(&portb, mem_offset); +} + +static void io_portc_read(uint16_t mem_offset) +{ + port_read(&portc, mem_offset); +} + +static void io_portd_read(uint16_t mem_offset) +{ + port_read(&portd, mem_offset); +} + +static void io_pinb_read(uint16_t mem_offset) +{ + pin_read(&portb, mem_offset); +} + +static void io_pinc_read(uint16_t mem_offset) +{ + pin_read(&portc, mem_offset); +} + +static void io_pind_read(uint16_t mem_offset) +{ + pin_read(&portd, mem_offset); +} + +static void io_ddrb_read(uint16_t mem_offset) +{ + ddr_read(&portb, mem_offset); +} + +static void io_ddrc_read(uint16_t mem_offset) +{ + ddr_read(&portc, mem_offset); +} + +static void io_ddrd_read(uint16_t mem_offset) +{ + ddr_read(&portd, mem_offset); +} + +static int port_init(struct port *port) +{ + int err; + + memset(port, 0, sizeof(struct port)); + err = pthread_spin_init(&port->lock, 0); + if (err) { + fprintf(stderr, "Ports: Failed to init spinlock\n"); + return err; + } + + return 0; +} + +static void port_cleanup(struct port *port) +{ + pthread_spin_destroy(&port->lock); +} + +int avrdev_ports_init(void) +{ + int err; + + err = port_init(&portb); + if (err) + goto error; + err = port_init(&portc); + if (err) + goto err_cleanup_portb; + err = port_init(&portd); + if (err) + goto err_cleanup_portc; + + err = 0; + err |= register_io_write_handler(io_portb_write, IO_PORTB); + err |= register_io_write_handler(io_portc_write, IO_PORTC); + err |= register_io_write_handler(io_portd_write, IO_PORTD); + err |= register_io_write_handler(io_ddrb_write, IO_DDRB); + err |= register_io_write_handler(io_ddrc_write, IO_DDRC); + err |= register_io_write_handler(io_ddrd_write, IO_DDRD); + err |= register_io_read_handler(io_portb_read, IO_PORTB); + err |= register_io_read_handler(io_portc_read, IO_PORTC); + err |= register_io_read_handler(io_portd_read, IO_PORTD); + err |= register_io_read_handler(io_pinb_read, IO_PINB); + err |= register_io_read_handler(io_pinc_read, IO_PINC); + err |= register_io_read_handler(io_pind_read, IO_PIND); + err |= register_io_read_handler(io_ddrb_read, IO_DDRB); + err |= register_io_read_handler(io_ddrc_read, IO_DDRC); + err |= register_io_read_handler(io_ddrd_read, IO_DDRD); + if (err) { + err = -ENOMEM; + fprintf(stderr, "Failed to register port handlers.\n"); + goto err_cleanup_portd; + } + + return 0; + +err_cleanup_portd: + port_cleanup(&portd); +err_cleanup_portc: + port_cleanup(&portc); +err_cleanup_portb: + port_cleanup(&portb); +error: + return err; +} + +void avrdev_ports_exit(void) +{//FIXME somebody must call me + port_cleanup(&portd); + port_cleanup(&portc); + port_cleanup(&portb); +} diff --git a/emulator/devices/ports.h b/emulator/devices/ports.h new file mode 100644 index 0000000..cd89f40 --- /dev/null +++ b/emulator/devices/ports.h @@ -0,0 +1,7 @@ +#ifndef AVREMU_DEVICES_PORTS_H_ +#define AVREMU_DEVICES_PORTS_H_ + +int avrdev_ports_init(void); +void avrdev_ports_exit(void); + +#endif /* AVREMU_DEVICES_PORTS_H_ */ diff --git a/emulator/devices/timers.c b/emulator/devices/timers.c new file mode 100644 index 0000000..2653852 --- /dev/null +++ b/emulator/devices/timers.c @@ -0,0 +1,28 @@ +/* + * AVR emulator + * Timer devices + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "memory.h" + + +int avrdev_timers_init(void) +{ + return 0; +} + +void avrdev_timers_exit(void) +{ +} diff --git a/emulator/devices/timers.h b/emulator/devices/timers.h new file mode 100644 index 0000000..a007135 --- /dev/null +++ b/emulator/devices/timers.h @@ -0,0 +1,7 @@ +#ifndef AVREMU_DEVICES_TIMERS_H_ +#define AVREMU_DEVICES_TIMERS_H_ + +int avrdev_timers_init(void); +void avrdev_timers_exit(void); + +#endif /* AVREMU_DEVICES_TIMERS_H_ */ diff --git a/emulator/devprocess.c b/emulator/devprocess.c new file mode 100644 index 0000000..1003839 --- /dev/null +++ b/emulator/devprocess.c @@ -0,0 +1,156 @@ +/* + * AVR emulator + * The device emulator subprocess + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "devprocess.h" +#include "hardware.h" +#include "cpu.h" + +#include +#include + + +/* Global pointer to the device instance from within the device process. */ +struct avremu_device_instance *avremu_dev; + + +static void device_cleanup(void) +{ + cpu_exit(); + dump_cpu(stdout); + avr_do_cleanup(); +} + +static void __attribute__((noreturn)) device_shutdown(int error) +{ + device_cleanup(); + sendnotify_to_host(avremu_dev, HOSTMSG_EXIT); + exit(error >= 0 ? error : -error); +} + +static void device_signal_handler(int signr) +{ + switch (signr) { + case SIGSEGV: + fprintf(stderr, "Segmentation fault\n"); + if (CHECK(1)) + fprintf(stderr, "This is probably a bug in the emulator.\n"); + else + fprintf(stderr, "This is either a bug in the running AVR " + "program or in the emulator software.\n"); + device_shutdown(1); + break; + case SIGTERM: + case SIGINT: + printf("Emulated device shutting down...\n"); + device_shutdown(0); + break; + default: + fprintf(stderr, "Device caught unknown signal %d\n", signr); + device_shutdown(ENOSYS); + } +} + +static int device_handle_msg_from_host(struct avrmsg_to_device *msg) +{ + char *data; + size_t size; + int err; + ipc_result_t result; + +//FIXME we should probably try to drain the buffer on error instead of exiting. + + switch (msg->msg) { + case DEVMSG_LOADFLASH: + size = msg->size; + data = avr_malloc(size); + if (!data) { + ipc_send_result(&avremu_dev->msg_from_host, -ENOMEM); + return 0; + } + ipc_send_result(&avremu_dev->msg_from_host, 0); + err = ipc_payload_receive(&avremu_dev->msg_from_host, data, size); + if (err) { + ipc_send_result(&avremu_dev->msg_from_host, err); + free(data); + return 0; + } + result = cpu_initialize(data, size); + free(data); + ipc_send_result(&avremu_dev->msg_from_host, result); + if (!result) + printf("CPU initialized\n"); + break; + case DEVMSG_RESET: + result = cpu_reset(); + ipc_send_result(&avremu_dev->msg_from_host, result); + if (!result) + printf("CPU reset\n"); + break; + } + + return 0; +} + +int avr_device_process_run(struct avremu_device_instance *dev) +{ + struct sigaction sa; + int err; + struct avrmsg_to_device msg; + sigset_t blockset; + + avremu_dev = dev; + + sa.sa_handler = device_signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + /* Block these signals while processing messages. */ + sigemptyset(&blockset); + sigaddset(&blockset, SIGTERM); + sigaddset(&blockset, SIGINT); + + err = avr_do_setup(dev->type); + if (err) + return err; + err = sendnotify_to_host(dev, HOSTMSG_READY); + if (err) + return -EIO; + + while (1) { + err = pollmsg_from_host(dev, &msg); + if (err == 0) + continue; + if (unlikely(err < 0)) { + fprintf(stderr, "Device message RX failed %d\n", err); + err = -EIO; + goto error; + } + pthread_sigmask(SIG_BLOCK, &blockset, NULL); + err = device_handle_msg_from_host(&msg); + pthread_sigmask(SIG_UNBLOCK, &blockset, NULL); + if (unlikely(err)) + goto error; + } + + return ENOTTY; +error: + device_cleanup(); + return err; +} diff --git a/emulator/devprocess.h b/emulator/devprocess.h new file mode 100644 index 0000000..4bdcf6a --- /dev/null +++ b/emulator/devprocess.h @@ -0,0 +1,9 @@ +#ifndef AVREMU_DEVPROCESS_H_ +#define AVREMU_DEVPROCESS_H_ + +#include "subprocess.h" + + +int avr_device_process_run(struct avremu_device_instance *dev); + +#endif /* AVREMU_DEVPROCESS_H_ */ diff --git a/emulator/hardware.c b/emulator/hardware.c new file mode 100644 index 0000000..ea3fb9e --- /dev/null +++ b/emulator/hardware.c @@ -0,0 +1,270 @@ +/* + * AVR emulator + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "hardware.h" +#include "memory.h" +#include "pinout.h" + +#include "devices/timers.h" +#include "devices/ports.h" + +#include +#include +#include + + +struct avr_setup active_setup; + + +static void init_iomap(struct avr_memory *mem) +{ + int i; + + for (i = 0; i < NR_VIRT_IO_PORTS; i++) + mem->physical_io_map[i] = IO_PORT_UNAVAILABLE; +} + +#undef IO +#define IO(virt, phys) mem->physical_io_map[IO_##virt] = (phys); + +static void mega8_io_setup(struct avr_memory *mem) +{ + init_iomap(mem); + IO(TWBR, 0x20); + IO(TWSR, 0x21); + IO(TWAR, 0x22); + IO(TWDR, 0x23); + IO(ADCL, 0x24); + IO(ADCH, 0x25); + IO(ADCSRA, 0x26); + IO(ADMUX, 0x27); + IO(ACSR, 0x28); + IO(UBRRL, 0x29); + IO(UCSRB, 0x2A); + IO(UCSRA, 0x2B); + IO(UDR, 0x2C); + IO(SPCR, 0x2D); + IO(SPSR, 0x2E); + IO(SPDR, 0x2F); + IO(PIND, 0x30); + IO(DDRD, 0x31); + IO(PORTD, 0x32); + IO(PINC, 0x33); + IO(DDRC, 0x34); + IO(PORTC, 0x35); + IO(PINB, 0x36); + IO(DDRB, 0x37); + IO(PORTB, 0x38); + IO(EECR, 0x3C); + IO(EEDR, 0x3D); + IO(EEARL, 0x3E); + IO(EEARH, 0x3F); + IO(UCSRC, 0x40); + IO(UBRRH, 0x40); + IO(WDTCR, 0x41); + IO(ASSR, 0x42); + IO(OCR2, 0x43); + IO(TCNT2, 0x44); + IO(TCCR2, 0x45); + IO(ICR1L, 0x46); + IO(ICR1H, 0x47); + IO(OCR1BL, 0x48); + IO(OCR1BH, 0x49); + IO(OCR1AL, 0x4A); + IO(OCR1AH, 0x4B); + IO(TCNT1L, 0x4C); + IO(TCNT1H, 0x4D); + IO(TCCR1B, 0x4E); + IO(TCCR1A, 0x4F); + IO(SFIOR, 0x50); + IO(OSCAL, 0x51); + IO(TCNT0, 0x52); + IO(TCCR0, 0x53); + IO(MCUCSR, 0x54); + IO(MCUCR, 0x55); + IO(TWCR, 0x56); + IO(SPMCR, 0x57); + IO(TIFR, 0x58); + IO(TIMSK, 0x59); + IO(GIFR, 0x5A); + IO(GICR, 0x5B); + IO(SPL, 0x5D); + IO(SPH, 0x5E); + IO(SREG, 0x5F); + + //TODO init the peripheral hardware. +} + +static void mega48_88_168_io_setup(struct avr_memory *mem) +{ + init_iomap(mem); + IO(PINB, 0x23); + IO(DDRB, 0x24); + IO(PORTB, 0x25); + IO(PINC, 0x26); + IO(DDRC, 0x27); + IO(PORTC, 0x28); + IO(PIND, 0x29); + IO(DDRD, 0x2A); + IO(PORTD, 0x2B); + IO(TIFR0, 0x35); + IO(TIFR1, 0x36); + IO(TIFR2, 0x37); + IO(PCIFR, 0x3B); + IO(EIFR, 0x3C); + IO(EIMSK, 0x3D); + IO(GPIOR0, 0x3E); + IO(EECR, 0x3F); + IO(EEDR, 0x40); + IO(EEARL, 0x41); + IO(EEARH, 0x42); + IO(GTCCR, 0x43); + IO(TCCR0A, 0x44); + IO(TCCR0B, 0x45); + IO(TCNT0, 0x46); + IO(OCR0A, 0x47); + IO(OCR0B, 0x48); + IO(GPIOR1, 0x4A); + IO(GPIOR2, 0x4B); + IO(SPCR, 0x4C); + IO(SPSR, 0x4D); + IO(SPDR, 0x4E); + IO(ACSR, 0x50); + IO(SMCR, 0x53); + IO(MCUSR, 0x54); + IO(MCUCR, 0x55); + IO(SPMCSR, 0x57); + IO(SPL, 0x5D); + IO(SPH, 0x5E); + IO(SREG, 0x5F); + IO(WDRCSR, 0x60); + IO(CLKPR, 0x61); + IO(PRR, 0x64); + IO(OSCAL, 0x66); + IO(PCICR, 0x68); + IO(EICRA, 0x69); + IO(PCMSK0, 0x6B); + IO(PCMSK1, 0x6C); + IO(PCMSK2, 0x6D); + IO(TIMSK0, 0x6E); + IO(TIMSK1, 0x6F); + IO(TIMSK2, 0x70); + IO(ADCL, 0x78); + IO(ADCH, 0x79); + IO(ADCSRA, 0x7A); + IO(ADCSRB, 0x7B); + IO(ADMUX, 0x7C); + IO(DIDR0, 0x7E); + IO(DIDR1, 0x7F); + IO(TCCR1A, 0x80); + IO(TCCR1B, 0x81); + IO(TCCR1C, 0x82); + IO(TCNT1L, 0x84); + IO(TCNT1H, 0x85); + IO(ICR1L, 0x86); + IO(ICR1H, 0x87); + IO(OCR1AL, 0x88); + IO(OCR1AH, 0x89); + IO(OCR1BL, 0x8A); + IO(OCR1BH, 0x8B); + IO(TCCR2A, 0xB0); + IO(TCCR2B, 0xB1); + IO(TCNT2, 0xB2); + IO(OCR2A, 0xB3); + IO(OCR2B, 0xB4); + IO(ASSR, 0xB6); + IO(TWBR, 0xB8); + IO(TWSR, 0xB9); + IO(TWAR, 0xBA); + IO(TWDR, 0xBB); + IO(TWCR, 0xBC); + IO(TWAMR, 0xBD); + IO(UCSR0A, 0xC0); + IO(UCSR0B, 0xC1); + IO(UCSR0C, 0xC2); + IO(UBRR0L, 0xC4); + IO(UBRR0H, 0xC5); + IO(UDR0, 0xC6); + + //TODO init the peripheral hardware. + avrdev_timers_init(); + avrdev_ports_init(); +} + +#undef IO + +#define kiB * 1024 +#define Mhz * 1000000 +static const struct avr_setup setup_list[] = { + [AVR_ATMEGA8] = { + .type = AVR_ATMEGA8, + .pinout = &atmega8_pinout, + .memory_size = 0x460, + .sram_offset = 0x60, + .sp_offset = 0x3D, + .pc_22bit = 0, + .flash_size = 8 kiB, + .eeprom_size = 512, + .max_hz = 16 Mhz, + .io_setup = mega8_io_setup, + }, + [AVR_ATMEGA168] = { + .type = AVR_ATMEGA168, + .pinout = &atmega48_88_168_pinout, + .memory_size = 0x500, + .sram_offset = 0x100, + .sp_offset = 0x3D, + .pc_22bit = 0, + .flash_size = 16 kiB, + .eeprom_size = 512, + .max_hz = 20 Mhz, + .io_setup = mega48_88_168_io_setup, + }, +}; + +void avr_do_cleanup(void) +{ + memory_cleanup(); + free(active_setup.memory_data); +} + +int avr_do_setup(enum avr_setup_type type) +{ + int err; + + break_linkage_on(ARRAY_SIZE(setup_list) != AVR_NR_TYPES, + setup_not_defined_for_all_architectures); + + if (type >= ARRAY_SIZE(setup_list)) { + fprintf(stderr, "Unsupported architecture\n"); + return -ENODEV; + } + memcpy(&active_setup, &(setup_list[type]), sizeof(active_setup)); + active_setup.memory_data = malloc(active_setup.memory_size); + if (!active_setup.memory_data) { + fprintf(stderr, "Out of memory\n"); + return -ENOMEM; + } + err = memory_initialize(); + if (err) + goto err_free_mem; + memset(&active_pinout_handlers, 0, sizeof(active_pinout_handlers)); + + return 0; +err_free_mem: + free(active_setup.memory_data); + return err; +} diff --git a/emulator/hardware.h b/emulator/hardware.h new file mode 100644 index 0000000..c5257eb --- /dev/null +++ b/emulator/hardware.h @@ -0,0 +1,57 @@ +#ifndef AVREMU_HARDWARE_H_ +#define AVREMU_HARDWARE_H_ + +#include "util.h" +#include "pinout.h" + +#include + + +struct avr_memory; +typedef void (*avr_io_setup_func_t)(struct avr_memory *mem); + + + +/* The type of the microcontroller to emulate. */ +enum avr_setup_type { + AVR_ATMEGA8, + AVR_ATMEGA168, + //TODO + AVR_NR_TYPES, +}; + +struct avr_setup { + /* The device type */ + enum avr_setup_type type; + /* Physical device pinout configuration. */ + const struct avr_device_pinout *pinout; + /* Size of the memory data space including + * the register file, IO, MMIO and SRAM. */ + size_t memory_size; + /* The offset where the general purpose SRAM starts. */ + size_t sram_offset; + /* The offset of the stack pointer in the IO space. */ + uint8_t sp_offset; + /* Using a 22 bit PC */ + bool pc_22bit; + /* Size of the flash memory. */ + size_t flash_size; + /* Size of the EEPROM memory. */ + size_t eeprom_size; + /* Maximum CPU frequency in HZ. */ + size_t max_hz; + /* Function to setup the virtual->physical IO map + * and initialize the peripheral IO devices. */ + avr_io_setup_func_t io_setup; + + /* Pointer to the memory data space (regs, IO, MMIO, SRAM) */ + uint8_t *memory_data;//FIXME this should be moved to struct avr_memory. +}; + +extern struct avr_setup active_setup; + +int avr_do_setup(enum avr_setup_type type); +void avr_do_cleanup(void); + + +#endif /* AVREMU_HARDWARE_H_ */ diff --git a/emulator/install.py b/emulator/install.py new file mode 100755 index 0000000..65a756c --- /dev/null +++ b/emulator/install.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +import sys +from shutil import * +from dircache import * + +bases = ( "/", "/usr", "/usr/local", "/opt" ) +instfiles = ( "pyrazer.so", ) + + +def usage(): + print "Usage: %s MOD_SRC_DIR" % sys.argv[0] + +try: + srcdir = sys.argv[1] +except IndexError: + usage() + sys.exit(1) + +pyver = sys.version.split()[0] # pyver == "X.X.X" +pyver = pyver.split(".") +major = pyver[0] +minor = pyver[1] +pydir = "python%s.%s" % (major, minor) +modpath = "/lib/" + pydir + "/site-packages" + +for base in bases: + try: + if not pydir in listdir(base + "/lib"): + continue + full_modpath = base + modpath + # Probe whether it exists + listdir(full_modpath) + except OSError: + continue + print "Python module path found in " + full_modpath + try: + for f in instfiles: + copy(srcdir + "/" + f, full_modpath) + print "Installed \"%s\"" % f + except IOError, e: + print "ERROR: Could not install module \"%s\"" % f + print e + sys.exit(1) + sys.exit(0) + + +print "ERROR: Python module install path not found." +print "Python modules are usually found in /usr/lib/pythonX.X/site-packages" +sys.exit(1) diff --git a/emulator/ipc.c b/emulator/ipc.c new file mode 100644 index 0000000..8131507 --- /dev/null +++ b/emulator/ipc.c @@ -0,0 +1,219 @@ +/* + * AVR emulator + * Interprocess communication + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "ipc.h" + +#include +#include +#include + + +#define IPC_POLL_MAX_USECS 10000 + + +int ipc_pipe_create(struct ipc_tx *tx, struct ipc_rx *rx) +{ + int message_pipe[2]; + int result_pipe[2]; + int err; + + err = pipe(message_pipe); + if (err) { + perror("Create IPC message pipe"); + return err; + } + err = pipe(result_pipe); + if (err) { + perror("Create IPC result pipe"); + close(message_pipe[0]); + close(message_pipe[1]); + return err; + } + tx->message_pipe = message_pipe[1]; + rx->message_pipe = message_pipe[0]; + tx->result_pipe = result_pipe[0]; + rx->result_pipe = result_pipe[1]; + + return 0; +} + +void ipc_pipe_destroy_tx(struct ipc_tx *tx) +{ + if (tx->message_pipe != -1) { + close(tx->message_pipe); + tx->message_pipe = -1; + } + if (tx->result_pipe != -1) { + close(tx->result_pipe); + tx->result_pipe = -1; + } +} + +void ipc_pipe_destroy_rx(struct ipc_rx *rx) +{ + if (rx->message_pipe != -1) { + close(rx->message_pipe); + rx->message_pipe = -1; + } + if (rx->result_pipe != -1) { + close(rx->result_pipe); + rx->result_pipe = -1; + } +} + +void ipc_pipe_destroy(struct ipc_tx *tx, struct ipc_rx *rx) +{ + ipc_pipe_destroy_tx(tx); + ipc_pipe_destroy_rx(rx); +} + +int ipc_dup2(struct ipc_tx *tx, int fd) +{ + return dup2(tx->message_pipe, fd); +} + +int ipc_message_send(struct ipc_tx *tx, + const void *message, ipc_size_t msg_size, + bool read_result) +{ + ssize_t res; + ipc_result_t result_code; + + res = write(tx->message_pipe, &msg_size, sizeof(msg_size)); + if (unlikely(res != sizeof(msg_size))) + return -EIO; + + res = write(tx->message_pipe, message, msg_size); + if (unlikely(res != msg_size)) + return -EIO; + + if (read_result) { + res = read(tx->result_pipe, &result_code, sizeof(result_code)); + if (unlikely(res != sizeof(result_code))) + return -EIO; + + return result_code; + } + + return 0; +} + +int ipc_message_poll(struct ipc_rx *rx, + void *message_buf, ipc_size_t buf_size) +{ + ssize_t res; + fd_set fdset; + ipc_size_t msg_size; + struct timeval tv = { + .tv_sec = 0, + .tv_usec = IPC_POLL_MAX_USECS, + }; + + FD_ZERO(&fdset); + FD_SET(rx->message_pipe, &fdset); + res = select(FD_SETSIZE, &fdset, NULL, NULL, &tv); + if (res <= 0) { + /* Timeout. No data. */ + return 0; + } + + res = read(rx->message_pipe, &msg_size, sizeof(msg_size)); + if (unlikely(res != sizeof(msg_size))) + return -EIO; + if (unlikely(msg_size > buf_size)) + return -ENOBUFS; + res = read(rx->message_pipe, message_buf, msg_size); + if (unlikely(res != msg_size)) + return -EIO; + + return msg_size; +} + +int ipc_raw_line_poll(struct ipc_rx *rx, + char *line_buf, size_t buf_size) +{ + ssize_t res; + fd_set fdset; + struct timeval tv = { + .tv_sec = 0, + .tv_usec = IPC_POLL_MAX_USECS, + }; + size_t i; + char c; + + FD_ZERO(&fdset); + FD_SET(rx->message_pipe, &fdset); + res = select(FD_SETSIZE, &fdset, NULL, NULL, &tv); + if (res <= 0) { + /* Timeout. No data. */ + return 0; + } + + for (i = 0; ; i++) { + res = read(rx->message_pipe, &c, sizeof(c)); + if (res != sizeof(c)) + break; + if (unlikely(i >= buf_size - 1)) + return -ENOBUFS; + line_buf[i] = c; + if (c == '\n') { + line_buf[i + 1] = '\0'; + break; + } + } + + return i; +} + +int ipc_send_result(struct ipc_rx *rx, ipc_result_t result_code) +{ + ssize_t res; + + res = write(rx->result_pipe, &result_code, sizeof(result_code)); + if (unlikely(res != sizeof(result_code))) + return -EIO; + + return 0; +} + +int ipc_payload_send(struct ipc_tx *tx, const void *buf, size_t size) +{ + ssize_t res; + ipc_result_t result_code; + + res = write(tx->message_pipe, buf, size); + if (unlikely(res != size)) + return -EIO; + + res = read(tx->result_pipe, &result_code, sizeof(result_code)); + if (unlikely(res != sizeof(result_code))) + return -EIO; + + return result_code; +} + +int ipc_payload_receive(struct ipc_rx *rx, void *buf, size_t size) +{ + ssize_t res; + + res = read(rx->message_pipe, buf, size); + if (unlikely(res != size)) + return -EIO; + + return 0; +} + diff --git a/emulator/ipc.h b/emulator/ipc.h new file mode 100644 index 0000000..eb0cb5c --- /dev/null +++ b/emulator/ipc.h @@ -0,0 +1,62 @@ +#ifndef AVREMU_IPC_H_ +#define AVREMU_IPC_H_ + +#include "util.h" + +#include + + +/* The sender side of an IPC connection. */ +struct ipc_tx { + /* Filedescriptor for the transmit side of the message pipe. */ + int message_pipe; + /* Filedescriptor for the result message pipe. */ + int result_pipe; +}; + +/* The receiver side of an IPC connection. */ +struct ipc_rx { + /* Filedescriptor for the receive side of the message pipe. */ + int message_pipe; + /* Filedescriptor for the result message pipe. */ + int result_pipe; +}; + +/* Type of a message result code. */ +typedef uint16_t ipc_result_t; +/* Type of a message size descriptor. */ +typedef uint16_t ipc_size_t; + + +/* Create a communication pipe. */ +int ipc_pipe_create(struct ipc_tx *tx, struct ipc_rx *rx); +void ipc_pipe_destroy_tx(struct ipc_tx *tx); +void ipc_pipe_destroy_rx(struct ipc_rx *rx); +void ipc_pipe_destroy(struct ipc_tx *tx, struct ipc_rx *rx); + +/* Map a given file descriptor to an IPC pipe. */ +int ipc_dup2(struct ipc_tx *tx, int fd); + +/* Send a message through the IPC pipe. + * The result (if requested) is returned as a positive number. + * If an IPC error occured, a negative number is returned. */ +int ipc_message_send(struct ipc_tx *tx, + const void *message, ipc_size_t msg_size, + bool read_result); + +/* Poll a message pipe to receive the next message. */ +int ipc_message_poll(struct ipc_rx *rx, + void *message_buf, ipc_size_t buf_size); + +/* Poll a raw line of data from the pipe. */ +int ipc_raw_line_poll(struct ipc_rx *rx, + char *line_buf, size_t buf_size); + +/* Send the result for a message. */ +int ipc_send_result(struct ipc_rx *rx, ipc_result_t result_code); + +/* Payload data transmission. */ +int ipc_payload_send(struct ipc_tx *tx, const void *buf, size_t size); +int ipc_payload_receive(struct ipc_rx *rx, void *buf, size_t size); + +#endif /* AVREMU_IPC_H_ */ diff --git a/emulator/list.h b/emulator/list.h new file mode 100644 index 0000000..110c0bd --- /dev/null +++ b/emulator/list.h @@ -0,0 +1,296 @@ +/* + * Copied from the Linux kernel source tree, version 2.6.0-test1. + * + * Licensed under the GPL v2 as per the whole kernel source tree. + * + */ + +#ifndef _LIST_H +#define _LIST_H + + +#define typeof __typeof__ +#define offsetof(type, member) __builtin_offsetof (type, member) + +/** + * container_of - cast a member of a structure out to the containing structure + * + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +/* + * These are non-NULL pointers that will result in page faults + * under normal circumstances, used to verify that nobody uses + * non-initialized list entries. + */ +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + +#ifdef RUNTIME_SANITY_CHECKS + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +#endif +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +#endif /* _LIST_H */ diff --git a/emulator/memory.c b/emulator/memory.c new file mode 100644 index 0000000..3c5d782 --- /dev/null +++ b/emulator/memory.c @@ -0,0 +1,118 @@ +/* + * AVR emulator + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "memory.h" +#include "util.h" +#include "cpu.h" +#include "hardware.h" + +#include +#include +#include + + +/* The memory (SRAM and MMIO). */ +struct avr_memory memory; + + +int register_io_write_handler(io_write_handler_t handler, enum avr_io_port virt) +{ + uint16_t phys; + + if (virt >= NR_VIRT_IO_PORTS) + goto unavailable; + phys = io_virt_to_phys(virt); + if (phys == IO_PORT_UNAVAILABLE) + goto unavailable; + phys -= IO_MEM_OFFSET; + if (memory.io_write_handlers[phys]) + goto busy; + memory.io_write_handlers[phys] = handler; + + return 0; +unavailable: + fprintf(stderr, "IO-write: Tried to register handler for unavailable IO port\n"); + return -EEXIST; +busy: + fprintf(stderr, "IO-write: Tried to register an IO handler twice\n"); + return -EALREADY; +} + +int register_io_read_handler(io_read_handler_t handler, enum avr_io_port virt) +{ + uint16_t phys; + + if (virt >= NR_VIRT_IO_PORTS) + goto unavailable; + phys = io_virt_to_phys(virt); + if (phys == IO_PORT_UNAVAILABLE) + goto unavailable; + phys -= IO_MEM_OFFSET; + if (memory.io_read_handlers[phys]) + goto busy; + memory.io_read_handlers[phys] = handler; + + return 0; +unavailable: + fprintf(stderr, "IO-read: Tried to register handler for unavailable IO port\n"); + return -EEXIST; +busy: + fprintf(stderr, "IO-read: Tried to register an IO handler twice\n"); + return -EALREADY; +} + +void memory_cleanup(void) +{ + //TODO +} + +int memory_initialize(void) +{ + int err; + size_t nr_io; + + memory.ram = active_setup.memory_data; + memory.size = active_setup.memory_size; + memory.io = memory.ram + IO_MEM_OFFSET; + memory.sram_offset = active_setup.sram_offset; + err = pthread_spin_init(&memory.io_lock, 0); + if (err) { + fprintf(stderr, "Failed to init the I/O spinlock\n"); + return -ENOMEM; + } + memory.sp_offset = active_setup.sp_offset; + + nr_io = active_setup.sram_offset - IO_MEM_OFFSET; + memory.iospace_size = nr_io; + + memory.io_write_handlers = malloc(sizeof(io_write_handler_t) * nr_io); + if (!memory.io_write_handlers) { + fprintf(stderr, "Failed to alloc IO write handlers\n"); + return -ENOMEM; + } + memset(memory.io_write_handlers, 0, sizeof(io_write_handler_t) * nr_io); + + memory.io_read_handlers = malloc(sizeof(io_read_handler_t) * nr_io); + if (!memory.io_read_handlers) { + fprintf(stderr, "Failed to alloc IO read handlers\n"); + return -ENOMEM; + } + memset(memory.io_read_handlers, 0, sizeof(io_read_handler_t) * nr_io); + + active_setup.io_setup(&memory); + + return 0; +} diff --git a/emulator/memory.h b/emulator/memory.h new file mode 100644 index 0000000..ba30999 --- /dev/null +++ b/emulator/memory.h @@ -0,0 +1,225 @@ +#ifndef AVREMU_MEMORY_H_ +#define AVREMU_MEMORY_H_ + +#include "util.h" + +#include +#include + + +typedef void (*io_write_handler_t)(uint16_t mem_offset, uint8_t old_val, uint8_t new_val); +typedef void (*io_read_handler_t)(uint16_t mem_offset); + + +/* ==== MEMORY SPACE LOCKING ==== + * + * Accessing the register file: + * From inside of the CPU the register file can + * be accessed without taking any locks. + * From outside of the CPU, the CPU must first be + * trapped to synchronize the register file. + * + * Accessing the conventional IO space or the MMIO space: + * The io_lock has to be acquired from any context. + * + * Accessing the SRAM memory space: + * From inside of the CPU the SRAM can + * be accessed without taking any locks. + * From outside of the CPU, the CPU must first be + * trapped to synchronize the SRAM. + * + * Exceptions: + * The Stack Pointer is accessed locklessly from inside + * of the CPU. From outside of the CPU, the CPU must first + * be trapped to synchronize and lock the stack pointer data. + */ + + +/* Abtract I/O port identifiers. */ +enum avr_io_port { + IO_TWBR, + IO_TWSR, + IO_TWAR, + IO_TWDR, + IO_ADCL, + IO_ADCH, + IO_ADCSRA, + IO_ADCSRB, + IO_ADMUX, + IO_ADCSR, + IO_UBRRL, + IO_UCSRB, + IO_UCSRA, + IO_UDR, + IO_SPCR, + IO_SPSR, + IO_SPDR, + IO_PIND, + IO_DDRD, + IO_PORTD, + IO_PINC, + IO_DDRC, + IO_PORTC, + IO_PINB, + IO_DDRB, + IO_PORTB, + IO_EECR, + IO_EEDR, + IO_EEARL, + IO_EEARH, + IO_UCSRC, + IO_UBRRH, + IO_WDTCR, + IO_ASSR, + IO_OCR2, + IO_TCNT2, + IO_TCCR2, + IO_ICR1L, + IO_ICR1H, + IO_OCR1BL, + IO_OCR1BH, + IO_OCR1AL, + IO_OCR1AH, + IO_TCNT1L, + IO_TCNT1H, + IO_TCCR1B, + IO_TCCR1A, + IO_SFIOR, + IO_OSCAL, + IO_TCNT0, + IO_TCCR0, + IO_MCUCSR, + IO_MCUCR, + IO_TWCR, + IO_SPMCR, + IO_TIFR, + IO_TIFR0, + IO_TIFR1, + IO_TIFR2, + IO_TIMSK, + IO_GIFR, + IO_GICR, + IO_SPL, + IO_SPH, + IO_SREG, + IO_PCIFR, + IO_EIFR, + IO_EIMSK, + IO_GPIOR0, + IO_GTCCR, + IO_TCCR0A, + IO_TCCR0B, + IO_OCR0A, + IO_OCR0B, + IO_GPIOR1, + IO_GPIOR2, + IO_ACSR, + IO_SMCR, + IO_MCUSR, + IO_SPMCSR, + IO_WDRCSR, + IO_CLKPR, + IO_PRR, + IO_PCICR, + IO_EICRA, + IO_PCMSK0, + IO_PCMSK1, + IO_PCMSK2, + IO_TIMSK0, + IO_TIMSK1, + IO_TIMSK2, + IO_DIDR0, + IO_DIDR1, + IO_TCCR1C, + IO_TCCR2A, + IO_TCCR2B, + IO_OCR2A, + IO_OCR2B, + IO_TWAMR, + IO_UCSR0A, + IO_UCSR0B, + IO_UCSR0C, + IO_UBRR0L, + IO_UBRR0H, + IO_UDR0, + NR_VIRT_IO_PORTS, +}; + +struct avr_memory { + /* The whole memory area. */ + uint8_t *ram; + /* Size of the whole memory region. */ + size_t size; + /* Conventional IO space. */ + uint8_t *io; + /* Size of the IO and MMIO space. */ + size_t iospace_size; + /* The offset where the general purpose SRAM starts. */ + size_t sram_offset; + + /* Lock for the IO and MMIO space. */ + pthread_spinlock_t io_lock; + + /* The offset of the stack pointer in the IO space. */ + uint8_t sp_offset; + + /* Array of I/O access handlers. One for each I/O and MMIO address. */ + io_write_handler_t *io_write_handlers; + io_read_handler_t *io_read_handlers; + + /* Map array for mapping abstract IO port IDs to their + * physical hardware address. */ + uint16_t physical_io_map[NR_VIRT_IO_PORTS]; +}; + +/* Value for the physical_io_map when the port doesn't exist. */ +#define IO_PORT_UNAVAILABLE 0xFFFF + +/* IO memory always starts at the 32th byte. */ +#define IO_MEM_OFFSET 32 + +extern struct avr_memory memory; + + +/* Map a virtual IO cookie to its physical address in the AVR memory space. */ +static inline uint16_t io_virt_to_phys(enum avr_io_port virt) +{ + return memory.physical_io_map[virt]; +} + +static inline bool mem_offset_in_io_region(uint16_t offset) +{ + return ((offset >= IO_MEM_OFFSET) && (offset < memory.sram_offset)); +} + +static inline void memory_lock(uint16_t offset, uint8_t *lock_flags) +{ + if (mem_offset_in_io_region(offset)) { + pthread_spin_lock(&memory.io_lock); + *lock_flags = 1; + return; + } + *lock_flags = 0; +} + +static inline void memory_unlock(uint8_t lock_flags) +{ + if (lock_flags) + pthread_spin_unlock(&memory.io_lock); +} + + +/* Register a handler for an I/O register access. + * Note that the handler will be called from CPU thread context! + * That means it will run in concurrency to the main worker process thread. + * Use locking. */ +int register_io_write_handler(io_write_handler_t handler, enum avr_io_port virt); +int register_io_read_handler(io_read_handler_t handler, enum avr_io_port virt); + + +int memory_initialize(void); +void memory_cleanup(void); + + + +#endif /* AVREMU_MEMORY_H_ */ diff --git a/emulator/pinout.c b/emulator/pinout.c new file mode 100644 index 0000000..47499ad --- /dev/null +++ b/emulator/pinout.c @@ -0,0 +1,129 @@ +/* + * AVR emulator + * Device pinout + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "pinout.h" +#include "util.h" + + +#undef p +#undef PIN +#define p(pinname) PINOUT_##pinname +#define PIN(_number, ...) \ + [_number - 1] = { \ + .number = _number, \ + .funcs = { __VA_ARGS__ }, \ + } + +static struct avr_hwpin atmega48_88_168_pins[] = { + PIN(1, p(PC6), p(RESET), p(PCINT14)), + PIN(2, p(PD0), p(RXD), p(PCINT16)), + PIN(3, p(PD1), p(TXD), p(PCINT17)), + PIN(4, p(PD2), p(INT0), p(PCINT18)), + PIN(5, p(PD3), p(INT1), p(OC2B), p(PCINT19)), + PIN(6, p(PD4), p(T0), p(XCK), p(PCINT20)), + PIN(7, p(VCC)), + PIN(8, p(GND)), + PIN(9, p(PB6), p(TOSC1), p(XTAL1), p(PCINT6)), + PIN(10, p(PB7), p(TOSC2), p(XTAL2), p(PCINT7)), + PIN(11, p(PD5), p(T1), p(OC0B), p(PCINT21)), + PIN(12, p(PD6), p(AIN0), p(OC0A), p(PCINT22)), + PIN(13, p(PD7), p(AIN1), p(PCINT23)), + PIN(14, p(PB0), p(ICP1), p(CLKO), p(PCINT0)), + PIN(15, p(PB1), p(OC1A), p(PCINT1)), + PIN(16, p(PB2), p(SS), p(OC1B), p(PCINT2)), + PIN(17, p(PB3), p(MOSI), p(OC2A), p(PCINT3)), + PIN(18, p(PB4), p(MISO), p(PCINT4)), + PIN(19, p(PB5), p(SCK), p(PCINT5)), + PIN(20, p(AVCC)), + PIN(21, p(AREF)), + PIN(22, p(GND)), + PIN(23, p(PC0), p(ADC0), p(PCINT8)), + PIN(24, p(PC1), p(ADC1), p(PCINT9)), + PIN(25, p(PC2), p(ADC2), p(PCINT10)), + PIN(26, p(PC3), p(ADC3), p(PCINT11)), + PIN(27, p(PC4), p(ADC4), p(SDA), p(PCINT12)), + PIN(28, p(PC5), p(ADC5), p(SCL), p(PCINT13)), +}; + +struct avr_device_pinout atmega48_88_168_pinout = { + .pins = atmega48_88_168_pins, + .nr_pins = ARRAY_SIZE(atmega48_88_168_pins), +}; + + +static struct avr_hwpin atmega8_pins[] = { + PIN(1, p(PC6), p(RESET)), + PIN(2, p(PD0), p(RXD)), + PIN(3, p(PD1), p(TXD)), + PIN(4, p(PD2), p(INT0)), + PIN(5, p(PD3), p(INT1)), + PIN(6, p(PD4), p(T0)), + PIN(7, p(VCC)), + PIN(8, p(GND)), + PIN(9, p(PB6), p(TOSC1), p(XTAL1)), + PIN(10, p(PB7), p(TOSC2), p(XTAL2)), + PIN(11, p(PD5), p(T1)), + PIN(12, p(PD6), p(AIN0)), + PIN(13, p(PD7), p(AIN1)), + PIN(14, p(PB0), p(ICP1)), + PIN(15, p(PB1), p(OC1A)), + PIN(16, p(PB2), p(SS), p(OC1B)), + PIN(17, p(PB3), p(MOSI), p(OC2)), + PIN(18, p(PB4), p(MISO)), + PIN(19, p(PB5), p(SCK)), + PIN(20, p(AVCC)), + PIN(21, p(AREF)), + PIN(22, p(GND)), + PIN(23, p(PC0), p(ADC0)), + PIN(24, p(PC1), p(ADC1)), + PIN(25, p(PC2), p(ADC2)), + PIN(26, p(PC3), p(ADC3)), + PIN(27, p(PC4), p(ADC4), p(SDA)), + PIN(28, p(PC5), p(ADC5), p(SCL)), +}; + +struct avr_device_pinout atmega8_pinout = { + .pins = atmega8_pins, + .nr_pins = ARRAY_SIZE(atmega8_pins), +}; + +#undef p +#undef PIN + +int avr_get_pin_number(struct avr_device_pinout *pinout, + enum avr_pinout_function func) +{ + struct avr_hwpin *pin; + enum avr_pinout_function f; + size_t i, j; + + for (i = 0; i < pinout->nr_pins; i++) { + pin = &(pinout->pins[i]); + + for (j = 0; j < AVR_MAX_PIN_FUNCS; i++) { + f = pin->funcs[j]; + if (f == PINOUT_NC) + break; + if (f == func) + return i + 1; + } + } + + return -1; +} + +struct avr_device_pinout_handlers active_pinout_handlers; diff --git a/emulator/pinout.h b/emulator/pinout.h new file mode 100644 index 0000000..87c9cf1 --- /dev/null +++ b/emulator/pinout.h @@ -0,0 +1,142 @@ +#ifndef AVREMU_PINOUT_H_ +#define AVREMU_PINOUT_H_ + +#include + + +/* Increase these constants as needed. */ + +/* Maximum number of functions per pin. */ +#define AVR_MAX_PIN_FUNCS 10 +/* Maximum number of physical pins per device */ +#define AVR_MAX_NR_PINS 28 + + + +/* The function of a physical pin on the device. */ +enum avr_pinout_function { + PINOUT_NC = 0, /* Not connected */ + PINOUT_VCC, + PINOUT_GND, + PINOUT_AVCC, + PINOUT_AREF, + PINOUT_PB0, + PINOUT_PB1, + PINOUT_PB2, + PINOUT_PB3, + PINOUT_PB4, + PINOUT_PB5, + PINOUT_PB6, + PINOUT_PB7, + PINOUT_PC0, + PINOUT_PC1, + PINOUT_PC2, + PINOUT_PC3, + PINOUT_PC4, + PINOUT_PC5, + PINOUT_PC6, + PINOUT_PD0, + PINOUT_PD1, + PINOUT_PD2, + PINOUT_PD3, + PINOUT_PD4, + PINOUT_PD5, + PINOUT_PD6, + PINOUT_PD7, + PINOUT_RESET, + PINOUT_RXD, + PINOUT_TXD, + PINOUT_INT0, + PINOUT_INT1, + PINOUT_T0, + PINOUT_TOSC1, + PINOUT_TOSC2, + PINOUT_T1, + PINOUT_AIN0, + PINOUT_AIN1, + PINOUT_ICP1, + PINOUT_ADC5, + PINOUT_ADC4, + PINOUT_ADC3, + PINOUT_ADC2, + PINOUT_ADC1, + PINOUT_ADC0, + PINOUT_SCK, + PINOUT_MISO, + PINOUT_MOSI, + PINOUT_SS, + PINOUT_OC1A, + PINOUT_PCINT0, + PINOUT_PCINT1, + PINOUT_PCINT2, + PINOUT_PCINT3, + PINOUT_PCINT4, + PINOUT_PCINT5, + PINOUT_PCINT6, + PINOUT_PCINT7, + PINOUT_PCINT8, + PINOUT_PCINT9, + PINOUT_PCINT10, + PINOUT_PCINT11, + PINOUT_PCINT12, + PINOUT_PCINT13, + PINOUT_PCINT14, + PINOUT_PCINT16, + PINOUT_PCINT17, + PINOUT_PCINT18, + PINOUT_PCINT19, + PINOUT_PCINT20, + PINOUT_PCINT21, + PINOUT_PCINT22, + PINOUT_PCINT23, + PINOUT_OC2B, + PINOUT_XCK, + PINOUT_XTAL1, + PINOUT_XTAL2, + PINOUT_OC0B, + PINOUT_OC0A, + PINOUT_CLKO, + PINOUT_SCL, + PINOUT_SDA, + PINOUT_OC2A, + PINOUT_OC1B, + PINOUT_OC2, +}; + +/* Data structure of a hardware pin. */ +struct avr_hwpin { + /* The assigned pin number. */ + unsigned int number; + /* The logical functions assigned to this pin. */ + enum avr_pinout_function funcs[AVR_MAX_PIN_FUNCS]; +}; + +/* Pinout definitions for a specific device. */ +struct avr_device_pinout { + /* Array of pins. */ + struct avr_hwpin *pins; + /* Number of pins on this device. */ + size_t nr_pins; +}; + +extern struct avr_device_pinout atmega48_88_168_pinout; +extern struct avr_device_pinout atmega8_pinout; + +int avr_get_pin_number(struct avr_device_pinout *pinout, + enum avr_pinout_function func); + + +typedef unsigned int (*pin_query_handler_t)(struct avr_hwpin *pin); +typedef void (*pin_change_handler_t)(struct avr_hwpin *pin, unsigned int state); + +/* Dynamic handlers for a physical device pin. */ +struct avr_device_pinout_handlers { + /* Handlers to query the state of a pin from inside of the device. */ + pin_query_handler_t query_handlers[AVR_MAX_NR_PINS]; + /* Handlers to set the state of a pin from inside of the device. */ + pin_change_handler_t change_handlers[AVR_MAX_NR_PINS]; +}; + +extern struct avr_device_pinout_handlers active_pinout_handlers; + +#endif /* AVREMU_PINOUT_H_ */ diff --git a/emulator/python.c b/emulator/python.c new file mode 100644 index 0000000..84b8bd9 --- /dev/null +++ b/emulator/python.c @@ -0,0 +1,206 @@ +/* + * AVR emulator + * Python interface + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "subprocess.h" +#include "util.h" + + +#define MODNAME "pyavr8emu" + + +static PyObject *pyavr_except; + +static void * raise_errno_exception(int err) +{ + int old_errno; + + if (err < 0) + err = -err; + old_errno = errno; + errno = err; + PyErr_SetFromErrno(pyavr_except); + errno = old_errno; + + return NULL; +} + +/***************************************************************************** + * Instance-Object definitions * + *****************************************************************************/ + +struct avremu_instance { + PyObject_HEAD + /* The host part of the emulator instance. */ + struct avremu_host_instance host; +}; + +static PyObject * method_instance_load_flash(PyObject *self, PyObject *args) +{ + struct avremu_instance *in = (struct avremu_instance *)self; + struct avrmsg_to_device msg; + int err; + const char *image; + int image_size; + + if (!PyArg_ParseTuple(args, "s#", &image, &image_size)) + return NULL; + + msg.msg = DEVMSG_LOADFLASH; + msg.size = image_size; + err = sendmsg_to_device(&in->host, &msg); + if (err) + return raise_errno_exception(err); + err = ipc_payload_send(&in->host.msg_to_device, image, image_size); + if (err) + return raise_errno_exception(err); + + return Py_None; +} + +static PyObject * method_instance_reset(PyObject *self, PyObject *args) +{ + struct avremu_instance *in = (struct avremu_instance *)self; + int err; + + err = sendnotify_to_device(&in->host, DEVMSG_RESET); + if (err) + return raise_errno_exception(err); + + return Py_None; +} + +static PyObject * method_instance_poll_status_msg(PyObject *self, PyObject *args) +{ + struct avremu_instance *in = (struct avremu_instance *)self; + int err; + char buf[1024]; + + err = ipc_raw_line_poll(&in->host.status_from_device, + buf, sizeof(buf)); + if (err == 0) + return Py_None; + if (err < 0) + return raise_errno_exception(err); + + return PyString_FromString(buf); +} + +static PyMethodDef avremu_instance_methods[] = { + { "loadFlash", method_instance_load_flash, METH_VARARGS, + "Load a binary flash image" }, + { "reset", method_instance_reset, METH_NOARGS, + "Reset the emulated microcontroller" }, + { "pollStatusMsg", method_instance_poll_status_msg, METH_NOARGS, + "Poll a status message line" }, + { NULL, }, +}; + +static PyObject * avremu_instance_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + struct avremu_instance *in; + + in = (struct avremu_instance *)type->tp_alloc(type, 0); + if (!in) + return NULL; + + return (PyObject *)in; +} + +static int avremu_instance_init(PyObject *obj, PyObject *args, PyObject *kwds) +{ + struct avremu_instance *in = (struct avremu_instance *)obj; + int err, arch; + static char *kwlist[] = {"arch", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", kwlist, &arch)) + return -1; + + err = avremu_instance_create(&in->host, arch); + if (err) { + raise_errno_exception(err); + return -1; + } + + return 0; +} + +static void avremu_instance_free(PyObject *obj) +{ + struct avremu_instance *in = (struct avremu_instance *)obj; + + avremu_instance_destroy(&in->host); + obj->ob_type->tp_free(obj); +} + +static PyTypeObject avremu_instance_type = { + PyObject_HEAD_INIT(NULL) + .tp_name = MODNAME ".Instance", + .tp_basicsize = sizeof(struct avremu_instance), + .tp_methods = avremu_instance_methods, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = avremu_instance_new, + .tp_init = avremu_instance_init, + .tp_dealloc = avremu_instance_free, + .tp_doc = "An emulator instance object", +}; + +/***************************************************************************** + * Module API functions * + *****************************************************************************/ + +static PyMethodDef pyavr_methods[] = { + { NULL, }, +}; + +/***************************************************************************** + * Initialization * + *****************************************************************************/ + +#define def_const(m, x) PyModule_AddIntConstant((m), stringify(x), (x)) + +PyMODINIT_FUNC initpyavr8emu(void) +{ + PyObject *m; + + if (PyType_Ready(&avremu_instance_type) < 0) + return; + + pyavr_except = PyErr_NewException(MODNAME ".AvrException", + PyExc_Exception, NULL); + if (!pyavr_except) + return; + Py_INCREF(pyavr_except); + + m = Py_InitModule(MODNAME, pyavr_methods); + if (!m) { + Py_DECREF(pyavr_except); + return; + } + + /* Define constants */ + def_const(m, AVR_ATMEGA8); + def_const(m, AVR_ATMEGA168); + + /* Build objects */ + PyModule_AddObject(m, "AvrException", pyavr_except); + + Py_INCREF(&avremu_instance_type); + PyModule_AddObject(m, "Instance", (PyObject *)&avremu_instance_type); +} diff --git a/emulator/subprocess.c b/emulator/subprocess.c new file mode 100644 index 0000000..969ae1b --- /dev/null +++ b/emulator/subprocess.c @@ -0,0 +1,167 @@ +/* + * AVR emulator + * Emulator subprocess management + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "subprocess.h" +#include "devprocess.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static int fork_device_instance(struct avremu_host_instance *host) +{ + struct avremu_device_instance *dev; + int err; + pid_t pid; + struct avrmsg_to_host hostmsg; + + dev = avr_malloc(sizeof(*dev)); + if (!dev) + return -ENOMEM; + dev->type = host->type; + err = ipc_pipe_create(&host->msg_to_device, &dev->msg_from_host); + if (err) { + err = -ENOMEM; + goto err_free_dev; + } + err = ipc_pipe_create(&dev->msg_to_host, &host->msg_from_device); + if (err) { + err = -ENOMEM; + goto err_destroy1; + } + err = ipc_pipe_create(&dev->status_to_host, &host->status_from_device); + if (err) { + err = -ENOMEM; + goto err_destroy2; + } + + pid = fork(); + if (pid == (pid_t)0) { + /* DEVICE process */ + ipc_pipe_destroy_tx(&host->msg_to_device); + ipc_pipe_destroy_rx(&host->msg_from_device); + ipc_pipe_destroy_rx(&host->status_from_device); + host = NULL; + + /* Map stderr and stdout to the status pipe. + * Also enable line buffering. */ + ipc_dup2(&dev->status_to_host, STDERR_FILENO); + ipc_dup2(&dev->status_to_host, STDOUT_FILENO); + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + + /* Run the device process. */ + err = avr_device_process_run(dev); + sendnotify_to_host(dev, HOSTMSG_EXIT); + exit(err >= 0 ? err : -err); + } else if (pid < (pid_t)0) { + /* Failure */ + err = -ENOMEM; + goto err_destroy3; + } else { + /* HOST process */ + ipc_pipe_destroy_tx(&dev->msg_to_host); + ipc_pipe_destroy_rx(&dev->msg_from_host); + ipc_pipe_destroy_tx(&dev->status_to_host); + free(dev); + dev = NULL; + + host->device_pid = pid; + /* Wait for the device to initialize itself. */ + while (1) { + err = pollmsg_from_device(host, &hostmsg); + if (err < 0) { + err = -EIO; + goto host_error; + } + if (err > 0) { + if (hostmsg.msg == HOSTMSG_READY) + break; + if (hostmsg.msg == HOSTMSG_EXIT) { + if (waitpid(pid, &err, 0) != pid) + err = -ESRCH; + else + err = WEXITSTATUS(err); + goto host_error; + } + } + } + host->device_running = 1; + return 0; + + host_error: + ipc_pipe_destroy_tx(&host->msg_to_device); + ipc_pipe_destroy_rx(&host->msg_from_device); + ipc_pipe_destroy_rx(&host->status_from_device); + return err; + } + + return 0; + +err_destroy3: + ipc_pipe_destroy(&dev->status_to_host, &host->status_from_device); +err_destroy2: + ipc_pipe_destroy(&dev->msg_to_host, &host->msg_from_device); +err_destroy1: + ipc_pipe_destroy(&host->msg_to_device, &dev->msg_from_host); +err_free_dev: + free(dev); + + return err; +} + +static void kill_device_instance(struct avremu_host_instance *host) +{ + int err; + +printf("KILLING INSTANCE\n"); + if (!host->device_running) + return; +printf("SEND TERM\n"); + err = kill(host->device_pid, SIGTERM); + if (err) { + perror("Kill device process"); + return; + } +printf("WAITING\n"); + waitpid(host->device_pid, NULL, 0); + host->device_running = 0; +printf("WAITING DONE.\n"); +} + +int avremu_instance_create(struct avremu_host_instance *host, + enum avr_setup_type type) +{ + int err; + + host->type = type; + err = fork_device_instance(host); + + return err; +} + +void avremu_instance_destroy(struct avremu_host_instance *host) +{ + kill_device_instance(host); +} diff --git a/emulator/subprocess.h b/emulator/subprocess.h new file mode 100644 index 0000000..04791ef --- /dev/null +++ b/emulator/subprocess.h @@ -0,0 +1,116 @@ +#ifndef AVREMU_SUBPROCESS_H_ +#define AVREMU_SUBPROCESS_H_ + +#include "hardware.h" +#include "ipc.h" +#include "util.h" + +#include + + +/* Instance of an emulated device on the controlling host process. */ +struct avremu_host_instance { + /* Type of microcontroller we are emulating. */ + enum avr_setup_type type; + /* PID of the device process. */ + pid_t device_pid; + /* Is the device subprocess running? */ + bool device_running; + + struct ipc_tx msg_to_device; + struct ipc_rx msg_from_device; + struct ipc_rx status_from_device; +}; + +/* Instance of an emulated device. + * This is the process running the actual code. */ +struct avremu_device_instance { + /* Type of microcontroller we are emulating. */ + enum avr_setup_type type; + + struct ipc_tx msg_to_host; + struct ipc_rx msg_from_host; + struct ipc_tx status_to_host; +}; + +int avremu_instance_create(struct avremu_host_instance *host, + enum avr_setup_type type); +void avremu_instance_destroy(struct avremu_host_instance *host); + + +enum avrmsg_to_device_type { + DEVMSG_LOADFLASH, /* Load a binary flash image. */ + DEVMSG_RESET, /* Reset the microcontroller. */ +}; + +struct avrmsg_to_device { + enum avrmsg_to_device_type msg; + union { + uint16_t size; + }; +}; + +static inline +int sendmsg_to_device(struct avremu_host_instance *host, + const struct avrmsg_to_device *msg) +{ + return ipc_message_send(&host->msg_to_device, + msg, sizeof(*msg), 1); +} + +static inline +int sendnotify_to_device(struct avremu_host_instance *host, + enum avrmsg_to_device_type msg) +{ + struct avrmsg_to_device m = { + .msg = msg, + }; + return sendmsg_to_device(host, &m); +} + +static inline +int pollmsg_from_host(struct avremu_device_instance *dev, + struct avrmsg_to_device *msg) +{ + return ipc_message_poll(&dev->msg_from_host, + msg, sizeof(*msg)); +} + + +enum avrmsg_to_host_type { + HOSTMSG_READY, /* Initialization complete. */ + HOSTMSG_EXIT, /* The device process exited. */ +}; + +struct avrmsg_to_host { + enum avrmsg_to_host_type msg; +}; + +static inline +int sendmsg_to_host(struct avremu_device_instance *dev, + const struct avrmsg_to_host *msg, + bool read_result) +{ + return ipc_message_send(&dev->msg_to_host, + msg, sizeof(*msg), read_result); +} + +static inline +int sendnotify_to_host(struct avremu_device_instance *dev, + enum avrmsg_to_host_type msg) +{ + struct avrmsg_to_host m = { + .msg = msg, + }; + return sendmsg_to_host(dev, &m, 0); +} + +static inline +int pollmsg_from_device(struct avremu_host_instance *host, + struct avrmsg_to_host *msg) +{ + return ipc_message_poll(&host->msg_from_device, + msg, sizeof(*msg)); +} + +#endif /* AVREMU_SUBPROCESS_H_ */ diff --git a/emulator/util.c b/emulator/util.c new file mode 100644 index 0000000..69c119d --- /dev/null +++ b/emulator/util.c @@ -0,0 +1,35 @@ +/* + * AVR emulator + * Utility functions + * + * Copyright (C) 2007 Michael Buesch + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "util.h" + +#include +#include + + +void * avr_malloc(size_t size) +{ + void *p; + + p = malloc(size); + if (p) + memset(p, 0, size); + else + fprintf(stderr, "Out of memory\n"); + + return p; +} diff --git a/emulator/util.h b/emulator/util.h new file mode 100644 index 0000000..321f46a --- /dev/null +++ b/emulator/util.h @@ -0,0 +1,74 @@ +#ifndef AVREMU_UTIL_H_ +#define AVREMU_UTIL_H_ + +#ifndef __GNUC__ +# error "Not using the GNU GCC compiler." +#endif + +#include "list.h" +#include +#include + + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#ifdef RUNTIME_SANITY_CHECKS +# define CHECK(x) unlikely(x) +#else +# define CHECK(x) 0 +#endif + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define is_const_value(x) __builtin_constant_p(x) + +/* Force reload a variable from memory. */ +#define reload_var(x) (*((volatile typeof(x) *)&(x))) + +/* Break the linkage of the binary, if the constant condition is true. + * This is useful for compiletime checks of constant values. */ +#define break_linkage_on(constant_condition, reason) do { \ + if (constant_condition) { \ + extern void __avremu_bug__##reason(void); \ + __avremu_bug__##reason(); \ + } \ +} while (0) + +#define __stringify(x) #x +#define stringify(x) __stringify(x) + + +typedef uint16_t le16_t; +typedef uint32_t le32_t; +typedef _Bool bool; + + +static inline uint16_t swap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +static inline uint16_t le16_to_cpu(le16_t x) +{ +#ifdef BIG_ENDIAN_HOST + return swap16((uint16_t)x); +#else + return (uint16_t)x; +#endif +} + +static inline le16_t cpu_to_le16(uint16_t x) +{ +#ifdef BIG_ENDIAN_HOST + return (le16_t)swap16(x); +#else + return (le16_t)x; +#endif +} + + +void * avr_malloc(size_t size); + + +#endif /* AVREMU_UTIL_H_ */ diff --git a/gui/avr8emu b/gui/avr8emu new file mode 100755 index 0000000..cae4ebc --- /dev/null +++ b/gui/avr8emu @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# +# AVR-8 emulator GUI +# +# Copyright (C) 2007 Michael Buesch +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +AVR8EMU_VERSION = "001" + +import tempfile +import sys +import os +import pyavr8emu as emu +from PyQt4.QtCore import * +from PyQt4.QtGui import * + + +#FIXME +inst = emu.Instance(0) + + +class EmulatorStatus(QTextBrowser): + def __init__(self, parent=None): + QTextBrowser.__init__(self, parent) + self.setFontFamily("Courier") + self.setFontPointSize(10) + self.setLineWrapMode(QTextEdit.NoWrap) + + self.timer = QTimer() + self.timer.setInterval(300) + self.timer.setSingleShot(False) + self.connect(self.timer, SIGNAL("timeout()"), self.pollMsgs) + self.timer.start() + + def pollMsgs(self): + while True: + msg = inst.pollStatusMsg() + if not msg: + return + self.append(msg[:-1]) + +class CodeEditor(QTextEdit): + def __init__(self, parent): + QTextEdit.__init__(self, parent) + self.setFontFamily("Courier") + self.setFontPointSize(12) + self.setLineWrapMode(QTextEdit.NoWrap) + + def statusBar(self): + return self.parent().statusBar() + + def loadFile(self, filename): + fd = QFile(filename) + if not fd.open(QIODevice.ReadOnly): + self.statusBar().showMessage("Could not open file %s" % filename) + return + data = fd.readAll() + fd.close() + if not data: + self.statusBar().showMessage("Could not read file %s" % filename) + return + self.setText(str(data)) + +class CentralWidget(QWidget): + def __init__(self, mainwnd): + QWidget.__init__(self, mainwnd) + + self.mainwnd = mainwnd + l = QVBoxLayout(self) + sp = QSplitter(self) + sp.setOrientation(Qt.Vertical) + self.editor = CodeEditor(self) + sp.addWidget(self.editor) + self.emuStatus = EmulatorStatus(self) + sp.addWidget(self.emuStatus) + l.addWidget(sp) + + def statusBar(self): + return self.parent().statusBar() + + def loadFile(self): + fn = QFileDialog.getOpenFileName(self, "Open assembly file") + if not fn: + return + inst.loadFlash(file(fn).read()) +#FIXME self.editor.loadFile(fn) + + def __assemble(self, assembly): + #FIXME not portable + asmfile = tempfile.NamedTemporaryFile() + asmfile.write(assembly) + asmfile.flush() + os.system("avra %s" % asmfile.name) + hexfile = asmfile.name + ".hex" + binfile = asmfile.name + ".bin" + os.system("avr-objcopy -I ihex -O binary %s %s" % (hexfile, binfile)) + fd = file(binfile) + bindata = fd.read() + fd.close() + os.system("rm %s.*" % asmfile.name) + print "Got %d bytes binary" % len(bindata) + return bindata + + def reset(self): + inst.reset() + +class StatusBar(QStatusBar): + def showMessage(self, msg): + QStatusBar.showMessage(self, msg, 10000) + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + QMainWindow.__init__(self, parent) + self.setWindowTitle("Fast! Atmel AVR 8-bit emulator") + + self.setCentralWidget(CentralWidget(self)) + self.setStatusBar(StatusBar(self)) + + mb = QMenuBar(self) + filemen = QMenu("&File", mb) + filemen.addAction("&Load...", self.centralWidget().loadFile) + filemen.addAction("&Reset", self.centralWidget().reset) + filemen.addSeparator() + filemen.addAction("&Exit", self.close) + mb.addMenu(filemen) + mb.addSeparator() + helpmen = QMenu("&Help", mb) + helpmen.addAction("&About", self.about) + mb.addMenu(helpmen) + + self.setMenuBar(mb) + + def about(self): + QMessageBox.information(self, "About", + "Fast! Atmel AVR 8-bit emulator\n" + "Version %s\n" + "Copyright (c) 2007 Michael Buesch" + % AVR8EMU_VERSION) + +def main(): + app = QApplication(sys.argv) + mainwnd = MainWindow() + mainwnd.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main() -- cgit v1.2.3