From 7b7fe69a0d204c5691db77a41e51ec3d33e2b568 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Wed, 4 Dec 2024 15:49:23 -0800 Subject: [PATCH] Add semihosting support (SerialSemi and SemiFS) Enable ARM-only semihosting mode. This mode allows applications on the Pico to write to the OpenOCD console and read and write files on the host system (i.e. debugging dump information, etc.) It is not very fast because of the way it uses breakpoints on the Pico to communicate, but it is useful in cases when you want to get a single file off of the Pico while debugging. Note that this **requires** a connected OpenOCD and GDB or else the semihosting will cause a system panic. --- docs/index.rst | 1 + docs/semihosting.rst | 56 ++++ .../examples/hellosemi/hellosemi.ino | 43 +++ libraries/Semihosting/keywords.txt | 18 ++ libraries/Semihosting/library.properties | 10 + libraries/Semihosting/src/SemiFS.h | 297 ++++++++++++++++++ libraries/Semihosting/src/Semihosting.cpp | 4 + libraries/Semihosting/src/Semihosting.h | 71 +++++ libraries/Semihosting/src/SerialSemi.h | 98 ++++++ tests/restyle.sh | 3 +- 10 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 docs/semihosting.rst create mode 100644 libraries/Semihosting/examples/hellosemi/hellosemi.ino create mode 100644 libraries/Semihosting/keywords.txt create mode 100644 libraries/Semihosting/library.properties create mode 100644 libraries/Semihosting/src/SemiFS.h create mode 100644 libraries/Semihosting/src/Semihosting.cpp create mode 100644 libraries/Semihosting/src/Semihosting.h create mode 100644 libraries/Semihosting/src/SerialSemi.h diff --git a/docs/index.rst b/docs/index.rst index 6759b3e07..8fd2b65bf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,6 +43,7 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p File Systems (SD, SDFS, LittleFS) USB (Arduino and Adafruit_TinyUSB) Multicore Processing + Semihosting RP2350 Specific Notes RP2350 PSRAM diff --git a/docs/semihosting.rst b/docs/semihosting.rst new file mode 100644 index 000000000..c7fbb142c --- /dev/null +++ b/docs/semihosting.rst @@ -0,0 +1,56 @@ +Semihosting Support +=================== + +Using special debugger breakpoints and commands, the Pico can read and write to the debugging console as well +as read and write files on a development PC. The ``Semihosting`` library allows applications to use the +semihosting support as a normal filesystem or serial port. + +**NOTE** Semihosting only works when connected to an OpenOCD + GDB debug session. Running an application +compiled for Semihosting without the debugger will cause a panic and hang the chip. + +As of now, only ARM has support for Semihosting. + +Running Semihosting on the Development Host +------------------------------------------- + +Start OpenOCD normally from inside a directory that you can read and write files within (i.e. do not run from +``C:\\Program Files\\..`` on Windows where general users aren't allowed to write). The starting +directory will be where the Pico will read and write files using the ``SemiFS`` class. +Be sure to keep the terminal window you ran OpenOCD in open, because all ``SerialSemi`` input and output +will go to **that** terminal and not ``gdb``'s. + +Start GDB normally and connect to the OpenOCD debugger and enable semihosting support + +.. code:: + + (gdb) target extended-remote localhost:3333 + (gdb) monitor arm semihosting enable + +At this point load and run your ``ELF`` application as normal. Again, all ``SerialSemi`` output will go +to the **OpenOCD** window, not GDB. + +See the ``hellosemi`` example in the ``Semihosting`` library. + +SerialSemi - Serial over Semihosting +------------------------------------ + +Simply include ```` in your application and use ``SerialSemi`` as you would any other +``Serial`` port with the following limitations: + +* Baud rate, bit width, etc. are all ignored +* Input is limited because ``read`` may hang indefinitely in the host and ``available`` is not part of the spec + +SemiFS - Host filesystem access through Semihosting +--------------------------------------------------- + +Use ``SemiFS`` the same way as any other file system. Note that only file creation and renaming are supported, with +no provision for iterating over directories or listing files. In most cases simply opening a ``File`` and writing out +a debug dump is all that's needed: + +.. code:: + + SemiFS.begin(); + File f = SemiFS.open("debug.dmp", "w"); + f.write(buffer, size); + f.close(); + SerialSemi.printf("Debug dump nopw available on host.\n"); diff --git a/libraries/Semihosting/examples/hellosemi/hellosemi.ino b/libraries/Semihosting/examples/hellosemi/hellosemi.ino new file mode 100644 index 000000000..cb326c604 --- /dev/null +++ b/libraries/Semihosting/examples/hellosemi/hellosemi.ino @@ -0,0 +1,43 @@ +// This example uses semihosting to send serial output to the OpenOcD screen +// and write binary files to the host system. +// +// Semihosting **ONLY** works with an OpenOCD and GDB setup. If you build +// and run a semihosting app without GDB connected, it **WILL CRASH** +// +// Start OpenOCD normally, but leave the terminal window visible because +// is it OpenOCD, not GDB, which will display the semihosting output. +// OpenOCD will also create files in the current working directory, so +// be sure it is a place you can find and write to. +// +// In GDB,connect to OpenOCD and then enable semihosting +// (gdb) target extended-remote localhost:3333 +// (gdb) monitor arm semihosting enable + +#ifdef __riscv +void setup() { + // No semihosting for RISCV yet +} +void loop() { +} +#else + +#include + +int c = 0; + +void setup() { + SerialSemi.begin(); + SerialSemi.printf("HELLO, GDB!\n"); + SemiFS.begin(); + File f = SemiFS.open("out.bin", "w"); + f.printf("I made a file!\n"); + f.close(); + SerialSemi.printf("Just wrote a file 'out.bin'\n"); +} + +void loop() { + SerialSemi.printf("SH Loop Count: %d\n", c++); + Serial.printf("USB Loop Count: %d\n", c++); + delay(1000); +} +#endif diff --git a/libraries/Semihosting/keywords.txt b/libraries/Semihosting/keywords.txt new file mode 100644 index 000000000..77aa6184a --- /dev/null +++ b/libraries/Semihosting/keywords.txt @@ -0,0 +1,18 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SerialSemi KEYWORD1 +SemiFS KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/Semihosting/library.properties b/libraries/Semihosting/library.properties new file mode 100644 index 000000000..27978a9ca --- /dev/null +++ b/libraries/Semihosting/library.properties @@ -0,0 +1,10 @@ +name=Semihosting +version=1.0.0 +author=Earle F. Philhower, III +maintainer=Earle F. Philhower, III +sentence=Semihosted serial and filesystem access for the Pico and OpenOCD +paragraph=Semihosted serial and filesystem access for the Pico and OpenOCD +category=Communications +url=https://github.com/earlephilhower/arduino-pico +architectures=rp2040 +dot_a_linkage=true diff --git a/libraries/Semihosting/src/SemiFS.h b/libraries/Semihosting/src/SemiFS.h new file mode 100644 index 000000000..b07e1ec03 --- /dev/null +++ b/libraries/Semihosting/src/SemiFS.h @@ -0,0 +1,297 @@ +/* + SemiFS.h - File system wrapper for Semihosting ARM + Copyright (c) 2024 Earle F. Philhower, III. All rights reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include "Semihosting.h" +#include +#include + +using namespace fs; + +namespace semifs { + +class SemiFSFileImpl; +class SemiFSConfig : public FSConfig { +public: + static constexpr uint32_t FSId = 0x53454d49; + SemiFSConfig() : FSConfig(FSId, false) { } +}; + +class SemiFSFileImpl : public FileImpl { +public: + SemiFSFileImpl(int fd, const char *name, bool writable) + : _fd(fd), _opened(true), _writable(writable) { + _name = std::shared_ptr(new char[strlen(name) + 1], std::default_delete()); + strcpy(_name.get(), name); + } + + ~SemiFSFileImpl() override { + flush(); + close(); + } + + int availableForWrite() override { + return 1; // TODO - not implemented? _opened ? _fd->availableSpaceForWrite() : 0; + } + + size_t write(const uint8_t *buf, size_t size) override { + if (_opened) { + uint32_t a[3]; + a[0] = _fd; + a[1] = (uint32_t)buf; + a[2] = size; + return 0 == Semihost(SYS_WRITE, a) ? size : -1; + } + return -1; // some kind of error + } + + int read(uint8_t* buf, size_t size) override { + if (_opened) { + uint32_t a[3]; + a[0] = _fd; + a[1] = (uint32_t)buf; + a[2] = size; + int ret = Semihost(SYS_READ, a); + if (ret == 0) { + return size; + } else if (ret == (int)size) { + return -1; + } else { + return ret; + } + } + return -1; + } + + void flush() override { + /* noop */ + } + + bool seek(uint32_t pos, SeekMode mode) override { + if (!_opened || (mode != SeekSet)) { + // No seek cur/end in semihost + return false; + } + uint32_t a[2]; + a[0] = _fd; + a[1] = pos; + return !Semihost(SYS_SEEK, a); + } + + size_t position() const override { + return 0; // Not available semihost + } + + size_t size() const override { + if (!_opened) { + return 0; + } + uint32_t a; + a = _fd; + int ret = Semihost(SYS_FLEN, &a); + if (ret < 0) { + return 0; + } + return ret; + } + + bool truncate(uint32_t size) override { + return false; // Not allowed + } + + void close() override { + if (_opened) { + uint32_t a = _fd; + Semihost(SYS_CLOSE, &a); + _opened = false; + } + } + + const char* name() const override { + if (!_opened) { + DEBUGV("SemiFSFileImpl::name: file not opened\n"); + return nullptr; + } else { + const char *p = _name.get(); + const char *slash = strrchr(p, '/'); + // For names w/o any path elements, return directly + // If there are slashes, return name after the last slash + // (note that strrchr will return the address of the slash, + // so need to increment to ckip it) + return (slash && slash[1]) ? slash + 1 : p; + } + } + + const char* fullName() const override { + return _opened ? _name.get() : nullptr; + } + + bool isFile() const override { + return _opened; // Could look at ISTTY but that's not the sense here. Just differentiating between dirs and files + } + + bool isDirectory() const override { + return false; + } + + time_t getLastWrite() override { + return getCreationTime(); // TODO - FatFS doesn't seem to report both filetimes + } + + time_t getCreationTime() override { + time_t ftime = 0; + return ftime; + } + +protected: + int _fd; + std::shared_ptr _name; + bool _opened; + bool _writable; +}; + + +class SemiFSImpl : public FSImpl { +public: + SemiFSImpl() { + /* noop */ + } + + FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override { + if (!path || !path[0]) { + DEBUGV("SemiFSImpl::open() called with invalid filename\n"); + return FileImplPtr(); + } + // Mode conversion https://developer.arm.com/documentation/dui0471/m/what-is-semihosting-/sys-open--0x01-?lang=en + int mode = 1; // "rb" + if (accessMode == AM_READ) { + mode = 1; // "rb" + } else if (accessMode == AM_WRITE) { + if (openMode & OM_APPEND) { + mode = 9; // "ab"; + } else { + mode = 5; // "wb"; + } + } else { + if (openMode & OM_TRUNCATE) { + mode = 7; // "w+b"; + } else if (openMode & OM_APPEND) { + mode = 3; // "r+b" + } else { + mode = 11; // "a+b"; + } + } + uint32_t a[3]; + a[0] = (uint32_t)path; + a[1] = mode; + a[2] = strlen(path); + int handle = Semihost(SYS_OPEN, a); + if (handle < 0) { + return FileImplPtr(); + } + return std::make_shared(handle, path, (accessMode & AM_WRITE) ? true : false); + } + + bool exists(const char* path) override { + File f = open(path, OM_DEFAULT, AM_READ); + return f ? true : false; + } + + DirImplPtr openDir(const char* path) override { + // No directories + return DirImplPtr(); + } + + bool rename(const char* pathFrom, const char* pathTo) override { + uint32_t a[4]; + a[0] = (uint32_t)pathFrom; + a[1] = strlen(pathFrom); + a[2] = (uint32_t)pathTo; + a[3] = strlen(pathTo); + return !Semihost(SYS_RENAME, a); + } + + bool info(FSInfo& info) override { + // Not available + return false; + } + + bool remove(const char* path) override { + uint32_t a[2]; + a[0] = (uint32_t)path; + a[1] = strlen(path); + return !Semihost(SYS_REMOVE, a); + } + + bool mkdir(const char* path) override { + // No mkdir + return false; + } + + bool rmdir(const char* path) override { + // No rmdir + return false; + } + + bool stat(const char *path, FSStat *st) override { + if (!path || !path[0]) { + return false; + } + uint32_t a[3]; + a[0] = (uint32_t)path; + a[1] = 0; // READ + a[2] = strlen(path); + int fn = Semihost(SYS_OPEN, a); + if (fn < 0) { + return false; + } + bzero(st, sizeof(*st)); + a[0] = fn; + st->size = Semihost(SYS_FLEN, a); + a[0] = fn; + Semihost(SYS_CLOSE, a); + return true; + } + + bool setConfig(const FSConfig &cfg) override { + return true; + } + + bool begin() override { + /* noop */ + return true; + } + + void end() override { + /* noop */ + } + + bool format() override { + return false; + } +}; + + +}; // namespace sdfs + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SEMIFS) +extern FS SemiFS; +using semifs::SemiFSConfig; +#endif diff --git a/libraries/Semihosting/src/Semihosting.cpp b/libraries/Semihosting/src/Semihosting.cpp new file mode 100644 index 000000000..90378f9e0 --- /dev/null +++ b/libraries/Semihosting/src/Semihosting.cpp @@ -0,0 +1,4 @@ +#include "Semihosting.h" + +SerialSemiClass SerialSemi; +FS SemiFS = FS(FSImplPtr(new semifs::SemiFSImpl())); diff --git a/libraries/Semihosting/src/Semihosting.h b/libraries/Semihosting/src/Semihosting.h new file mode 100644 index 000000000..47fa5c883 --- /dev/null +++ b/libraries/Semihosting/src/Semihosting.h @@ -0,0 +1,71 @@ +/* + Semihosting.h - Semihosting for Serial and FS access via GDB + Copyright (c) 2024 Earle F. Philhower, III. All rights reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +// Be sure to only use this library with GDB and to enable the ARM semihosting support +// (gdb) monitor arm semihosting enable + +// Input/output will be handled by OpenOCD + +// From https://developer.arm.com/documentation/dui0471/g/Semihosting/Semihosting-operations?lang=en +typedef enum { + SYS_CLOSE = 0x02, + SYS_CLOCK = 0x10, + SYS_ELAPSED = 0x30, + SYS_ERRNO = 0x13, + SYS_FLEN = 0x0C, + SYS_GET_CMDLINE = 0x15, + SYS_HEAPINFO = 0x16, + SYS_ISERROR = 0x08, + SYS_ISTTY = 0x09, + SYS_OPEN = 0x01, + SYS_READ = 0x06, + SYS_READC = 0x07, + SYS_REMOVE = 0x0E, + SYS_RENAME = 0x0F, + SYS_SEEK = 0x0A, + SYS_SYSTEM = 0x12, + SYS_TICKFREQ = 0x31, + SYS_TIME = 0x11, + SYS_TMPNAM = 0x0D, + SYS_WRITE = 0x05, + SYS_WRITEC = 0x03, + SYS_WRITE0 = 0x04 +} ARM_SEMIHOST; + + +// From https://github.com/ErichStyger/mcuoneclipse/blob/master/Examples/MCUXpresso/FRDM-K22F/FRDM-K22F_Semihosting/source/McuSemihost.c +static inline int __attribute__((always_inline)) Semihost(int reason, void *arg) { + int value; + __asm volatile( + "mov r0, %[rsn] \n" /* place semihost operation code into R0 */ + "mov r1, %[arg] \n" /* R1 points to the argument array */ + "bkpt 0xAB \n" /* call debugger */ + "mov %[val], r0 \n" /* debugger has stored result code in R0 */ + + : [val] "=r"(value) /* outputs */ + : [rsn] "r"(reason), [arg] "r"(arg) /* inputs */ + : "r0", "r1", "r2", "r3", "ip", "lr", "memory", "cc" /* clobber */ + ); + return value; /* return result code, stored in R0 */ +} + +#include "SerialSemi.h" +#include "SemiFS.h" diff --git a/libraries/Semihosting/src/SerialSemi.h b/libraries/Semihosting/src/SerialSemi.h new file mode 100644 index 000000000..4481a2956 --- /dev/null +++ b/libraries/Semihosting/src/SerialSemi.h @@ -0,0 +1,98 @@ +/* + SerialSemi.h - Serial port over Semihosting for ARM + Copyright (c) 2024 Earle F. Philhower, III. All rights reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include "Semihosting.h" + +#include +#include "api/HardwareSerial.h" + +class SerialSemiClass : public HardwareSerial { +public: + SerialSemiClass() { + /* noop */ + } + + ~SerialSemiClass() { + /* noop */ + } + + void begin(unsigned long baudIgnored = 115200) override { + (void)baudIgnored; + } + + void begin(unsigned long baudIgnored, uint16_t configIgnored) override { + (void)baudIgnored; + (void)configIgnored; + } + + void end() override { + /* noop */ + } + + virtual int peek() override { + // Can't really peek on SH, so fake it best we can + if (!_peeked) { + _peekedChar = read(); + _peeked = true; + } + return _peekedChar; + } + + virtual int read() override { + if (_peeked) { + _peeked = false; + return _peekedChar; + } + return Semihost(SYS_READC, nullptr); + } + + virtual int available() override { + // Can't really tell with SH, so always true. Buyer beware + return 1; + } + + virtual int availableForWrite() override { + // Can't really tell with SH, so always true. Buyer beware + return 1; + } + + virtual void flush() override { + /* noop */ + } + + virtual size_t write(uint8_t c) override { + int32_t param = c; + Semihost(SYS_WRITEC, ¶m); + return 1; + } + + using Print::write; + + operator bool() override { + return true; + } + +private: + bool _peeked = false; + uint8_t _peekedChar; +}; + +extern SerialSemiClass SerialSemi; diff --git a/tests/restyle.sh b/tests/restyle.sh index 4d22c0b30..925adb008 100755 --- a/tests/restyle.sh +++ b/tests/restyle.sh @@ -17,7 +17,8 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleF ./libraries/SPISlave ./libraries/lwIP_ESPHost ./libraries/FatFS\ ./libraries/FatFSUSB ./libraries/BluetoothAudio ./libraries/BluetoothHCI \ ./libraries/BluetoothHIDMaster ./libraries/NetBIOS ./libraries/Ticker \ - ./libraries/VFS ./libraries/rp2350 ./libraries/SimpleMDNS; do + ./libraries/VFS ./libraries/rp2350 ./libraries/SimpleMDNS \ + ./libraries/Semihosting; do find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \; find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \; done