' +
+ '
How to change Python environment if you have another installed:' +
+ '
' +
+ 'sudo update-alternatives --set python3 /usr/bin/python3.9' +
+ ' ' +
' ' +
' ' +
' ' +
@@ -708,7 +736,25 @@
'
' +
' ' +
' ' +
- ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' Install Openocd
' +
+ ' Install the GDB web interface: pip3 install gdbgui ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ ' ' +
' Download Driver' +
' ' +
' ' +
diff --git a/gateway/esp32-rv/.gitignore b/gateway/esp32-rv/.gitignore
deleted file mode 100644
index fbae2f16b..000000000
--- a/gateway/esp32-rv/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-sdkconfig
-sdkconfig.old
-tmp_assembly.s
-build
-main/program.s
diff --git a/gateway/esp32-rv/CMakeLists.txt b/gateway/esp32-rv/CMakeLists.txt
index 0a454d064..cc2ff9e57 100644
--- a/gateway/esp32-rv/CMakeLists.txt
+++ b/gateway/esp32-rv/CMakeLists.txt
@@ -4,3 +4,4 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hello_world)
+target_link_libraries(${project_elf} PRIVATE "-Wl,--wrap=esp_panic_handler")
\ No newline at end of file
diff --git a/gateway/esp32-rv/creator/io.h b/gateway/esp32-rv/creator/io.h
deleted file mode 100644
index 276ca38a1..000000000
--- a/gateway/esp32-rv/creator/io.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
- *
- * This file is part of CREATOR.
- *
- * CREATOR is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * CREATOR 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 Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with CREATOR. If not, see .
- *
- */
-
-/** @file io.h
- * @brief Input Output Interface for Creator
- * @author José Antonio Verde Jiménez
- * @copyright Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
- *
- * This file provides a common interface for routines for input and output.
- * The implementation is written in assembly and might change depending on the
- * device. But the interface is the same.
- */
-
-#ifndef __CREATOR_IO_HPP
-#define __CREATOR_IO_HPP
-
-/** @defgroup Creator_Input Input routines for Creator
- * @brief Input routines for Creator
- *
- * Functions get_character(), peek_character() and get_string(), use an
- * internal buffer so editing is possible. A line feed or carriage return is
- * needed to continue.
- *
- * @{
- */
-
-/** Read a character from standard input immediately. Don't show the character
- * on screen. This function doesn't work well along get_character(),
- * peek_character() or get_string(), because they need to write to a buffer to
- * work well.
- *
- * @return
- * Returns a character between 0 and 255 on success.
- * It returns -1 on error or when EOF is reached.
- */
-int get_immediate ();
-
-/** Read a character from standard input. It echos the character.
- *
- * @return
- * Returns a character between 0 and 255 on success.
- * It returns -1 on error or when EOF is reached.
- */
-int get_character ();
-
-/** Peek on the next character on the standard input, but don't read it. That
- * way you can see the next character without "destroying it".
- *
- * @return
- * Returns a character between 0 and 255 on success.
- * It returns -1 on error or when EOF is reached.
- */
-int peek_character ();
-
-/** Read from standard input into a string buffer. The last character is
- * written to be '\0'.
- *
- * This function supposes that the length of the string is *at least*,
- * length + 1 characters long. string[length] will be 0.
- *
- * Note that carriage returns, line feeds and carriage return + line feed are
- * all treated like line feeds.
- *
- * @param[out] string
- * A pointer to the begining of the string buffer.
- *
- * @param[in] length
- * The amount of characters to be read from standard input.
- */
-void get_string (char *string, int length);
-
-/** @} */
-
-/** @defgroup Creator_Output Output routines for Creator
- * @brief Output routines for Creator
- *
- * @{
- */
-
-
-/** Write a character immediately (flush and synchronise it) on standard output
- * and display it on the screen.
- *
- * @param[in] c
- * A character.
- */
-void put_immediate (char c);
-
-/** Write a character zero-terminated string on standard output, flush and
- * synchronise standard output and display it on the screen.
- *
- * @param[in] string
- * The pointer to the begining of the string.
- */
-void put_zstring (char *string);
-
-/** Write a string with a given length on standard output, flush and
- * synchronise standard output and display it on the screen.
- *
- * @param[in] string
- * The pointer to the begining of the string.
- *
- * @param[in] length
- * The length of said string
- */
-void put_string (char *string, int length);
-
-/** @} */
-
-#endif//__CREATOR_IO_HPP
diff --git a/gateway/esp32-rv/gateway.py b/gateway/esp32-rv/gateway.py
index f09d5ee64..08673c8c2 100755
--- a/gateway/esp32-rv/gateway.py
+++ b/gateway/esp32-rv/gateway.py
@@ -2,7 +2,7 @@
#
-# Copyright 2022-2024 Felix Garcia Carballeira, Diego Carmarmas Alonso, Alejandro Calderon Mateos
+# Copyright 2022-2024 Felix Garcia Carballeira, Diego Carmarmas Alonso, Alejandro Calderon Mateos, Elisa Utrilla Arroyo
#
# This file is part of CREATOR.
#
@@ -21,10 +21,69 @@
#
+import glob
+import sys
+import threading
+from time import sleep, time
from flask import Flask, request, jsonify, send_file, Response
from flask_cors import CORS, cross_origin
import subprocess, os, signal
+import logging
+# Configure logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
+
+BUILD_PATH = '.'
+process_holder = {}
+
+#### (*) Cleaning functions
+def do_fullclean_request(request):
+ """ Full clean the build directory """
+ try:
+ req_data = request.get_json()
+ target_device = req_data['target_port']
+ req_data['status'] = ''
+ # flashing steps...
+ if error ==0:
+ do_cmd_output(req_data, ['idf.py','-C', BUILD_PATH,'fullclean'])
+ if error == 0:
+ req_data['status'] += 'Full clean done.\n'
+ except Exception as e:
+ req_data['status'] += str(e) + '\n'
+ return jsonify(req_data)
+
+def do_eraseflash_request(request):
+ """ Erase flash the target device """
+ try:
+ req_data = request.get_json()
+ target_device = req_data['target_port']
+ req_data['status'] = ''
+ # flashing steps...
+ if error == 0:
+ error = do_cmd_output(req_data, ['idf.py','-C', BUILD_PATH,'-p',target_device,'erase-flash'])
+ if error == 0:
+ req_data['status'] += 'Erase flash done. Please, unplug and plug the cable(s) again\n'
+
+ except Exception as e:
+ req_data['status'] += str(e) + '\n'
+
+ return jsonify(req_data)
+
+def do_stop_monitor_request(request):
+ """Shortcut for stopping Monitor / debug """
+ try:
+ req_data = request.get_json()
+ req_data['status'] = ''
+ print("Killing Monitor")
+ error = kill_all_processes("idf.py")
+ if error == 0:
+ req_data['status'] += 'Process stopped\n'
+
+
+ except Exception as e:
+ req_data['status'] += str(e) + '\n'
+
+ return jsonify(req_data)
# (1) Get form values
def do_get_form(request):
@@ -67,60 +126,6 @@ def creator_build(file_in, file_out):
fout.write("####################\n")
continue
- if (data[0] == 'ecall'):
- fout.write("#### ecall ####\n")
- fout.write("addi sp, sp, -128\n")
- fout.write("sw x1, 120(sp)\n")
- fout.write("sw x3, 112(sp)\n")
- fout.write("sw x4, 108(sp)\n")
- fout.write("sw x5, 104(sp)\n")
- fout.write("sw x6, 100(sp)\n")
- fout.write("sw x7, 96(sp)\n")
- fout.write("sw x8, 92(sp)\n")
- fout.write("sw x9, 88(sp)\n")
- fout.write("sw x18, 52(sp)\n")
- fout.write("sw x19, 48(sp)\n")
- fout.write("sw x20, 44(sp)\n")
- fout.write("sw x21, 40(sp)\n")
- fout.write("sw x22, 36(sp)\n")
- fout.write("sw x23, 32(sp)\n")
- fout.write("sw x24, 28(sp)\n")
- fout.write("sw x25, 24(sp)\n")
- fout.write("sw x26, 20(sp)\n")
- fout.write("sw x27, 16(sp)\n")
- fout.write("sw x28, 12(sp)\n")
- fout.write("sw x29, 8(sp)\n")
- fout.write("sw x30, 4(sp)\n")
- fout.write("sw x31, 0(sp)\n")
-
- fout.write("jal _myecall\n")
-
- fout.write("lw x1, 120(sp)\n")
- fout.write("lw x3, 112(sp)\n")
- fout.write("lw x4, 108(sp)\n")
- fout.write("lw x5, 104(sp)\n")
- fout.write("lw x6, 100(sp)\n")
- fout.write("lw x7, 96(sp)\n")
- fout.write("lw x8, 92(sp)\n")
- fout.write("lw x9, 88(sp)\n")
- fout.write("lw x18, 52(sp)\n")
- fout.write("lw x19, 48(sp)\n")
- fout.write("lw x20, 44(sp)\n")
- fout.write("lw x21, 40(sp)\n")
- fout.write("lw x22, 36(sp)\n")
- fout.write("lw x23, 32(sp)\n")
- fout.write("lw x24, 28(sp)\n")
- fout.write("lw x25, 24(sp)\n")
- fout.write("lw x26, 20(sp)\n")
- fout.write("lw x27, 16(sp)\n")
- fout.write("lw x28, 12(sp)\n")
- fout.write("lw x29, 8(sp)\n")
- fout.write("lw x30, 4(sp)\n")
- fout.write("lw x31, 0(sp)\n")
- fout.write("addi sp, sp, 128\n")
- fout.write("###############\n")
- continue
-
fout.write(line)
# close input + output files
@@ -174,14 +179,60 @@ def do_flash_request(request):
ret = text_file.write(asm_code)
text_file.close()
+ # Kill debug processes
+ if 'openocd' in process_holder:
+ logging.debug('Killing OpenOCD')
+ kill_all_processes("openocd")
+ process_holder.pop('openocd', None)
+
+ if 'gdbgui' in process_holder:
+ logging.debug('Killing GDBGUI')
+ kill_all_processes("gdbgui")
+ process_holder.pop('gdbgui', None)
+
# transform th temporal assembly file
error = creator_build('tmp_assembly.s', "main/program.s");
if error != 0:
req_data['status'] += 'Error adapting assembly file...\n'
# flashing steps...
+ if error == 0 :
+ error = check_uart_connection()
+ if error != 0:
+ req_data['status'] += 'No UART port found.\n'
+ logging.error("No UART port found.")
+ raise Exception("No UART port found")
if error == 0:
error = do_cmd(req_data, ['idf.py', 'fullclean'])
+ # Disable memory protection
+ sdkconfig_path = os.path.join(BUILD_PATH, "sdkconfig")
+ # 1. Check out previous sdkconfig
+ # (*) This configuration is EXCUSIVELY FOR ESP-IDF WITHOUT ARDUINO
+ defaults_path = os.path.join(BUILD_PATH, "sdkconfig.defaults")
+ if target_board == 'esp32c3':
+ with open(defaults_path, "w") as f:
+ f.write(
+ "# CONFIG_ESP_SYSTEM_MEMPROT_FEATURE is not set\n"
+ "# CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK is not set\n"
+ )
+ #TODO: Add other boards here...
+
+ # 2. If previous sdkconfig exists, check if mem protection is disabled (for debug purposes)
+ if os.path.exists(sdkconfig_path):
+ if target_board == 'esp32c3':
+ # Memory Protection
+ do_cmd(req_data, [
+ 'sed', '-i',
+ r'/^CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=/c\# CONFIG_ESP_SYSTEM_MEMPROT_FEATURE is not set',
+ sdkconfig_path
+ ])
+ # Memory protection lock
+ do_cmd(req_data, [
+ 'sed', '-i',
+ r'/^CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=/c\# CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK is not set',
+ sdkconfig_path
+ ])
+ #TODO: Add other boards here...
if error == 0:
error = do_cmd(req_data, ['idf.py', 'set-target', target_board])
if error == 0:
@@ -202,6 +253,18 @@ def do_monitor_request(request):
target_device = req_data['target_port']
req_data['status'] = ''
+ # Kill debug process
+
+ if 'openocd' in process_holder:
+ logging.debug('Killing OpenOCD')
+ kill_all_processes("openocd")
+ process_holder.pop('openocd', None)
+
+ if 'gdbgui' in process_holder:
+ logging.debug('Killing GDBGUI')
+ kill_all_processes("gdbgui")
+ process_holder.pop('gdbgui', None)
+
do_cmd(req_data, ['idf.py', '-p', target_device, 'monitor'])
except Exception as e:
@@ -261,6 +324,244 @@ def do_stop_flash_request(request):
return jsonify(req_data)
+# (6) Debug
+
+# (6.1) Physical connections check
+def check_uart_connection():
+ """ Checks UART devices """
+ devices = glob.glob('/dev/ttyUSB*')
+ logging.debug(f"Found devices: {devices}")
+ if "/dev/ttyUSB0" in devices:
+ logging.info("Found UART.")
+ return 0
+ elif devices:
+ logging.error("Other UART devices found (Is the name OK?).")
+ return 0
+ else:
+ logging.error("NO UART port found.")
+ return 1
+
+def check_jtag_connection():
+ """ Checks JTAG devices """
+ command = ["lsusb"]
+ try:
+ lsof = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ output, errs = lsof.communicate(timeout=5)
+ if output:
+ output_text = output.decode(errors="ignore")
+ if "JTAG" in output_text:
+ logging.info("JTAG found")
+ return True
+ else:
+ logging.warning("JTAG missing")
+ return False
+ except subprocess.TimeoutExpired:
+ lsof.kill()
+ output, errs = lsof.communicate()
+ except Exception as e:
+ logging.error(f"Error checking JTAG: {e}")
+ return None
+ return False
+# --- (6.2) Debug processes monitoring functions ---
+
+def check_gdb_connection():
+ """ Checks gdb status """
+ command = ["lsof", "-i", ":3333"]
+ try:
+ lsof = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ output, errs = lsof.communicate(timeout=5)
+ logging.debug("GDB connection output: %s", output.decode())
+ return output.decode()
+ except subprocess.TimeoutExpired:
+ lsof.kill()
+ output, errs = lsof.communicate()
+ logging.error(" GDB:Timeout waiting for GDB connection.")
+ except Exception as e:
+ logging.error(f"Error checking GDB: {e}")
+ return None
+ return False
+
+def monitor_openocd_output(req_data, cmd_args, name):
+ logfile_path = os.path.join(BUILD_PATH, f"{name}.log")
+ try:
+ with open(logfile_path, "a", encoding="utf-8") as logfile:
+ process_holder[name] = subprocess.Popen(
+ cmd_args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ bufsize=1,
+ universal_newlines=True
+ )
+ proc = process_holder[name]
+
+ # Logfile
+ for line in proc.stdout:
+ line = line.rstrip()
+ logfile.write(line + "\n")
+ logfile.flush()
+ logging.debug(f"[{name}] {line}")
+
+ proc.stdout.close()
+ retcode = proc.wait()
+ logging.debug(f"Process {name} finished with code {retcode}")
+ return proc
+
+ except Exception as e:
+ logging.error(f"Error executing command {name}: {e}")
+ process_holder.pop(name, None)
+ return None
+
+def kill_all_processes(process_name):
+ try:
+ if not process_name:
+ logging.error("El nombre del proceso no puede estar vacío.")
+ return 1
+
+ # Comando para obtener los PIDs de los procesos
+ get_pids_cmd = f"ps aux | grep '[{process_name[0]}]{process_name[1:]}' | awk '{{print $2}}'"
+ result = subprocess.run(get_pids_cmd, shell=True, capture_output=True, text=True)
+
+ # PIDs list
+ pids = result.stdout.strip().split()
+
+ if not pids:
+ logging.warning(f"Not processes found '{process_name}'.")
+ return 1 # Devuelve 1 para indicar que no se hizo nada
+
+ # Ejecuta kill solo si hay PIDs
+ kill_cmd = f"kill -9 {' '.join(pids)}"
+ result = subprocess.run(kill_cmd, shell=True, capture_output=True, timeout=120, check=False)
+
+ if result.returncode != 0:
+ logging.error(f"Error al intentar matar los procesos {process_name}. Salida: {result.stderr.strip()}")
+ else:
+ logging.info(f"Todos los procesos '{process_name}' han sido eliminados.")
+
+ return result.returncode
+
+ except subprocess.TimeoutExpired as e:
+ logging.error(f"El proceso excedió el tiempo de espera: {e}")
+ return 1
+
+ except subprocess.CalledProcessError as e:
+ logging.error(f"Error al ejecutar el comando: {e}")
+ return 1
+
+ except Exception as e:
+ logging.error(f"Ocurrió un error inesperado: {e}")
+ return 1
+
+
+# (6.3) OpenOCD Function
+def start_openocd_thread(req_data):
+ try:
+ thread = threading.Thread(
+ target=monitor_openocd_output,
+ args=(req_data, ['openocd', '-f', './openscript.cfg'], 'openocd'),
+ daemon=True
+ )
+ thread.start()
+ logging.debug("Starting OpenOCD thread...")
+ return thread
+ except Exception as e:
+ req_data['status'] += f"Error starting OpenOCD: {str(e)}\n"
+ logging.error(f"Error starting OpenOCD: {str(e)}")
+ return None
+# (6.4) GDBGUI function
+def start_gdbgui(req_data):
+ route = os.path.join(BUILD_PATH, 'gdbinit')
+ logging.debug(f"GDB route: {route}")
+ route = os.path.join(BUILD_PATH, 'gdbinit')
+ if os.path.exists(route) and os.path.exists("./gbdscript.gdb"):
+ logging.debug(f"GDB route: {route} exists.")
+ else:
+ logging.error(f"GDB route: {route} does not exist.")
+ req_data['status'] += f"GDB route: {route} does not exist.\n"
+ return jsonify(req_data)
+ req_data['status'] = ''
+ if check_uart_connection:
+ logging.info("Starting GDBGUI...")
+ gdbgui_cmd = ['idf.py', '-C', BUILD_PATH, 'gdbgui', '-x', route, 'monitor']
+ sleep(5)
+ try:
+ process_holder['gdbgui'] = subprocess.run(
+ gdbgui_cmd,
+ stdout=sys.stdout,
+ stderr=sys.stderr,
+ text=True
+ )
+ if process_holder['gdbgui'].returncode != -9 and process_holder['gdbgui'].returncode != 0:
+ logging.error(f"Command failed with return code {process_holder['gdbgui'].returncode}")
+
+ except subprocess.CalledProcessError as e:
+ logging.error("Failed to start GDBGUI: %s", e)
+ req_data['status'] += f"Error starting GDBGUI (code {e.returncode}): {e.stderr}\n"
+ return None
+ except Exception as e:
+ logging.error("Unexpected error in GDBGUI: %s", e)
+ req_data['status'] += f"Unexpected error starting GDBGUI: {e}\n"
+ return None
+
+ req_data['status'] += f"Finished debug session: {e}\n"
+ else:
+ req_data['status'] += f"UART not connected: {e}\n"
+ return jsonify(req_data)
+
+
+
+def do_debug_request(request):
+ global stop_event
+ global process_holder
+ error = 0
+ try:
+ req_data = request.get_json()
+ target_device = req_data['target_port']
+ req_data['status'] = ''
+
+ # Check .elf files in BUILD_PATH
+ route = BUILD_PATH +'/build'
+ logging.debug(f"Checking for ELF files in {route}")
+ elf_files = [f for f in os.listdir(route) if f.endswith(".elf")]
+ if not elf_files:
+ req_data['status'] += "No ELF file found in build directory.\n"
+ logging.error("No ELF file found in build directory.")
+ return jsonify(req_data)
+ logging.debug("Delete previous work")
+ # Clean previous debug system
+ if error == 0:
+ if 'openocd' in process_holder:
+ logging.debug('Killing OpenOCD')
+ kill_all_processes("openocd")
+ process_holder.pop('openocd', None)
+
+ # Check if JTAG is connected
+ if not check_jtag_connection():
+ req_data['status'] += "No JTAG found\n"
+ return jsonify(req_data)
+
+ # Start OpenOCD
+ logging.info("Starting OpenOCD...")
+ openocd_thread = start_openocd_thread(req_data)
+ while process_holder.get('openocd') is None:
+ sleep(1)
+ #start_openocd_thread(req_data)
+
+ # Start gdbgui
+ #logging.info("Starting gdbgui")
+ error = start_gdbgui(req_data)
+ if error != 0:
+ req_data['status'] += "Error starting gdbgui\n"
+ return jsonify(req_data)
+ else:
+ req_data['status'] += "Build error\n"
+
+ except Exception as e:
+ req_data['status'] += f"Unexpected error: {str(e)}\n"
+ logging.error(f"Exception in do_debug_request: {e}")
+
+ return jsonify(req_data)
+
# Setup flask and cors:
app = Flask(__name__)
@@ -302,6 +603,30 @@ def post_job():
def post_stop_flash():
return do_stop_flash_request(request)
+# (6) POST /fullclean -> clean build directory
+@app.route("/fullclean", methods=["POST"])
+@cross_origin()
+def post_fullclean_flash():
+ return do_fullclean_request(request)
+
+# (7) POST /eraseflash -> clean board flash
+@app.route("/eraseflash", methods=["POST"])
+@cross_origin()
+def post_erase_flash():
+ return do_eraseflash_request(request)
+
+# (8) POST /debug -> debug
+@app.route("/debug", methods=["POST"])
+@cross_origin()
+def post_debug():
+ return do_debug_request(request)
+
+# (9) Stop monitor
+@app.route("/stopmonitor", methods=["POST"])
+@cross_origin()
+def post_stop_monitor():
+ return do_stop_monitor_request(request)
+
# Run
app.run(host='0.0.0.0', port=8080, use_reloader=False, debug=True)
\ No newline at end of file
diff --git a/gateway/esp32-rv/gbdscript.gdb b/gateway/esp32-rv/gbdscript.gdb
new file mode 100644
index 000000000..263322e31
--- /dev/null
+++ b/gateway/esp32-rv/gbdscript.gdb
@@ -0,0 +1,16 @@
+delete breakpoints
+target extended-remote :3333
+set remotetimeout 10
+monitor reset halt
+maintenance flush register-cache
+b main
+continue
+
+define hook-stop
+ set $inst = *(unsigned int *)$pc
+ if $inst == 0x00000073
+ set $next = $pc + 4
+ tbreak *$next
+ continue
+ end
+end
diff --git a/gateway/esp32-rv/gdbinit b/gateway/esp32-rv/gdbinit
new file mode 100644
index 000000000..77a153b98
--- /dev/null
+++ b/gateway/esp32-rv/gdbinit
@@ -0,0 +1,2 @@
+source ./build/gdbinit/symbols
+source ./gbdscript.gdb
diff --git a/gateway/esp32-rv/main/CMakeLists.txt b/gateway/esp32-rv/main/CMakeLists.txt
index 37526735f..defd3a360 100644
--- a/gateway/esp32-rv/main/CMakeLists.txt
+++ b/gateway/esp32-rv/main/CMakeLists.txt
@@ -1,9 +1,9 @@
idf_component_register(
SRCS
+ "syscall/test_panic.c"
+ "syscall/return_from_panic.S"
"program.s"
"creator-esp.c"
- "ecall.s"
- "io.s"
"creator.s"
INCLUDE_DIRS
"..")
diff --git a/gateway/esp32-rv/main/creator.s b/gateway/esp32-rv/main/creator.s
index 43069045d..4af8fd554 100644
--- a/gateway/esp32-rv/main/creator.s
+++ b/gateway/esp32-rv/main/creator.s
@@ -1,5 +1,5 @@
#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
+# Copyright 2018-2024 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
#
# This file is part of CREATOR.
#
diff --git a/gateway/esp32-rv/main/ecall.s b/gateway/esp32-rv/main/ecall.s
deleted file mode 100644
index 98eba8907..000000000
--- a/gateway/esp32-rv/main/ecall.s
+++ /dev/null
@@ -1,345 +0,0 @@
-#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
-#
-# This file is part of CREATOR.
-#
-# CREATOR is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# CREATOR 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 Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with CREATOR. If not, see .
-#
-
-# ecall Module
-# ============
-# This module implements the _myecall function, which will be used in a future
-# to implement the ecall instruction. For the time being, every instance of
-# ecall is replaced with a call to _myecall (pushing all the registers first,
-# so they can be restored after the call).
-#
-# AUTHOR: José Antonio Verde Jiménez
-
-# ==== .rodata ============================================================== #
-.section .rodata
-
-.align 2
-
-vector:
- .word unknown # a7 = 0
- .word print_int # a7 = 1
- .word print_float # a7 = 2
- .word print_double # a7 = 3
- .word print_string # a7 = 4
- .word read_int # a7 = 5
- .word read_float # a7 = 6
- .word read_double # a7 = 7
- .word read_string # a7 = 8
- .word sbrk # a7 = 9
- .word exit # a7 = 10
- .word print_char # a7 = 11
- .word read_char # a7 = 12
-
-vector_size:
- .word (vector_size - vector) / 4
-
-unknown_msg:
- .asciz "\033[31;1m¡Unknown system call!\033[0m\n"
-
-not_implemented_msg:
- .asciz "\033[31;1mNot implemented\033[0m\n"
-
-sbrk_error_msg:
- .asciz "\033[31;1mSBRK: Memory exhausted\033[0m\n"
-
-integers:
- .byte -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
- .byte -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
- .byte -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
- .byte 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1
- .byte -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1
- .byte -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
- .byte -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1
- .byte -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
-
-images: .byte '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
-
-# ==== .data ================================================================ #
-.section .data
-
-.set POOL_CAPACITY, 65536
-
-.align 2
-pool_capacity: .word POOL_CAPACITY
-pool_size: .word 0
-
-pool: .space POOL_CAPACITY
-
-# A small buffer for strings
-buffer: .space 256
-buffer_end:
-
-# ==== .text ================================================================ #
-.section .text
-
-.type _ecall, @function
-.globl _myecall
-
-_not_implemented:
- addi sp, sp, -4
- sw ra, 0(sp)
- la a0, not_implemented_msg
- li a7, 4
- jal ra, _myecall
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-unknown:
- addi sp, sp, -4
- sw ra, 0(sp)
- li a7, 4
- la a0, unknown_msg
- jal ra, _myecall
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: void print_int (int number, int base = 10)
-# PARAMETERS: int a0 : The number base, it is ignored, 10 is always used.
-# RETURNS: NONE
-# DESCRIPTION: Print an integer into the string using the given base.
-
-print_int:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- # a0 : number
- # a1 : base
- li a1, 10
- la a2, buffer_end
- la a3, images
-
- sgtz t0, a0 # t0 := a0 > 0 ? 1 : 0
- seqz t1, a0 # t1 := a0 == 0 ? 1 : 0
- or t0, t0, t1 # t0 := t0 or t1
- slli t0, t0, 1 # t0 := 2 * t0
- addi t4, t0, -1 # t4 := t0 - 1
- # Now, t4 is -1 if negative and 1 if positive or zero
- sltz a4, a0 # a4 := a0 < 0 ? 1 : 0
-
- # End the string with a zero
- sb zero, -1(a2)
- addi a2, a2, -2
-
- print_int_loop:
- rem t0, a0, a1 # value := number rem base
- mul t0, t0, t4 # Negative number case
- div a0, a0, a1 # number := number / base
- add t0, a3, t0 # images + value
- lb t0, 0(t0) # *(images + value)
- sb t0, 0(a2) # *buffer = *(images + value)
- addi a2, a2, -1
- bne a0, zero, print_int_loop
-
- beq a4, zero, print_int_positive
- print_int_negative:
- li t0, '-'
- sb t0, 0(a2)
- addi a2, a2, -1
-
- print_int_positive:
- addi a0, a2, 1
-
- jal ra, put_zstring
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-print_float:
- j _not_implemented # TODO
-print_double:
- j _not_implemented # TODO
-
-print_string:
- j put_zstring
-
-# FUNCTION: (int, bool) read_int (int base = 10)
-# PARAMETERS: int a0 : The number base, it is ignored, 10 is always used.
-# RETURNS: int a0 : The result of reading the integer
-# bool a1 : true if it succeeded, false if it failed
-# DESCRIPTION: Read an integer from standard input and return it. It stops
-# reading if it finds a non integer value.
-read_int:
- addi sp, sp, -20
- sw ra, 0(sp)
- sw s0, 4(sp) # For the result
- sw s1, 8(sp) # For the base
- sw s2, 12(sp) # For the sign
- sw s3, 16(sp) # Characters read
-
- jal ra, skip_separators
-
- mv s0, zero # Result = 0
- li a0, 10 # s0 will contain the base, in a future this parameter will
- mv s1, a0 # be accepted.
- li s2, 1 # Positive
- mv s3, zero
-
- li t0, 2
- li t1, 16
- blt s1, t0, read_int_bad_base # a1 < 2 (Invalid base)
- bgt s1, t1, read_int_bad_base # a1 > 16 (Invalid base)
-
- jal ra, peek_character
- li t0, '+'
- li t1, '-'
- beq a0, t0, read_int_positive
- beq a0, t1, read_int_negative
- j read_int_parse
- read_int_negative:
- li s2, -1
- read_int_positive:
- jal ra, get_character # Feed a character and ignore it.
- read_int_parse:
- jal ra, peek_character
- blt a0, zero, read_int_stop # exit when char < 0
- la t0, integers
- add t0, t0, a0
- lb t0, 0(t0) # t0 = integers[char]
- blt t0, zero, read_int_stop # exit when t0 < -1
- bge t0, s1, read_int_stop # exit when not in base
- addi s3, s3, 1
- mul s0, s0, s1
- add s0, s0, t0 # result = result * base + integers[char]
- jal ra, get_character # read it
- j read_int_parse
-
- read_int_stop:
- beq s3, zero, read_int_failed # No characters read.
- mul a0, s0, s2
- li a1, 1 # return (result * sign, true)
- j read_int_end
-
- read_int_failed:
- read_int_bad_base:
- mv a0, zero
- mv a1, zero
- j read_int_end
-
- read_int_end:
- lw ra, 0(sp)
- lw s0, 4(sp)
- lw s1, 8(sp)
- lw s2, 12(sp)
- lw s3, 16(sp)
- addi sp, sp, 20
- jr ra
-
-read_float:
- j _not_implemented # TODO
-read_double:
- j _not_implemented # TODO
-read_string:
- j get_string
-
-# TODO: Implement a good memory pool
-sbrk:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- la t0, pool_capacity
- la t1, pool_size
- la t2, pool
-
- lw t3, 0(t0) # capacity
- lw t4, 0(t1) # size
- add t5, t4, a0 # t5 = size + a0
- bgt t5, t3, sbrk_overflow
-
- sbrk_valid:
- sw t5, 0(t1) # size = size'old + a0
- add a0, t2, t4 # return pool + size'old
- j sbrk_end
-
- sbrk_overflow:
- la a0, sbrk_error_msg
- la a7, 4
- jal ra, _myecall
-
- sbrk_end:
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-exit:
- j _exit
-
-print_char:
- j put_immediate
-
-read_char:
- j get_character
-
-_myecall:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- # Check it is in range
- la t0, vector_size
- lw t0, 0(t0)
- blt a7, zero, _myecall_unknown
- bgt a7, t0, _myecall_unknown
-
- # # Call it
- la t0, vector
- slli a7, a7, 2
- add t0, t0, a7
- lw t0, 0(t0)
- jalr ra, t0 # goto vector[a7 * 4]
- j _myecall_end
-
- _myecall_unknown:
- la a0, unknown_msg
- li a7, 4
- jal ra, _myecall # print_string
-
- _myecall_end:
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: skip_separators
-# PARAMETERS: NONE
-# RETURNS: NONE
-# DESCRIPTION: This function skips separators (spaces, tabulations...), until
-# it finds something different.
- .type skip_separators, @function
-skip_separators:
- addi sp, sp, -4
- sw ra, 0(sp)
- skip_separators_loop:
- jal ra, peek_character
- li t0, '\n' # Line feed
- li t1, '\r' # Carriage return
- li t2, ' ' # Space
- li t3, '\t' # Tabulator
- beq a0, t0, skip_separators_skip_it
- beq a0, t1, skip_separators_skip_it
- beq a0, t2, skip_separators_skip_it
- beq a0, t3, skip_separators_skip_it
- j skip_separators_end
- skip_separators_skip_it:
- jal ra, get_character
- j skip_separators_loop
- skip_separators_end:
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
diff --git a/gateway/esp32-rv/main/io.s b/gateway/esp32-rv/main/io.s
deleted file mode 100644
index ce6c46e85..000000000
--- a/gateway/esp32-rv/main/io.s
+++ /dev/null
@@ -1,571 +0,0 @@
-#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
-#
-# This file is part of CREATOR.
-#
-# CREATOR is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# CREATOR 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 Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with CREATOR. If not, see .
-#
-
-# I/O Module
-# ==========
-# The following constraints hold for the device's standard I/O:
-#
-# * Reading a character doesn't echo.
-# * Once a character is written you cannot delete it (backspace) or move
-# back (arrow keys).
-# * Writing doesn't echo until a line feed is issued.
-#
-# Therefore this module attempts to solve this by creating a very simple
-# editable line where input can be modified using arrow keys, and characters
-# can be removed with backspace.
-#
-# This file implements the following global functions:
-#
-# int get_immediate ()
-# int get_character ()
-# int peek_character ()
-# void get_string (char *string, int length);
-#
-# void put_immediate (char c)
-# void put_zstring (char *c);
-# void put_string (char *c, int length);
-#
-# TODO: Add latin-1 for accents
-#
-# AUTHOR: José Antonio Verde Jiménez
-
-# ==== .rodata ============================================================== #
-.section .rodata
-
-go_right_str: .asciz "\033[1C"
-go_left_str: .asciz "\033[1D"
-
-# ==== .data ================================================================ #
-
-.section .data
-
-# char line[]
-# A very long string buffer (UTF-8) where the input characters are stored and
-# modified. Writing is done to this string until a newline character is found
-# (\n, \r or \r\n), then I/O operations can continue.
-.set LINE_SIZE, 80
-.align 2
-line: .space LINE_SIZE
-.align 2
-
-# int begin
-# Begin cursor, points to the beginning of the line, it increases while the
-# line is being read.
-begin: .word 0
-
-# int end
-# End cursor, points to the end of the line.
-end: .word 0
-
-# int next
-# The next character to be read if any.
-# If the number is -1, then we reached EOF.
-# Otherwise, it stores the next 8 bit character in buffer.
-next: .word 0
-
-# bool full
-# True if there is a buffered character.
-full: .word 0
-
-# ==== .rodata ============================================================== #
-
-.section .rodata
-
-.align 2
-line_capacity: .word LINE_SIZE
-
-# ==== .text ================================================================ #
-
-.section .text
-
-# FUNCTION: int get_immediate ()
-# PARAMETERS: NONE
-# RETURNS: int a0 : -1 on EOF, otherwise the character read
-# DESCRIPTION: Read the next character without echoing it.
- .type get_immediate, @function
- .globl get_immediate
-get_immediate:
- addi sp, sp, -8
- sw ra, 0(sp)
- sw s0, 4(sp)
-
- la t0, next
- la t1, full
- lw t2, 0(t1)
- beq t2, zero, get_immediate_read_new # If not full read it.
-
- get_immediate_buffered:
- sw zero, 0(t1) # full = 0
- lw a0, 0(t0) # return next
- li t0, -1
- j get_immediate_end
-
- get_immediate_read_new:
- mv a0, zero
- la a1, next
- li a2, 1
- jal ra, read # read(0, &next, 1)
- beq zero, a0, get_immediate_eof
- # TODO: Check if UTF-8 valid string
- la t0, next # t0 = &next
- lb a0, 0(t0) # a0 = next
- j get_immediate_end
-
- get_immediate_eof:
- li a0, -1 # Return -1
-
- get_immediate_end:
- lw ra, 0(sp)
- lw s0, 4(sp)
- addi sp, sp, 8
- jr ra
-
-# FUNCTION: void put_immediate (char c)
-# PARAMETERS: a0 : char c
-# RETURNS: NONE
-# DESCRIPTION: Put a character immediately on the screen.
- .type put_immediate, @function
- .globl put_immediate
-put_immediate:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- jal ra, put
- jal ra, synchronise
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: void put_zstring (char *str)
-# PARAMETERS: a0 : char *str
-# RETURNS: NONE
-# DESCRIPTION: Put a zero-terminated string on the screen.
- .type put_zstring, @function
- .globl put_zstring
-put_zstring:
- addi sp, sp, -8
- sw ra, 0(sp)
- sw s0, 4(sp)
-
- mv s0, a0
- put_zstring_loop:
- lb a0, 0(s0)
- beq a0, zero, put_zstring_end
- jal ra, put
- addi s0, s0, 1
- j put_zstring_loop
-
- put_zstring_end:
- mv a0, zero
- jal ra, synchronise
-
- lw ra, 0(sp)
- lw s0, 4(sp)
- addi sp, sp, 8
- jr ra
-
-# FUNCTION: void put_string (char *str, int length)
-# PARAMETERS: a0 : char *str
-# a1 : int length
-# RETURNS: NONE
-# DESCRIPTION: Put a `length' bytes long string on the screen.
- .type put_string, @function
- .globl put_string
-put_string:
- addi sp, sp, -12
- sw ra, 0(sp)
- sw s0, 4(sp)
- sw s1, 8(sp)
-
- mv s0, a0
- mv s1, a1
- put_string_loop:
- beq s1, zero, put_string_end
- lb a0, 0(s0)
- jal ra, put
- addi s0, s0, 1
- addi s1, s1, -1
- j put_string_loop
-
- put_string_end:
- jal ra, synchronise
-
- lw ra, 0(sp)
- lw s0, 4(sp)
- lw s1, 8(sp)
- addi sp, sp, 12
- jr ra
-
-# FUNCTION: void get_string (char *str, int length)
-# PARAMETERS: a0 : char *str
-# a1 : int length
-# RETURNS: NONE
-# DESCRIPTION: Read a `length' bytes long string.
- .type get_string, @function
- .globl get_string
-get_string:
- addi sp, sp, -12
- sw ra, 0(sp)
- sw s0, 4(sp)
- sw s1, 8(sp)
-
- mv s0, a0 # s0 = str
- mv s1, a1 # s1 = length
- get_string_loop:
- beq s1, zero, get_string_end # exit when length = 0
- jal ra, get_character # a0 = get_character()
- li t0, -1
- beq t0, a0, get_string_end # exit when EOF
- sw a0, 0(s0) # *s0 = a0
- addi s1, s1, -1
- addi s0, s0, 1
- j get_string_loop
-
- get_string_end:
- lw ra, 0(sp)
- lw s0, 4(sp)
- lw s1, 8(sp)
- addi sp, sp, 12
- jr ra
-
-# FUNCTION: int get_character ()
-# PARAMETERS: NONE
-# RETURNS: a0 : int
-# DESCRIPTION: Read one character from standard input
- .type get_character, @function
- .globl get_character
-get_character:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- jal ra, refill
- la t0, begin
- la t1, line
- lw t2, 0(t0)
- add t3, t2, t1 # t2 = line + begin
- lb a0, 0(t3) # return *(line + begin)
- addi t2, t2, 1
- sw t2, 0(t0) # begin++
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: int peek_character ()
-# PARAMETERS: NONE
-# RETURNS: a0 : int
-# DESCRIPTION: Peek which will be the next character from standard input.
- .type peek_character, @function
- .globl peek_character
-peek_character:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- jal ra, refill
- la t0, begin
- la t1, line
- lw t0, 0(t0)
- add t2, t1, t0
- lb a0, 0(t2)
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# ==== Utility ==== #
-
-# FUNCTION: int peek ()
-# PARAMETERS: NONE
-# RETURNS: a0 : int ch
-# DESCRIPTION: It checks the next character (if any) and returns it. The next
-# time it is read or peeked, it returns the same character until
-# it is read.
- .type peek, @function
-peek:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- la t1, full
- lw a1, 0(t1)
- bne a1, zero, peek_full # If full, goto peek_full
-
- peek_empty:
- jal ra, get_immediate
- la t1, full
- li a1, 1
- sw a1, 0(t1) # Fill it
-
- peek_full:
- la t0, next
- lw a0, 0(t0)
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: void read_line ()
-# PARAMETERS: NONE
-# RETURNS: NONE
-# DESCRIPTION: Reads full line.
- .type read_line, @function
-read_line:
- addi sp, sp, -24
- sw ra, 0(sp)
- sw s0, 4(sp)
- sw s1, 8(sp)
- sw s2, 12(sp)
- sw s3, 16(sp)
- sw s4, 20(sp)
-
- la s0, line # s0 (line)
- mv s1, zero # s1 (end)
- mv s2, zero # s2 (cursor)
-
- read_line_loop:
- jal ra, get_immediate
-
- li t0, -1 # EOF
- li t1, '\n' # LF
- li t2, '\r' # CR
- li t3, 27 # ESC
- li t4, '\b' # Backspace
-
- beq a0, t0, read_line_end # EOF
- beq a0, t1, read_line_LF # Line Feed
- beq a0, t2, read_line_CR # Carriage Return
- beq a0, t3, read_line_on_ESC # Escape Sequence
- beq a0, t4, read_line_on_DEL # Backspace
-
- read_line_normal:
- # Check line capacity
- la t0, line_capacity
- lw t0, 0(t0)
- addi t0, t0, -1
- bge s1, t0, read_line_loop # Line too long
-
- # Shift right the line
- mv t0, s1 # t0 = end
- add t1, s0, s1 # char *t1 = &line[end]
- read_line_shift_right:
- blt t0, s2, read_line_end_shift_right # exit when t0 = Cursor
- lb t2, 0(t1)
- sb t2, 1(t1)
- addi t1, t1, -1
- addi t0, t0, -1
- j read_line_shift_right
- read_line_end_shift_right:
-
- # Insert character
- add t0, s0, s2 # t0 = &line[cursor]
- sb a0, 0(t0) # line[cursor] = a0
- addi s1, s1, 1 # end++
- addi s2, s2, 1 # cursor++
-
- # Redraw
- add a0, s0, s2
- addi a0, a0, -1 # str = line + cursor - 1
- sub a1, s1, s2
- addi a1, a1, 1 # length = end - cursor + 1
- jal ra, put_string # put_string(t0, end - cursor + 1)
-
- # Move cursor back
- mv s3, s2 # s3 = cursor
- read_line_cursor_back:
- beq s3, s1, read_line_end_cursor_back
- la a0, go_left_str
- jal ra, put_zstring
- addi s3, s3, 1 # s3++
- j read_line_cursor_back
- read_line_end_cursor_back:
-
- j read_line_loop
-
- read_line_LF:
- add t0, s0, s1
- sb a0, 0(t0) # line[end] = '\n'
- addi s1, s1, 1
- jal ra, put_immediate
- j read_line_end
-
- read_line_CR:
- jal ra, peek
- li t0, '\n'
- beq t0, a0, read_line_CR_win
- read_line_CR_mac:
- mv a0, t0
- j read_line_LF
-
- read_line_CR_win:
- jal ra, get_immediate # Read it to skip it
- j read_line_LF
-
- read_line_on_ESC:
- jal ra, peek
- li t0, '['
- bne a0, t0, read_line_loop # Bad sequence
- jal ra, get_immediate
-
- jal ra, peek
- li t0, 'A'
- li t1, 'B'
- li t2, 'C'
- li t3, 'D'
-
- beq a0, t0, read_line_on_ESC_UP_DOWN
- beq a0, t1, read_line_on_ESC_UP_DOWN
- beq a0, t2, read_line_on_ESC_RIGHT
- beq a0, t3, read_line_on_ESC_LEFT
- j read_line_loop
-
- read_line_on_ESC_UP_DOWN:
- jal ra, get_immediate # Buffer it and ignore it.
- j read_line_loop
-
- read_line_on_ESC_LEFT:
- jal ra, get_immediate
- beq s2, zero, read_line_loop # Can't go to left.
- addi s2, s2, -1
- la a0, go_left_str
- jal ra, put_zstring
- j read_line_loop
-
- read_line_on_ESC_RIGHT:
- jal ra, get_immediate
- bge s2, s1, read_line_loop # Can't go right.
- addi s2, s2, 1
- la a0, go_right_str
- jal ra, put_zstring
- j read_line_loop
-
- read_line_on_DEL:
- beq s2, zero, read_line_loop # Cursor at the beginning
- beq s1, zero, read_line_loop # End at the beginning
-
- # Shift the string left
- jal ra, put # Go back, not immediate.
- addi s1, s1, -1 # end--
- addi s2, s2, -1 # cursor--
- mv s3, s2 # s3 = cursor
- add s4, s0, s2 # s4 = line + cursor
- read_line_shift_left:
- beq s3, s1, read_line_end_shift_left
- lb a0, 1(s4)
- sb a0, 0(s4) # *s4 = *(s4 + 1)
- jal ra, put # put(a0), Not buffered
- addi s3, s3, 1 # s3++
- addi s4, s4, 1 # s4++
- j read_line_shift_left
- read_line_end_shift_left:
- li a0, ' '
- jal ra, put
- li a0, '\b'
- jal ra, put_immediate
-
- # Return cursor to last position
- mv s3, s2 # s3 = cursor
- read_line_DEL_cursor_back:
- beq s3, s1, read_line_DEL_end_cursor_back
- la a0, go_left_str
- jal ra, put_zstring
- addi s3, s3, 1 # s3++
- j read_line_cursor_back
- read_line_DEL_end_cursor_back:
- j read_line_loop
-
- read_line_end:
-
- la t0, begin
- la t1, end
- sw zero, 0(t0) # begin = 0
- sw s1, 0(t1) # end = end
-
- lw ra, 0(sp)
- lw s0, 4(sp)
- lw s1, 8(sp)
- lw s2, 12(sp)
- lw s3, 16(sp)
- lw s4, 20(sp)
- addi sp, sp, 24
- jr ra
-
-# FUNCTION: void put (char c)
-# PARAMETERS: a0 : char c
-# RETURNS: NONE
-# DESCRIPTION: Put a character on the screen without synchronising. If the
-# character is a line feed (\n) it transforms it to (\r\n).
- .type put, @function
-put:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- li t0, '\n'
- bne t0, a0, put_skip
-
- put_CR:
- li a0, '\r'
- jal ra, putchar
- li a0, '\n'
-
- put_skip:
- jal ra, putchar
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: void synchronise ()
-# PARAMETERS: NONE
-# RETURNS: NONE
-# DESCRIPTION: Flush and synchronise output
- .type synchronise, @function
-synchronise:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- li a0, 1 # a0 = STDOUT
- jal ra, fsync
-
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# FUNCTION: void refill ()
-# PARAMETERS: NONE
-# RETURNS: NONE
-# DESCRIPTION: Refill the line buffer if it is empty.
- .type refill, @function
-refill:
- addi sp, sp, -4
- sw ra, 0(sp)
-
- la t0, begin
- la t1, end
- lw t2, 0(t0)
- lw t3, 0(t1)
- sub t5, t3, t2 # t5 = end - begin
- blt zero, t5, refill_end # return when end - begin > 0
- jal ra, read_line # Otherwise re-read the line
-
- refill_end:
- lw ra, 0(sp)
- addi sp, sp, 4
- jr ra
-
-# ======== END OF FILE ====================================================== #
diff --git a/gateway/esp32-rv/main/rdcycle.s b/gateway/esp32-rv/main/rdcycle.s
index d4b39658d..0acef85cd 100644
--- a/gateway/esp32-rv/main/rdcycle.s
+++ b/gateway/esp32-rv/main/rdcycle.s
@@ -1,5 +1,5 @@
#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
+# Copyright 2018-2024 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
#
# This file is part of CREATOR.
#
diff --git a/gateway/esp32-rv/main/syscall/return_from_panic.S b/gateway/esp32-rv/main/syscall/return_from_panic.S
new file mode 100644
index 000000000..388407304
--- /dev/null
+++ b/gateway/esp32-rv/main/syscall/return_from_panic.S
@@ -0,0 +1,71 @@
+// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "riscv/rvruntime-frames.h"
+
+/* The riscv panic handler in components/riscv/vectors.S doesn't allow the panic
+ handler function to return.
+
+ However, for the purposes of this test we want to allow the panic handler to return.
+
+ There is functionality in vectors.S restore the CPU state, but it only
+ restores when CONTEXT_SIZE registers are
+ pushed onto the stack not RV_STK_FRMSZ registers
+
+ Instead of messing with that, implement a full "restore from RvExcFrame"
+ function here which restores the CPU and then
+ returns from exception.
+
+ Called as return_from_panic_handler(RvExcFrame *frame)
+*/
+.global return_from_panic_handler
+return_from_panic_handler:
+ or t0, a0, a0 /* use t0 as the working register */
+
+ /* save general registers */
+ lw ra, RV_STK_RA(t0)
+ lw sp, RV_STK_SP(t0)
+ lw gp, RV_STK_GP(t0)
+ lw tp, RV_STK_TP(t0)
+ lw s0, RV_STK_S0(t0)
+ lw s1, RV_STK_S1(t0)
+ lw a0, RV_STK_A0(t0)
+ lw a1, RV_STK_A1(t0)
+ lw a2, RV_STK_A2(t0)
+ lw a3, RV_STK_A3(t0)
+ lw a4, RV_STK_A4(t0)
+ lw a5, RV_STK_A5(t0)
+ lw a6, RV_STK_A6(t0)
+ lw a7, RV_STK_A7(t0)
+ lw s2, RV_STK_S2(t0)
+ lw s3, RV_STK_S3(t0)
+ lw s4, RV_STK_S4(t0)
+ lw s5, RV_STK_S5(t0)
+ lw s6, RV_STK_S6(t0)
+ lw s7, RV_STK_S7(t0)
+ lw s8, RV_STK_S8(t0)
+ lw s9, RV_STK_S9(t0)
+ lw s10, RV_STK_S10(t0)
+ lw s11, RV_STK_S11(t0)
+ lw t3, RV_STK_T3(t0)
+ lw t4, RV_STK_T4(t0)
+ lw t5, RV_STK_T5(t0)
+ lw t6, RV_STK_T6(t0)
+
+ lw t2, RV_STK_MEPC(t0)
+ csrw mepc, t2
+
+ lw t1, RV_STK_T1(t0)
+ lw t2, RV_STK_T2(t0)
+ lw t0, RV_STK_T0(t0)
+ mret
diff --git a/gateway/esp32-rv/main/syscall/test_panic.c b/gateway/esp32-rv/main/syscall/test_panic.c
new file mode 100644
index 000000000..ae440bab8
--- /dev/null
+++ b/gateway/esp32-rv/main/syscall/test_panic.c
@@ -0,0 +1,267 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Panic handler wrapper in order to simulate ecalls in CREATOR using Espressif family
+ * Author: Elisa Utrilla Arroyo
+ *
+ */
+
+
+#include "riscv/rvruntime-frames.h"
+#include "esp_private/panic_internal.h"
+#include "esp_private/panic_reason.h"
+#include "hal/wdt_hal.h"
+#include "soc/timer_group_struct.h"
+#include "soc/timer_group_reg.h"
+#include "esp_rom_sys.h"
+#include "esp_attr.h"
+#include
+#include "rom/uart.h"
+#include "esp_task_wdt.h"
+
+#define POOL_CAPACITY 65536 // 64 KB poolç
+char memory_pool[POOL_CAPACITY];
+int current_offset = 0;
+
+void disable_all_hw_watchdogs() {
+ // TG0
+ TIMERG0.wdtwprotect.val = 0x50D83AA1;
+ TIMERG0.wdtconfig0.val = 0;
+ TIMERG0.wdtwprotect.val = 0;
+
+ // TG1
+ TIMERG1.wdtwprotect.val = 0x50D83AA1;
+ TIMERG1.wdtconfig0.val = 0;
+ TIMERG1.wdtwprotect.val = 0;
+}
+
+extern void esp_panic_handler(panic_info_t *info);
+volatile bool g_override_ecall = true;
+
+void __real_esp_panic_handler(panic_info_t *info);
+
+
+void return_from_panic_handler(RvExcFrame *frm) __attribute__((noreturn));
+// -------------Read int from UART0
+static char buffer_int[16];
+static int idx = 0;
+
+int read_buffer_int(){
+ unsigned char c = '\0';
+
+ uart_rx_one_char(&c);
+ //Is an space?? Finish it!!
+ if (c == '\n' || c == '\r') {
+ buffer_int[idx++] = '\0';
+ esp_rom_printf("\n"); //echo
+ if (idx > 0) //Buffer has things
+ {
+ idx = 0;
+ return 0;
+ }
+ else{ return -1; }
+
+ }
+ //Number: wait until another num comes up OR a \n
+ if (isdigit(c)) {
+ if (idx < sizeof(buffer_int) - 1) {
+ buffer_int[idx] = c;
+ esp_rom_printf("%c", c);
+ idx++;
+ return -1;
+ }
+ }
+ //TODO: Protocol for char instead of number
+
+ return -1;
+}
+int read_int(){
+ int value = 0;
+ int i = -1;
+ int sum = 0;
+ while (i == -1){
+ i = read_buffer_int();
+ if (i == -1){
+ //
+ for (int x =1;x< 1000; x++){
+ sum ++;
+
+ }
+ }
+ }
+ // Transform into number
+ for (int z = 0; buffer_int[z] != '\0'; z++) {
+ value = value * 10 + (buffer_int[z] - '0');
+ }
+ memset(buffer_int, 0, sizeof(buffer_int));
+ return value;
+
+}
+//------------- Read char from UART0
+char read_buffer_char(){
+ unsigned char c = '\0';
+ //esp_rom_printf("Value: %c",c);
+
+ uart_rx_one_char(&c);
+ if (c != '\0') {
+ esp_rom_printf("%c", c);
+ return c;
+ }
+ else{ return '\0'; }
+
+ return -1;
+}
+
+char read_char() {
+ char value = '\0';
+ int sum =0;
+ while (value == '\0'){
+ value = read_buffer_char();
+ if (value == '\0'){
+ for (int x =1;x< 1000; x++){
+ sum ++;
+
+ }
+ }
+ }
+ return value;
+}
+//------------- Read string from UART0
+int read_buffer_string(char *dest, int length){
+ unsigned char c = '\0';
+
+ uart_rx_one_char(&c);
+ //Is an space?? Finish it!!
+ if (c == '\n' || c == '\r') {
+ dest[idx++] = '\0';
+ esp_rom_printf("\n"); //echo
+ if (idx > 0 || idx >= length) //Buffer has things or surpass the length
+ {
+ idx = 0;
+ return 0;
+ }
+ else{ return -1; }
+
+ }
+ //Wait until another char comes
+ if (c != '\0') {
+ if (idx < length) {
+ dest[idx] = c;
+ esp_rom_printf("%c", c);
+ idx++;
+ return -1;
+ }
+ }
+ //TODO: Protocol for char instead of number
+
+ return -1;
+}
+
+void read_string(char *dest, int length) {
+ int value = 0;
+ int i = -1;
+ int sum = 0;
+ while (i == -1){
+ i = read_buffer_string(dest,length);
+ if (i == -1){
+ //
+ for (int x =1;x< 1000; x++){
+ sum ++;
+
+ }
+ }
+ }
+}
+
+IRAM_ATTR void __wrap_esp_panic_handler(panic_info_t *info)
+{
+ RvExcFrame *frm = (RvExcFrame *)info->frame;
+ if ((frm->mcause == 0x0000000b || frm->mcause == 0x00000008) && g_override_ecall == true) { //Only catches Ecall syscalls
+ disable_all_hw_watchdogs();
+ int cause = frm->a7;
+ //esp_rom_printf("Causa del panic (a7): %d\n", cause);
+ switch (cause) {
+ case 1: { //Print int
+ int value = frm->a0;
+ esp_rom_printf("%d\n", value);
+ break;
+ }
+ case 2: { //Print float TODO
+ esp_rom_printf("\033[1;31mFloat number operations not registered yet\033[0m\n");
+ break;
+ }
+ case 3: { //Print double TODO
+ esp_rom_printf("\033[1;31mDouble number operations not registered yet\033[0m\n");
+ break;
+ }
+ case 4: { //Print string
+ char* cadena = (char*) frm->a0;
+ esp_rom_printf("%s\n", cadena);
+ break;
+ }
+ case 5: { // Read int
+ int number_read = read_int();
+ frm->a0 = number_read;
+ break;
+ }
+ case 6:{ // Read float TODO
+ esp_rom_printf("\033[1;31mFloat number operations not registered yet\033[0m\n");
+ break;
+ }
+ case 7:{ //Read double TODO
+ esp_rom_printf("\033[1;31mDouble number operations not registered yet\033[0m\n");
+ break;
+ }
+ case 8:{ //Read string
+ char* dest = (char*) frm->a0;
+ int length = frm->a1;
+ read_string(dest,length);
+ break;
+ }
+ case 9: { // sbrk
+ int increment = frm->a0;
+ if (current_offset + increment > POOL_CAPACITY || current_offset + increment < 0) {
+ frm->a0 = -1; // Offlimits
+ esp_rom_printf("\033[31;1mSBRK: Memory exhausted\033[0m\n");
+ } else {
+ char *prev_brk = &memory_pool[current_offset];
+ current_offset += increment;
+ frm->a0 = (int)prev_brk;
+ }
+ break;
+ }
+ case 10: { //exit
+ break;
+ }
+ case 11:{ //Print char
+ char caract = (char) frm->a0;
+ esp_rom_printf("%c\n", caract);
+ break;
+ }
+ case 12:{ //Read char
+ char char_leido = read_char();
+ frm->a0 = char_leido;
+ break;
+ }
+ default:
+ esp_rom_printf("Not an ecall registered\n");
+ break;
+ }
+
+
+ //frm->mepc = frm->ra;
+ if (cause == 10)
+ {
+ frm->mepc = frm->ra;
+ }
+ else
+ {
+ frm->mepc += 4;
+ }
+
+ return_from_panic_handler(frm);
+ } else {
+ __real_esp_panic_handler(info); //Other fatal errors are treated as usual
+ }
+}
\ No newline at end of file
diff --git a/gateway/esp32-rv/openscript.cfg b/gateway/esp32-rv/openscript.cfg
new file mode 100644
index 000000000..837d99cd9
--- /dev/null
+++ b/gateway/esp32-rv/openscript.cfg
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Example OpenOCD configuration file for ESP32-C3 connected via builtin USB-JTAG adapter.
+#
+# For example, OpenOCD can be started for ESP32-C3 debugging on
+#
+# openocd -f board/esp32c3-builtin.cfg
+## Source the JTAG interface configuration file
+source [find interface/esp_usb_jtag.cfg]
+# Source the ESP32-C3 configuration file
+source [find target/esp32c3.cfg]
+# Inicializar target
+init
+reset halt
diff --git a/gateway/esp32-rv/tests/random.s b/gateway/esp32-rv/tests/random.s
index 8eca81744..f8384c09f 100644
--- a/gateway/esp32-rv/tests/random.s
+++ b/gateway/esp32-rv/tests/random.s
@@ -1,5 +1,5 @@
#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
+# Copyright 2018-2024 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
#
# This file is part of CREATOR.
#
diff --git a/gateway/esp32-rv/tests/random_array.s b/gateway/esp32-rv/tests/random_array.s
index f488ce28e..4cc218b95 100644
--- a/gateway/esp32-rv/tests/random_array.s
+++ b/gateway/esp32-rv/tests/random_array.s
@@ -1,5 +1,5 @@
#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
+# Copyright 2018-2024 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
#
# This file is part of CREATOR.
#
diff --git a/gateway/esp32-rv/tests/sleep.s b/gateway/esp32-rv/tests/sleep.s
index 614b70daf..f34f42fe6 100644
--- a/gateway/esp32-rv/tests/sleep.s
+++ b/gateway/esp32-rv/tests/sleep.s
@@ -1,5 +1,5 @@
#
-# Copyright 2018-2025 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
+# Copyright 2018-2024 Felix Garcia Carballeira, Alejandro Calderon Mateos, Diego Camarmas Alonso, José Antonio Verde Jiménez
#
# This file is part of CREATOR.
#
diff --git a/gateway/esp32c2.zip b/gateway/esp32c2.zip
index 76470e145f590c417f09c280d734ebd107a98538..195ad115cf96c1ab4149f2b52a40bc7ee84be172 100644
GIT binary patch
delta 10997
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
delta 12517
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
L0Kk&W&wu|9prn}N
diff --git a/gateway/esp32c6.zip b/gateway/esp32c6.zip
index bf22deb28d0e81d082a3a603319f753ddd500109..78ac063a42662393021a3eadcc83a10fa69a9e6e 100644
GIT binary patch
delta 10997
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