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