├── tools ├── elfexe │ ├── .gitignore │ ├── CMakeLists.txt │ ├── Makefile │ └── README.md └── uf2fat │ ├── .gitignore │ ├── README.md │ ├── CMakeLists.txt │ ├── Makefile │ ├── extern.h │ ├── uf2.h │ ├── dump.cpp │ ├── diskio.cpp │ └── main.cpp ├── apps ├── rz │ ├── README.md │ ├── crc.h │ ├── CMakeLists.txt │ ├── zmodem.h │ ├── znumbers.h │ ├── zserial.h │ ├── zheaders.c │ ├── znumbers.c │ └── zheaders.h ├── cmd │ ├── CMakeLists.txt │ └── cmd.c ├── free │ ├── CMakeLists.txt │ └── free.c ├── gpio │ └── CMakeLists.txt ├── hello │ ├── CMakeLists.txt │ ├── hello.c │ └── Makefile ├── printf │ └── CMakeLists.txt ├── export.sym └── CMakeLists.txt ├── fatfs ├── README.md ├── CMakeLists.txt ├── 00readme.txt ├── examples │ ├── demo_check_contig.c │ ├── test_raw_speed.c │ ├── demo_delete_node.c │ └── demo_alloc_contig.c ├── strerror.c └── LICENSE.txt ├── docs ├── Dynamic-Linking.md ├── Develop-Your-Own-Application.md ├── Documentation.md ├── Contributing.md ├── Build-From-Sources.md ├── Supported-Platforms.md └── Getting-Started.md ├── unix ├── README.md ├── bindings.c ├── CMakeLists.txt ├── loader_unix.c ├── main_unix.c └── fpm_unix.c ├── tests ├── hello.c ├── testputs.c ├── util.h ├── zmodem.h ├── dotw_test.cpp ├── console_util.cpp ├── loader2_test.cpp ├── loader_test.cpp ├── copy_test.cpp ├── tokenize_test.cpp └── CMakeLists.txt ├── .gitignore ├── kernel ├── fpm_wputs.c ├── fpm_get_dotw.c ├── fpm_strwlen.c ├── fpm_putwch.c ├── fpm_puts.c ├── fpm_getwch.c ├── CMakeLists.txt ├── cmd │ ├── cmd_reboot.c │ ├── cmd_ver.c │ ├── cmd_clear.c │ ├── cmd_eject.c │ ├── cmd_mkdir.c │ ├── cmd_echo.c │ ├── cmd_cd.c │ ├── cmd_rmdir.c │ ├── cmd_date.c │ ├── cmd_cat.c │ ├── cmd_help.c │ ├── cmd_time.c │ ├── cmd_format.c │ ├── cmd_mount.c │ └── cmd_rename.c ├── fpm_strtol.c ├── fpm_getkey.c ├── fpm_tokenize.c ├── fpm_shell.c ├── fpm_strlcpy.c └── fpm_exec.c ├── pico ├── flashfs │ └── readme.txt ├── main_pico.c ├── flash.h ├── sd_pico │ ├── crc.h │ ├── sd_spi.h │ ├── spi.h │ ├── sd_card.h │ └── sd_spi.c ├── rtc_pico.c ├── bindings.c ├── flash_image.cmake ├── fpm_pico.c └── CMakeLists.txt ├── Makefile ├── LICENSE ├── include └── fpm │ ├── context.h │ ├── getopt.h │ ├── loader.h │ ├── internal.h │ ├── bindings.h │ ├── diskio.h │ └── api.h ├── README.md └── .clang-format /tools/elfexe/.gitignore: -------------------------------------------------------------------------------- 1 | elfexe 2 | -------------------------------------------------------------------------------- /tools/uf2fat/.gitignore: -------------------------------------------------------------------------------- 1 | uf2fat 2 | *.uf2 3 | contents 4 | -------------------------------------------------------------------------------- /apps/rz/README.md: -------------------------------------------------------------------------------- 1 | Based on code from https://github.com/roscopeco/mbzm 2 | -------------------------------------------------------------------------------- /apps/cmd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(cmd 2 | cmd.c 3 | ) 4 | fpm_target_options(cmd) 5 | -------------------------------------------------------------------------------- /apps/free/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(free 2 | free.c 3 | ) 4 | fpm_target_options(free) 5 | -------------------------------------------------------------------------------- /apps/gpio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(gpio 2 | gpio.c 3 | ) 4 | fpm_target_options(gpio) 5 | -------------------------------------------------------------------------------- /apps/hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(hello 2 | hello.c 3 | ) 4 | fpm_target_options(hello) 5 | -------------------------------------------------------------------------------- /apps/printf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(printf 2 | printf.c 3 | ) 4 | fpm_target_options(printf) 5 | -------------------------------------------------------------------------------- /fatfs/README.md: -------------------------------------------------------------------------------- 1 | FatFS implementation imported from: https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico 2 | -------------------------------------------------------------------------------- /docs/Dynamic-Linking.md: -------------------------------------------------------------------------------- 1 | # Dynamic Linking 2 | 3 | TODO: describe how *.exe binary is linked to FP/M kernel at run time. 4 | -------------------------------------------------------------------------------- /unix/README.md: -------------------------------------------------------------------------------- 1 | This directory contains implementation of a virtual FP/M system 2 | as a Unix application, for demonstration purposes. 3 | -------------------------------------------------------------------------------- /apps/cmd/cmd.c: -------------------------------------------------------------------------------- 1 | // 2 | // Interactive shell. 3 | // 4 | #include 5 | 6 | int main() 7 | { 8 | fpm_shell(); 9 | } 10 | -------------------------------------------------------------------------------- /apps/export.sym: -------------------------------------------------------------------------------- 1 | FPM_0.1 { 2 | global: 3 | __CTOR_LIST__; 4 | __DTOR_LIST__; 5 | 6 | local: 7 | *; 8 | }; 9 | -------------------------------------------------------------------------------- /docs/Develop-Your-Own-Application.md: -------------------------------------------------------------------------------- 1 | # Develop Your Own Application 2 | 3 | TODO: describe how to build and deploy *.exe binary for FP/M. 4 | -------------------------------------------------------------------------------- /tools/elfexe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(elfexe elfexe.c) 2 | target_include_directories(elfexe BEFORE PUBLIC 3 | ../../include 4 | ) 5 | -------------------------------------------------------------------------------- /tools/elfexe/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -O -Wall -Werror -I../../include 2 | 3 | all: elfexe 4 | 5 | clean: 6 | rm -f elfexe *.o *.dis *.elf 7 | -------------------------------------------------------------------------------- /tests/hello.c: -------------------------------------------------------------------------------- 1 | // 2 | // Print simple text message. 3 | // 4 | #include 5 | 6 | int main() 7 | { 8 | fpm_puts("Hello, World!\r\n"); 9 | } 10 | -------------------------------------------------------------------------------- /apps/hello/hello.c: -------------------------------------------------------------------------------- 1 | // 2 | // Print simple text message. 3 | // 4 | #include 5 | 6 | int main() 7 | { 8 | fpm_puts("Hello, World!\r\n"); 9 | } 10 | -------------------------------------------------------------------------------- /tools/uf2fat/README.md: -------------------------------------------------------------------------------- 1 | Create a FAT filesystemat the end of UF2 data. 2 | Optionally fill it with given contents. 3 | 4 | uf2fat format fpm_pico.uf2 2M contents -o fpm_pico_2m.uf2 5 | -------------------------------------------------------------------------------- /fatfs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(fpm_fatfs STATIC 2 | fatfs.c 3 | unicode.c 4 | strerror.c 5 | ) 6 | target_include_directories(fpm_fatfs BEFORE PUBLIC 7 | ../include 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pico/build-* 2 | unix/build 3 | tests/build 4 | TODO 5 | *.o 6 | *.so 7 | *.c- 8 | *.cpp- 9 | *.h- 10 | *.img 11 | *.hd 12 | *.elf 13 | *.exe 14 | *.dis 15 | *.nm 16 | *.readelf 17 | -------------------------------------------------------------------------------- /docs/Documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | * [Build from sources](Build-From-Sources.md) 4 | * [Develop your own application](Develop-Your-Own-Application.md) 5 | * [Dynamic linking](Dynamic-Linking.md) 6 | -------------------------------------------------------------------------------- /kernel/fpm_wputs.c: -------------------------------------------------------------------------------- 1 | // 2 | // Write UTF-8 string to output. 3 | // 4 | #include 5 | 6 | void fpm_wputs(const uint16_t *input) 7 | { 8 | while (*input) { 9 | fpm_putwch(*input++); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/rz/crc.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const uint16_t CRC_START_XMODEM = 0u; 4 | static const uint32_t CRC_START_32 = ~0u; 5 | 6 | uint16_t update_crc16_ccitt(uint8_t ch, uint16_t crc); 7 | uint32_t update_crc32(uint8_t ch, uint32_t crc); 8 | -------------------------------------------------------------------------------- /apps/rz/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(rz 2 | rz.c 3 | zserial.c 4 | zheaders.c 5 | znumbers.c 6 | crc.c 7 | ) 8 | fpm_target_options(rz) 9 | 10 | # Enable debug output. 11 | #target_compile_options(rz PRIVATE -DZDEBUG=1 -DZTRACE=1) 12 | -------------------------------------------------------------------------------- /unix/bindings.c: -------------------------------------------------------------------------------- 1 | // 2 | // Export dynamically linked routines. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | fpm_binding_t fpm_bindings[] = { 11 | #include 12 | {}, 13 | }; 14 | -------------------------------------------------------------------------------- /tools/uf2fat/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(uf2fat 2 | diskio.cpp 3 | dump.cpp 4 | format.cpp 5 | main.cpp 6 | ../../fatfs/fatfs.c 7 | ../../fatfs/unicode.c 8 | ) 9 | target_include_directories(uf2fat BEFORE PUBLIC 10 | ../../include 11 | ) 12 | install(TARGETS uf2fat DESTINATION $ENV{HOME}/.local/bin) 13 | -------------------------------------------------------------------------------- /tests/testputs.c: -------------------------------------------------------------------------------- 1 | // 2 | // Invoke three FP/M calls: 3 | // fpm_print_version() 4 | // fpm_puts() 5 | // fpm_wputs() 6 | // 7 | #include 8 | 9 | int main() 10 | { 11 | fpm_print_version(); 12 | 13 | fpm_puts("puts\r\n"); 14 | 15 | static const uint16_t wmessage[] = { 'w', 'p', 'u', 't', 's', '\r', '\n' }; 16 | fpm_wputs(wmessage); 17 | } 18 | -------------------------------------------------------------------------------- /kernel/fpm_get_dotw.c: -------------------------------------------------------------------------------- 1 | // 2 | // Compute day of the week. 3 | // Sunday is 0. 4 | // 5 | #include 6 | 7 | int fpm_get_dotw(int year, int month, int day) 8 | { 9 | // Solution by Tomohiko Sakamoto. 10 | static const int offset[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; 11 | 12 | year -= (month < 3); 13 | return (year + year/4 - year/100 + year/400 + offset[month - 1] + day) % 7; 14 | } 15 | -------------------------------------------------------------------------------- /apps/free/free.c: -------------------------------------------------------------------------------- 1 | // 2 | // Print amount of available memory on the heap. 3 | // 4 | #include 5 | 6 | static void print_value(const char *name, size_t size) 7 | { 8 | if (size < 1000) { 9 | fpm_printf("%s: %u bytes\r\n", name, size); 10 | } else { 11 | fpm_printf("%s: %u,%03u bytes\r\n", name, size / 1000, size % 1000); 12 | } 13 | } 14 | 15 | int main() 16 | { 17 | print_value(" Free heap", fpm_heap_available()); 18 | print_value("Free stack", fpm_stack_available()); 19 | } 20 | -------------------------------------------------------------------------------- /kernel/fpm_strwlen.c: -------------------------------------------------------------------------------- 1 | // 2 | // Compute the length of the Unicode string s. 3 | // Return the number of characters that precede the terminating NUL character. 4 | // 5 | #include 6 | 7 | size_t fpm_strwlen(const uint16_t *input) 8 | { 9 | const uint16_t *s; 10 | 11 | for (s = input; *s; ++s) 12 | continue; 13 | 14 | return s - input; 15 | } 16 | 17 | size_t fpm_utf8len(const char *input) 18 | { 19 | size_t count = 0; 20 | 21 | while (*input) { 22 | count += ((*input++ & 0xc0) != 0x80); 23 | } 24 | return count; 25 | } 26 | -------------------------------------------------------------------------------- /apps/hello/Makefile: -------------------------------------------------------------------------------- 1 | PROG = hello.exe 2 | 3 | CC = arm-none-eabi-gcc 4 | LD = arm-fpm-ld 5 | OBJDUMP = arm-none-eabi-objdump 6 | NM = arm-none-eabi-nm 7 | READELF = arm-none-eabi-readelf 8 | 9 | CFLAGS = -mcpu=cortex-m0plus -mthumb -fPIC -O1 -I../include 10 | LDFLAGS = -shared -fPIC -e main 11 | 12 | all: $(PROG) 13 | 14 | clean: 15 | rm -f *.o *.exe *.dis *.nm *.readelf 16 | 17 | %.exe: %.c 18 | $(CC) $(CFLAGS) -c $< -o $*.o 19 | $(LD) $(LDFLAGS) $*.o -o $@ 20 | $(OBJDUMP) -d $@ > $*.dis 21 | $(NM) -n $@ > $*.nm 22 | $(READELF) -a $@ > $*.readelf 23 | -------------------------------------------------------------------------------- /tools/uf2fat/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc -g 2 | CXX = g++ -std=c++17 -g 3 | CFLAGS = -O -Wall -I../../include 4 | CXXFLAGS = $(CFLAGS) 5 | DESTDIR = /usr/local 6 | PROG = uf2fat 7 | OBJS = main.o dump.o format.o fatfs.o unicode.o diskio.o 8 | VPATH = ../../fatfs 9 | 10 | all: $(PROG) 11 | 12 | install: $(PROG) 13 | install -s $(PROG) ${DESTDIR}/bin/$(PROG) 14 | clean: 15 | rm -rf *~ *.o *.lst *.dis $(PROG) $(PROG).dSYM 16 | 17 | $(PROG): $(OBJS) 18 | $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) 19 | 20 | upload: fpm_pico.uf2 21 | picotool load -f -x fpm_pico.uf2 22 | -------------------------------------------------------------------------------- /kernel/fpm_putwch.c: -------------------------------------------------------------------------------- 1 | // 2 | // Write Unicode character to the console. 3 | // 4 | // Convert to UTF-8 encoding: 5 | // 00000000.0xxxxxxx -> 0xxxxxxx 6 | // 00000xxx.xxyyyyyy -> 110xxxxx, 10yyyyyy 7 | // xxxxyyyy.yyzzzzzz -> 1110xxxx, 10yyyyyy, 10zzzzzz 8 | // 9 | #include 10 | 11 | void fpm_putwch(uint16_t ch) 12 | { 13 | if (ch < 0x80) { 14 | fpm_putchar(ch); 15 | } else if (ch < 0x800) { 16 | fpm_putchar(ch >> 6 | 0xc0); 17 | fpm_putchar((ch & 0x3f) | 0x80); 18 | } else { 19 | fpm_putchar(ch >> 12 | 0xe0); 20 | fpm_putchar(((ch >> 6) & 0x3f) | 0x80); 21 | fpm_putchar((ch & 0x3f) | 0x80); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kernel/fpm_puts.c: -------------------------------------------------------------------------------- 1 | // 2 | // Write UTF-8 string to output. 3 | // 4 | #include 5 | 6 | void fpm_puts(const char *input) 7 | { 8 | for (;;) { 9 | uint8_t c1 = *input++; 10 | if (c1 == 0) 11 | break; 12 | 13 | // Decode utf-8 to unicode. 14 | if (! (c1 & 0x80)) { 15 | fpm_putwch(c1); 16 | } else { 17 | uint8_t c2 = *input++; 18 | if (! (c1 & 0x20)) { 19 | fpm_putwch((c1 & 0x1f) << 6 | (c2 & 0x3f)); 20 | } else { 21 | uint8_t c3 = *input++; 22 | fpm_putwch((c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 | (c3 & 0x3f)); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/util.h: -------------------------------------------------------------------------------- 1 | extern const char *input; // Input stream for the current test, utf-8 encoded 2 | 3 | // 4 | // Get Unicode character from input buffer. 5 | // 6 | char fpm_getchar(); 7 | 8 | // 9 | // Write Unicode character to output buffer. 10 | // 11 | void fpm_putchar(char ch); 12 | 13 | // 14 | // Create a file with given name and contents. 15 | // 16 | void write_file(const char *filename, const char *contents); 17 | 18 | // 19 | // Check contents of the file. 20 | // 21 | void read_file(const char *filename, const char *contents); 22 | 23 | // 24 | // Create new directory. 25 | // 26 | void create_directory(const char *dirname); 27 | 28 | // 29 | // Make sure directory exists. 30 | // 31 | void check_directory(const char *dirname); 32 | -------------------------------------------------------------------------------- /pico/flashfs/readme.txt: -------------------------------------------------------------------------------- 1 | FP/M - Flash Program for Microcontrollers 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Use HELP to see a list of built-in commands. 5 | 6 | For details on any command, run it with -h option. 7 | For example: 8 | 9 | flash:/ > mount -h 10 | Usage: 11 | mount [sd: | flash:] 12 | 13 | When SD card is present, run MOUNT SD: to enable it. 14 | Command MOUNT without arguments shows a list of 15 | active filesystems. 16 | 17 | Command line editing is evailable, with a history of one line. 18 | Use Up arrow or ^P to recall the previous command from history. 19 | Use Left or Right arrows, or ^B/^F, to move cursor one 20 | character to the left or right. Use Home or End keys, or ^A/^E, 21 | to move cursor to the beginning or to the end of the line. 22 | -------------------------------------------------------------------------------- /tools/uf2fat/extern.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // 7 | // Sector size is 4096 bytes for Flash memory. 8 | // 9 | static const unsigned SECTOR_SIZE = 4096; 10 | 11 | // 12 | // Data buffer for one sector. 13 | // 14 | using SectorData = std::array; 15 | 16 | // 17 | // Size of filesystem in bytes. 18 | // 19 | extern unsigned fs_nbytes; 20 | 21 | // 22 | // Collection of sectors, ordered by sector number. 23 | // 24 | extern std::map fs_image; 25 | 26 | void dump_file(const std::string &input_filename); 27 | 28 | // 29 | // Create filesystem with optional contents. 30 | // 31 | void format_filesystem(const std::string &input_filename, size_t flash_bytes, 32 | const std::string &contents_dir, const std::string &output_filename); 33 | -------------------------------------------------------------------------------- /kernel/fpm_getwch.c: -------------------------------------------------------------------------------- 1 | // 2 | // Get Unicode character from the console. 3 | // 4 | // Convert from UTF-8 encoding to 16-bit value: 5 | // 0xxxxxxx -> 00000000.0xxxxxxx 6 | // 110xxxxx, 10yyyyyy -> 00000xxx.xxyyyyyy 7 | // 1110xxxx, 10yyyyyy, 10zzzzzz -> xxxxyyyy.yyzzzzzz 8 | // 9 | #include 10 | 11 | uint16_t fpm_getwch() 12 | { 13 | // Read one byte. 14 | uint8_t c1 = fpm_getchar(); 15 | if (! (c1 & 0x80)) { 16 | return c1; 17 | } 18 | 19 | // 20 | // Decode utf-8 to unicode. 21 | // 22 | 23 | // Read second byte. 24 | uint8_t c2 = fpm_getchar(); 25 | if (! (c1 & 0x20)) { 26 | return (c1 & 0x1f) << 6 | (c2 & 0x3f); 27 | } 28 | 29 | // Read third byte. 30 | uint8_t c3 = fpm_getchar(); 31 | return (c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 | (c3 & 0x3f); 32 | } 33 | -------------------------------------------------------------------------------- /tests/zmodem.h: -------------------------------------------------------------------------------- 1 | /* 2 | *------------------------------------------------------------ 3 | * ___ ___ _ 4 | * ___ ___ ___ ___ ___ _____| _| . | |_ 5 | * | _| . |_ -| _| . | | | . | . | '_| 6 | * |_| |___|___|___|___|_____|_|_|_|___|___|_,_| 7 | * |_____| firmware v1 8 | * ------------------------------------------------------------ 9 | * Copyright (c)2020 Ross Bamford 10 | * See top-level LICENSE.md for licence information. 11 | * 12 | * Basic Zmodem implementation for kernel loader. 13 | * ------------------------------------------------------------ 14 | */ 15 | 16 | #ifndef __ROSCO_M68K_ZMODEM_H 17 | #define __ROSCO_M68K_ZMODEM_H 18 | 19 | #include "ztypes.h" 20 | #include "znumbers.h" 21 | #include "zheaders.h" 22 | #include "zserial.h" 23 | 24 | #endif /* __ROSCO_M68K_ZMODEM_H */ 25 | -------------------------------------------------------------------------------- /docs/Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Join our growing community and help shape the future of this project. 4 | We welcome your contributions in any form, whether it's code, 5 | documentation, design, or simply spreading the word. 6 | Let's work together to make this project even better! 7 | 8 | Want to contribute code? Great! Submit a pull request [on GitHub](https://github.com/fp-m/fpm-embedded/pulls). 9 | We'll review your code and provide feedback as soon as possible. 10 | 11 | To maintain consistency, we use clang-format to enforce our code style. 12 | You can find the style rules in the [.clang-format](https://github.com/fp-m/fpm-embedded/blob/main/.clang-format) file. 13 | Please run clang-format on your code before submitting a pull request to automatically format it correctly. 14 | 15 | We prefer submissions licensed under the MIT License or a compatible open-source license. 16 | -------------------------------------------------------------------------------- /fatfs/00readme.txt: -------------------------------------------------------------------------------- 1 | FatFs Module Source Files R0.15 2 | 3 | 4 | FILES 5 | 6 | 00readme.txt This file. 7 | 00history.txt Revision history. 8 | ff.c FatFs module. 9 | ffconf.h Configuration file of FatFs module. 10 | fatfs.h Common include file for FatFs and application module. 11 | diskio.h Common include file for FatFs and disk I/O module. 12 | diskio.c An example of glue function to attach existing disk I/O module to FatFs. 13 | ffunicode.c Optional Unicode utility functions. 14 | ffsystem.c An example of optional O/S related functions. 15 | 16 | 17 | Low level disk I/O module is not included in this archive because the FatFs 18 | module is only a generic file system layer and it does not depend on any specific 19 | storage device. You need to provide a low level disk I/O module written to 20 | control the storage device that attached to the target system. 21 | -------------------------------------------------------------------------------- /kernel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(fpm_kernel STATIC 2 | fpm_editline.c 3 | fpm_getkey.c 4 | fpm_getwch.c 5 | fpm_puts.c 6 | fpm_putwch.c 7 | fpm_shell.c 8 | fpm_exec.c 9 | fpm_strlcpy.c 10 | fpm_strwlen.c 11 | fpm_tokenize.c 12 | fpm_getopt.c 13 | fpm_wputs.c 14 | fpm_get_dotw.c 15 | fpm_alloc.c 16 | fpm_loader.c 17 | fpm_strtol.c 18 | 19 | cmd/cmd_cat.c 20 | cmd/cmd_cd.c 21 | cmd/cmd_clear.c 22 | cmd/cmd_copy.c 23 | cmd/cmd_date.c 24 | cmd/cmd_dir.c 25 | cmd/cmd_echo.c 26 | cmd/cmd_eject.c 27 | cmd/cmd_format.c 28 | cmd/cmd_help.c 29 | cmd/cmd_mkdir.c 30 | cmd/cmd_mount.c 31 | cmd/cmd_reboot.c 32 | cmd/cmd_remove.c 33 | cmd/cmd_rename.c 34 | cmd/cmd_rmdir.c 35 | cmd/cmd_time.c 36 | cmd/cmd_ver.c 37 | cmd/cmd_vol.c 38 | ) 39 | target_include_directories(fpm_kernel BEFORE PUBLIC 40 | ../include 41 | ) 42 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_reboot.c: -------------------------------------------------------------------------------- 1 | // 2 | // Restart the FP/M kernel 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | void fpm_cmd_reboot(int argc, char *argv[]) 9 | { 10 | static const struct fpm_option long_opts[] = { 11 | { "help", FPM_NO_ARG, NULL, 'h' }, 12 | {}, 13 | }; 14 | struct fpm_opt opt = {}; 15 | 16 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 17 | switch (opt.ret) { 18 | case 1: 19 | fpm_printf("%s: Unexpected argument `%s`\r\n\n", argv[0], opt.arg); 20 | return; 21 | 22 | case '?': 23 | // Unknown option: message already printed. 24 | fpm_puts("\r\n"); 25 | return; 26 | 27 | case 'h': 28 | fpm_puts("Usage: reboot\r\n\n"); 29 | return; 30 | } 31 | } 32 | 33 | fpm_puts("Reboot....\r\n\r\n"); 34 | fpm_reboot(); 35 | } 36 | -------------------------------------------------------------------------------- /pico/main_pico.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | extern void fpm_init_datetime(void); 10 | 11 | int main() 12 | { 13 | // Initialize chosen serial port. 14 | stdio_init_all(); 15 | 16 | // Setup heap area. 17 | fpm_context_t context_base; 18 | extern char end[], __HeapLimit[]; 19 | fpm_heap_init(&context_base, (size_t)&end[0], __HeapLimit - end); 20 | 21 | fpm_init_datetime(); 22 | disk_setup(); 23 | 24 | // Try to mount flash at startup. 25 | // It may fail, which is OK. 26 | f_mount("flash:"); 27 | 28 | // Start interactive dialog. 29 | for (;;) { 30 | #if LIB_PICO_STDIO_USB 31 | // Make sure console is connected. 32 | while (!stdio_usb_connected()) { 33 | sleep_ms(100); 34 | } 35 | #endif 36 | fpm_shell(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_ver.c: -------------------------------------------------------------------------------- 1 | // 2 | // Show the version of FP/M software 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | void fpm_cmd_ver(int argc, char *argv[]) 9 | { 10 | static const struct fpm_option long_opts[] = { 11 | { "help", FPM_NO_ARG, NULL, 'h' }, 12 | {}, 13 | }; 14 | struct fpm_opt opt = {}; 15 | 16 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 17 | switch (opt.ret) { 18 | case 1: 19 | fpm_printf("%s: Unexpected argument `%s`\r\n\n", argv[0], opt.arg); 20 | return; 21 | 22 | case '?': 23 | // Unknown option: message already printed. 24 | fpm_puts("\r\n"); 25 | return; 26 | 27 | case 'h': 28 | fpm_puts("Usage: ver\r\n\n"); 29 | return; 30 | } 31 | } 32 | 33 | // Display FP/M version. 34 | fpm_puts("\r\n"); 35 | fpm_print_version(); 36 | fpm_puts("\r\n"); 37 | } 38 | -------------------------------------------------------------------------------- /kernel/fpm_strtol.c: -------------------------------------------------------------------------------- 1 | // Cannot include here. 2 | #include 3 | #include 4 | #include 5 | 6 | // 7 | // Convert a string value to a long integer. 8 | // Return true when value is out of range. 9 | // 10 | bool fpm_strtol(long *output, const char *str, char **endptr, int base) 11 | { 12 | errno = 0; 13 | *output = strtol(str, endptr, base); 14 | return (errno != 0); 15 | } 16 | 17 | // 18 | // Convert a string value to an unsigned long integer. 19 | // Return true when value is out of range. 20 | // 21 | bool fpm_strtoul(unsigned long *output, const char *str, char **endptr, int base) 22 | { 23 | errno = 0; 24 | *output = strtoul(str, endptr, base); 25 | return (errno != 0); 26 | } 27 | 28 | // 29 | // Convert string to floating point. 30 | // Return true when value is out of range. 31 | // 32 | bool fpm_strtod(double *output, const char *str, char **endptr) 33 | { 34 | errno = 0; 35 | *output = strtod(str, endptr); 36 | return (errno != 0); 37 | } 38 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_clear.c: -------------------------------------------------------------------------------- 1 | // 2 | // Clear the console screen 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | void fpm_cmd_clear(int argc, char *argv[]) 9 | { 10 | static const struct fpm_option long_opts[] = { 11 | { "help", FPM_NO_ARG, NULL, 'h' }, 12 | {}, 13 | }; 14 | struct fpm_opt opt = {}; 15 | 16 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 17 | switch (opt.ret) { 18 | case 1: 19 | fpm_printf("%s: Unexpected argument `%s`\r\n\n", argv[0], opt.arg); 20 | return; 21 | 22 | case '?': 23 | // Unknown option: message already printed. 24 | fpm_puts("\r\n"); 25 | return; 26 | 27 | case 'h': 28 | fpm_puts("Usage:\r\n" 29 | " clear\r\n" 30 | " cls\r\n" 31 | "\n"); 32 | return; 33 | } 34 | } 35 | 36 | // Clear screen. 37 | fpm_puts("\33[H\33[J"); 38 | } 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # make 3 | # make all -- build everything 4 | # 5 | # make test -- run unit tests 6 | # 7 | # make install -- install FP/M binaries to /usr/local 8 | # 9 | # make clean -- remove build files 10 | # 11 | 12 | all: unix/build pico/build-rp2040 pico/build-rp2350-arm 13 | $(MAKE) -C unix/build $@ 14 | $(MAKE) -C pico/build-rp2040 $@ 15 | $(MAKE) -C pico/build-rp2350-arm $@ 16 | 17 | test: tests/build 18 | $(MAKE) -C tests/build all 19 | ctest --test-dir tests/build 20 | 21 | install: unix/build pico/build-rp2040 pico/build-rp2350-arm 22 | $(MAKE) -C unix/build $@ 23 | $(MAKE) -C pico/build-rp2040 $@ 24 | $(MAKE) -C pico/build-rp2350-arm $@ 25 | 26 | clean: 27 | rm -rf unix/build pico/build-rp2040 pico/build-rp2350-arm tests/build 28 | 29 | pico/build-rp2040: 30 | mkdir $@ 31 | cmake -B $@ pico -DRP2040=1 32 | 33 | pico/build-rp2350-arm: 34 | mkdir $@ 35 | cmake -B $@ pico -DRP2350_ARM=1 36 | 37 | unix/build: 38 | mkdir $@ 39 | cmake -B $@ unix 40 | 41 | tests/build: 42 | mkdir $@ 43 | cmake -B $@ tests #-DCMAKE_BUILD_TYPE=Debug 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Serge Vakulenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/fpm/context.h: -------------------------------------------------------------------------------- 1 | // 2 | // Program context. 3 | // 4 | #ifndef FPM_CONTEXT_H 5 | #define FPM_CONTEXT_H 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | // 12 | // Context of the current program being running. 13 | // 14 | typedef struct _fpm_context_t { 15 | // Pointer to a parent program, or NULL. 16 | volatile struct _fpm_context_t *parent; 17 | 18 | // Heap info for memory allocation. 19 | size_t heap_start; // Heap starts at this address 20 | size_t heap_size; // Size of the heap in bytes 21 | size_t free_size; // Total amount of free memory 22 | void *free_list; // Linked list of memory gaps, sorted by address in ascending order 23 | 24 | // Info about current program being executed. 25 | void *base; // File is mapped at this address 26 | unsigned num_links; // Number of linked procedures 27 | const void *rel_section; // Header of .rela.plt section 28 | int exit_code; // Return value of invoked object 29 | 30 | // For Unix only. 31 | int fd; // File descriptor 32 | size_t file_size; // Size of file in bytes 33 | } fpm_context_t; 34 | 35 | #ifdef __cplusplus 36 | } 37 | #endif 38 | 39 | #endif // FPM_CONTEXT_H 40 | -------------------------------------------------------------------------------- /tests/dotw_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Test fpm_get_dotw() - Day Of The Week function. 3 | // 4 | #include 5 | #include 6 | 7 | enum { SUN = 0, MON, TUE, WED, THU, FRI, SAT }; 8 | 9 | TEST(dotw, arbitrary_wednesday) 10 | { 11 | // Wednesday, February 15, 2023 - when I wrote this test. 12 | int wday = fpm_get_dotw(2023, 2, 15); 13 | EXPECT_EQ(wday, WED); 14 | } 15 | 16 | TEST(dotw, space_x_endeavor) 17 | { 18 | // Saturday, May 30, 2020 - Space-X launches astronauts to space station. 19 | int wday = fpm_get_dotw(2020, 5, 30); 20 | EXPECT_EQ(wday, SAT); 21 | } 22 | 23 | TEST(dotw, war_on_japan) 24 | { 25 | // Monday, December 8, 1941 - U.S. and Britain declare war on Japan. 26 | int wday = fpm_get_dotw(1941, 12, 8); 27 | EXPECT_EQ(wday, MON); 28 | } 29 | 30 | TEST(dotw, emancipation_proclamation) 31 | { 32 | // Thursday, January 1, 1863 - Abraham Lincoln signs the Emancipation Proclamation ending slavery. 33 | int wday = fpm_get_dotw(1863, 1, 1); 34 | EXPECT_EQ(wday, THU); 35 | } 36 | 37 | TEST(dotw, u_s_capital) 38 | { 39 | // Friday, December 12, 1800 - Washington, D.C. is established as the U.S. capital. 40 | int wday = fpm_get_dotw(1800, 12, 12); 41 | EXPECT_EQ(wday, FRI); 42 | } 43 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_eject.c: -------------------------------------------------------------------------------- 1 | // 2 | // Release removable disk device 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void eject(const char *path) 10 | { 11 | fs_result_t result = f_unmount(path); 12 | if (result != FR_OK) { 13 | fpm_puts(f_strerror(result)); 14 | fpm_puts("\r\n\n"); 15 | } 16 | } 17 | 18 | void fpm_cmd_eject(int argc, char *argv[]) 19 | { 20 | static const struct fpm_option long_opts[] = { 21 | { "help", FPM_NO_ARG, NULL, 'h' }, 22 | {}, 23 | }; 24 | struct fpm_opt opt = {}; 25 | 26 | if (argc > 2) { 27 | usage: 28 | fpm_puts("Usage:\r\n" 29 | " eject [sd: | flash:]\r\n" 30 | "\n"); 31 | return; 32 | } 33 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 34 | switch (opt.ret) { 35 | case 1: 36 | eject(opt.arg); 37 | return; 38 | 39 | case '?': 40 | // Unknown option: message already printed. 41 | fpm_puts("\r\n"); 42 | return; 43 | 44 | case 'h': 45 | goto usage; 46 | } 47 | } 48 | 49 | // Eject SD card by default. 50 | eject("sd:"); 51 | } 52 | -------------------------------------------------------------------------------- /tests/console_util.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Test fpm_editline() - command line editor. 3 | // 4 | #include 5 | #include 6 | #include "util.h" 7 | 8 | const char *input; // Input stream for the current test, utf-8 encoded 9 | 10 | // 11 | // Get Unicode character from input buffer. 12 | // 13 | char fpm_getchar() 14 | { 15 | if (input == nullptr || *input == 0) { 16 | // Should not happen. 17 | throw std::runtime_error("No input in fpm_getchar()"); 18 | } 19 | return *input++; 20 | } 21 | 22 | // 23 | // Write Unicode character to output buffer. 24 | // 25 | void fpm_putchar(char ch) 26 | { 27 | putchar(ch); 28 | fflush(stdout); 29 | } 30 | 31 | // 32 | // Posix-compatible formatted output to string. 33 | // 34 | int fpm_snprintf(char *str, size_t size, const char *format, ...) 35 | { 36 | va_list args; 37 | va_start(args, format); 38 | int retval = vsnprintf(str, size, format, args); 39 | va_end(args); 40 | return retval; 41 | } 42 | 43 | // 44 | // Posix-compatible formatted output to console. 45 | // 46 | int fpm_printf(const char *format, ...) 47 | { 48 | va_list args; 49 | va_start(args, format); 50 | int retval = vprintf(format, args); 51 | va_end(args); 52 | fflush(stdout); 53 | return retval; 54 | } 55 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_mkdir.c: -------------------------------------------------------------------------------- 1 | // 2 | // Create a directory 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void create_directory(const char *path) 10 | { 11 | fs_result_t result = f_mkdir(path); 12 | if (result != FR_OK) { 13 | fpm_printf("%s: %s\r\n", path, f_strerror(result)); 14 | } 15 | } 16 | 17 | void fpm_cmd_mkdir(int argc, char *argv[]) 18 | { 19 | static const struct fpm_option long_opts[] = { 20 | { "help", FPM_NO_ARG, NULL, 'h' }, 21 | {}, 22 | }; 23 | struct fpm_opt opt = {}; 24 | unsigned argcount = 0; 25 | 26 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 27 | switch (opt.ret) { 28 | case 1: 29 | create_directory(opt.arg); 30 | argcount++; 31 | break; 32 | 33 | case '?': 34 | // Unknown option: message already printed. 35 | fpm_puts("\r\n"); 36 | return; 37 | 38 | case 'h': 39 | usage: fpm_puts("Usage:\r\n" 40 | " mkdir name ...\r\n" 41 | "\n"); 42 | return; 43 | } 44 | } 45 | 46 | if (argcount == 0) { 47 | // Nothing to create. 48 | goto usage; 49 | } 50 | fpm_puts("\r\n"); 51 | } 52 | -------------------------------------------------------------------------------- /apps/rz/zmodem.h: -------------------------------------------------------------------------------- 1 | // 2 | // Basic Zmodem implementation. 3 | // 4 | // Copyright (c) 2020 Ross Bamford 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include "ztypes.h" 25 | #include "znumbers.h" 26 | #include "zheaders.h" 27 | #include "zserial.h" 28 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_echo.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copy text directly to the console output 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | void fpm_cmd_echo(int argc, char *argv[]) 9 | { 10 | static const struct fpm_option long_opts[] = { 11 | { "help", FPM_NO_ARG, NULL, 'h' }, 12 | {}, 13 | }; 14 | struct fpm_opt opt = {}; 15 | bool no_newline = false; 16 | int count = 0; 17 | 18 | while (fpm_getopt(argc, argv, "hn", long_opts, &opt) >= 0) { 19 | switch (opt.ret) { 20 | case 1: 21 | // Display arguments. 22 | if (count > 0) 23 | fpm_putchar(' '); 24 | fpm_puts(opt.arg); 25 | count++; 26 | continue; 27 | 28 | case '?': 29 | // Unknown option: message already printed. 30 | continue; 31 | 32 | case 'n': 33 | no_newline = true; 34 | continue; 35 | 36 | case 'h': 37 | fpm_puts("Usage:\r\n" 38 | " echo [-n] [string ...]\r\n" 39 | "\n" 40 | " -n Do not output the trailing newline\r\n" 41 | "\n"); 42 | return; 43 | } 44 | } 45 | 46 | if (!no_newline) { 47 | fpm_puts("\r\n"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_cd.c: -------------------------------------------------------------------------------- 1 | // 2 | // Show or change current directory 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void chdir(const char *path) 10 | { 11 | fs_result_t result = f_chdir(path); 12 | if (result != FR_OK) { 13 | fpm_printf("%s: %s\r\n\n", path, f_strerror(result)); 14 | } 15 | } 16 | 17 | void fpm_cmd_cd(int argc, char *argv[]) 18 | { 19 | static const struct fpm_option long_opts[] = { 20 | { "help", FPM_NO_ARG, NULL, 'h' }, 21 | {}, 22 | }; 23 | struct fpm_opt opt = {}; 24 | 25 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 26 | switch (opt.ret) { 27 | case 1: 28 | chdir(opt.arg); 29 | return; 30 | 31 | case '?': 32 | // Unknown option: message already printed. 33 | fpm_puts("\r\n"); 34 | return; 35 | 36 | case 'h': 37 | fpm_puts("Usage:\r\n" 38 | " cd\r\n" 39 | " cd path\r\n" 40 | " cd ..\r\n" 41 | "\n"); 42 | return; 43 | } 44 | } 45 | 46 | char path[4096]; 47 | fs_result_t result = f_getcwd(path, sizeof(path)); 48 | if (result == FR_OK) { 49 | fpm_puts(path); 50 | } else { 51 | fpm_puts(f_strerror(result)); 52 | } 53 | fpm_puts("\r\n\n"); 54 | } 55 | -------------------------------------------------------------------------------- /pico/flash.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Serge Vakulenko 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | unsigned flash_block_count(void); 23 | unsigned flash_block_size(void); 24 | disk_result_t flash_read(uint8_t *buf, unsigned block, unsigned count); 25 | disk_result_t flash_write(const uint8_t *buf, unsigned block, unsigned count); 26 | void flash_identify(disk_info_t *output); 27 | -------------------------------------------------------------------------------- /unix/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set minimum required version of CMake 2 | cmake_minimum_required(VERSION 3.12) 3 | 4 | # Set name of project (as PROJECT_NAME) and C/C++ standards 5 | project(fpm-demo C CXX) 6 | set(CMAKE_C_STANDARD 11) 7 | set(CMAKE_CXX_STANDARD 17) 8 | add_compile_options(-Wall -Werror -Wshadow) 9 | 10 | # Tell CMake where to find the executable source file 11 | add_executable(${PROJECT_NAME} 12 | main_unix.c 13 | fpm_unix.c 14 | diskio_unix.c 15 | loader_unix.c 16 | bindings.c 17 | ) 18 | add_subdirectory(../kernel kernel EXCLUDE_FROM_ALL) 19 | add_subdirectory(../fatfs fatfs EXCLUDE_FROM_ALL) 20 | add_subdirectory(../tools/elfexe elfexe) 21 | add_subdirectory(../tools/uf2fat uf2fat) 22 | 23 | target_include_directories(${PROJECT_NAME} BEFORE PUBLIC 24 | ../include 25 | ) 26 | 27 | target_link_libraries(${PROJECT_NAME} 28 | fpm_kernel 29 | fpm_fatfs 30 | ) 31 | 32 | # Get git commit hash and revision count 33 | execute_process( 34 | COMMAND git log -1 --format=%h 35 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 36 | OUTPUT_STRIP_TRAILING_WHITESPACE 37 | OUTPUT_VARIABLE GIT_COMMIT 38 | ) 39 | execute_process( 40 | COMMAND git rev-list HEAD --count 41 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 42 | OUTPUT_STRIP_TRAILING_WHITESPACE 43 | OUTPUT_VARIABLE GIT_REVCOUNT 44 | ) 45 | set_source_files_properties(fpm_unix.c 46 | PROPERTIES COMPILE_FLAGS "-DGIT_REVCOUNT=\\\"${GIT_REVCOUNT}\\\" -DGIT_COMMIT=\\\"${GIT_COMMIT}\\\"" 47 | ) 48 | 49 | install(TARGETS 50 | ${PROJECT_NAME} 51 | DESTINATION $ENV{HOME}/.local/bin 52 | ) 53 | -------------------------------------------------------------------------------- /unix/loader_unix.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // 11 | // Load dynamic binary. 12 | // Return true on success. 13 | // 14 | bool fpm_load_arch(fpm_context_t *ctx, const char *filename) 15 | { 16 | // Unix: open the shared library file in read-only mode. 17 | ctx->fd = open(filename, O_RDONLY); 18 | if (ctx->fd < 0) { 19 | fpm_printf("%s: Cannot open\r\n", filename); 20 | return false; 21 | } 22 | 23 | // Get the size of the file 24 | struct stat sb; 25 | if (fstat(ctx->fd, &sb) < 0) { 26 | fpm_printf("%s: Cannot fstat\r\n", filename); 27 | err: close(ctx->fd); 28 | return false; 29 | } 30 | ctx->file_size = sb.st_size; 31 | 32 | // Map the file into memory 33 | ctx->base = mmap(NULL, ctx->file_size, PROT_READ | PROT_EXEC, MAP_SHARED, ctx->fd, 0); 34 | if (ctx->base == MAP_FAILED) { 35 | fpm_printf("%s: Cannot mmap\r\n", filename); 36 | goto err; 37 | } 38 | return true; 39 | } 40 | 41 | // 42 | // Unmap ELF binary from memory. 43 | // 44 | void fpm_unload_arch(fpm_context_t *ctx) 45 | { 46 | // Unmap the file from memory. 47 | if (ctx->base != NULL) { 48 | munmap(ctx->base, ctx->file_size); 49 | ctx->base = NULL; 50 | } 51 | 52 | // Close file. 53 | // Note: descriptor cannot be zero. 54 | if (ctx->fd > 0) { 55 | close(ctx->fd); 56 | ctx->fd = 0; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FP/M is an experimental embedded operating system designed for modern microprocessors. 2 | The name stands for "Flash Program for Microcontrollers." 3 | It aims to provide a lightweight and efficient environment for developing embedded applications, 4 | drawing inspiration from historical operating systems like CP/M and PC DOS. 5 | 6 | The kernel API is designed for direct execution of binary executables from a primary Flash file system. 7 | User interaction occurs through a command-line interface (CLI). 8 | An optional SD card slot is supported for file transfer and additional storage. 9 | 10 | ## Quick Start 11 | Never tried FP/M? Read the [Getting Started](docs/Getting-Started.md) guide! 12 | If you don't have a board available, FP/M has a demo that you can run on Linux or MacOS. 13 | 14 | ## Getting Help 15 | You can ask for help on [Reddit](https://www.reddit.com/r/FP_M/). 16 | Please send bug reports and feature requests to [GitHub](https://github.com/fp-m/fpm-embedded/issues). 17 | 18 | ## Supported Boards 19 | Currently FP/M supports only a RP2040 platform. 20 | See the list of boards on the [Supported Platforms](docs/Supported-Platforms.md) page. 21 | 22 | ## Documentation 23 | You can find the current documentation on the [Documentation Page](docs/Documentation.md). 24 | 25 | ## Contributing 26 | If you wish to contribute to the FP/M project, read the [Contributing](docs/Contributing.md) 27 | guidelines for information on Git usage, coding standard and the development workflow. 28 | 29 | ## License 30 | The code in FP/M project is under the MIT license, or a compatible open-source license. 31 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | BasedOnStyle: Google 4 | 5 | # 6 | # Indent by four columns. 7 | # 8 | IndentWidth: 4 9 | 10 | # 11 | # Limit source width by 100 columns. 12 | # 13 | ColumnLimit: 100 14 | 15 | # 16 | # The brace breaking style: 17 | # attach braces to surrounding context, but break before functions. 18 | # 19 | BreakBeforeBraces: WebKit 20 | 21 | # 22 | # For case labels, use the same indentation level as for the switch statement. 23 | # 24 | IndentCaseLabels: false 25 | 26 | # 27 | # Put spaces after { and before } in initializers. 28 | # 29 | Cpp11BracedListStyle: false 30 | 31 | # 32 | # For access modifiers (like public:), 33 | # use the same indentation level as the class statement. 34 | # 35 | AccessModifierOffset: -4 36 | 37 | # 38 | # Align pointers to the right, like: int *a; 39 | # 40 | DerivePointerAlignment: false 41 | PointerAlignment: Right 42 | 43 | # 44 | # Short functions, loops or ifs should be split into multiple lines. 45 | # 46 | AllowShortFunctionsOnASingleLine: InlineOnly 47 | AllowShortIfStatementsOnASingleLine: false 48 | AllowShortLoopsOnASingleLine: false 49 | 50 | # 51 | # Break inheritance list and constructor initializers before colon. 52 | # 53 | BreakInheritanceList: BeforeColon 54 | BreakConstructorInitializers: BeforeColon 55 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 56 | 57 | # 58 | # Align trailing comments. 59 | # 60 | AlignTrailingComments: true 61 | SpacesBeforeTrailingComments: 1 62 | 63 | # 64 | # Allow packing of multiple function definition’s parameters per line. 65 | # 66 | BinPackParameters: true 67 | AllowAllParametersOfDeclarationOnNextLine: false 68 | -------------------------------------------------------------------------------- /fatfs/examples/demo_check_contig.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------/ 2 | / Test if the file is contiguous / 3 | /----------------------------------------------------------------------*/ 4 | 5 | fs_result_t test_contiguous_file(file_t *fp, /* [IN] Open file object to be checked */ 6 | int *cont) /* [OUT] 1:Contiguous, 0:Fragmented or zero-length */ 7 | { 8 | uint32_t clst, clsz, step; 9 | fs_size_t fsz; 10 | fs_result_t fr; 11 | 12 | *cont = 0; 13 | fr = f_rewind(fp); /* Validates and prepares the file */ 14 | if (fr != FR_OK) 15 | return fr; 16 | 17 | #if FF_MAX_SS == FF_MIN_SS 18 | clsz = (uint32_t)fp->obj.fs->csize * FF_MAX_SS; /* Cluster size */ 19 | #else 20 | clsz = (uint32_t)fp->obj.fs->csize * fp->obj.fs->ssize; 21 | #endif 22 | fsz = f_size(fp); 23 | if (fsz > 0) { 24 | clst = fp->obj.sclust - 1; /* A cluster leading the first cluster for first test */ 25 | while (fsz) { 26 | step = (fsz >= clsz) ? clsz : (uint32_t)fsz; 27 | fr = f_lseek(fp, f_tell(fp) + step); /* Advances file pointer a cluster */ 28 | if (fr != FR_OK) 29 | return fr; 30 | if (clst + 1 != fp->clust) 31 | break; /* Is not the cluster next to previous one? */ 32 | clst = fp->clust; 33 | fsz -= step; /* Get current cluster for next test */ 34 | } 35 | if (fsz == 0) 36 | *cont = 1; /* All done without fail? */ 37 | } 38 | 39 | return FR_OK; 40 | } 41 | -------------------------------------------------------------------------------- /include/fpm/getopt.h: -------------------------------------------------------------------------------- 1 | // 2 | // Get long options from command line argument list. 3 | // 4 | #ifndef FPM_GETOPT_H 5 | #define FPM_GETOPT_H 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | enum { // For has_arg: 12 | FPM_NO_ARG = 0, // no argument to the option is expected 13 | FPM_REQUIRED_ARG = 1, // an argument to the option is required 14 | FPM_OPTIONAL_ARG = 2, // an argument to the option may be presented 15 | }; 16 | 17 | struct fpm_option { 18 | const char *name; // The name of the long option. 19 | int has_arg; // One of the above enums. 20 | int *flag; // Determines if fpm_getopt() returns a value for a long option. 21 | // If it is non-NULL, 0 is returned as a function value and 22 | // the value of val is stored in the area pointed to by flag. 23 | // Otherwise, val is returned. 24 | int val; // Determines the value to return if flag is NULL. 25 | 26 | }; 27 | 28 | struct fpm_opt { 29 | int ret; // Returned value 30 | int opt; // Current option 31 | const char *arg; // Current argument 32 | int long_index; // Index of long option 33 | int ind; // Index of next argv 34 | int silent; // Suppress error messages 35 | int where; // Offset inside current argument 36 | }; 37 | 38 | int fpm_getopt(int argc, char *const argv[], const char *optstring, 39 | const struct fpm_option *longopts, struct fpm_opt *opt); 40 | 41 | #ifdef __cplusplus 42 | } 43 | #endif 44 | 45 | #endif // FPM_GETOPT_H 46 | -------------------------------------------------------------------------------- /pico/sd_pico/crc.h: -------------------------------------------------------------------------------- 1 | /* crc.h 2 | Copyright 2021 Carl John Kugler III 3 | 4 | Licensed under the Apache License, Version 2.0 (the License); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | */ 14 | /* Derived from: 15 | * SD/MMC File System Library 16 | * Copyright (c) 2016 Neil Thiessen 17 | * 18 | * Licensed under the Apache License, Version 2.0 (the "License"); 19 | * you may not use this file except in compliance with the License. 20 | * You may obtain a copy of the License at 21 | * 22 | * http://www.apache.org/licenses/LICENSE-2.0 23 | * 24 | * Unless required by applicable law or agreed to in writing, software 25 | * distributed under the License is distributed on an "AS IS" BASIS, 26 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27 | * See the License for the specific language governing permissions and 28 | * limitations under the License. 29 | */ 30 | 31 | #ifndef SD_CRC_H 32 | #define SD_CRC_H 33 | 34 | #include 35 | 36 | char crc7(const char *data, int length); 37 | unsigned short crc16(const char *data, int length); 38 | void update_crc16(unsigned short *pCrc16, const char data[], size_t length); 39 | 40 | #endif 41 | 42 | /* [] END OF FILE */ 43 | -------------------------------------------------------------------------------- /unix/main_unix.c: -------------------------------------------------------------------------------- 1 | // 2 | // Run shell on Linux or MacOS 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static uint8_t core_memory[1024*1024]; 14 | 15 | static struct termios saved_term; 16 | 17 | static void restore() 18 | { 19 | //printf("Restore terminal\r\n"); 20 | tcsetattr(0, TCSADRAIN, &saved_term); 21 | } 22 | 23 | static void init() 24 | { 25 | //printf("Initialize terminal\n"); 26 | if (tcgetattr(0, &saved_term) < 0) { 27 | printf("No terminal on stdin\n"); 28 | exit(1); 29 | } 30 | atexit(restore); 31 | 32 | struct termios term = saved_term; 33 | term.c_iflag &= ~(ICRNL | INLCR | IXON | IXOFF); 34 | term.c_oflag &= ~(OPOST); 35 | term.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 36 | term.c_cc[VMIN] = 1; 37 | term.c_cc[VTIME] = 0; 38 | if (tcsetattr(0, TCSADRAIN, &term) < 0) { 39 | printf("Cannot initialize terminal\n"); 40 | exit(1); 41 | } 42 | } 43 | 44 | int main() 45 | { 46 | // Setup heap area. 47 | fpm_context_t context_base; 48 | fpm_heap_init(&context_base, (size_t)&core_memory[0], sizeof(core_memory)); 49 | 50 | // Switch stdin to the raw mode (no line buffering and editing). 51 | init(); 52 | disk_setup(); 53 | f_mount("flash:"); 54 | f_mount("sd:"); 55 | 56 | printf("Start FP/M on Unix\r\n"); 57 | printf("Use '?' for help or 'exit' to quit.\r\n\r\n"); 58 | 59 | // Start interactive dialog. 60 | fpm_shell(); 61 | } 62 | -------------------------------------------------------------------------------- /include/fpm/loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Dynamic Loader Interface. 3 | // 4 | #ifndef FPM_LOADER_H 5 | #define FPM_LOADER_H 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #include 12 | 13 | // 14 | // Context of the current program being running. 15 | // 16 | struct _fpm_context_t; 17 | typedef struct _fpm_context_t fpm_context_t; 18 | 19 | // 20 | // Definition of exported procedure for dynamic linking. 21 | // 22 | typedef struct { 23 | const char *name; // Name of procedure 24 | void *address; // Address of procedure 25 | } fpm_binding_t; 26 | 27 | extern fpm_binding_t fpm_bindings[]; 28 | 29 | // 30 | // Load dynamic binary. 31 | // Return true on success. 32 | // 33 | bool fpm_load(fpm_context_t *ctx, const char *filename); 34 | 35 | // 36 | // Unmap ELF binary from memory. 37 | // 38 | void fpm_unload(fpm_context_t *ctx); 39 | 40 | // 41 | // Internal platform-dependent helper routines. 42 | // 43 | bool fpm_load_arch(fpm_context_t *ctx, const char *filename); 44 | void fpm_unload_arch(fpm_context_t *ctx); 45 | 46 | // 47 | // Get names of linked procedures. 48 | // 49 | void fpm_get_symbols(fpm_context_t *ctx, const char *symbols[]); 50 | 51 | // 52 | // Invoke entry address of the ELF binary with argc, argv arguments. 53 | // Bind dynamic symbols of the binary according to the given linkmap. 54 | // Assume the entry has signature: 55 | // 56 | // int main(int argc, char *argv[]) 57 | // 58 | // Return the exit code. 59 | // 60 | bool fpm_invoke(fpm_context_t *ctx, fpm_binding_t linkmap[], int argc, char *argv[]); 61 | 62 | #ifdef __cplusplus 63 | } 64 | #endif 65 | 66 | #endif // FPM_LOADER_H 67 | -------------------------------------------------------------------------------- /pico/rtc_pico.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | #include "pico/aon_timer.h" 4 | 5 | // 6 | // Get date and time (local). 7 | // 8 | void fpm_get_datetime(int *year, int *month, int *day, int *dotw, int *hour, int *min, int *sec) 9 | { 10 | struct tm timestamp = {}; 11 | aon_timer_get_time_calendar(×tamp); 12 | 13 | *year = 1900 + timestamp.tm_year; 14 | *month = 1 + timestamp.tm_mon; 15 | *day = timestamp.tm_mday; 16 | *dotw = timestamp.tm_wday; 17 | *hour = timestamp.tm_hour; 18 | *min = timestamp.tm_min; 19 | *sec = timestamp.tm_sec; 20 | } 21 | 22 | // 23 | // Set date and time. 24 | // 25 | void fpm_set_datetime(int year, int month, int day, int hour, int min, int sec) 26 | { 27 | const struct tm timestamp = { 28 | .tm_sec = sec, 29 | .tm_min = min, 30 | .tm_hour = hour, 31 | .tm_mday = day, 32 | .tm_mon = month - 1, 33 | .tm_year = year - 1900, 34 | .tm_wday = fpm_get_dotw(year, month, day), 35 | }; 36 | aon_timer_set_time_calendar(×tamp); 37 | sleep_us(64); 38 | } 39 | 40 | void fpm_init_datetime() 41 | { 42 | //TODO: get time/date from battery backed RTC or from filesystem 43 | static const struct tm build_timestamp = { 44 | .tm_sec = BUILD_SEC, 45 | .tm_min = BUILD_MIN, 46 | .tm_hour = BUILD_HOUR, 47 | .tm_mday = BUILD_DAY, 48 | .tm_mon = BUILD_MONTH - 1, 49 | .tm_year = BUILD_YEAR - 1900, 50 | .tm_wday = BUILD_DOTW, // 0 is Sunday, so 5 is Friday 51 | }; 52 | 53 | aon_timer_start_calendar(&build_timestamp); 54 | sleep_us(64); 55 | } 56 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_rmdir.c: -------------------------------------------------------------------------------- 1 | // 2 | // Remove a directory 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static void remove_directory(const char *path) 10 | { 11 | file_info_t info; 12 | fs_result_t result = f_stat(path, &info); 13 | if (result != FR_OK) { 14 | fpm_printf("%s: %s\r\n", path, f_strerror(result)); 15 | return; 16 | } 17 | if (!(info.fattrib & AM_DIR)) { 18 | fpm_printf("%s: Not a directory\r\n", path); 19 | return; 20 | } 21 | result = f_unlink(path); 22 | if (result != FR_OK) { 23 | fpm_printf("%s: %s\r\n", path, f_strerror(result)); 24 | } 25 | } 26 | 27 | void fpm_cmd_rmdir(int argc, char *argv[]) 28 | { 29 | static const struct fpm_option long_opts[] = { 30 | { "help", FPM_NO_ARG, NULL, 'h' }, 31 | {}, 32 | }; 33 | struct fpm_opt opt = {}; 34 | unsigned argcount = 0; 35 | 36 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 37 | switch (opt.ret) { 38 | case 1: 39 | remove_directory(opt.arg); 40 | argcount++; 41 | break; 42 | 43 | case '?': 44 | // Unknown option: message already printed. 45 | fpm_puts("\r\n"); 46 | return; 47 | 48 | case 'h': 49 | usage: fpm_puts("Usage:\r\n" 50 | " rmdir directory ...\r\n" 51 | "\n"); 52 | return; 53 | } 54 | } 55 | 56 | if (argcount == 0) { 57 | // Nothing to remove. 58 | goto usage; 59 | } 60 | fpm_puts("\r\n"); 61 | } 62 | -------------------------------------------------------------------------------- /fatfs/strerror.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const char *f_strerror(fs_result_t errnum) 5 | { 6 | switch (errnum) { 7 | case FR_OK: return "Succeeded"; 8 | case FR_DISK_ERR: return "Input/output error"; 9 | case FR_INT_ERR: return "Assertion failed"; 10 | case FR_NOT_READY: return "Media is not present"; 11 | case FR_NO_FILE: return "No such file or directory"; 12 | case FR_NO_PATH: return "No such path name"; 13 | case FR_INVALID_NAME: return "The path name format is invalid"; 14 | case FR_DENIED: return "Permission denied"; 15 | case FR_EXIST: return "Access is prohibited"; 16 | case FR_INVALID_OBJECT: return "The file/directory object is invalid"; 17 | case FR_WRITE_PROTECTED: return "The media is write protected"; 18 | case FR_INVALID_DRIVE: return "The logical drive number is invalid"; 19 | case FR_NOT_ENABLED: return "The volume has no work area"; 20 | case FR_NO_FILESYSTEM: return "There is no valid FAT volume"; 21 | case FR_MKFS_ABORTED: return "Cannot create filesystem: disk too small/big"; 22 | case FR_TIMEOUT: return "Operation timed out"; 23 | case FR_LOCKED: return "Resource busy"; 24 | case FR_NOT_ENOUGH_CORE: return "Cannot allocate memory"; 25 | case FR_TOO_MANY_OPEN_FILES: return "Too many open files"; 26 | case FR_INVALID_PARAMETER: return "Given parameter is invalid"; 27 | default: break; 28 | } 29 | 30 | static char buf[40]; 31 | fpm_snprintf(buf, sizeof(buf), "Unknown error: %u", errnum); 32 | return buf; 33 | } 34 | -------------------------------------------------------------------------------- /pico/sd_pico/sd_spi.h: -------------------------------------------------------------------------------- 1 | /* sd_spi.h 2 | Copyright 2021 Carl John Kugler III 3 | 4 | Licensed under the Apache License, Version 2.0 (the License); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | */ 14 | 15 | #ifndef _SD_SPI_H_ 16 | #define _SD_SPI_H_ 17 | 18 | #include 19 | 20 | #include "sd_card.h" 21 | 22 | /* Transfer tx to SPI while receiving SPI to rx. 23 | tx or rx can be NULL if not important. */ 24 | bool sd_spi_transfer(sd_card_t *pSD, const uint8_t *tx, uint8_t *rx, size_t length); 25 | uint8_t sd_spi_write(sd_card_t *pSD, const uint8_t value); 26 | void sd_spi_deselect_pulse(sd_card_t *pSD); 27 | void sd_spi_acquire(sd_card_t *pSD); 28 | void sd_spi_release(sd_card_t *pSD); 29 | void sd_spi_go_low_frequency(sd_card_t *this); 30 | void sd_spi_go_high_frequency(sd_card_t *this); 31 | 32 | /* 33 | After power up, the host starts the clock and sends the initializing sequence on the CMD line. 34 | This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of 1msec, 35 | 74 clocks or the supply-ramp-uptime; the additional 10 clocks 36 | (over the 64 clocks after what the card should be ready for communication) is 37 | provided to eliminate power-up synchronization problems. 38 | */ 39 | void sd_spi_send_initializing_sequence(sd_card_t *pSD); 40 | 41 | #endif 42 | 43 | /* [] END OF FILE */ 44 | -------------------------------------------------------------------------------- /apps/rz/znumbers.h: -------------------------------------------------------------------------------- 1 | // 2 | // Numeric-related routines for Zmodem implementation. 3 | // 4 | // Copyright (c) 2020 Ross Bamford 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | ZRESULT zm_hex_to_nybble(char c1); 31 | 32 | ZRESULT zm_nybble_to_hex(uint8_t nybble); 33 | 34 | // 35 | // *buf MUST have space for exactly two characters! 36 | // 37 | // Returns OK on success, or an error code. 38 | // If an error occues, the buffer will be unchanged. 39 | /// 40 | ZRESULT zm_byte_to_hex(uint8_t byte, uint8_t *buf); 41 | 42 | ZRESULT zm_hex_to_byte(unsigned char c1, unsigned char c2); 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | -------------------------------------------------------------------------------- /pico/bindings.c: -------------------------------------------------------------------------------- 1 | // 2 | // Export dynamically linked routines. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int gpio_get_pad(uint gpio); // missing in hardware/gpio.h 12 | 13 | fpm_binding_t fpm_bindings[] = { 14 | #include 15 | 16 | // GPIO functions. 17 | FPM_BIND(gpio_acknowledge_irq), 18 | FPM_BIND(gpio_add_raw_irq_handler_masked), 19 | FPM_BIND(gpio_add_raw_irq_handler_masked64), 20 | FPM_BIND(gpio_add_raw_irq_handler_with_order_priority_masked), 21 | FPM_BIND(gpio_add_raw_irq_handler_with_order_priority_masked64), 22 | FPM_BIND(gpio_debug_pins_init), 23 | FPM_BIND(gpio_deinit), 24 | FPM_BIND(gpio_get_drive_strength), 25 | FPM_BIND(gpio_get_function), 26 | FPM_BIND(gpio_get_pad), 27 | FPM_BIND(gpio_get_slew_rate), 28 | FPM_BIND(gpio_init), 29 | FPM_BIND(gpio_init_mask), 30 | FPM_BIND(gpio_is_input_hysteresis_enabled), 31 | FPM_BIND(gpio_remove_raw_irq_handler_masked), 32 | FPM_BIND(gpio_remove_raw_irq_handler_masked64), 33 | FPM_BIND(gpio_set_dormant_irq_enabled), 34 | FPM_BIND(gpio_set_drive_strength), 35 | FPM_BIND(gpio_set_function), 36 | FPM_BIND(gpio_set_function_masked), 37 | FPM_BIND(gpio_set_function_masked64), 38 | FPM_BIND(gpio_set_inover), 39 | FPM_BIND(gpio_set_input_enabled), 40 | FPM_BIND(gpio_set_input_hysteresis_enabled), 41 | FPM_BIND(gpio_set_irq_callback), 42 | FPM_BIND(gpio_set_irq_enabled), 43 | FPM_BIND(gpio_set_irq_enabled_with_callback), 44 | FPM_BIND(gpio_set_irqover), 45 | FPM_BIND(gpio_set_oeover), 46 | FPM_BIND(gpio_set_outover), 47 | FPM_BIND(gpio_set_pulls), 48 | FPM_BIND(gpio_set_slew_rate), 49 | 50 | {}, 51 | }; 52 | -------------------------------------------------------------------------------- /tests/loader2_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Test dynamic loader. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static std::stringstream puts_result; 12 | 13 | static void mock_puts(const char *message) 14 | { 15 | fputs(message, stdout); 16 | fflush(stdout); 17 | 18 | // Save output. 19 | puts_result << message; 20 | } 21 | 22 | static void mock_wputs(const uint16_t *message) 23 | { 24 | for (;;) { 25 | unsigned ch = *message++; 26 | if (!ch) 27 | break; 28 | putchar(ch); 29 | puts_result << (char) ch; 30 | } 31 | fflush(stdout); 32 | } 33 | 34 | static void mock_print_version() 35 | { 36 | mock_puts("Loader Test\r\n"); 37 | } 38 | 39 | TEST(loader, print_version_puts_wputs) 40 | { 41 | fpm_context_t ctx{}; 42 | ASSERT_TRUE(fpm_load(&ctx, "testputs.exe")); 43 | 44 | // Export dynamically linked routines. 45 | static fpm_binding_t linkmap[] = { 46 | { "", NULL }, 47 | { "fpm_puts", (void*) mock_puts }, 48 | { "fpm_wputs", (void*) mock_wputs }, 49 | { "fpm_print_version", (void*) mock_print_version }, 50 | {}, 51 | }; 52 | char filename[] = { "hello" }; 53 | char *argv[] = { filename }; 54 | 55 | bool exec_status = fpm_invoke(&ctx, linkmap, 1, argv); 56 | 57 | #if __APPLE__ && __x86_64__ 58 | // Cannot set %gs register on MacOS. 59 | ASSERT_FALSE(exec_status); 60 | #else 61 | ASSERT_TRUE(exec_status); 62 | ASSERT_EQ(ctx.exit_code, 0); 63 | 64 | // Check output. 65 | ASSERT_EQ(puts_result.str(), 66 | "Loader Test\r\n" 67 | "puts\r\n" 68 | "wputs\r\n" 69 | ); 70 | #endif 71 | 72 | fpm_unload(&ctx); 73 | } 74 | -------------------------------------------------------------------------------- /include/fpm/internal.h: -------------------------------------------------------------------------------- 1 | #ifndef FPM_INTERNAL_H 2 | #define FPM_INTERNAL_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #define FPM_VERSION "0.2" 11 | 12 | // 13 | // Resume on ^C. 14 | // 15 | extern jmp_buf fpm_saved_point; 16 | 17 | // 18 | // Context of the current program being running. 19 | // 20 | struct _fpm_context_t; 21 | typedef struct _fpm_context_t fpm_context_t; 22 | extern volatile fpm_context_t *fpm_context; 23 | 24 | // 25 | // Initialize region of memory for dynamic allocation. 26 | // 27 | void fpm_heap_init(fpm_context_t *ctx, size_t start, size_t nbytes); 28 | void fpm_heap_print_free_list(void); 29 | 30 | // 31 | // Push/pop program context. 32 | // 33 | bool fpm_context_push(fpm_context_t *ctx); 34 | void fpm_context_pop(void); 35 | 36 | // 37 | // Shell commands. 38 | // 39 | void fpm_cmd_cat(int argc, char *argv[]); 40 | void fpm_cmd_cd(int argc, char *argv[]); 41 | void fpm_cmd_clear(int argc, char *argv[]); 42 | void fpm_cmd_copy(int argc, char *argv[]); 43 | void fpm_cmd_date(int argc, char *argv[]); 44 | void fpm_cmd_dir(int argc, char *argv[]); 45 | void fpm_cmd_echo(int argc, char *argv[]); 46 | void fpm_cmd_eject(int argc, char *argv[]); 47 | void fpm_cmd_format(int argc, char *argv[]); 48 | void fpm_cmd_help(int argc, char *argv[]); 49 | void fpm_cmd_mkdir(int argc, char *argv[]); 50 | void fpm_cmd_mount(int argc, char *argv[]); 51 | void fpm_cmd_pwd(int argc, char *argv[]); 52 | void fpm_cmd_reboot(int argc, char *argv[]); 53 | void fpm_cmd_remove(int argc, char *argv[]); 54 | void fpm_cmd_rename(int argc, char *argv[]); 55 | void fpm_cmd_rmdir(int argc, char *argv[]); 56 | void fpm_cmd_time(int argc, char *argv[]); 57 | void fpm_cmd_ver(int argc, char *argv[]); 58 | void fpm_cmd_vol(int argc, char *argv[]); 59 | 60 | #ifdef __cplusplus 61 | } 62 | #endif 63 | 64 | #endif // FPM_INTERNAL_H 65 | -------------------------------------------------------------------------------- /tests/loader_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Test dynamic loader. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | TEST(loader, elf_binary) 12 | { 13 | fpm_context_t ctx{}; 14 | 15 | // Map ELF file into memory. 16 | bool load_status = fpm_load(&ctx, "hello.exe"); 17 | ASSERT_TRUE(load_status); 18 | 19 | fpm_unload(&ctx); 20 | } 21 | 22 | TEST(loader, linked_symbols) 23 | { 24 | fpm_context_t ctx{}; 25 | 26 | const int expect_num_links = 1; 27 | ASSERT_TRUE(fpm_load(&ctx, "hello.exe")); 28 | ASSERT_EQ(ctx.num_links, expect_num_links); 29 | 30 | // Get names of dynamically linked routines. 31 | const char *symbols[expect_num_links]{}; 32 | fpm_get_symbols(&ctx, symbols); 33 | ASSERT_STREQ(symbols[0], "fpm_puts"); 34 | 35 | fpm_unload(&ctx); 36 | } 37 | 38 | static std::stringstream puts_result; 39 | 40 | static void mock_puts(const char *message) 41 | { 42 | fputs(message, stdout); 43 | fflush(stdout); 44 | 45 | // Save output. 46 | puts_result << message; 47 | } 48 | 49 | TEST(loader, run_puts) 50 | { 51 | fpm_context_t ctx{}; 52 | ASSERT_TRUE(fpm_load(&ctx, "hello.exe")); 53 | 54 | // Export dynamically linked routines. 55 | static fpm_binding_t linkmap[] = { 56 | { "", NULL }, 57 | { "fpm_puts", (void*) mock_puts }, 58 | {}, 59 | }; 60 | char filename[] = { "hello" }; 61 | char *argv[] = { filename }; 62 | 63 | bool exec_status = fpm_invoke(&ctx, linkmap, 1, argv); 64 | 65 | #if __APPLE__ && __x86_64__ 66 | // Cannot set %gs register on MacOS. 67 | ASSERT_FALSE(exec_status); 68 | #else 69 | ASSERT_TRUE(exec_status); 70 | ASSERT_EQ(ctx.exit_code, 0); 71 | 72 | // Check output. 73 | ASSERT_EQ(puts_result.str(), "Hello, World!\r\n"); 74 | #endif 75 | 76 | fpm_unload(&ctx); 77 | } 78 | -------------------------------------------------------------------------------- /fatfs/LICENSE.txt: -------------------------------------------------------------------------------- 1 | FatFs License 2 | 3 | FatFs has being developped as a personal project of the author, ChaN. It is free from the code anyone else wrote at current release. Following code block shows a copy of the FatFs license document that heading the source files. 4 | 5 | /*----------------------------------------------------------------------------/ 6 | / FatFs - Generic FAT Filesystem Module Rx.xx / 7 | /-----------------------------------------------------------------------------/ 8 | / 9 | / Copyright (C) 20xx, ChaN, all right reserved. 10 | / 11 | / FatFs module is an open source software. Redistribution and use of FatFs in 12 | / source and binary forms, with or without modification, are permitted provided 13 | / that the following condition is met: 14 | / 15 | / 1. Redistributions of source code must retain the above copyright notice, 16 | / this condition and the following disclaimer. 17 | / 18 | / This software is provided by the copyright holder and contributors "AS IS" 19 | / and any warranties related to this software are DISCLAIMED. 20 | / The copyright owner or contributors be NOT LIABLE for any damages caused 21 | / by use of this software. 22 | /----------------------------------------------------------------------------*/ 23 | 24 | Therefore FatFs license is one of the BSD-style licenses, but there is a significant feature. FatFs is mainly intended for embedded systems. In order to extend the usability for commercial products, the redistributions of FatFs in binary form, such as embedded code, binary library and any forms without source code, do not need to include about FatFs in the documentations. This is equivalent to the 1-clause BSD license. Of course FatFs is compatible with the most of open source software licenses include GNU GPL. When you redistribute the FatFs source code with changes or create a fork, the license can also be changed to GNU GPL, BSD-style license or any open source software license that not conflict with FatFs license. 25 | -------------------------------------------------------------------------------- /kernel/fpm_getkey.c: -------------------------------------------------------------------------------- 1 | // 2 | // Get a Unicode character from console. 3 | // Decode escape sequences. 4 | // 5 | #include 6 | 7 | uint16_t fpm_getkey() 8 | { 9 | for (;;) { 10 | uint16_t key = fpm_getwch(); 11 | if (key != '\33') { 12 | return key; 13 | } 14 | 15 | // Decode escape sequence. 16 | key = fpm_getwch(); 17 | if (key != '[' && key != 'O') { 18 | // Unknown prefix - ignore. 19 | continue; 20 | } 21 | 22 | key = fpm_getwch(); 23 | switch (key) { 24 | case 'A': // Esc-[-A Esc-O-A 25 | return FPM_UPWARDS_ARROW; 26 | 27 | case 'B': // Esc-[-B Esc-O-B 28 | return FPM_DOWNWARDS_ARROW; 29 | 30 | case 'D': // Esc-[-D Esc-O-D 31 | return FPM_LEFTWARDS_ARROW; 32 | 33 | case 'C': // Esc-[-C Esc-O-C 34 | return FPM_RIGHTWARDS_ARROW; 35 | 36 | case 'H': // Esc-[-H Esc-O-H 37 | return FPM_LEFTWARDS_TO_BAR; 38 | 39 | case 'F': // Esc-[-F Esc-O-F 40 | return FPM_RIGHTWARDS_TO_BAR; 41 | 42 | case '0': case '1': case '2': case '3': case '4': 43 | case '5': case '6': case '7': case '8': case '9': { // Esc-N-~ 44 | // Read the number. 45 | unsigned num = 0; 46 | do { 47 | num = num*10 + (key - '0'); 48 | key = fpm_getwch(); 49 | } while (key >= '0' && key <= '9'); 50 | 51 | if (key != '~') { 52 | // Unknown terminator - ignore. 53 | continue; 54 | } 55 | switch (num) { 56 | case 3: // Esc-[-3-~ - Delete on Linux 57 | return FPM_DELETE_KEY; 58 | default: // Unknown keycode - ignore. 59 | continue; 60 | } 61 | } 62 | default: 63 | // Unknown keycode - ignore. 64 | continue; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/Build-From-Sources.md: -------------------------------------------------------------------------------- 1 | # Build FP/M from sources 2 | 3 | ### Install cross C/C++ compiler for ARM-32 target 4 | 5 | On Ubuntu: 6 | 7 | sudo apt install gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential flex cmake texinfo 8 | 9 | On MacOS: 10 | 11 | * Download and install package [arm-gnu-toolchain-14.2.rel1-darwin-x86_64-arm-none-eabi.pkg](https://developer.arm.com/-/media/Files/downloads/gnu/14.2.rel1/binrel/arm-gnu-toolchain-14.2.rel1-darwin-x86_64-arm-none-eabi.pkg). 12 | * Add directory `/Applications/ArmGNUToolchain/14.2.rel1/arm-none-eabi/bin` to your $PATH. 13 | 14 | ### Install Pico SDK 15 | 16 | git clone https://github.com/raspberrypi/pico-sdk 17 | git clone https://github.com/raspberrypi/pico-examples 18 | cd pico-sdk 19 | git submodule update --init 20 | 21 | Add two lines to your ~/.bashrc script: 22 | 23 | export PICO_SDK_PATH=$HOME/pico-sdk 24 | export PICO_EXAMPLES_PATH=$HOME/pico-examples 25 | 26 | ### Build and install FP/M linker 27 | 28 | git clone https://github.com/fp-m/fpm-linker.git 29 | cd fpm-linker 30 | make 31 | make install 32 | 33 | The linker will be installed as `~/.local/lib/fpm/arm/ld` in your home directory. 34 | 35 | ### Build FP/M from sources 36 | 37 | git clone https://github.com/fp-m/fpm-embedded.git 38 | cd fpm-embedded 39 | make 40 | 41 | Resulting firmware images for RP2040 are located in pico/build directory: 42 | 43 | * fpm-rp2040-2mb.uf2 - for devices with 2-Mbyte Flash chip 44 | * fpm-rp2040-8mb.uf2 - for devices with 8-Mbyte Flash chip 45 | * fpm-rp2040-16mb.uf2 - for devices with 16-Mbyte Flash chip 46 | 47 | ### Unix demo 48 | 49 | To explore the FP/M command-line interface without the need for actual hardware, 50 | you can find the fpm-demo binary within the unix/build directory. 51 | This provides a simulated environment for interacting with FP/M commands. 52 | 53 | ``` 54 | $ cd unix/build 55 | $ ./fpm-demo 56 | Start FP/M on Unix 57 | Use '?' for help or 'exit' to quit. 58 | 59 | flash:/ > ver 60 | 61 | FP/M version 0.1.213 62 | Git commit 5a64844, built on Jan 20 2025 at 01:44:42 63 | Unix Darwin x86_64 version 24.2.0 64 | Free memory 1024 kbytes 65 | 66 | flash:/ > exit 67 | ``` 68 | -------------------------------------------------------------------------------- /fatfs/examples/test_raw_speed.c: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------*/ 2 | /* Raw Read/Write Throughput Checker */ 3 | /*---------------------------------------------------------------------*/ 4 | 5 | #include 6 | #include 7 | 8 | #include "diskio.h" 9 | #include "fatfs.h" 10 | 11 | int test_raw_speed(uint8_t pdrv, /* Physical drive number */ 12 | uint32_t lba, /* Start LBA for read/write test */ 13 | uint32_t len, /* Number of bytes to read/write (must be multiple of sz_buff) */ 14 | void *buff, /* Read/write buffer */ 15 | unsigned sz_buff) /* Size of read/write buffer (must be multiple of FF_MAX_SS) */ 16 | { 17 | uint16_t ss; 18 | uint32_t ofs, tmr; 19 | 20 | #if FF_MIN_SS != FF_MAX_SS 21 | if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != DISK_OK) { 22 | printf("\ndisk_ioctl() failed.\n"); 23 | return 0; 24 | } 25 | #else 26 | ss = FF_MAX_SS; 27 | #endif 28 | 29 | printf("Starting raw write test at sector %lu in %u bytes of data chunks...", lba, sz_buff); 30 | tmr = systimer(); 31 | for (ofs = 0; ofs < len / ss; ofs += sz_buff / ss) { 32 | if (disk_write(pdrv, buff, lba + ofs, sz_buff / ss) != DISK_OK) { 33 | printf("\ndisk_write() failed.\n"); 34 | return 0; 35 | } 36 | } 37 | if (disk_ioctl(pdrv, CTRL_SYNC, 0) != DISK_OK) { 38 | printf("\ndisk_ioctl() failed.\n"); 39 | return 0; 40 | } 41 | tmr = systimer() - tmr; 42 | printf("\n%lu bytes written and it took %lu timer ticks.\n", len, tmr); 43 | 44 | printf("Starting raw read test at sector %lu in %u bytes of data chunks...", lba, sz_buff); 45 | tmr = systimer(); 46 | for (ofs = 0; ofs < len / ss; ofs += sz_buff / ss) { 47 | if (disk_read(pdrv, buff, lba + ofs, sz_buff / ss) != DISK_OK) { 48 | printf("\ndisk_read() failed.\n"); 49 | return 0; 50 | } 51 | } 52 | tmr = systimer() - tmr; 53 | printf("\n%lu bytes read and it took %lu timer ticks.\n", len, tmr); 54 | 55 | printf("Test completed.\n"); 56 | return 1; 57 | } 58 | -------------------------------------------------------------------------------- /pico/sd_pico/spi.h: -------------------------------------------------------------------------------- 1 | /* spi.h 2 | Copyright 2021 Carl John Kugler III 3 | 4 | Licensed under the Apache License, Version 2.0 (the License); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | // Pico includes 21 | #include "hardware/dma.h" 22 | #include "hardware/irq.h" 23 | #include "hardware/spi.h" 24 | #include "pico/mutex.h" 25 | #include "pico/sem.h" 26 | #include "pico/types.h" 27 | 28 | #define SPI_FILL_CHAR (0xFF) 29 | 30 | // "Class" representing SPIs 31 | typedef struct _spi_t { 32 | // SPI HW 33 | spi_inst_t *hw_inst; 34 | uint miso_gpio; // SPI MISO GPIO number (not pin number) 35 | uint mosi_gpio; 36 | uint sck_gpio; 37 | uint baud_rate; 38 | 39 | // State variables: 40 | uint tx_dma; 41 | uint rx_dma; 42 | dma_channel_config tx_dma_cfg; 43 | dma_channel_config rx_dma_cfg; 44 | irq_handler_t dma_isr; 45 | bool initialized; 46 | semaphore_t sem; 47 | mutex_t mutex; 48 | } spi_t; 49 | 50 | #ifdef __cplusplus 51 | extern "C" { 52 | #endif 53 | 54 | // SPI DMA interrupts 55 | void __not_in_flash_func(spi_irq_handler)(spi_t *pSPI); 56 | 57 | bool __not_in_flash_func(spi_transfer)(spi_t *pSPI, const uint8_t *tx, uint8_t *rx, size_t length); 58 | void spi_lock(spi_t *pSPI); 59 | void spi_unlock(spi_t *pSPI); 60 | void spi_init_port(spi_t *pSPI); 61 | void spi_deinit_port(spi_t *pSPI); 62 | void set_spi_dma_irq_channel(bool useChannel1, bool shared); 63 | 64 | #if 1 65 | // No debug output 66 | #define DBG_PRINTF(fmt, args...) 67 | #else 68 | // Enable debug output 69 | #define DBG_PRINTF fpm_printf 70 | #endif 71 | 72 | //#define myASSERT(__e) ((__e) ? (void)0 : my_assert_func(__FILE__, __LINE__, __func__, #__e)) 73 | #define myASSERT(__e) 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | // Use LED at pin 25 to show SPI activity. 80 | //#define SPI_LED_PIN 25 81 | 82 | /* [] END OF FILE */ 83 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_date.c: -------------------------------------------------------------------------------- 1 | // 2 | // Show or change the system date 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | static void set_date(const char *str) 9 | { 10 | int year, month, day, dotw, hour, min, sec; 11 | fpm_get_datetime(&year, &month, &day, &dotw, &hour, &min, &sec); 12 | 13 | if (fpm_sscanf(str, "%d/%d/%d", &month, &day, &year) != 3 || 14 | month < 1 || month > 12 || 15 | day < 1 || day > 31 || 16 | year < 2000 || year > 9999) { 17 | fpm_puts("The specified date is not correct.\r\n" 18 | "\n"); 19 | return; 20 | } 21 | 22 | // Set date. 23 | fpm_set_datetime(year, month, day, hour, min, sec); 24 | fpm_puts("\r\n"); 25 | } 26 | 27 | void fpm_cmd_date(int argc, char *argv[]) 28 | { 29 | static const struct fpm_option long_opts[] = { 30 | { "help", FPM_NO_ARG, NULL, 'h' }, 31 | {}, 32 | }; 33 | struct fpm_opt opt = {}; 34 | bool terse = false; 35 | 36 | while (fpm_getopt(argc, argv, "ht", long_opts, &opt) >= 0) { 37 | switch (opt.ret) { 38 | case 1: 39 | set_date(opt.arg); 40 | return; 41 | 42 | case '?': 43 | // Unknown option: message already printed. 44 | fpm_puts("\r\n"); 45 | return; 46 | 47 | case 't': 48 | terse = true; 49 | continue; 50 | 51 | case 'h': 52 | fpm_puts("Usage:\r\n" 53 | " date [-t]\r\n" 54 | " date MM/DD/YYYY\r\n" 55 | "\n" 56 | " -t Only display date\r\n" 57 | " MM/DD/YYYY New date to set\r\n" 58 | "\n"); 59 | return; 60 | } 61 | } 62 | 63 | // Display current date. 64 | static const char *dotw_name[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; 65 | int year, month, day, dotw, hour, min, sec; 66 | fpm_get_datetime(&year, &month, &day, &dotw, &hour, &min, &sec); 67 | 68 | if (terse) { 69 | fpm_printf("%02d/%02d/%04d\r\n", month, day, year); 70 | } else { 71 | fpm_printf("Current Date: %s %d/%d/%04d\r\n", dotw_name[dotw], month, day, year); 72 | fpm_puts("Type 'date MM/DD/YYYY' to change.\r\n"); 73 | } 74 | fpm_puts("\r\n"); 75 | } 76 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_cat.c: -------------------------------------------------------------------------------- 1 | // 2 | // Display the contents of a text file 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // 11 | // Print string, inserting \r before newline when needed. 12 | // 13 | static void puts_with_cr(const char *str, bool *need_cr) 14 | { 15 | for (;;) { 16 | char ch = *str++; 17 | if (!ch) 18 | break; 19 | 20 | switch (ch) { 21 | case '\n': 22 | if (*need_cr) { 23 | fpm_putchar('\r'); 24 | *need_cr = false; 25 | } 26 | break; 27 | case '\r': 28 | *need_cr = false; 29 | break; 30 | default: 31 | *need_cr = true; 32 | break; 33 | } 34 | fpm_putchar(ch); 35 | } 36 | } 37 | 38 | static void display_file(const char *path) 39 | { 40 | // Open file. 41 | file_t *fp = alloca(f_sizeof_file_t()); 42 | fs_result_t result = f_open(fp, path, FA_READ); 43 | if (result != FR_OK) { 44 | fpm_printf("%s: %s\r\n", path, f_strerror(result)); 45 | return; 46 | } 47 | 48 | // Read data. 49 | char buf[1024]; 50 | bool need_cr = false; 51 | while (f_gets(buf, sizeof(buf), fp)) { 52 | puts_with_cr(buf, &need_cr); 53 | } 54 | if (need_cr) { 55 | fpm_puts("\r\n"); 56 | } 57 | 58 | // Close the file. 59 | f_close(fp); 60 | } 61 | 62 | void fpm_cmd_cat(int argc, char *argv[]) 63 | { 64 | static const struct fpm_option long_opts[] = { 65 | { "help", FPM_NO_ARG, NULL, 'h' }, 66 | {}, 67 | }; 68 | struct fpm_opt opt = {}; 69 | unsigned arg_count = 0; 70 | 71 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 72 | switch (opt.ret) { 73 | case 1: 74 | display_file(opt.arg); 75 | arg_count++; 76 | continue; 77 | 78 | case '?': 79 | // Unknown option: message already printed. 80 | fpm_puts("\r\n"); 81 | return; 82 | 83 | case 'h': 84 | usage: 85 | fpm_puts("Usage:\r\n" 86 | " cat filename ...\r\n" 87 | " type filename ...\r\n" 88 | "\n"); 89 | return; 90 | } 91 | } 92 | 93 | if (arg_count == 0) 94 | goto usage; 95 | 96 | fpm_puts("\r\n"); 97 | } 98 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_help.c: -------------------------------------------------------------------------------- 1 | // 2 | // Show all built-in commands. 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | void fpm_cmd_help(int argc, char *argv[]) 9 | { 10 | static const struct fpm_option long_opts[] = { 11 | { "help", FPM_NO_ARG, NULL, 'h' }, 12 | {}, 13 | }; 14 | struct fpm_opt opt = {}; 15 | 16 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 17 | switch (opt.ret) { 18 | case 1: 19 | fpm_printf("%s: Unexpected argument `%s`\r\n\n", argv[0], opt.arg); 20 | return; 21 | case '?': 22 | // Unknown option: message already printed. 23 | fpm_puts("\r\n"); 24 | return; 25 | case 'h': 26 | fpm_puts("Usage: help\r\n\n"); 27 | return; 28 | } 29 | } 30 | 31 | fpm_puts("FP/M built-in commands are:\r\n"); 32 | fpm_puts("cat or type Display the contents of a text file\r\n"); 33 | fpm_puts("cd Show or change current directory\r\n"); 34 | fpm_puts("clear or cls Clear the console screen\r\n"); 35 | fpm_puts("cp or copy Copy files or directories\r\n"); 36 | fpm_puts("date Show or change the system date\r\n"); 37 | fpm_puts("echo Copy text directly to the console output\r\n"); 38 | fpm_puts("eject Release removable disk device\r\n"); 39 | fpm_puts("format Create filesystem on a disk device\r\n"); 40 | fpm_puts("help or ? Show all built-in commands\r\n"); 41 | fpm_puts("ls or dir List the contents of a directory\r\n"); 42 | fpm_puts("mkdir Create a directory\r\n"); 43 | fpm_puts("mount Engage removable disk device\r\n"); 44 | fpm_puts("mv or rename Rename or move files and directories\r\n"); 45 | fpm_puts("reboot Restart the FP/M kernel\r\n"); 46 | fpm_puts("rm or erase Delete a file or set of files\r\n"); 47 | fpm_puts("rmdir Remove a directory\r\n"); 48 | fpm_puts("time Set or show the current system time\r\n"); 49 | fpm_puts("ver Show the version of FP/M software\r\n"); 50 | fpm_puts("vol Show the volume label of a disk device\r\n"); 51 | fpm_puts("exit Close down the command interpreter\r\n"); 52 | fpm_puts("\r\n"); 53 | fpm_puts("Enter 'command -h' for more information on any of the above commands.\r\n"); 54 | fpm_puts("\r\n"); 55 | } 56 | -------------------------------------------------------------------------------- /tools/elfexe/README.md: -------------------------------------------------------------------------------- 1 | Utility elfexe takes an ELF binary compiled for FP/M and modifies the code 2 | in .plt section for proper linking by dynamic loader at run time. 3 | 4 | # x86_64 5 | Example of .plt section on x86_64 (or amd64) architecture: 6 | ``` 7 | Disassembly of section .plt: 8 | 9 | 0000000000001000 <.plt>: 10 | 1000: ff 35 ea 2f 00 00 push 0x2fea(%rip) # 3ff0 <_GLOBAL_OFFSET_TABLE_+0x8> 11 | 1006: ff 25 ec 2f 00 00 jmp *0x2fec(%rip) # 3ff8 <_GLOBAL_OFFSET_TABLE_+0x10> 12 | 100c: 0f 1f 40 00 nopl 0x0(%rax) 13 | 1010: f3 0f 1e fa endbr64 14 | 1014: 68 00 00 00 00 push $0x0 15 | 1019: e9 e2 ff ff ff jmp 1000 16 | 101e: 66 90 xchg %ax,%ax 17 | 18 | Disassembly of section .plt.sec: 19 | 20 | 0000000000001020 : 21 | 1020: f3 0f 1e fa endbr64 22 | 1024: ff 25 d6 2f 00 00 jmp *0x2fd6(%rip) # 4000 23 | 102a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 24 | ``` 25 | 26 | # arm64 27 | Example of .plt section on arm64 architecture: 28 | ``` 29 | 0000000000000220 <.plt>: 30 | 220: a9bf7bf0 stp x16, x30, [sp, #-16]! 31 | 224: f00000f0 adrp x16, 1f000 32 | 228: f947fe11 ldr x17, [x16, #4088] 33 | 22c: 913fe210 add x16, x16, #0xff8 34 | 230: d61f0220 br x17 35 | 234: d503201f nop 36 | 238: d503201f nop 37 | 23c: d503201f nop 38 | 39 | 0000000000000240 : 40 | 240: 90000110 adrp x16, 20000 41 | 244: f9400211 ldr x17, [x16] 42 | 248: 91000210 add x16, x16, #0x0 43 | 24c: d61f0220 br x17 44 | ``` 45 | 46 | # arm32 47 | Example of .plt section on arm32 architecture: 48 | ``` 49 | Disassembly of section .plt: 50 | 51 | 00000160 <.plt>: 52 | 160: e52de004 push {lr} @ (str lr, [sp, #-4]!) 53 | 164: e59fe004 ldr lr, [pc, #4] @ 170 <.plt+0x10> 54 | 168: e08fe00e add lr, pc, lr 55 | 16c: e5bef008 ldr pc, [lr, #8]! 56 | 170: 00001e90 .word 0x00001e90 57 | 58 | 00000174 : 59 | 174: e28fc600 add ip, pc, #0, 12 60 | 178: e28cca01 add ip, ip, #4096 @ 0x1000 61 | 17c: e5bcfe90 ldr pc, [ip, #3728]! @ 0xe90 62 | ``` 63 | -------------------------------------------------------------------------------- /tools/uf2fat/uf2.h: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Microsoft UF2 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) Microsoft Corporation 8 | 9 | All rights reserved. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | #ifndef UF2FORMAT_H 31 | #define UF2FORMAT_H 1 32 | 33 | #include 34 | #include 35 | 36 | // All entries are little endian. 37 | 38 | #define UF2_MAGIC_START0 0x0A324655UL // "UF2\n" 39 | #define UF2_MAGIC_START1 0x9E5D5157UL // Randomly selected 40 | #define UF2_MAGIC_END 0x0AB16F30UL // Ditto 41 | 42 | #define UF2_FLAG_NOFLASH 0x00000001 // If set, the block is "comment" 43 | // and should not be flashed to the device 44 | #define UF2_FLAG_FILECONTAINER 0x00001000 // file container 45 | #define UF2_FLAG_FAMILY_ID 0x00002000 // familyID present 46 | #define UF2_FLAG_MD5_CHKSUM 0x00004000 // MD5 checksum present 47 | #define UF2_FLAG_EXTENSION_TAGS 0x00008000 // extension tags present 48 | 49 | typedef struct { 50 | // 32 byte header 51 | uint32_t magicStart0; 52 | uint32_t magicStart1; 53 | uint32_t flags; 54 | uint32_t targetAddr; 55 | uint32_t payloadSize; 56 | uint32_t blockNo; 57 | uint32_t numBlocks; 58 | uint32_t reserved; 59 | 60 | // raw data; 61 | uint8_t data[476]; 62 | 63 | // store magic also at the end to limit damage from partial block reads 64 | uint32_t magicEnd; 65 | } UF2_Block; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_time.c: -------------------------------------------------------------------------------- 1 | // 2 | // Set or show the current system time 3 | // 4 | #include 5 | #include 6 | #include 7 | 8 | static void set_time(const char *str) 9 | { 10 | int year, month, day, dotw, hour, min, sec; 11 | fpm_get_datetime(&year, &month, &day, &dotw, &hour, &min, &sec); 12 | 13 | if (fpm_sscanf(str, "%d:%d:%d", &hour, &min, &sec) != 3 || 14 | hour < 0 || hour > 23 || 15 | min < 0 || min > 59 || 16 | sec < 0 || sec > 59) { 17 | fpm_puts("The specified time is not correct.\r\n" 18 | "\n"); 19 | return; 20 | } 21 | 22 | // Set time. 23 | fpm_set_datetime(year, month, day, hour, min, sec); 24 | fpm_puts("\r\n"); 25 | } 26 | 27 | void fpm_cmd_time(int argc, char *argv[]) 28 | { 29 | static const struct fpm_option long_opts[] = { 30 | { "help", FPM_NO_ARG, NULL, 'h' }, 31 | {}, 32 | }; 33 | struct fpm_opt opt = {}; 34 | bool terse = false; 35 | 36 | while (fpm_getopt(argc, argv, "ht", long_opts, &opt) >= 0) { 37 | switch (opt.ret) { 38 | case 1: 39 | set_time(opt.arg); 40 | return; 41 | 42 | case '?': 43 | // Unknown option: message already printed. 44 | fpm_puts("\r\n"); 45 | return; 46 | 47 | case 't': 48 | terse = true; 49 | continue; 50 | 51 | case 'h': 52 | fpm_puts("Usage:\r\n" 53 | " time [-t]\r\n" 54 | " time hh:mm:ss\r\n" 55 | "\n" 56 | " -t Display simple time\r\n" 57 | " hh:mm:ss New time to set\r\n" 58 | "\n"); 59 | return; 60 | } 61 | } 62 | 63 | // Display current time. 64 | int year, month, day, dotw, hour, min, sec; 65 | fpm_get_datetime(&year, &month, &day, &dotw, &hour, &min, &sec); 66 | 67 | if (terse) { 68 | fpm_printf("%02d:%02d:%02d\r\n", hour, min, sec); 69 | } else { 70 | // Convert 24-hour clock time to 12-hour clock. 71 | char am_pm = (hour <= 11) ? 'A' : 'P'; 72 | if (hour == 0) { 73 | hour += 12; 74 | } else if (hour > 12) { 75 | hour -= 12; 76 | } 77 | fpm_printf("Current Time: %02d:%02d:%02d %cM\r\n", hour, min, sec, am_pm); 78 | fpm_puts("Type 'time hh:mm:ss' to change.\r\n"); 79 | } 80 | fpm_puts("\r\n"); 81 | } 82 | -------------------------------------------------------------------------------- /kernel/fpm_tokenize.c: -------------------------------------------------------------------------------- 1 | // 2 | // Parse a command line and split it into tokens (in place). 3 | // Return NULL on success. 4 | // Fill an argument vector. 5 | // On error, return a message. 6 | // 7 | #include 8 | 9 | const char *fpm_tokenize(char *argv[], int *argc, char *cmd_line) 10 | { 11 | const char *src = cmd_line; // Copy from here... 12 | char *dest = cmd_line; // ...to there 13 | bool seen_space = true; 14 | bool seen_apostrophe = false; 15 | bool seen_quote = false; 16 | 17 | // Scan the line symbol by symbol. 18 | // Look for delimiters: space, backslash, single quote, double quote. 19 | *argc = 0; 20 | for (;; src++) { 21 | char ch = *src; 22 | 23 | switch (ch) { 24 | case '\0': 25 | // End of line. 26 | if (seen_apostrophe) { 27 | return "Unterminated apostrophe"; 28 | } 29 | if (seen_quote) { 30 | return "Unterminated quote"; 31 | } 32 | if (! seen_space) { 33 | *dest++ = '\0'; 34 | } 35 | // Append extra NUL. 36 | *dest = '\0'; 37 | argv[*argc] = 0; 38 | return 0; 39 | 40 | case ' ': 41 | // Space. 42 | if (seen_apostrophe || seen_quote) { 43 | goto consume; 44 | } 45 | if (! seen_space) { 46 | *dest++ = '\0'; 47 | seen_space = true; 48 | } 49 | break; 50 | 51 | case '\\': 52 | // Backslash - quote next char. 53 | ch = *++src; 54 | if (ch == '\0') { 55 | return "Incomplete backslash"; 56 | } 57 | goto consume; 58 | 59 | case '\'': 60 | // Apostrophe, or single quote. 61 | if (seen_quote) { 62 | goto consume; 63 | } 64 | seen_apostrophe = !seen_apostrophe; 65 | break; 66 | 67 | case '"': 68 | // Double quote. 69 | if (seen_apostrophe) { 70 | goto consume; 71 | } 72 | seen_quote = !seen_quote; 73 | break; 74 | 75 | default: 76 | consume: // Ordinary symbol. 77 | if (seen_space) { 78 | // Next argument. 79 | argv[(*argc)++] = dest; 80 | seen_space = false; 81 | } 82 | *dest++ = ch; 83 | break; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tools/uf2fat/dump.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "extern.h" 5 | #include "uf2.h" 6 | 7 | // 8 | // Show block contents. 9 | // 10 | static void print_block(const UF2_Block &block, const size_t offset) 11 | { 12 | // 32 byte header 13 | if (block.magicStart0 != UF2_MAGIC_START0) { 14 | std::cerr << "Bad magicStart0=" << std::hex << block.magicStart0 15 | << std::dec << " at offset " << offset << "\n"; 16 | exit(EXIT_FAILURE); 17 | } 18 | if (block.magicStart1 != UF2_MAGIC_START1) { 19 | std::cerr << "Bad magicStart1=" << std::hex << block.magicStart1 20 | << std::dec << " at offset " << offset << "\n"; 21 | exit(EXIT_FAILURE); 22 | } 23 | if (block.magicEnd != UF2_MAGIC_END) { 24 | std::cerr << "Bad magicEnd=" << std::hex << block.magicEnd 25 | << std::dec << " at offset " << offset << "\n"; 26 | exit(EXIT_FAILURE); 27 | } 28 | 29 | std::cout << std::hex << std::setw(5) << block.flags 30 | << " " << std::setw(8) << block.targetAddr 31 | << " " << std::dec << std::setw(4) << block.payloadSize 32 | << " " << std::setw(6) << block.blockNo 33 | << " " << std::setw(6) << block.numBlocks 34 | << " " << std::hex << std::setw(8) << block.reserved; 35 | 36 | std::cout << std::hex << " " << (unsigned)(uint8_t)block.data[0] 37 | << "-" << (unsigned)(uint8_t)block.data[1] 38 | << "-" << (unsigned)(uint8_t)block.data[2] 39 | << "-...-" << (unsigned)(uint8_t)block.data[474] 40 | << "-" << (unsigned)(uint8_t)block.data[475] 41 | << "\n"; 42 | } 43 | 44 | // 45 | // Show contents of UF2 file. 46 | // 47 | void dump_file(const std::string &input_filename) 48 | { 49 | std::ifstream file(input_filename, std::ios::binary); 50 | if (!file.is_open()) { 51 | std::cerr << input_filename << ": Cannot not open file\n"; 52 | exit(EXIT_FAILURE); 53 | } 54 | 55 | UF2_Block block{}; 56 | auto const block_size = sizeof(block); 57 | if (block_size != 512) { 58 | std::cerr << "Bad UF2 block size: " << block_size << "\n"; 59 | exit(EXIT_FAILURE); 60 | } 61 | 62 | std::cout << "Flags TargetAddr PayloadSize BlockNo NumBlocks FamilyID Data\n"; 63 | size_t offset{}; 64 | while (file.read((char*)&block, block_size)) { 65 | print_block(block, offset); 66 | offset += block_size; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Set CC and link options for *.exe target. 3 | # 4 | function(fpm_target_options arg) 5 | target_include_directories(${arg} BEFORE PUBLIC 6 | ../../include 7 | $ENV{PICO_SDK_PATH}/src/common/pico_base_headers/include 8 | $ENV{PICO_SDK_PATH}/src/common/pico_stdlib_headers/include 9 | $ENV{PICO_SDK_PATH}/src/common/pico_time/include 10 | $ENV{PICO_SDK_PATH}/src/rp2_common/pico_platform_compiler/include 11 | $ENV{PICO_SDK_PATH}/src/rp2_common/pico_platform_sections/include 12 | $ENV{PICO_SDK_PATH}/src/rp2_common/pico_platform_panic/include 13 | $ENV{PICO_SDK_PATH}/src/rp2_common/pico_stdio/include 14 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_base/include 15 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_timer/include 16 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_gpio/include 17 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_irq/include 18 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_uart/include 19 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_resets/include 20 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_sync/include 21 | $ENV{PICO_SDK_PATH}/src/rp2_common/hardware_sync_spin_lock/include 22 | $ENV{PICO_SDK_PATH}/src/rp2040/hardware_structs/include 23 | $ENV{PICO_SDK_PATH}/src/rp2040/pico_platform/include 24 | $ENV{PICO_SDK_PATH}/src/rp2040/hardware_regs/include 25 | ${CMAKE_BINARY_DIR}/generated/pico_base 26 | ) 27 | set_target_properties(${arg} PROPERTIES SUFFIX ".exe") 28 | target_compile_options(${arg} PRIVATE -fPIC -fno-asynchronous-unwind-tables -DPICO_RP2040=1) 29 | target_link_options(${arg} PRIVATE -shared -fPIC -Wl,--version-script=${CMAKE_SOURCE_DIR}/../apps/export.sym -nostartfiles -e main -B$ENV{HOME}/.local/lib/fpm/arm) 30 | 31 | # 32 | # Disassemble 33 | # 34 | add_custom_command(OUTPUT ${arg}.dis DEPENDS ${arg} 35 | COMMAND arm-none-eabi-objdump -d ${arg}.exe > ${arg}.dis 36 | ) 37 | add_custom_command(OUTPUT ${arg}.nm DEPENDS ${arg} 38 | COMMAND arm-none-eabi-nm -n ${arg}.exe > ${arg}.nm 39 | ) 40 | add_custom_command(OUTPUT ${arg}.readelf DEPENDS ${arg} 41 | COMMAND arm-none-eabi-readelf -a ${arg}.exe > ${arg}.readelf 42 | ) 43 | add_custom_target(${arg}-objdump ALL DEPENDS 44 | ${arg}.dis 45 | ${arg}.nm 46 | ${arg}.readelf 47 | ) 48 | endfunction() 49 | 50 | add_subdirectory(hello) 51 | add_subdirectory(cmd) 52 | add_subdirectory(free) 53 | add_subdirectory(printf) 54 | add_subdirectory(gpio) 55 | add_subdirectory(rz) 56 | -------------------------------------------------------------------------------- /tests/copy_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Test FatFS routines. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "util.h" 9 | 10 | TEST(cmd, copy_file) 11 | { 12 | disk_setup(); 13 | write_file("foo.txt", "'Twas brillig, and the slithy toves"); 14 | 15 | // Copy foo to bar. 16 | const char *argv[] = { "cp", "-v", "foo.txt", "bar.txt", nullptr }; 17 | fpm_cmd_copy(4, (char**) argv); 18 | 19 | read_file("foo.txt", "'Twas brillig, and the slithy toves"); 20 | read_file("bar.txt", "'Twas brillig, and the slithy toves"); 21 | } 22 | 23 | TEST(cmd, copy_recursive_existing_target) 24 | { 25 | disk_setup(); 26 | create_directory("a"); 27 | create_directory("a/b"); 28 | create_directory("x"); 29 | write_file("a/b/c", "foobar"); 30 | 31 | // Copy directory recursively to existing directory. 32 | const char *argv[] = { "cp", "-v", "-r", "a/b", "x", nullptr }; 33 | fpm_cmd_copy(5, (char**) argv); 34 | 35 | check_directory("x/b"); 36 | read_file("x/b/c", "foobar"); 37 | } 38 | 39 | TEST(cmd, copy_recursive_nonexisting_target) 40 | { 41 | disk_setup(); 42 | create_directory("a"); 43 | create_directory("a/b"); 44 | create_directory("x"); 45 | write_file("a/b/c", "foobar"); 46 | 47 | // Copy directory recursively to non-existing directory. 48 | const char *argv[] = { "cp", "-v", "-r", "a/b", "y", nullptr }; 49 | fpm_cmd_copy(5, (char**) argv); 50 | 51 | check_directory("y"); 52 | read_file("y/c", "foobar"); 53 | } 54 | 55 | TEST(cmd, copy_recursive_trailing_slash_existing_target) 56 | { 57 | disk_setup(); 58 | create_directory("a"); 59 | create_directory("a/b"); 60 | create_directory("x"); 61 | write_file("a/b/c", "foobar"); 62 | 63 | // Copy directory recursively to existing directory. 64 | // Note trailing slash in the source path. 65 | const char *argv[] = { "cp", "-v", "-r", "a/b/", "x", nullptr }; 66 | fpm_cmd_copy(5, (char**) argv); 67 | 68 | read_file("x/c", "foobar"); 69 | } 70 | 71 | TEST(cmd, copy_recursive_trailing_slash_nonexisting_target) 72 | { 73 | disk_setup(); 74 | create_directory("a"); 75 | create_directory("a/b"); 76 | create_directory("x"); 77 | write_file("a/b/c", "foobar"); 78 | 79 | // Copy directory recursively to non-existing directory. 80 | // Note trailing slash in the source path. 81 | const char *argv[] = { "cp", "-v", "-r", "a/b/", "y", nullptr }; 82 | fpm_cmd_copy(5, (char**) argv); 83 | 84 | check_directory("y"); 85 | read_file("y/c", "foobar"); 86 | } 87 | -------------------------------------------------------------------------------- /pico/flash_image.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Build contents of Flash filesystem 3 | # 4 | add_custom_command(OUTPUT flashfs 5 | COMMENT "Build flash contents" 6 | DEPENDS cmd.exe 7 | free.exe 8 | hello.exe 9 | printf.exe 10 | gpio.exe 11 | rz.exe 12 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 13 | COMMAND ${CMAKE_COMMAND} -E copy_directory 14 | ${CMAKE_CURRENT_SOURCE_DIR}/flashfs 15 | flashfs 16 | COMMAND ${CMAKE_COMMAND} -E copy 17 | ${CMAKE_BINARY_DIR}/apps/cmd/cmd.exe 18 | flashfs/bin/cmd.exe 19 | COMMAND ${CMAKE_COMMAND} -E copy 20 | ${CMAKE_BINARY_DIR}/apps/free/free.exe 21 | flashfs/bin/free.exe 22 | COMMAND ${CMAKE_COMMAND} -E copy 23 | ${CMAKE_BINARY_DIR}/apps/hello/hello.exe 24 | flashfs/bin/hello.exe 25 | COMMAND ${CMAKE_COMMAND} -E copy 26 | ${CMAKE_BINARY_DIR}/apps/printf/printf.exe 27 | flashfs/bin/printf.exe 28 | COMMAND ${CMAKE_COMMAND} -E copy 29 | ${CMAKE_BINARY_DIR}/apps/gpio/gpio.exe 30 | flashfs/bin/gpio.exe 31 | COMMAND ${CMAKE_COMMAND} -E copy 32 | ${CMAKE_BINARY_DIR}/apps/rz/rz.exe 33 | flashfs/bin/rz.exe 34 | COMMAND arm-none-eabi-strip flashfs/bin/*.exe 35 | ) 36 | 37 | set(UF2FAT ${CMAKE_SOURCE_DIR}/../unix/build/uf2fat/uf2fat) 38 | 39 | # 40 | # Create images for Flash sizes 2/8/16 Mbytes 41 | # 42 | add_custom_command(OUTPUT ${PROJECT_NAME}-2mb.uf2 43 | COMMENT "Build 2-Mb Flash image" 44 | DEPENDS ${PROJECT_NAME} flashfs 45 | COMMAND ${UF2FAT} format ${PROJECT_NAME}.uf2 2m flashfs -o ${PROJECT_NAME}-2mb.uf2 46 | ) 47 | add_custom_command(OUTPUT ${PROJECT_NAME}-4mb.uf2 48 | COMMENT "Build 4-Mb Flash image" 49 | DEPENDS ${PROJECT_NAME} flashfs 50 | COMMAND ${UF2FAT} format ${PROJECT_NAME}.uf2 4m flashfs -o ${PROJECT_NAME}-4mb.uf2 51 | ) 52 | add_custom_command(OUTPUT ${PROJECT_NAME}-8mb.uf2 53 | COMMENT "Build 8-Mb Flash image" 54 | DEPENDS ${PROJECT_NAME} flashfs 55 | COMMAND ${UF2FAT} format ${PROJECT_NAME}.uf2 8m flashfs -o ${PROJECT_NAME}-8mb.uf2 56 | ) 57 | add_custom_command(OUTPUT ${PROJECT_NAME}-16mb.uf2 58 | COMMENT "Build 16-Mb Flash image" 59 | DEPENDS ${PROJECT_NAME} flashfs 60 | COMMAND ${UF2FAT} format ${PROJECT_NAME}.uf2 16m flashfs -o ${PROJECT_NAME}-16mb.uf2 61 | ) 62 | add_custom_target(images ALL DEPENDS 63 | ${PROJECT_NAME}-2mb.uf2 64 | ${PROJECT_NAME}-4mb.uf2 65 | ${PROJECT_NAME}-8mb.uf2 66 | ${PROJECT_NAME}-16mb.uf2 67 | ) 68 | -------------------------------------------------------------------------------- /fatfs/examples/demo_delete_node.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------/ 2 | / Delete a sub-directory even if it contains any file 3 | /-------------------------------------------------------------/ 4 | / The delete_node() function is for R0.12+. 5 | / It works regardless of FF_FS_RPATH. 6 | */ 7 | 8 | fs_result_t delete_node(char *path, /* Path name buffer with the sub-directory to delete */ 9 | unsigned sz_buff, /* Size of path name buffer (items) */ 10 | file_info_t *fno) /* Name read buffer */ 11 | { 12 | unsigned i, j; 13 | fs_result_t fr; 14 | directory_t *dir = alloca(f_sizeof_directory_t()); 15 | 16 | fr = f_opendir(dir, path); /* Open the sub-directory to make it empty */ 17 | if (fr != FR_OK) 18 | return fr; 19 | 20 | for (i = 0; path[i]; i++) 21 | ; /* Get current path length */ 22 | path[i++] = _T('/'); 23 | 24 | for (;;) { 25 | fr = f_readdir(dir, fno); /* Get a directory item */ 26 | if (fr != FR_OK || !fno->fname[0]) 27 | break; /* End of directory? */ 28 | j = 0; 29 | do { /* Make a path name */ 30 | if (i + j >= sz_buff) { /* Buffer over flow? */ 31 | fr = 100; 32 | break; /* Fails with 100 when buffer overflow */ 33 | } 34 | path[i + j] = fno->fname[j]; 35 | } while (fno->fname[j++]); 36 | if (fno->fattrib & AM_DIR) { /* Item is a sub-directory */ 37 | fr = delete_node(path, sz_buff, fno); 38 | } else { /* Item is a file */ 39 | fr = f_unlink(path); 40 | } 41 | if (fr != FR_OK) 42 | break; 43 | } 44 | 45 | path[--i] = 0; /* Restore the path name */ 46 | f_closedir(dir); 47 | 48 | if (fr == FR_OK) 49 | fr = f_unlink(path); /* Delete the empty sub-directory */ 50 | return fr; 51 | } 52 | 53 | int main(void) /* How to use */ 54 | { 55 | fs_result_t fr; 56 | filesystem_t *fs = alloca(f_sizeof_filesystem_t()); 57 | char buff[256]; 58 | file_info_t fno; 59 | 60 | f_mount(fs, _T("5:"), 0); 61 | 62 | /* Directory to be deleted */ 63 | _tcscpy(buff, _T("5:dir")); 64 | 65 | /* Delete the directory */ 66 | fr = delete_node(buff, sizeof buff / sizeof buff[0], &fno); 67 | 68 | /* Check the result */ 69 | if (fr) { 70 | _tprintf(_T("Failed to delete the directory. (%u)\n"), fr); 71 | return fr; 72 | } else { 73 | _tprintf(_T("The directory and the contents have successfully been deleted.\n"), buff); 74 | return 0; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /apps/rz/zserial.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generic serial routines for Zmodem. 3 | // 4 | // Copyright (c) 2020 Ross Bamford 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include 25 | #include 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | typedef struct { 32 | uint8_t in_32bit_block; 33 | } zmodem_t; 34 | 35 | #define NONCONTROL(c) ((bool)((uint8_t)(c & 0xe0))) 36 | 37 | // 38 | // The lib doesn't implement these - they need to be provided. 39 | // 40 | ZRESULT zm_recv(); 41 | ZRESULT zm_send(uint8_t chr); 42 | 43 | // 44 | // Receive CR/LF (with CR being optional). 45 | // 46 | ZRESULT zm_read_crlf(); 47 | 48 | // 49 | // Read two ASCII characters and convert them from hex. 50 | // 51 | ZRESULT zm_read_hex_byte(); 52 | 53 | // 54 | // Read character, taking care of ZMODEM Data Link Escapes (ZDLE) 55 | // and swallowing XON/XOFF. 56 | // 57 | ZRESULT zm_read_escaped(); 58 | 59 | ZRESULT zm_await_zdle(); 60 | ZRESULT zm_await_header(zmodem_t *z, ZHDR *hdr); 61 | 62 | ZRESULT zm_read_hex_header(zmodem_t *z, ZHDR *hdr); 63 | ZRESULT zm_read_binary16_header(zmodem_t *z, ZHDR *hdr); 64 | ZRESULT zm_read_binary32_header(zmodem_t *z, ZHDR *hdr); 65 | 66 | // 67 | // len specifies the maximum length to read on entry, 68 | // and contains actual length on return. 69 | // 70 | ZRESULT zm_read_data_block(zmodem_t *z, uint8_t *buf, uint16_t *len); 71 | 72 | // 73 | // Send a null-terminated string. 74 | // 75 | ZRESULT zm_send_sz(uint8_t *data); 76 | 77 | // 78 | // Send the given header as hex, with ZPAD/ZDLE preamble. 79 | // 80 | ZRESULT zm_send_hex_hdr(ZHDR *hdr); 81 | 82 | // 83 | // Convenience function to build and send a position header as hex. 84 | // 85 | ZRESULT zm_send_pos_hdr(uint8_t type, uint32_t pos); 86 | 87 | // 88 | // Convenience function to build and send a flags header as hex. 89 | // 90 | ZRESULT zm_send_flags_hdr(uint8_t type, uint8_t f0, uint8_t f1, uint8_t f2, uint8_t f3); 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | -------------------------------------------------------------------------------- /tools/uf2fat/diskio.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "extern.h" 7 | 8 | // 9 | // Size of filesystem in bytes. 10 | // 11 | unsigned fs_nbytes; 12 | 13 | // 14 | // Collection of sectors, ordered by sector number. 15 | // 16 | std::map fs_image; 17 | 18 | // 19 | // Disk IO data and routines for FatFS. 20 | // 21 | const char *disk_name[DISK_VOLUMES] = { "flash", "sd" }; 22 | 23 | media_status_t disk_status(uint8_t unit) 24 | { 25 | return 0; 26 | } 27 | 28 | media_status_t disk_initialize(uint8_t unit) 29 | { 30 | return 0; 31 | } 32 | 33 | disk_result_t disk_ioctl(uint8_t unit, uint8_t cmd, void *buf) 34 | { 35 | switch (cmd) { 36 | case GET_BLOCK_SIZE: 37 | // Get erase block size. 38 | *(uint32_t *)buf = 1; 39 | return DISK_OK; 40 | case GET_SECTOR_SIZE: 41 | *(uint16_t *)buf = SECTOR_SIZE; 42 | return DISK_OK; 43 | case GET_SECTOR_COUNT: 44 | // Get media size. 45 | *(uint32_t *)buf = fs_nbytes / SECTOR_SIZE; 46 | return DISK_OK; 47 | case CTRL_SYNC: 48 | // Complete pending write process. 49 | return DISK_OK; 50 | default: 51 | return DISK_PARERR; 52 | } 53 | } 54 | 55 | disk_result_t disk_read(uint8_t unit, uint8_t *buf, unsigned sector, unsigned count) 56 | { 57 | // std::cout << "--- read sector " << sector << "\n"; 58 | if (count == 0) 59 | throw std::runtime_error("Zero count in disk_read()"); 60 | if (sector + count > fs_nbytes / SECTOR_SIZE) 61 | throw std::runtime_error("Too large count in disk_read()"); 62 | 63 | for (; count-- > 0; buf += SECTOR_SIZE, sector++) { 64 | // Throws if sector not found. 65 | auto &data = fs_image.at(sector); 66 | memcpy(buf, &data, SECTOR_SIZE); 67 | } 68 | return DISK_OK; 69 | } 70 | 71 | disk_result_t disk_write(uint8_t unit, const uint8_t *buf, unsigned sector, unsigned count) 72 | { 73 | // std::cout << "--- write sector " << sector << "\n"; 74 | if (count == 0) 75 | throw std::runtime_error("Zero count in disk_write()"); 76 | if (sector + count > fs_nbytes / SECTOR_SIZE) 77 | throw std::runtime_error("Too large count in disk_write()"); 78 | 79 | for (; count-- > 0; buf += SECTOR_SIZE, sector++) { 80 | SectorData data; 81 | memcpy(&data, buf, SECTOR_SIZE); 82 | fs_image.insert_or_assign(sector, data); 83 | } 84 | return DISK_OK; 85 | } 86 | 87 | // 88 | // Get date and time (local). 89 | // 90 | extern "C" { 91 | void fpm_get_datetime(int *year, int *month, int *day, int *dotw, int *hour, int *min, int *sec) 92 | { 93 | struct timeval tv; 94 | gettimeofday(&tv, 0); 95 | 96 | time_t now = tv.tv_sec; 97 | struct tm *info = localtime(&now); 98 | 99 | *year = 1900 + info->tm_year; 100 | *month = 1 + info->tm_mon; 101 | *day = info->tm_mday; 102 | *dotw = info->tm_wday; 103 | *hour = info->tm_hour; 104 | *min = info->tm_min; 105 | *sec = info->tm_sec; 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /include/fpm/bindings.h: -------------------------------------------------------------------------------- 1 | // 2 | // Initialization of bindings for loader. 3 | // 4 | #define FPM_BIND(name) { #name, (void*) name } 5 | 6 | { "", NULL }, 7 | 8 | // Kernel routines. 9 | FPM_BIND(fpm_alloc), 10 | FPM_BIND(fpm_alloc_dirty), 11 | FPM_BIND(fpm_delay_msec), 12 | FPM_BIND(fpm_delay_usec), 13 | FPM_BIND(fpm_editline), 14 | FPM_BIND(fpm_exec), 15 | FPM_BIND(fpm_free), 16 | FPM_BIND(fpm_get_datetime), 17 | FPM_BIND(fpm_get_dotw), 18 | FPM_BIND(fpm_getchar), 19 | FPM_BIND(fpm_getkey), 20 | FPM_BIND(fpm_getopt), 21 | FPM_BIND(fpm_getwch), 22 | FPM_BIND(fpm_heap_available), 23 | FPM_BIND(fpm_print_version), 24 | FPM_BIND(fpm_printf), 25 | FPM_BIND(fpm_putchar), 26 | FPM_BIND(fpm_puts), 27 | FPM_BIND(fpm_putwch), 28 | FPM_BIND(fpm_realloc), 29 | FPM_BIND(fpm_reboot), 30 | FPM_BIND(fpm_set_datetime), 31 | FPM_BIND(fpm_shell), 32 | FPM_BIND(fpm_sizeof), 33 | FPM_BIND(fpm_snprintf), 34 | FPM_BIND(fpm_sscanf), 35 | FPM_BIND(fpm_stack_available), 36 | FPM_BIND(fpm_strlcpy), 37 | FPM_BIND(fpm_strlcpy_from_utf8), 38 | FPM_BIND(fpm_strlcpy_to_utf8), 39 | FPM_BIND(fpm_strlcpy_unicode), 40 | FPM_BIND(fpm_strtod), 41 | FPM_BIND(fpm_strtol), 42 | FPM_BIND(fpm_strtoul), 43 | FPM_BIND(fpm_strwlen), 44 | FPM_BIND(fpm_time_usec), 45 | FPM_BIND(fpm_tokenize), 46 | FPM_BIND(fpm_truncate), 47 | FPM_BIND(fpm_utf8len), 48 | FPM_BIND(fpm_vprintf), 49 | FPM_BIND(fpm_vsnprintf), 50 | FPM_BIND(fpm_vsscanf), 51 | FPM_BIND(fpm_wputs), 52 | 53 | // FIlesystem routines. 54 | FPM_BIND(f_chdir), 55 | FPM_BIND(f_chdrive), 56 | FPM_BIND(f_chmod), 57 | FPM_BIND(f_close), 58 | FPM_BIND(f_closedir), 59 | FPM_BIND(f_eof), 60 | FPM_BIND(f_error), 61 | FPM_BIND(f_expand), 62 | FPM_BIND(f_findfirst), 63 | FPM_BIND(f_findnext), 64 | FPM_BIND(f_forward), 65 | FPM_BIND(f_getcwd), 66 | FPM_BIND(f_getdrive), 67 | FPM_BIND(f_getlabel), 68 | FPM_BIND(f_gets), 69 | FPM_BIND(f_lseek), 70 | FPM_BIND(f_mkdir), 71 | FPM_BIND(f_mkfs), 72 | FPM_BIND(f_mount), 73 | FPM_BIND(f_open), 74 | FPM_BIND(f_opendir), 75 | FPM_BIND(f_printf), 76 | FPM_BIND(f_putc), 77 | FPM_BIND(f_puts), 78 | FPM_BIND(f_read), 79 | FPM_BIND(f_readdir), 80 | FPM_BIND(f_rename), 81 | FPM_BIND(f_setlabel), 82 | FPM_BIND(f_size), 83 | FPM_BIND(f_sizeof_directory_t), 84 | FPM_BIND(f_sizeof_file_t), 85 | FPM_BIND(f_stat), 86 | FPM_BIND(f_statfs), 87 | FPM_BIND(f_strerror), 88 | FPM_BIND(f_sync), 89 | FPM_BIND(f_tell), 90 | FPM_BIND(f_truncate), 91 | FPM_BIND(f_unlink), 92 | FPM_BIND(f_unmount), 93 | FPM_BIND(f_utime), 94 | FPM_BIND(f_write), 95 | 96 | // Standard C library. 97 | FPM_BIND(atof), 98 | FPM_BIND(memcmp), 99 | FPM_BIND(memmove), 100 | FPM_BIND(memset), 101 | FPM_BIND(stpcpy), 102 | FPM_BIND(stpncpy), 103 | FPM_BIND(strcasecmp), 104 | FPM_BIND(strcat), 105 | FPM_BIND(strchr), 106 | FPM_BIND(strcmp), 107 | FPM_BIND(strcpy), 108 | FPM_BIND(strlen), 109 | FPM_BIND(strncmp), 110 | FPM_BIND(strrchr), 111 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_format.c: -------------------------------------------------------------------------------- 1 | // 2 | // Create filesystem on a disk device 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // 11 | // Find disk unit number by name. 12 | // 13 | static int find_drive_unit(const char *drive_name) 14 | { 15 | const char *path = drive_name; 16 | int disk_unit = f_getdrive(&path); 17 | if (disk_unit < 0 || *path != '\0') { 18 | fpm_printf("%s: Invalid drive name\r\n", drive_name); 19 | return -1; 20 | } 21 | return disk_unit; 22 | } 23 | 24 | static void format_drive(const char *drive_name) 25 | { 26 | static unsigned const disk_fmt[DISK_VOLUMES] = { 27 | FM_FAT | FM_SFD, // Drive 0 - Flash memory - FAT12/16 non-partitioned 28 | FM_FAT32, // Drive 1 - SD card - FAT32 with partition table 29 | }; 30 | 31 | // Find disk number by name. 32 | int disk_unit = find_drive_unit(drive_name); 33 | if (disk_unit < 0) { 34 | return; 35 | } 36 | 37 | // Get disk size. 38 | disk_info_t info; 39 | if (disk_identify(disk_unit, &info) != DISK_OK) { 40 | //fpm_printf("%s Cannot identify\r\n", drive_name); 41 | return; 42 | } 43 | 44 | // Ask user. 45 | uint16_t reply[32]; 46 | fpm_printf("Do you really want to format drive %s?\r\n", disk_name[disk_unit]); 47 | fpm_printf("Disk '%s', size %u.%u Mbytes.\r\n", info.product_name, 48 | (unsigned) (info.num_bytes / 1024 / 1024), 49 | (unsigned) (info.num_bytes * 10 / 1024 / 1024 % 10)); 50 | fpm_printf("All data on the disk will be lost.\r\n"); 51 | fpm_editline(reply, sizeof(reply), 1, "Confirm? y/n [n] ", 0); 52 | fpm_puts("\r\n"); 53 | if (reply[0] != 'y' && reply[0] != 'Y') { 54 | fpm_printf("Not formatted.\r\n"); 55 | return; 56 | } 57 | 58 | char buf[4*1024]; 59 | fs_result_t result = f_mkfs(drive_name, disk_fmt[disk_unit], buf, sizeof(buf)); 60 | if (result != FR_OK) { 61 | fpm_printf("%s: %s\r\n", drive_name, f_strerror(result)); 62 | return; 63 | } 64 | 65 | // Done. 66 | fpm_printf("Disk %s formatted.\r\n", disk_name[disk_unit]); 67 | } 68 | 69 | void fpm_cmd_format(int argc, char *argv[]) 70 | { 71 | static const struct fpm_option long_opts[] = { 72 | { "help", FPM_NO_ARG, NULL, 'h' }, 73 | {}, 74 | }; 75 | struct fpm_opt opt = {}; 76 | const char *drive_name = 0; 77 | 78 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 79 | switch (opt.ret) { 80 | case 1: 81 | if (drive_name != 0) { 82 | // To many arguments. 83 | goto usage; 84 | } 85 | drive_name = opt.arg; 86 | break; 87 | 88 | case '?': 89 | // Unknown option: message already printed. 90 | fpm_puts("\r\n"); 91 | return; 92 | 93 | case 'h': 94 | usage: fpm_puts("Usage:\r\n" 95 | " format sd:\r\n" 96 | " format flash:\r\n" 97 | "\n"); 98 | return; 99 | } 100 | } 101 | 102 | if (drive_name == 0) { 103 | // No arguments. 104 | goto usage; 105 | } 106 | format_drive(drive_name); 107 | fpm_puts("\r\n"); 108 | } 109 | -------------------------------------------------------------------------------- /kernel/fpm_shell.c: -------------------------------------------------------------------------------- 1 | // 2 | // Interactive shell for FP/M. 3 | // It must use only routines defined in fpm/api.h. 4 | // 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // 11 | // Area of system data. 12 | // 13 | jmp_buf fpm_saved_point; 14 | uint16_t fpm_history[FPM_CMDLINE_SIZE]; 15 | 16 | // 17 | // Build the prompt string. 18 | // 19 | static void build_prompt(char *prompt, unsigned max_length) 20 | { 21 | prompt[0] = 0; 22 | strcat(prompt, "\033[0;31m"); // dim red color 23 | strcat(prompt, disk_name[f_getdrive(NULL)]); // current disk 24 | 25 | // Get current directory. 26 | char path[4096]; 27 | if (f_getcwd(path, sizeof(path)) == FR_OK) { 28 | strcat(prompt, ":"); 29 | 30 | char *basename = strrchr(path, '/'); 31 | if (basename == 0) { 32 | strcat(prompt, "?"); // cannot happen 33 | } else if (basename[-1] != ':') { 34 | strcat(prompt, basename + 1); // only basename 35 | } else { 36 | strcat(prompt, basename); // full path 37 | } 38 | } 39 | 40 | strcat(prompt, "\033[1;32m"); // bright green color 41 | strcat(prompt, " >"); 42 | strcat(prompt, "\033[m"); // default color 43 | strcat(prompt, " "); 44 | } 45 | 46 | // 47 | // Interactive dialog. 48 | // 49 | void fpm_shell() 50 | { 51 | // TODO: Mount the SD card. 52 | 53 | // TODO: Load the autoexec.bat config file. 54 | //if (coldBoot > 0) { 55 | // source("autoexec.cmd"); 56 | //} 57 | 58 | // Clear history. 59 | fpm_history[0] = 0; 60 | 61 | // Restart on ^C. 62 | if (setjmp(fpm_saved_point) != 0) { 63 | // TODO: Re-initialize internal state. 64 | } 65 | 66 | // The main loop. 67 | for (;;) { 68 | // Create prompt. 69 | char prompt[FPM_CMDLINE_SIZE]; 70 | build_prompt(prompt, sizeof(prompt)); 71 | 72 | // Call the line editor. 73 | uint16_t buf_unicode[FPM_CMDLINE_SIZE]; 74 | fpm_editline(buf_unicode, sizeof(buf_unicode), 1, prompt, fpm_history); 75 | fpm_puts("\r\n"); 76 | 77 | // Encode as utf8. 78 | char cmd_line[3 * FPM_CMDLINE_SIZE]; 79 | fpm_strlcpy_to_utf8(cmd_line, buf_unicode, sizeof(cmd_line)); 80 | 81 | // Split into argument vector. 82 | char *argv[FPM_CMDLINE_SIZE / 3]; 83 | int argc; 84 | const char *error = fpm_tokenize(argv, &argc, cmd_line); 85 | if (error) { 86 | fpm_puts(error); 87 | fpm_puts("\r\n"); 88 | 89 | // Save wrong line to the history. 90 | fpm_strlcpy_unicode(fpm_history, buf_unicode, sizeof(fpm_history)/sizeof(uint16_t)); 91 | continue; 92 | } 93 | 94 | // Ignore empty commands. 95 | if (argc == 0) { 96 | continue; 97 | } 98 | 99 | // Add the line to the history. 100 | fpm_strlcpy_unicode(fpm_history, buf_unicode, sizeof(fpm_history)/sizeof(uint16_t)); 101 | 102 | if (strcmp(argv[0], "exit") == 0) { 103 | if (argc > 1) { 104 | fpm_puts("Usage: exit\r\n\r\n"); 105 | continue; 106 | } 107 | return; 108 | } 109 | 110 | // Execute the command. 111 | fpm_exec(argc, argv); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pico/sd_pico/sd_card.h: -------------------------------------------------------------------------------- 1 | /* sd_card.h 2 | Copyright 2021 Carl John Kugler III 3 | 4 | Licensed under the Apache License, Version 2.0 (the License); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | */ 14 | 15 | // Note: The model used here is one FatFS per SD card. 16 | // Multiple partitions on a card are not supported. 17 | 18 | #ifndef _SD_CARD_H_ 19 | #define _SD_CARD_H_ 20 | 21 | #include 22 | #include /* Declarations of media status */ 23 | #include "pico/mutex.h" 24 | #include "spi.h" 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | // 31 | // "Class" representing SD Cards 32 | // 33 | typedef struct _sd_card_t { 34 | struct _sd_card_t *sd_cards; // List of all SD cards configured 35 | struct _spi_t *spi; // SPI port for this SD card 36 | const char *board_name; // Name of the board 37 | 38 | // Slave select is here in sd_card_t because multiple SDs can share an SPI 39 | uint ss_gpio; // Slave select for this SD card 40 | bool use_card_detect; // GPIO signal for card detect is available 41 | bool doing_reinit; // Doing card reinitialization right now 42 | uint card_detect_gpio; // Card detect; ignored if !use_card_detect 43 | uint card_detected_true; // Varies with card socket; ignored if !use_card_detect 44 | 45 | // Following fields are used to keep track of the state of the card: 46 | media_status_t m_Status; // Card status 47 | uint64_t sectors; // Assigned dynamically 48 | int card_type; // Assigned dynamically 49 | mutex_t mutex; 50 | bool mounted; 51 | 52 | // Card identification 53 | char oem_id[2+1]; 54 | char product_name[5+1]; 55 | uint32_t product_serial_number; 56 | } sd_card_t; 57 | 58 | #define SD_BLOCK_DEVICE_ERROR_NONE 0 59 | #define SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK -5001 /*!< operation would block */ 60 | #define SD_BLOCK_DEVICE_ERROR_UNSUPPORTED -5002 /*!< unsupported operation */ 61 | #define SD_BLOCK_DEVICE_ERROR_PARAMETER -5003 /*!< invalid parameter */ 62 | #define SD_BLOCK_DEVICE_ERROR_NO_INIT -5004 /*!< uninitialized */ 63 | #define SD_BLOCK_DEVICE_ERROR_NO_DEVICE -5005 /*!< device is missing or not connected */ 64 | #define SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED -5006 /*!< write protected */ 65 | #define SD_BLOCK_DEVICE_ERROR_UNUSABLE -5007 /*!< unusable card */ 66 | #define SD_BLOCK_DEVICE_ERROR_NO_RESPONSE -5008 /*!< No response from device */ 67 | #define SD_BLOCK_DEVICE_ERROR_CRC -5009 /*!< CRC error */ 68 | #define SD_BLOCK_DEVICE_ERROR_ERASE -5010 /*!< Erase error: reset/sequence */ 69 | #define SD_BLOCK_DEVICE_ERROR_WRITE -5011 /*!< SPI Write error: !SPI_DATA_ACCEPTED */ 70 | 71 | // Find valid SD configuration. 72 | sd_card_t *sd_configure(sd_card_t sd_list[]); 73 | 74 | media_status_t sd_init(sd_card_t *pSD); 75 | int sd_write_blocks(sd_card_t *pSD, const uint8_t *buffer, uint64_t ulSectorNumber, 76 | uint32_t blockCnt); 77 | int sd_read_blocks(sd_card_t *pSD, uint8_t *buffer, uint64_t ulSectorNumber, 78 | uint32_t ulSectorCount); 79 | bool sd_card_detect(sd_card_t *pSD); 80 | uint64_t sd_sectors(sd_card_t *pSD); 81 | 82 | #ifdef __cplusplus 83 | } 84 | #endif 85 | 86 | #endif 87 | /* [] END OF FILE */ 88 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_mount.c: -------------------------------------------------------------------------------- 1 | // 2 | // Engage removable disk device 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // 11 | // Convert statfs returned filesystem size into 1-kbyte units. 12 | // Attempts to avoid overflow for large filesystems. 13 | // 14 | static unsigned long blk_to_kbytes(unsigned long num, unsigned bsize) 15 | { 16 | if (bsize == 0) { 17 | return num; 18 | } else if (bsize < 1024) { 19 | return num / (1024 / bsize); 20 | } else { 21 | return num * (bsize / 1024); 22 | } 23 | } 24 | 25 | static void mount(const char *path) 26 | { 27 | fs_result_t result = f_mount(path); 28 | if (result != FR_OK) { 29 | fpm_puts(f_strerror(result)); 30 | fpm_puts("\r\n\n"); 31 | return; 32 | } 33 | 34 | // Print media info. 35 | const char *filename = path; 36 | int drive = f_getdrive(&filename); 37 | if (drive < 0) { 38 | return; 39 | } 40 | disk_info_t disk_info; 41 | if (disk_identify(drive, &disk_info) != DISK_OK) { 42 | return; 43 | } 44 | fpm_printf("Media '%s' mounted as %s", disk_info.product_name, disk_name[drive]); 45 | 46 | fs_info_t fs_info; 47 | if (f_statfs(path, &fs_info) != FR_OK) { 48 | fpm_puts("\r\n\n"); 49 | return; 50 | } 51 | fpm_printf(": %lu kbytes used, %lu free\r\n\n", 52 | blk_to_kbytes(fs_info.f_blocks - fs_info.f_bfree, fs_info.f_bsize), 53 | blk_to_kbytes(fs_info.f_bavail, fs_info.f_bsize)); 54 | } 55 | 56 | static void show(int i) 57 | { 58 | char path[32]; 59 | strcpy(path, disk_name[i]); 60 | strcat(path, ":"); 61 | 62 | fs_info_t info; 63 | fs_result_t result = f_statfs(path, &info); 64 | 65 | fpm_printf("%5s: ", disk_name[i]); 66 | if (result != FR_OK) { 67 | // Drive not mounted. 68 | fpm_puts(" ---\r\n"); 69 | return; 70 | } 71 | 72 | switch (info.f_type) { 73 | case FS_FAT12: fpm_puts("FAT-12"); break; 74 | case FS_FAT16: fpm_puts("FAT-16"); break; 75 | case FS_FAT32: fpm_puts("FAT-32"); break; 76 | case FS_EXFAT: fpm_puts(" exFAT"); break; 77 | default: fpm_puts("Unknwn"); break; 78 | } 79 | 80 | unsigned used = info.f_blocks - info.f_bfree; 81 | unsigned avail = info.f_bavail + used; 82 | fpm_printf(" %10lu", blk_to_kbytes(info.f_blocks, info.f_bsize)); 83 | fpm_printf(" %9lu", blk_to_kbytes(used, info.f_bsize)); 84 | fpm_printf(" %9lu", blk_to_kbytes(info.f_bavail, info.f_bsize)); 85 | 86 | if (avail == 0) { 87 | fpm_puts(" 100%"); 88 | } else { 89 | fpm_printf(" %5u%%", (unsigned) ((used * 200ULL + avail) / avail / 2)); 90 | } 91 | fpm_puts("\r\n"); 92 | } 93 | 94 | void fpm_cmd_mount(int argc, char *argv[]) 95 | { 96 | static const struct fpm_option long_opts[] = { 97 | { "help", FPM_NO_ARG, NULL, 'h' }, 98 | {}, 99 | }; 100 | struct fpm_opt opt = {}; 101 | 102 | if (argc > 2) { 103 | usage: 104 | fpm_puts("Usage:\r\n" 105 | " mount [sd: | flash:]\r\n" 106 | "\n"); 107 | return; 108 | } 109 | 110 | while (fpm_getopt(argc, argv, "h", long_opts, &opt) >= 0) { 111 | switch (opt.ret) { 112 | case 1: 113 | mount(opt.arg); 114 | return; 115 | 116 | case '?': 117 | // Unknown option: message already printed. 118 | fpm_puts("\r\n"); 119 | return; 120 | 121 | case 'h': 122 | goto usage; 123 | } 124 | } 125 | 126 | // 127 | // Show mounted volumes. 128 | // 129 | fpm_printf(" Drive Type 1k-blocks Used Available Capacity\r\n"); 130 | for (int i = 0; i < DISK_VOLUMES; i++) { 131 | show(i); 132 | } 133 | fpm_puts("\n"); 134 | } 135 | -------------------------------------------------------------------------------- /apps/rz/zheaders.c: -------------------------------------------------------------------------------- 1 | // 2 | // Routines for working with Zmodem headers. 3 | // 4 | // Copyright (c) 2020 Ross Bamford 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include 25 | #include "crc.h" 26 | #include "ztypes.h" 27 | #include "znumbers.h" 28 | #include "zheaders.h" 29 | 30 | void zm_calc_hdr_crc(ZHDR *hdr) 31 | { 32 | uint16_t crc = update_crc16_ccitt(hdr->type, CRC_START_XMODEM); 33 | crc = update_crc16_ccitt(hdr->flags.f3, crc); 34 | crc = update_crc16_ccitt(hdr->flags.f2, crc); 35 | crc = update_crc16_ccitt(hdr->flags.f1, crc); 36 | crc = update_crc16_ccitt(hdr->flags.f0, crc); 37 | 38 | hdr->crc1 = CRC_MSB(crc); 39 | hdr->crc2 = CRC_LSB(crc); 40 | } 41 | 42 | uint16_t zm_calc_data_crc(uint8_t *buf, uint16_t len) 43 | { 44 | uint16_t crc = CRC_START_XMODEM; 45 | 46 | for (int i = 0; i < len; i++) { 47 | crc = update_crc16_ccitt(buf[i], crc); 48 | } 49 | return crc; 50 | } 51 | 52 | // 53 | // Returns CRC-32 of sequence of bytes (binary or ASCIIZ). 54 | // Pass len of 0 to auto-determine ASCIIZ string length. 55 | // or non-zero for arbitrary binary data/ 56 | // 57 | uint32_t zm_calc_data_crc32(uint8_t *buf, uint16_t len) 58 | { 59 | uint32_t crc = CRC_START_32; 60 | 61 | if (buf != 0) { 62 | if (len == 0) { 63 | len = strlen((const char *)buf); 64 | } 65 | for (unsigned l = 0; l < len; l++) { 66 | crc = update_crc32(buf[l], crc); 67 | } 68 | } 69 | return ~crc; 70 | } 71 | 72 | ZRESULT zm_to_hex_header(ZHDR *hdr, uint8_t *buf, int max_len) 73 | { 74 | if (max_len < HEX_HDR_STR_LEN) { 75 | return OUT_OF_SPACE; 76 | } else { 77 | *buf++ = 'B'; // 01 78 | 79 | zm_byte_to_hex(hdr->type, buf); // 03 80 | buf += 2; 81 | zm_byte_to_hex(hdr->flags.f3, buf); // 05 82 | buf += 2; 83 | zm_byte_to_hex(hdr->flags.f2, buf); // 07 84 | buf += 2; 85 | zm_byte_to_hex(hdr->flags.f1, buf); // 09 86 | buf += 2; 87 | zm_byte_to_hex(hdr->flags.f0, buf); // 0b 88 | buf += 2; 89 | zm_byte_to_hex(hdr->crc1, buf); // 0d 90 | buf += 2; 91 | zm_byte_to_hex(hdr->crc2, buf); // 0f 92 | buf += 2; 93 | *buf++ = CR; // 10 94 | *buf++ = LF | 0x80; // 11 95 | 96 | return HEX_HDR_STR_LEN; 97 | } 98 | } 99 | 100 | ZRESULT zm_check_header_crc16(ZHDR *hdr, uint16_t crc) 101 | { 102 | if (CRC(hdr->crc1, hdr->crc2) == crc) { 103 | return OK; 104 | } else { 105 | return BAD_CRC; 106 | } 107 | } 108 | 109 | ZRESULT zm_check_header_crc32(ZHDR *hdr, uint32_t crc) 110 | { 111 | if (CRC32(hdr->crc1, hdr->crc2, hdr->crc3, hdr->crc4) == crc) { 112 | return OK; 113 | } else { 114 | return BAD_CRC; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /apps/rz/znumbers.c: -------------------------------------------------------------------------------- 1 | // 2 | // Numeric-related routines for Zmodem implementation. 3 | // 4 | // Copyright (c) 2020 Ross Bamford 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include 25 | 26 | #include "ztypes.h" 27 | #include "znumbers.h" 28 | 29 | ZRESULT zm_hex_to_nybble(char c1) 30 | { 31 | switch (c1) { 32 | case '0': 33 | return 0x00; 34 | case '1': 35 | return 0x01; 36 | case '2': 37 | return 0x02; 38 | case '3': 39 | return 0x03; 40 | case '4': 41 | return 0x04; 42 | case '5': 43 | return 0x05; 44 | case '6': 45 | return 0x06; 46 | case '7': 47 | return 0x07; 48 | case '8': 49 | return 0x08; 50 | case '9': 51 | return 0x09; 52 | case 'a': 53 | return 0x0a; 54 | case 'b': 55 | return 0x0b; 56 | case 'c': 57 | return 0x0c; 58 | case 'd': 59 | return 0x0d; 60 | case 'e': 61 | return 0x0e; 62 | case 'f': 63 | return 0x0f; 64 | default: 65 | return BAD_DIGIT; 66 | } 67 | } 68 | 69 | ZRESULT zm_nybble_to_hex(uint8_t nybble) 70 | { 71 | switch (nybble) { 72 | case 0x0: 73 | return '0'; 74 | case 0x1: 75 | return '1'; 76 | case 0x2: 77 | return '2'; 78 | case 0x3: 79 | return '3'; 80 | case 0x4: 81 | return '4'; 82 | case 0x5: 83 | return '5'; 84 | case 0x6: 85 | return '6'; 86 | case 0x7: 87 | return '7'; 88 | case 0x8: 89 | return '8'; 90 | case 0x9: 91 | return '9'; 92 | case 0xa: 93 | return 'a'; 94 | case 0xb: 95 | return 'b'; 96 | case 0xc: 97 | return 'c'; 98 | case 0xd: 99 | return 'd'; 100 | case 0xe: 101 | return 'e'; 102 | case 0xf: 103 | return 'f'; 104 | default: 105 | return OUT_OF_RANGE; 106 | } 107 | } 108 | 109 | ZRESULT zm_byte_to_hex(uint8_t byte, uint8_t *buf) 110 | { 111 | uint16_t h1 = zm_nybble_to_hex(BMSN(byte)); 112 | 113 | if (IS_ERROR(h1)) { 114 | return h1; 115 | } else { 116 | uint16_t h2 = zm_nybble_to_hex(BLSN(byte)); 117 | 118 | if (IS_ERROR(h2)) { 119 | return h2; 120 | } else { 121 | *buf++ = h1; 122 | *buf = h2; 123 | 124 | return OK; 125 | } 126 | } 127 | } 128 | 129 | ZRESULT zm_hex_to_byte(unsigned char c1, unsigned char c2) 130 | { 131 | uint16_t n1, n2; 132 | 133 | n1 = zm_hex_to_nybble(c1); 134 | n2 = zm_hex_to_nybble(c2); 135 | 136 | if (n1 == BAD_DIGIT || n2 == BAD_DIGIT) { 137 | DEBUGF("Got bad digit: [0x%02x, 0x%02x]\n", c1, c2); 138 | return BAD_DIGIT; 139 | } else { 140 | return NTOB(n1, n2); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /docs/Supported-Platforms.md: -------------------------------------------------------------------------------- 1 | # Supported Platforms 2 | 3 | The following is a list of platforms and boards supported in FP/M. 4 | 5 | * Raspberry Pi Pico with RP2040 6 | * Raspberry Pi Pico 2 with RP2350 7 | * Unix demo 8 | 9 | ## Raspberry Pi Pico with RP2040 10 | 11 | Boards with SD card: 12 | 13 | * [SparkFun Thing Plus - RP2040](https://learn.sparkfun.com/tutorials/rp2040-thing-plus-hookup-guide/hardware-overview) 14 | * [Challenger RP2040 SD/RTC](https://ilabs.se/challenger-rp2040-sd-rtc-datasheet) 15 | * [Waveshare RP2040-PiZero](https://www.waveshare.com/wiki/RP2040-PiZero) 16 | * [HackyPi](https://shop.sb-components.co.uk/products/hackypi-compact-diy-usb-hacking-tool) 17 | * [MusicPi](https://shop.sb-components.co.uk/products/musicpi-high-quality-stereo-audio) 18 | * [ArdiPi](https://shop.sb-components.co.uk/products/ardipi-uno-r3-alternative-board-based-on-pico-w) 19 | * [Olimex RP2040-PICO-PC](https://www.olimex.com/Products/RaspberryPi/PICO/RP2040-PICO-PC/open-source-hardware) 20 | 21 | Boards without SD card: 22 | 23 | * [Raspberry Pi Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) 24 | * [Raspberry Pi Pico W](https://www.raspberrypi.com/products/raspberry-pi-pico/) 25 | * [Arduino Nano RP2040 Connect](https://store-usa.arduino.cc/products/arduino-nano-rp2040-connect) 26 | * [Waveshare RP2040-Plus](https://www.waveshare.com/wiki/RP2040-Plus) 27 | * [Waveshare RP2040-Zero](https://www.waveshare.com/wiki/RP2040-Zero) 28 | * [Waveshare RP2040-Matrix](https://www.waveshare.com/wiki/RP2040-Matrix) 29 | * [Waveshare Pico Eval Board](https://www.waveshare.com/wiki/Pico-Eval-Board) 30 | * [YD-RP2040](https://circuitpython.org/board/vcc_gnd_yd_rp2040/) by VCC-GND Studios 31 | 32 | Flash memory size: 33 | 34 | RP2040 Board | Flash size | SD card | Display | WiFi 35 | -----------------------------|------------|---------|---------|----- 36 | SparkFun Thing Plus - RP2040 | 16 Mbytes | yes | --- | --- 37 | Challenger RP2040 SD/RTC | 8 Mbytes | yes | --- | --- 38 | Waveshare RP2040-PiZero | 16 Mbytes | yes | --- | --- 39 | HackyPi | 2 Mbytes | yes | 240x135 | --- 40 | MusicPi | --- | yes | 240x135 | --- 41 | ArdiPi | 2 Mbytes | yes | --- | --- 42 | Olimex RP2040-PICO-PC | --- | yes | --- | --- 43 | Raspberry Pi Pico | 2 Mbytes | no | --- | --- 44 | Raspberry Pi Pico W | 2 Mbytes | no | --- | CYW43439 45 | Arduino Nano RP2040 Connect | 16 Mbytes | no | --- | NINA-W102 46 | Waveshare RP2040-Plus | 16 Mbytes | no | --- | --- 47 | Waveshare RP2040-Zero | 2 Mbytes | no | --- | --- 48 | Waveshare RP2040-Matrix | 2 Mbytes | no | --- | --- 49 | Waveshare Pico Eval Board | --- | unusable| 480×320 | --- 50 | YD-RP2040 | 2 Mbytes | no | --- | --- 51 | 52 | ## Raspberry Pi Pico 2 with RP2350 53 | 54 | Boards with SD card: 55 | 56 | * [Waveshare RP2350-GEEK](https://www.waveshare.com/wiki/RP2350-GEEK) 57 | 58 | Boards without SD card: 59 | 60 | * [Raspberry Pi Pico 2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) 61 | * [Raspberry Pi Pico 2 W](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) 62 | * [Waveshare RP2350-Plus](https://www.waveshare.com/wiki/RP2350-Plus) 63 | * [Waveshare RP2350-Zero](https://www.waveshare.com/wiki/RP2350-Zero) 64 | 65 | Flash memory size: 66 | 67 | RP2350 Board | Flash size | SD card | Display | WiFi 68 | -----------------------------|------------|---------|---------|----- 69 | Waveshare RP2350-GEEK | 16 Mbytes | yes | 240x135 | --- 70 | Raspberry Pi Pico 2 | 4 Mbytes | no | --- | --- 71 | Raspberry Pi Pico 2 W | 4 Mbytes | no | --- | CYW43439 72 | Waveshare RP2350-Plus | 16 Mbytes | no | --- | --- 73 | Waveshare RP2350-Zero | 2 Mbytes | no | --- | --- 74 | 75 | ## Unix 76 | 77 | If you don't have a board available, FP/M has a demo that you can run on Linux or MacOS. 78 | -------------------------------------------------------------------------------- /pico/fpm_pico.c: -------------------------------------------------------------------------------- 1 | // 2 | // API for FP/M, implemented with Pico SDK. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include "pico/stdlib.h" 8 | #include "hardware/watchdog.h" 9 | #include "hardware/clocks.h" 10 | 11 | // 12 | // Wait for console input. 13 | // Return ASCII keycode. 14 | // 15 | char fpm_getchar() 16 | { 17 | int ch; 18 | 19 | for (;;) { 20 | #if LIB_PICO_STDIO_USB 21 | // Make sure console is connected. 22 | while (!stdio_usb_connected()) { 23 | sleep_ms(100); 24 | } 25 | #endif 26 | // Read one byte. 27 | ch = getchar(); 28 | if (ch >= 0) { 29 | break; 30 | } 31 | } 32 | #if 0 33 | // ^C - kill the process. 34 | if (ch == '\3') { 35 | fpm_puts("^C\r\n"); 36 | longjmp(fpm_saved_point, 1); 37 | } 38 | #endif 39 | return ch; 40 | } 41 | 42 | // 43 | // Write Unicode character to the console. 44 | // 45 | // Convert to UTF-8 encoding: 46 | // 00000000.0xxxxxxx -> 0xxxxxxx 47 | // 00000xxx.xxyyyyyy -> 110xxxxx, 10yyyyyy 48 | // xxxxyyyy.yyzzzzzz -> 1110xxxx, 10yyyyyy, 10zzzzzz 49 | // 50 | void fpm_putchar(char ch) 51 | { 52 | putchar(ch); 53 | } 54 | 55 | // 56 | // Posix-compatible formatted output to console. 57 | // 58 | int fpm_printf(const char *format, ...) 59 | { 60 | va_list args; 61 | va_start(args, format); 62 | int retval = vprintf(format, args); 63 | va_end(args); 64 | fflush(stdout); 65 | return retval; 66 | } 67 | 68 | int fpm_vprintf(const char *format, va_list args) 69 | { 70 | int retval = vprintf(format, args); 71 | fflush(stdout); 72 | return retval; 73 | } 74 | 75 | // 76 | // Posix-compatible formatted output to string. 77 | // 78 | int fpm_snprintf(char *str, size_t size, const char *format, ...) 79 | { 80 | va_list args; 81 | va_start(args, format); 82 | int retval = vsnprintf(str, size, format, args); 83 | va_end(args); 84 | return retval; 85 | } 86 | 87 | int fpm_vsnprintf(char *str, size_t size, const char *format , va_list args) 88 | { 89 | return vsnprintf(str, size, format, args); 90 | } 91 | 92 | // 93 | // Posix-compatible formatted input. 94 | // 95 | int fpm_sscanf(const char *str, const char *format, ...) 96 | { 97 | va_list args; 98 | va_start(args, format); 99 | int retval = vsscanf(str, format, args); 100 | va_end(args); 101 | return retval; 102 | } 103 | 104 | int fpm_vsscanf(const char *str, const char *format, va_list args) 105 | { 106 | return vsscanf(str, format, args); 107 | } 108 | 109 | void fpm_print_version() 110 | { 111 | fpm_puts("FP/M version "FPM_VERSION"."GIT_REVCOUNT"\r\n"); 112 | fpm_puts("Git commit "GIT_COMMIT", built on "__DATE__" at "__TIME__"\r\n"); 113 | fpm_puts("Pico SDK version "PICO_SDK_VERSION_STRING"\r\n"); 114 | #if PICO_RP2040 115 | fpm_printf("RP2040 revision B%u ", rp2040_chip_version()); 116 | #elif PICO_RP2350 117 | fpm_printf("RP2350 revision A%u ", rp2350_chip_version()); 118 | #endif 119 | fpm_printf("at %u MHz, ROM version %d\r\n", clock_get_hz(clk_sys) / 1000000, rp2040_rom_version()); 120 | fpm_printf("Free memory %u kbytes, stack %u kbytes\r\n", fpm_heap_available() / 1024, fpm_stack_available() / 1024); 121 | } 122 | 123 | // 124 | // Reboot the processor. 125 | // 126 | void fpm_reboot() 127 | { 128 | watchdog_reboot(0, 0, 0); 129 | } 130 | 131 | // 132 | // Return the current 64-bit timestamp value in microseconds. 133 | // 134 | uint64_t fpm_time_usec() 135 | { 136 | return time_us_64(); 137 | } 138 | 139 | // 140 | // Busy wait for the given 64-bit number of microseconds. 141 | // 142 | void fpm_delay_usec(uint64_t microseconds) 143 | { 144 | busy_wait_us(microseconds); 145 | } 146 | 147 | // 148 | // Busy wait for the given number of milliseconds. 149 | // 150 | void fpm_delay_msec(unsigned milliseconds) 151 | { 152 | busy_wait_ms(milliseconds); 153 | } 154 | 155 | // 156 | // Return the amount of stack space. 157 | // 158 | size_t fpm_stack_available() 159 | { 160 | extern char __StackBottom[]; 161 | char *sp = NULL; 162 | asm volatile("mov %0, sp" : "=r"(sp)); 163 | return sp - __StackBottom; 164 | } 165 | -------------------------------------------------------------------------------- /unix/fpm_unix.c: -------------------------------------------------------------------------------- 1 | // 2 | // API for FP/M, implemented with Posix. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // 15 | // Read byte from the console. 16 | // 17 | char fpm_getchar() 18 | { 19 | char ch; 20 | if (read(0, &ch, 1) != 1) { 21 | // Console closed. 22 | exit(-1); 23 | } 24 | 25 | // ^C - kill the process. 26 | if (ch == '\3') { 27 | fpm_puts("^C\r\n"); 28 | longjmp(fpm_saved_point, 1); 29 | } 30 | return ch; 31 | } 32 | 33 | // 34 | // Write byte to the console. 35 | // 36 | void fpm_putchar(char ch) 37 | { 38 | putchar(ch); 39 | fflush(stdout); 40 | } 41 | 42 | // 43 | // Posix-compatible formatted output to console. 44 | // 45 | int fpm_printf(const char *format, ...) 46 | { 47 | va_list args; 48 | va_start(args, format); 49 | int retval = vprintf(format, args); 50 | va_end(args); 51 | fflush(stdout); 52 | return retval; 53 | } 54 | 55 | int fpm_vprintf(const char *format, va_list args) 56 | { 57 | int retval = vprintf(format, args); 58 | fflush(stdout); 59 | return retval; 60 | } 61 | 62 | // 63 | // Posix-compatible formatted output to string. 64 | // 65 | int fpm_snprintf(char *str, size_t size, const char *format, ...) 66 | { 67 | va_list args; 68 | va_start(args, format); 69 | int retval = vsnprintf(str, size, format, args); 70 | va_end(args); 71 | return retval; 72 | } 73 | 74 | int fpm_vsnprintf(char *str, size_t size, const char *format , va_list args) 75 | { 76 | return vsnprintf(str, size, format, args); 77 | } 78 | 79 | // 80 | // Posix-compatible formatted input. 81 | // 82 | int fpm_sscanf(const char *str, const char *format, ...) 83 | { 84 | va_list args; 85 | va_start(args, format); 86 | int retval = vsscanf(str, format, args); 87 | va_end(args); 88 | return retval; 89 | } 90 | 91 | int fpm_vsscanf(const char *str, const char *format, va_list args) 92 | { 93 | return vsscanf(str, format, args); 94 | } 95 | 96 | void fpm_print_version() 97 | { 98 | fpm_puts("FP/M version "FPM_VERSION"."GIT_REVCOUNT"\r\n"); 99 | fpm_puts("Git commit "GIT_COMMIT", built on "__DATE__" at "__TIME__"\r\n"); 100 | 101 | struct utsname u; 102 | if (uname(&u) == 0) { 103 | fpm_printf("Unix %s %s version %s\r\n", u.sysname, u.machine, u.release); 104 | } 105 | fpm_printf("Free memory %u kbytes\r\n", fpm_heap_available() / 1024); 106 | } 107 | 108 | // 109 | // Get date and time (local). 110 | // 111 | void fpm_get_datetime(int *year, int *month, int *day, int *dotw, int *hour, int *min, int *sec) 112 | { 113 | struct timeval tv; 114 | gettimeofday(&tv, 0); 115 | 116 | time_t now = tv.tv_sec; 117 | struct tm *info = localtime(&now); 118 | 119 | *year = 1900 + info->tm_year; 120 | *month = 1 + info->tm_mon; 121 | *day = info->tm_mday; 122 | *dotw = info->tm_wday; 123 | *hour = info->tm_hour; 124 | *min = info->tm_min; 125 | *sec = info->tm_sec; 126 | } 127 | 128 | // 129 | // Set date and time. 130 | // 131 | void fpm_set_datetime(int year, int month, int day, int hour, int min, int sec) 132 | { 133 | // Ignore. 134 | } 135 | 136 | // 137 | // Reboot the processor. 138 | // 139 | void fpm_reboot() 140 | { 141 | fpm_puts("Cannot reboot Unix, sorry.\r\n\r\n"); 142 | } 143 | 144 | // 145 | // Return the current 64-bit timestamp value in microseconds. 146 | // 147 | uint64_t fpm_time_usec() 148 | { 149 | struct timeval t; 150 | 151 | gettimeofday(&t, 0); 152 | return t.tv_sec * 1000000ULL + t.tv_usec; 153 | } 154 | 155 | // 156 | // Busy wait for the given 64-bit number of microseconds. 157 | // 158 | void fpm_delay_usec(uint64_t microseconds) 159 | { 160 | usleep(microseconds); 161 | } 162 | 163 | // 164 | // Busy wait for the given number of milliseconds. 165 | // 166 | void fpm_delay_msec(unsigned milliseconds) 167 | { 168 | usleep(milliseconds * 1000ULL); 169 | } 170 | 171 | // 172 | // Return the amount of stack space. 173 | // 174 | size_t fpm_stack_available() 175 | { 176 | struct rlimit rl; 177 | if (getrlimit(RLIMIT_STACK, &rl) < 0) { 178 | return 0; 179 | } 180 | return rl.rlim_cur; 181 | } 182 | -------------------------------------------------------------------------------- /pico/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set minimum required version of CMake 2 | cmake_minimum_required(VERSION 3.12) 3 | 4 | if(RP2040) 5 | # Build for rp2040 6 | set(PICO_BOARD pico) 7 | set(PICO_PLATFORM rp2040) 8 | elseif(RP2350_ARM) 9 | # Build for rp2350 in ARM mode 10 | set(PICO_BOARD pico2) 11 | set(PICO_PLATFORM rp2350-arm-s) 12 | elseif(RP2350_RISCV) 13 | # Build for rp2350 in RISC-V mode 14 | set(PICO_BOARD pico2) 15 | set(PICO_PLATFORM rp2350-riscv) 16 | else() 17 | message(FATAL_ERROR "Please select target: -DRP2040=1 or -DRP2350_ARM=1 or -DRP2350_RISCV=1") 18 | endif() 19 | 20 | # Include build functions from Pico SDK 21 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 22 | 23 | # Set name of project (as PROJECT_NAME) and C/C++ standards 24 | project(fpm-${PICO_PLATFORM} C CXX ASM) 25 | set(CMAKE_C_STANDARD 11) 26 | set(CMAKE_CXX_STANDARD 17) 27 | add_compile_options(-Wall -Werror) 28 | 29 | # Creates a pico-sdk subdirectory in our project for the libraries 30 | pico_sdk_init() 31 | 32 | # Tell CMake where to find the executable source file 33 | add_executable(${PROJECT_NAME} 34 | main_pico.c 35 | fpm_pico.c 36 | rtc_pico.c 37 | diskio.c 38 | flash.c 39 | bindings.c 40 | sd_pico/crc.c 41 | sd_pico/sd_card.c 42 | sd_pico/sd_spi.c 43 | sd_pico/spi.c 44 | ) 45 | add_subdirectory(../kernel kernel EXCLUDE_FROM_ALL) 46 | add_subdirectory(../fatfs fatfs EXCLUDE_FROM_ALL) 47 | add_subdirectory(../apps apps) 48 | 49 | target_include_directories(${PROJECT_NAME} BEFORE PUBLIC 50 | ../include 51 | sd_pico 52 | ) 53 | target_compile_definitions(${PROJECT_NAME} PRIVATE 54 | PICO_STACK_SIZE=32768 55 | ) 56 | 57 | # Create uf2 file. 58 | # Assume picotool is already installed. 59 | set(picotool_FOUND 1) 60 | pico_add_uf2_output(${PROJECT_NAME}) 61 | pico_add_dis_output(${PROJECT_NAME}) 62 | 63 | # Create .nm file. 64 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 65 | COMMAND arm-none-eabi-nm -n ${PROJECT_NAME}.elf > ${PROJECT_NAME}.nm 66 | VERBATIM 67 | ) 68 | 69 | # Link to pico_stdlib (gpio, time, etc. functions) 70 | target_link_libraries(${PROJECT_NAME} 71 | fpm_kernel 72 | fpm_fatfs 73 | pico_stdlib 74 | pico_aon_timer 75 | hardware_dma 76 | hardware_spi 77 | hardware_flash 78 | ) 79 | if(PICO_RP2040) 80 | target_link_libraries(${PROJECT_NAME} hardware_rtc) 81 | endif() 82 | 83 | # Get git commit hash and revision count 84 | execute_process( 85 | COMMAND git log -1 --format=%h 86 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 87 | OUTPUT_STRIP_TRAILING_WHITESPACE 88 | OUTPUT_VARIABLE GIT_COMMIT 89 | ) 90 | execute_process( 91 | COMMAND git rev-list HEAD --count 92 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 93 | OUTPUT_STRIP_TRAILING_WHITESPACE 94 | OUTPUT_VARIABLE GIT_REVCOUNT 95 | ) 96 | set_source_files_properties(fpm_pico.c 97 | PROPERTIES COMPILE_FLAGS "-DGIT_REVCOUNT=\\\"${GIT_REVCOUNT}\\\" -DGIT_COMMIT=\\\"${GIT_COMMIT}\\\"" 98 | ) 99 | 100 | # Create build timestamp 101 | string(TIMESTAMP BUILD_YEAR "%Y") 102 | string(TIMESTAMP BUILD_MONTH "%m") 103 | string(TIMESTAMP BUILD_DAY "%d") 104 | string(TIMESTAMP BUILD_HOUR "%H") 105 | string(TIMESTAMP BUILD_MIN "%M") 106 | string(TIMESTAMP BUILD_SEC "%S") 107 | string(TIMESTAMP BUILD_DOTW "%w") 108 | math(EXPR BUILD_MONTH "${BUILD_MONTH} * 1") 109 | math(EXPR BUILD_DAY "${BUILD_DAY} * 1") 110 | math(EXPR BUILD_HOUR "${BUILD_HOUR} * 1") 111 | math(EXPR BUILD_MIN "${BUILD_MIN} * 1") 112 | math(EXPR BUILD_SEC "${BUILD_SEC} * 1") 113 | set_source_files_properties(rtc_pico.c PROPERTIES COMPILE_FLAGS 114 | "-DBUILD_YEAR=${BUILD_YEAR} \ 115 | -DBUILD_MONTH=${BUILD_MONTH} \ 116 | -DBUILD_DAY=${BUILD_DAY} \ 117 | -DBUILD_HOUR=${BUILD_HOUR} \ 118 | -DBUILD_MIN=${BUILD_MIN} \ 119 | -DBUILD_SEC=${BUILD_SEC} \ 120 | -DBUILD_DOTW=${BUILD_DOTW}" 121 | ) 122 | 123 | # Enable usb output, disable uart output 124 | pico_enable_stdio_usb(${PROJECT_NAME} 1) 125 | pico_enable_stdio_uart(${PROJECT_NAME} 0) 126 | 127 | include(flash_image.cmake) 128 | 129 | # 'make upload' to program the binary 130 | add_custom_target(upload 131 | picotool load -f -x ${PROJECT_NAME}-2mb.uf2 132 | COMMENT "picotool load -f -x ${PROJECT_NAME}-2mb.uf2" 133 | DEPENDS ${PROJECT_NAME} 134 | ) 135 | 136 | install(FILES 137 | ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.elf 138 | ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.uf2 139 | DESTINATION $ENV{HOME}/.local/lib/fpm 140 | ) 141 | -------------------------------------------------------------------------------- /include/fpm/diskio.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------/ 2 | / Low level disk interface modlue include file (C)ChaN, 2019 / 3 | /-----------------------------------------------------------------------*/ 4 | 5 | #ifndef FPM_DISKIO_H 6 | #define FPM_DISKIO_H 7 | 8 | #include 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | // 15 | // Number of volumes (logical drives) to be used, in range 1-10. 16 | // 17 | #define DISK_VOLUMES 2 18 | 19 | // 20 | // Names of disk volumes, like "flash" and "sd". 21 | // 22 | extern const char *disk_name[DISK_VOLUMES]; 23 | 24 | // 25 | // Bits of SD card status. 26 | // 27 | typedef int media_status_t; 28 | enum { 29 | MEDIA_NOINIT = 0x01, 30 | // Indicates that the device has not been initialized and not 31 | // ready to work. This flag is set on system reset, media removal 32 | // or failure of disk_initialize function. It is cleared on 33 | // disk_initialize function succeeded. Any media change that 34 | // occurs asynchronously must be captured and reflect it to the 35 | // status flags, or auto-mount function will not work correctly. 36 | // If the system does not support media change detection, 37 | // application program needs to explicitly re-mount the volume 38 | // with f_mount() function after each media change. 39 | 40 | MEDIA_NODISK = 0x02, 41 | // Indicates that no medium in the drive. This is always 42 | // cleared when the drive is non-removable class. 43 | // Note that FatFs does not refer this flag. 44 | 45 | MEDIA_PROTECT = 0x04, 46 | // Indicates that the medium is write protected. This is always 47 | // cleared when the drive has no write protect function. 48 | // Not valid if MEDIA_NODISK is set. 49 | }; 50 | 51 | // 52 | // Results of Disk Functions. 53 | // 54 | typedef enum { 55 | DISK_OK = 0, // Successful 56 | DISK_ERROR = 1, // R/W Error 57 | DISK_WRPRT = 2, // Write Protected 58 | DISK_NOTRDY = 3, // Not Ready 59 | DISK_PARERR = 4, // Invalid Parameter 60 | } disk_result_t; 61 | 62 | // 63 | // Information about the disk. 64 | // 65 | typedef struct { 66 | uint64_t num_bytes; // Size in bytes 67 | uint64_t serial_number; // Serial number, 32 bits or 64 bits 68 | char product_name[32]; // Product name 69 | } disk_info_t; 70 | 71 | // 72 | // Disk control functions. 73 | // 74 | void disk_setup(void); 75 | media_status_t disk_initialize(uint8_t pdrv); 76 | media_status_t disk_status(uint8_t pdrv); 77 | disk_result_t disk_read(uint8_t pdrv, uint8_t *buff, unsigned sector, unsigned count); 78 | disk_result_t disk_write(uint8_t pdrv, const uint8_t *buff, unsigned sector, unsigned count); 79 | disk_result_t disk_ioctl(uint8_t pdrv, uint8_t cmd, void *buff); 80 | disk_result_t disk_identify(uint8_t pdrv, disk_info_t *output); 81 | 82 | // 83 | // Command code for disk_ioctl() fucntion. 84 | // 85 | enum { 86 | // Generic command (Used by FatFs) 87 | CTRL_SYNC = 0, // Complete pending write process (needed at FF_FS_READONLY == 0) 88 | GET_SECTOR_COUNT = 1, // Get media size (needed at FF_USE_MKFS == 1) 89 | GET_SECTOR_SIZE = 2, // Get sector size (needed at FF_MAX_SS != FF_MIN_SS) 90 | GET_BLOCK_SIZE = 3, // Get erase block size (needed at FF_USE_MKFS == 1) 91 | CTRL_TRIM = 4, // Inform device that the data on the block of sectors is 92 | // no longer used (needed at FF_USE_TRIM == 1) 93 | 94 | // Generic command (Not used by FatFs) 95 | CTRL_POWER = 5, // Get/Set power status 96 | CTRL_LOCK = 6, // Lock/Unlock media removal 97 | CTRL_EJECT = 7, // Eject media 98 | CTRL_FORMAT = 8, // Create physical format on the media 99 | 100 | // MMC/SDC specific ioctl command 101 | MMC_GET_TYPE = 10, // Get card type 102 | MMC_GET_CSD = 11, // Get CSD 103 | MMC_GET_CID = 12, // Get CID 104 | MMC_GET_OCR = 13, // Get OCR 105 | MMC_GET_SDSTAT = 14, // Get SD status 106 | ISDIO_READ = 55, // Read data form SD iSDIO register 107 | ISDIO_WRITE = 56, // Write data to SD iSDIO register 108 | ISDIO_MRITE = 57, // Masked write data to SD iSDIO register 109 | 110 | // ATA/CF specific ioctl command 111 | ATA_GET_REV = 20, // Get F/W revision 112 | ATA_GET_MODEL = 21, // Get model name 113 | ATA_GET_SN = 22, // Get serial number 114 | }; 115 | 116 | #ifdef __cplusplus 117 | } 118 | #endif 119 | 120 | #endif // FPM_DISKIO_H 121 | -------------------------------------------------------------------------------- /tools/uf2fat/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "extern.h" 6 | 7 | // 8 | // CLI options. 9 | // 10 | static const struct option long_options[] = { 11 | // clang-format off 12 | { "help", no_argument, nullptr, 'h' }, 13 | { "version", no_argument, nullptr, 'V' }, 14 | { "output", required_argument, nullptr, 'o' }, 15 | {}, 16 | // clang-format on 17 | }; 18 | 19 | // 20 | // Print usage message. 21 | // 22 | static void print_usage(std::ostream &out, const char *prog_name) 23 | { 24 | out << "UF2 FAT Filesystem Tool, Version 0.1\n"; 25 | out << "Usage:\n"; 26 | out << " " << prog_name << " [options...] format input.uf2 size [contents]\n"; 27 | out << " " << prog_name << " [options...] dump input.uf2\n"; 28 | out << "Commands:\n"; 29 | out << " format Create filesystem with optional contents\n"; 30 | out << " dump Show contents of UF2 file\n"; 31 | out << "Arguments:\n"; 32 | out << " input.uf2 Input file in UF2 format\n"; 33 | out << " size Target size in bytes, with optional suffix k/M\n"; 34 | out << " contents Optional directory with filesystem contents\n"; 35 | out << "Options:\n"; 36 | out << " -o FILE, --output=FILE Write output to given file instead of input.uf2\n"; 37 | out << " -h, --help Display this help and exit\n"; 38 | out << " -V, --version Show version information and exit\n"; 39 | } 40 | 41 | // 42 | // Parse number with optional suffix k/M. 43 | // 44 | static size_t parse_size(std::string str) 45 | { 46 | std::size_t pos{}; 47 | auto num = std::stoul(str, &pos, 0); 48 | if (pos < str.size()) { 49 | // Scale by suffix. 50 | auto ch = str[pos]; 51 | if (ch == 'k' || ch == 'K') { 52 | num *= 1024; 53 | } else if (ch == 'm' || ch == 'M') { 54 | num *= 1024 * 1024; 55 | } else { 56 | std::cerr << "Bad size suffix: " << str << "\n"; 57 | exit(EXIT_FAILURE); 58 | } 59 | } 60 | return num; 61 | } 62 | 63 | // 64 | // Main routine of the simulator, 65 | // when invoked from a command line. 66 | // 67 | int main(int argc, char *argv[]) 68 | { 69 | // Get the program name. 70 | const char *prog_name = strrchr(argv[0], '/'); 71 | if (prog_name == nullptr) { 72 | prog_name = argv[0]; 73 | } else { 74 | prog_name++; 75 | } 76 | 77 | // Parse command line options. 78 | std::string command, input_filename, output_filename, contents_dir; 79 | size_t flash_bytes = 0; 80 | 81 | for (;;) { 82 | switch (getopt_long(argc, argv, "-hvo:", long_options, nullptr)) { 83 | case EOF: 84 | break; 85 | 86 | case 0: 87 | continue; 88 | 89 | case 1: 90 | // Regular argument. 91 | if (command.empty()) { 92 | command = optarg; 93 | } else if (input_filename.empty()) { 94 | input_filename = optarg; 95 | } else if (flash_bytes == 0) { 96 | flash_bytes = parse_size(optarg); 97 | } else if (contents_dir.empty()) { 98 | contents_dir = optarg; 99 | } else { 100 | print_usage(std::cout, prog_name); 101 | exit(EXIT_FAILURE); 102 | } 103 | continue; 104 | 105 | case 'h': 106 | // Show usage message and exit. 107 | print_usage(std::cout, prog_name); 108 | exit(EXIT_SUCCESS); 109 | 110 | case 'V': 111 | // Show version and exit. 112 | std::cout << "Version 0.1\n"; 113 | exit(EXIT_SUCCESS); 114 | 115 | case 'o': 116 | // Output file name. 117 | output_filename = optarg; 118 | continue; 119 | 120 | default: 121 | fail: print_usage(std::cerr, prog_name); 122 | exit(EXIT_FAILURE); 123 | } 124 | break; 125 | } 126 | 127 | // Must specify a command. 128 | if (command.empty() || input_filename.empty()) { 129 | goto fail; 130 | } 131 | 132 | if (command == "format") { 133 | if (flash_bytes == 0) { 134 | goto fail; 135 | } 136 | format_filesystem(input_filename, flash_bytes, contents_dir, output_filename); 137 | } else if (command == "dump") { 138 | dump_file(input_filename); 139 | } else { 140 | goto fail; 141 | } 142 | 143 | return EXIT_SUCCESS; 144 | } 145 | -------------------------------------------------------------------------------- /pico/sd_pico/sd_spi.c: -------------------------------------------------------------------------------- 1 | /* sd_spi.c 2 | Copyright 2021 Carl John Kugler III 3 | 4 | Licensed under the Apache License, Version 2.0 (the License); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | */ 14 | 15 | /* Standard includes. */ 16 | #include 17 | #include 18 | #include 19 | 20 | #include "hardware/gpio.h" 21 | 22 | #include "sd_card.h" 23 | #include "sd_spi.h" 24 | #include "spi.h" 25 | 26 | #if 1 27 | // No trace output 28 | #define TRACE_PRINTF(fmt, args...) 29 | #else 30 | // Enable trace output 31 | #define TRACE_PRINTF fpm_printf 32 | #endif 33 | 34 | void sd_spi_go_high_frequency(sd_card_t *pSD) 35 | { 36 | uint actual __attribute__((unused)) = spi_set_baudrate(pSD->spi->hw_inst, pSD->spi->baud_rate); 37 | TRACE_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual); 38 | } 39 | 40 | void sd_spi_go_low_frequency(sd_card_t *pSD) 41 | { 42 | uint actual __attribute__((unused)) = spi_set_baudrate(pSD->spi->hw_inst, 400 * 1000); // Actual frequency: 398089 43 | TRACE_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual); 44 | } 45 | 46 | static void sd_spi_lock(sd_card_t *pSD) 47 | { 48 | spi_lock(pSD->spi); 49 | } 50 | 51 | static void sd_spi_unlock(sd_card_t *pSD) 52 | { 53 | spi_unlock(pSD->spi); 54 | } 55 | 56 | // Would do nothing if pSD->ss_gpio were set to GPIO_FUNC_SPI. 57 | static void sd_spi_select(sd_card_t *pSD) 58 | { 59 | gpio_put(pSD->ss_gpio, 0); 60 | // A fill byte seems to be necessary, sometimes: 61 | uint8_t fill = SPI_FILL_CHAR; 62 | spi_write_blocking(pSD->spi->hw_inst, &fill, 1); 63 | 64 | #ifdef SPI_LED_PIN 65 | // LED on. 66 | gpio_put(SPI_LED_PIN, 1); 67 | #endif 68 | } 69 | 70 | static void sd_spi_deselect(sd_card_t *pSD) 71 | { 72 | gpio_put(pSD->ss_gpio, 1); 73 | #ifdef SPI_LED_PIN 74 | // LED off. 75 | gpio_put(SPI_LED_PIN, 0); 76 | #endif 77 | /* 78 | MMC/SDC enables/disables the DO output in synchronising to the SCLK. This 79 | means there is a posibility of bus conflict with MMC/SDC and another SPI 80 | slave that shares an SPI bus. Therefore to make MMC/SDC release the MISO 81 | line, the master device needs to send a byte after the CS signal is 82 | deasserted. 83 | */ 84 | uint8_t fill = SPI_FILL_CHAR; 85 | spi_write_blocking(pSD->spi->hw_inst, &fill, 1); 86 | } 87 | 88 | /* Some SD cards want to be deselected between every bus transaction */ 89 | void sd_spi_deselect_pulse(sd_card_t *pSD) 90 | { 91 | sd_spi_deselect(pSD); 92 | // tCSH Pulse duration, CS high 200 ns 93 | sd_spi_select(pSD); 94 | } 95 | 96 | void sd_spi_acquire(sd_card_t *pSD) 97 | { 98 | sd_spi_lock(pSD); 99 | sd_spi_select(pSD); 100 | } 101 | 102 | void sd_spi_release(sd_card_t *pSD) 103 | { 104 | sd_spi_deselect(pSD); 105 | sd_spi_unlock(pSD); 106 | } 107 | 108 | bool sd_spi_transfer(sd_card_t *pSD, const uint8_t *tx, uint8_t *rx, size_t length) 109 | { 110 | return spi_transfer(pSD->spi, tx, rx, length); 111 | } 112 | 113 | uint8_t sd_spi_write(sd_card_t *pSD, const uint8_t value) 114 | { 115 | // TRACE_PRINTF("%s\n", __FUNCTION__); 116 | uint8_t received = SPI_FILL_CHAR; 117 | #if 0 118 | int num = spi_write_read_blocking(pSD->spi->hw_inst, &value, &received, 1); 119 | myASSERT(1 == num); 120 | #else 121 | bool success __attribute__((unused)) = spi_transfer(pSD->spi, &value, &received, 1); 122 | myASSERT(success); 123 | #endif 124 | return received; 125 | } 126 | 127 | void sd_spi_send_initializing_sequence(sd_card_t *pSD) 128 | { 129 | bool old_ss = gpio_get(pSD->ss_gpio); 130 | // Set DI and CS high and apply 74 or more clock pulses to SCLK: 131 | gpio_put(pSD->ss_gpio, 1); 132 | uint8_t ones[10]; 133 | memset(ones, 0xFF, sizeof ones); 134 | absolute_time_t timeout_time = make_timeout_time_ms(1); 135 | do { 136 | sd_spi_transfer(pSD, ones, NULL, sizeof ones); 137 | } while (0 < absolute_time_diff_us(get_absolute_time(), timeout_time)); 138 | gpio_put(pSD->ss_gpio, old_ss); 139 | } 140 | 141 | void sd_spi_init_pl022(sd_card_t *pSD) 142 | { 143 | // Let the PL022 SPI handle it. 144 | // the CS line is brought high between each byte during transmission. 145 | gpio_set_function(pSD->ss_gpio, GPIO_FUNC_SPI); 146 | } 147 | 148 | /* [] END OF FILE */ 149 | -------------------------------------------------------------------------------- /fatfs/examples/demo_alloc_contig.c: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------/ 2 | / Allocate a contiguous area to the file 3 | /-----------------------------------------------------------------------/ 4 | / This function checks if the file is contiguous with desired size. 5 | / If not, a block of contiguous sectors is allocated to the file. 6 | / If the file has been opened without FA_WRITE flag, it only checks if 7 | / the file is contiguous and returns the resulut. 8 | /-----------------------------------------------------------------------/ 9 | / This function can work with FatFs R0.09 - R0.11a. 10 | / It is incompatible with R0.12+. Use f_expand function instead. 11 | /----------------------------------------------------------------------*/ 12 | 13 | /* Declarations of FatFs internal functions accessible from applications. 14 | / This is intended to be used for disk checking/fixing or dirty hacks :-) */ 15 | uint32_t clust2sect(filesystem_t *fs, uint32_t clst); 16 | uint32_t get_fat(filesystem_t *fs, uint32_t clst); 17 | fs_result_t put_fat(filesystem_t *fs, uint32_t clst, uint32_t val); 18 | 19 | /* Returns the first sector in LBA (0:error or not contiguous) */ 20 | uint32_t allocate_contiguous_clusters(file_t *fp, /* Pointer to the open file object */ 21 | uint32_t len) /* Number of bytes to allocate */ 22 | { 23 | uint32_t csz, tcl, ncl, ccl, cl; 24 | 25 | if (f_lseek(fp, 0) || !len) /* Check if the given parameters are valid */ 26 | return 0; 27 | csz = 512UL * fp->fs->csize; /* Cluster size in unit of byte (assuming 512 bytes/sector) */ 28 | tcl = (len + csz - 1) / csz; /* Total number of clusters required */ 29 | len = tcl * csz; /* Round-up file size to the cluster boundary */ 30 | 31 | /* Check if the existing cluster chain is contiguous */ 32 | if (len == fp->fsize) { 33 | ncl = 0; 34 | ccl = fp->sclust; 35 | do { 36 | cl = get_fat(fp->fs, ccl); /* Get the cluster status */ 37 | if (cl + 1 < 3) 38 | return 0; /* Hard error? */ 39 | if (cl != ccl + 1 && cl < fp->fs->n_fatent) 40 | break; /* Not contiguous? */ 41 | ccl = cl; 42 | } while (++ncl < tcl); 43 | if (ncl == tcl) /* Is the file contiguous? */ 44 | return clust2sect(fp->fs, fp->sclust); /* File is contiguous. Return the start sector */ 45 | } 46 | 47 | /* File is not contiguous */ 48 | #if _FS_READONLY 49 | return 0; /* Exit if in read-only cfg. */ 50 | #else 51 | if (!(fp->flag & FA_WRITE)) 52 | return 0; /* Exit if the file object is for read-only */ 53 | 54 | if (f_truncate(fp)) 55 | return 0; /* Remove the non-contiguous chain */ 56 | 57 | /* Find a free contiguous area */ 58 | ccl = cl = 2; 59 | ncl = 0; 60 | do { 61 | if (cl >= fp->fs->n_fatent) 62 | return 0; /* No contiguous area is found. */ 63 | if (get_fat(fp->fs, cl)) { /* Encounterd a cluster in use */ 64 | do { /* Skip the block of used clusters */ 65 | cl++; 66 | if (cl >= fp->fs->n_fatent) 67 | return 0; /* No contiguous area is found. */ 68 | } while (get_fat(fp->fs, cl)); 69 | ccl = cl; 70 | ncl = 0; 71 | } 72 | cl++; 73 | ncl++; 74 | } while (ncl < tcl); 75 | 76 | /* Create a contiguous cluster chain */ 77 | fp->fs->last_clust = ccl - 1; 78 | if (f_lseek(fp, len)) 79 | return 0; 80 | 81 | return clust2sect(fp->fs, fp->sclust); /* Return file start sector */ 82 | #endif 83 | } 84 | 85 | int main(void) 86 | { 87 | fs_result_t fr; 88 | disk_result_t dr; 89 | filesystem_t *fs = alloca(f_sizeof_filesystem_t()); 90 | file_t *fil = alloca(f_sizeof_file_t()); 91 | uint32_t org; 92 | 93 | /* Open or create a file to write */ 94 | f_mount(fs, "", 0); 95 | fr = f_open(fil, "fastrec.log", FA_READ | FA_WRITE | FA_OPEN_ALWAYS); 96 | if (fr) 97 | return 1; 98 | 99 | /* Check if the file is 256MB in size and occupies a contiguous area. 100 | / If not, a contiguous area will be re-allocated to the file. */ 101 | org = allocate_contiguous_clusters(fil, 0x10000000); 102 | if (!org) { 103 | printf("Function failed due to any error or insufficient contiguous area.\n"); 104 | f_close(fil); 105 | return 1; 106 | } 107 | 108 | /* Now you can read/write the file without filesystem layer. */ 109 | ... dr = disk_write(fil->fs->drv, Buff, org, 1024); /* Write 512KiB from top of the file */ 110 | ... 111 | 112 | f_close(fil); 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /tests/tokenize_test.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Test fpm_tokenize() - split a command line into arguments. 3 | // 4 | #include 5 | #include 6 | 7 | static void tokenize_test(const char *input, int expect_argc, const char *expect_argv[], const char *expect_error) 8 | { 9 | char buffer[100]; 10 | char *argv[32] = { (char*)"some", (char*)"garbage" }; 11 | int argc = 12345; 12 | 13 | strncpy(buffer, input, sizeof(buffer)); 14 | const char *error = fpm_tokenize(argv, &argc, buffer); 15 | 16 | if (expect_error) { 17 | // Error is expected. 18 | if (error) 19 | EXPECT_STREQ(error, expect_error); 20 | else 21 | FAIL() << "Error is expected, but got no error"; 22 | return; 23 | } 24 | 25 | // Check argc, argc. 26 | EXPECT_EQ(argc, expect_argc); 27 | for (int i = 0; i < argc && i < expect_argc; i++) { 28 | EXPECT_STREQ(argv[i], expect_argv[i]); 29 | } 30 | } 31 | 32 | // 33 | // "" -> nothing 34 | // 35 | TEST(tokenize, empty_input) 36 | { 37 | tokenize_test("", 0, 0, 0); 38 | } 39 | 40 | // 41 | // " " -> nothing 42 | // 43 | TEST(tokenize, spaces_only) 44 | { 45 | tokenize_test(" ", 0, 0, 0); 46 | } 47 | 48 | // 49 | // "foo" -> "foo" 50 | // 51 | TEST(tokenize, one_word) 52 | { 53 | const char *expect_argv[] = { "foo" }; 54 | tokenize_test("foo", 1, expect_argv, 0); 55 | } 56 | 57 | // 58 | // " foo" -> "foo" 59 | // 60 | TEST(tokenize, spaces_at_beginning) 61 | { 62 | const char *expect_argv[] = { "foo" }; 63 | tokenize_test(" foo", 1, expect_argv, 0); 64 | } 65 | 66 | // 67 | // "foo " -> "foo" 68 | // 69 | TEST(tokenize, spaces_at_end) 70 | { 71 | const char *expect_argv[] = { "foo" }; 72 | tokenize_test("foo ", 1, expect_argv, 0); 73 | } 74 | 75 | // 76 | // "abra ca dabra" -> "abra", "ca", "dabra" 77 | // 78 | TEST(tokenize, three_words) 79 | { 80 | const char *expect_argv[] = { "abra", "ca", "dabra" }; 81 | tokenize_test("abra ca dabra", 3, expect_argv, 0); 82 | } 83 | 84 | // 85 | // "abra ca dabra" -> "abra", "ca", "dabra" 86 | // 87 | TEST(tokenize, extra_spaces) 88 | { 89 | const char *expect_argv[] = { "abra", "ca", "dabra" }; 90 | tokenize_test("abra ca dabra", 3, expect_argv, 0); 91 | } 92 | 93 | // 94 | // "\" -> error 'Incomplete backslash' 95 | // 96 | TEST(tokenize, incomplete_backslash) 97 | { 98 | tokenize_test("\\", 0, 0, "Incomplete backslash"); 99 | } 100 | 101 | // 102 | // "foo\bar" -> "foobar" 103 | // 104 | TEST(tokenize, backslash_char) 105 | { 106 | const char *expect_argv[] = { "foobar" }; 107 | tokenize_test("foo\\bar", 1, expect_argv, 0); 108 | } 109 | 110 | // 111 | // " \ \ b" -> " ", " b" 112 | // 113 | TEST(tokenize, backslash_space_after_space) 114 | { 115 | const char *expect_argv[] = { " ", " b", }; 116 | tokenize_test(" \\ \\ b", 2, expect_argv, 0); 117 | } 118 | 119 | // 120 | // "foo\ bar\ b" -> "foo bar b" 121 | // 122 | TEST(tokenize, backslash_space_after_char) 123 | { 124 | const char *expect_argv[] = { "foo bar b", }; 125 | tokenize_test("foo\\ bar\\ b", 1, expect_argv, 0); 126 | } 127 | 128 | // 129 | // "foo'bar" -> error 'Unterminated apostrophe' 130 | // 131 | TEST(tokenize, unterminated_apostrophe) 132 | { 133 | tokenize_test("foo'bar", 0, 0, "Unterminated apostrophe"); 134 | } 135 | 136 | // 137 | // 'foo"bar' -> error 'Unterminated quote' 138 | // 139 | TEST(tokenize, unterminated_quote) 140 | { 141 | tokenize_test("foo\"bar", 0, 0, "Unterminated quote"); 142 | } 143 | 144 | // 145 | // "foo b'a'r" -> "foo", "bar" 146 | // 147 | TEST(tokenize, valid_apostrophe) 148 | { 149 | const char *expect_argv[] = { "foo", "bar", }; 150 | tokenize_test("'foo' b'a'r", 2, expect_argv, 0); 151 | } 152 | 153 | // 154 | // '"foo" b"a"r' -> "foo", "bar" 155 | // 156 | TEST(tokenize, valid_quote) 157 | { 158 | const char *expect_argv[] = { "foo", "bar", }; 159 | tokenize_test("\"foo\" b\"a\"r", 2, expect_argv, 0); 160 | } 161 | 162 | // 163 | // '"fo'o" b"'a'"r' -> "fo'o", "b'a'r" 164 | // 165 | TEST(tokenize, apostrophe_inside_quotes) 166 | { 167 | const char *expect_argv[] = { "fo'o", "b'a'r", }; 168 | tokenize_test("\"fo'o\" b\"'a'\"r", 2, expect_argv, 0); 169 | } 170 | 171 | // 172 | // "'fo"o' b'"a"'r' -> "fo"o", "b"a"r" 173 | // 174 | TEST(tokenize, quote_inside_apostrophes) 175 | { 176 | const char *expect_argv[] = { "fo\"o", "b\"a\"r", }; 177 | tokenize_test("'fo\"o' b'\"a\"'r", 2, expect_argv, 0); 178 | } 179 | 180 | TEST(tokenize, masked_apostrophe) 181 | { 182 | const char *expect_argv[] = { "fo'o", "bar'", }; 183 | tokenize_test("fo\\'o bar\\'", 2, expect_argv, 0); 184 | } 185 | 186 | TEST(tokenize, masked_quote) 187 | { 188 | const char *expect_argv[] = { "fo\"o", "bar\"", }; 189 | tokenize_test("fo\\\"o bar\\\"", 2, expect_argv, 0); 190 | } 191 | -------------------------------------------------------------------------------- /apps/rz/zheaders.h: -------------------------------------------------------------------------------- 1 | // 2 | // Routines for working with Zmodem headers. 3 | // 4 | // Copyright (c) 2020 Ross Bamford 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | // 31 | // One-shot calculate the CRC for a ZHDR and set the 32 | // crc1 and crc2 fields appropriately. 33 | // 34 | void zm_calc_hdr_crc(ZHDR *hdr); 35 | 36 | uint16_t zm_calc_data_crc(uint8_t *buf, uint16_t len); 37 | uint32_t zm_calc_data_crc32(uint8_t *buf, uint16_t len); 38 | 39 | // 40 | // Converts ZHDR to wire-format hex header. Expects CRC is already 41 | // computed. Result placed in the supplied buffer. 42 | // 43 | // The encoded header includes the 'B' header-type character and 44 | // trailing CRLF, but does not include other Zmodem control 45 | // characters (e.g. leading ZBUF/ZDLE etc). 46 | // 47 | // Returns actual used length (max 0xff bytes), or OUT_OF_SPACE 48 | // if the supplied buffer is not large enough. 49 | // 50 | ZRESULT zm_to_hex_header(ZHDR *hdr, uint8_t *buf, int max_len); 51 | 52 | ZRESULT zm_check_header_crc16(ZHDR *hdr, uint16_t crc); 53 | ZRESULT zm_check_header_crc32(ZHDR *hdr, uint32_t crc); 54 | 55 | #ifdef ZDEBUG 56 | // this is wasteful, but only if debugging is on, so, y'know... 57 | static const char *__hdrtypes[] 58 | __attribute__((unused)) = { "ZRQINIT", "ZRINIT", "ZSINIT", "ZACK", "ZFILE", 59 | "ZSKIP", "ZNAK", "ZABORT", "ZFIN", "ZRPOS", 60 | "ZDATA", "ZEOF", "ZERR", "ZCRC", "ZCHALLENGE", 61 | "ZCOMPL", "ZCAN", "ZFREECOUNT", "ZCOMMAND", "ZSTDERR" }; 62 | 63 | #define DEBUG_DUMPHDR_F(hdr) \ 64 | DEBUGF("DEBUG: Header type [%s]:\n", __hdrtypes[hdr->type]); \ 65 | DEBUGF(" type: 0x%02x\n", hdr->type); \ 66 | DEBUGF(" f0: 0x%02x\n", hdr->flags.f0); \ 67 | DEBUGF(" f1: 0x%02x\n", hdr->flags.f1); \ 68 | DEBUGF(" f2: 0x%02x\n", hdr->flags.f2); \ 69 | DEBUGF(" f3: 0x%02x\n", hdr->flags.f3); \ 70 | DEBUGF(" crc1: 0x%02x\n", hdr->crc1); \ 71 | DEBUGF(" crc2: 0x%02x\n", hdr->crc2); \ 72 | DEBUGF(" RES: 0x%02x\n", hdr->PADDING); \ 73 | DEBUGF("\n"); 74 | 75 | #define DEBUG_DUMPHDR_P(hdr) \ 76 | DEBUGF("DEBUG: Header type [%s]:\n", __hdrtypes[hdr->type]); \ 77 | DEBUGF(" type: 0x%02x\n", hdr->type); \ 78 | DEBUGF(" p0: 0x%02x\n", hdr->position.p0); \ 79 | DEBUGF(" p1: 0x%02x\n", hdr->position.p1); \ 80 | DEBUGF(" p2: 0x%02x\n", hdr->position.p2); \ 81 | DEBUGF(" p3: 0x%02x\n", hdr->position.p3); \ 82 | DEBUGF(" crc1: 0x%02x\n", hdr->crc1); \ 83 | DEBUGF(" crc2: 0x%02x\n", hdr->crc2); \ 84 | DEBUGF(" RES: 0x%02x\n", hdr->PADDING); \ 85 | DEBUGF("\n"); 86 | 87 | #define DEBUG_DUMPHDR_R(hdr) \ 88 | DEBUGF("DEBUG: Header received [%s]:\n", __hdrtypes[hdr->type]); \ 89 | DEBUGF(" type: 0x%02x\n", hdr->type); \ 90 | DEBUGF(" p0/f3: 0x%02x\n", hdr->position.p0); \ 91 | DEBUGF(" p1/f2: 0x%02x\n", hdr->position.p1); \ 92 | DEBUGF(" p2/f1: 0x%02x\n", hdr->position.p2); \ 93 | DEBUGF(" p3/f0: 0x%02x\n", hdr->position.p3); \ 94 | DEBUGF(" crc1: 0x%02x\n", hdr->crc1); \ 95 | DEBUGF(" crc2: 0x%02x\n", hdr->crc2); \ 96 | DEBUGF(" crc3: 0x%02x\n", hdr->crc3); \ 97 | DEBUGF(" crc4: 0x%02x\n", hdr->crc4); \ 98 | DEBUGF(" RES: 0x%02x\n", hdr->PADDING); \ 99 | DEBUGF("\n"); 100 | 101 | #define DEBUG_DUMPHDR DEBUG_DUMPHDR_F 102 | #else 103 | #define DEBUG_DUMPHDR_F(hdr) 104 | #define DEBUG_DUMPHDR_P(hdr) 105 | #define DEBUG_DUMPHDR_R(hdr) 106 | #define DEBUG_DUMPHDR(hdr) 107 | #endif 108 | 109 | #ifdef __cplusplus 110 | } 111 | #endif 112 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set minimum required version of CMake 2 | cmake_minimum_required(VERSION 3.12) 3 | 4 | # Set name of project and C/C++ standards 5 | project(fpm-test C CXX) 6 | set(CMAKE_C_STANDARD 11) 7 | set(CMAKE_CXX_STANDARD 17) 8 | add_compile_options(-Wall -Werror) 9 | 10 | add_subdirectory(../kernel kernel EXCLUDE_FROM_ALL) 11 | add_subdirectory(../fatfs fatfs EXCLUDE_FROM_ALL) 12 | add_subdirectory(../tools/elfexe elfexe) 13 | 14 | # 15 | # Download GoogleTest 16 | # 17 | include(FetchContent) 18 | FetchContent_Declare( 19 | googletest 20 | GIT_REPOSITORY https://github.com/google/googletest.git 21 | GIT_TAG v1.15.2 22 | EXCLUDE_FROM_ALL 23 | ) 24 | FetchContent_MakeAvailable(googletest) 25 | include(GoogleTest) 26 | enable_testing() 27 | 28 | # 29 | # Common includes and libraries for all tests. 30 | # 31 | include_directories(BEFORE ../include) 32 | link_libraries(gtest gtest_main) 33 | 34 | # 35 | # Check fpm_editline() routine. 36 | # 37 | add_executable(editline_tests 38 | editline_test.cpp 39 | ../kernel/fpm_editline.c 40 | ../kernel/fpm_puts.c 41 | ../kernel/fpm_wputs.c 42 | ../kernel/fpm_strwlen.c 43 | ../kernel/fpm_getwch.c 44 | ../kernel/fpm_getkey.c 45 | ../kernel/fpm_putwch.c 46 | ../kernel/fpm_strlcpy.c 47 | ) 48 | gtest_discover_tests(editline_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 49 | 50 | # 51 | # Check fpm_tokenize() routine. 52 | # 53 | add_executable(tokenize_tests 54 | tokenize_test.cpp 55 | ../kernel/fpm_tokenize.c 56 | ) 57 | gtest_discover_tests(tokenize_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 58 | 59 | # 60 | # Check fpm_getopt() routine. 61 | # 62 | add_executable(getopt_tests 63 | getopt_test.cpp 64 | ../kernel/fpm_getopt.c 65 | ) 66 | gtest_discover_tests(getopt_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 67 | 68 | # 69 | # Check fpm_get_dotw() routine. 70 | # 71 | add_executable(dotw_tests 72 | dotw_test.cpp 73 | ../kernel/fpm_get_dotw.c 74 | ) 75 | gtest_discover_tests(dotw_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 76 | 77 | # 78 | # Check fpm_fatfs() routine. 79 | # 80 | add_executable(fatfs_tests 81 | fatfs_test.cpp 82 | ../fatfs/fatfs.c 83 | ../fatfs/unicode.c 84 | ) 85 | gtest_discover_tests(fatfs_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 86 | 87 | # 88 | # Check cp/copy command. 89 | # 90 | add_executable(copy_tests 91 | copy_test.cpp 92 | fs_util.cpp 93 | console_util.cpp 94 | ../fatfs/fatfs.c 95 | ../fatfs/unicode.c 96 | ../kernel/cmd/cmd_copy.c 97 | ) 98 | target_link_libraries(copy_tests 99 | fpm_kernel 100 | fpm_fatfs 101 | ) 102 | gtest_discover_tests(copy_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 103 | 104 | # 105 | # Check memory allocation: fpm_alloc() and others. 106 | # 107 | add_executable(alloc_tests 108 | alloc_test.cpp 109 | console_util.cpp 110 | ../kernel/fpm_alloc.c 111 | ) 112 | gtest_discover_tests(alloc_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 113 | 114 | # 115 | # Check dynamic loader. 116 | # 117 | add_executable(loader_tests 118 | loader_test.cpp 119 | console_util.cpp 120 | ../kernel/fpm_loader.c 121 | ../unix/loader_unix.c 122 | ) 123 | gtest_discover_tests(loader_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 124 | 125 | add_executable(loader2_tests 126 | loader2_test.cpp 127 | console_util.cpp 128 | ../kernel/fpm_loader.c 129 | ../unix/loader_unix.c 130 | ) 131 | gtest_discover_tests(loader2_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 132 | 133 | # 134 | # Check Zmodem protocol. 135 | # 136 | add_executable(zmodem_tests 137 | zmodem_test.cpp 138 | ../apps/rz/znumbers.c 139 | ../apps/rz/zserial.c 140 | ../apps/rz/zheaders.c 141 | ../apps/rz/crc.c 142 | console_util.cpp 143 | ) 144 | target_include_directories(zmodem_tests BEFORE PUBLIC 145 | ../apps/rz 146 | ) 147 | gtest_discover_tests(zmodem_tests EXTRA_ARGS --gtest_repeat=1 PROPERTIES TIMEOUT 120) 148 | 149 | # Create -I options for exe build. 150 | set(EXE_INCLUDES -I${CMAKE_SOURCE_DIR}/../include) 151 | if (APPLE) 152 | # Get MacOS SDK path 153 | execute_process(COMMAND xcrun --show-sdk-path 154 | OUTPUT_VARIABLE SDK_PATH 155 | OUTPUT_STRIP_TRAILING_WHITESPACE) 156 | list(APPEND EXE_INCLUDES -I${SDK_PATH}/usr/include) 157 | 158 | # Set toolchain prefix. 159 | set(P x86_64-elf-) 160 | endif() 161 | 162 | # 163 | # Build exe binaries for tests. 164 | # 165 | add_custom_command(OUTPUT hello.exe 166 | COMMAND ${P}gcc -fPIC -g -O1 -c ${EXE_INCLUDES} ${CMAKE_CURRENT_SOURCE_DIR}/hello.c -o hello.o 167 | COMMAND ${P}ld -shared -fPIC -g -e main hello.o -o hello.exe 168 | COMMAND elfexe/elfexe hello.exe 169 | COMMAND ${P}objdump -d hello.exe > hello.dis 170 | COMMAND ${P}nm -n hello.exe > hello.nm 171 | COMMAND ${P}readelf -a hello.exe > hello.readelf 172 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/hello.c elfexe 173 | VERBATIM 174 | ) 175 | add_custom_command(OUTPUT testputs.exe 176 | COMMAND ${P}gcc -fPIC -g -O1 -c ${EXE_INCLUDES} ${CMAKE_CURRENT_SOURCE_DIR}/testputs.c -o testputs.o 177 | COMMAND ${P}ld -shared -fPIC -g -e main testputs.o -o testputs.exe 178 | COMMAND elfexe/elfexe testputs.exe 179 | COMMAND ${P}objdump -d testputs.exe > testputs.dis 180 | COMMAND ${P}nm -n testputs.exe > testputs.nm 181 | COMMAND ${P}readelf -a testputs.exe > testputs.readelf 182 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/testputs.c elfexe 183 | VERBATIM 184 | ) 185 | add_custom_target(generate-exe ALL DEPENDS hello.exe testputs.exe) 186 | -------------------------------------------------------------------------------- /docs/Getting-Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Prerequisites 4 | 5 | To play with FP/M you will need the following: 6 | 7 | * A development board with RP2040 chip. [Raspberry Pi Pico suits](https://www.raspberrypi.com/products/raspberry-pi-pico/) just fine. 8 | Full list of supported boards is available on [Supported Platforms](docs/Supported-Platforms.md) page. 9 | Boards with SD card are more fun. 10 | * A computer with USB port and appropriate cable to connect to USB port of the development board. 11 | * [Minicom](https://en.wikipedia.org/wiki/Minicom) or any other terminal emulator installed in your computer. 12 | * Many RP2040 development boards have a slot for micro-SD card. 13 | You can use any SD card, preformatted to FAT-32 or exFAT filesystem. 14 | An SD card is not required for the FP/M, but it allows you to easily 15 | transfer files between the internal Flash file system and an external computer. 16 | 17 | ## Install FP/M 18 | 19 | * Download file [fpm-rp2040-2mb.uf2](https://github.com/fp-m/fpm-embedded/releases/download/v0.1.0/fpm-rp2040-2mb.uf2). 20 | This image is suitable for all development boards. 21 | It provides about 2 Mbytes of space on Flash filesystem. 22 | In case you have a Flash chip of larger size, images `fpm-rp2040-8mb.uf2` 23 | and ``fpm-rp2040-16mb.uf2` are also available. 24 | * Upload the firmware image onto your board. There are two ways: 25 | * Press and hold BOOTSEL button, and power on or reset the board. 26 | The RP2040 now runs the RP2040 factory bootloader. 27 | An USB drive named RPI-RP2 should appear on your computer. 28 | Copy the .uf2 firmware file onto the USB drive. 29 | Eject the USB drive. At this step, the firmware should be uploaded and running. 30 | * In case you have a `picotool` utility installed on your computer, 31 | use: "picotool load -f -x fpm-rp2040-2mb.uf2". 32 | * Connect via USB to the console port at speed 115200 bit/sec. 33 | Use minicom or any other terminal emulator. 34 | I recommend a color mode. 35 | Name of virtual serial port varies on different platforms. 36 | On my machine the required command is "minicom -con -D /dev/tty.usbmodem142101". 37 | Press Enter. You should see prompt "flash:/ >". 38 | 39 | ## Internal commands 40 | 41 | Type `help` and press Enter. You will see: 42 | ``` 43 | flash:/ > help 44 | FP/M built-in commands are: 45 | cat or type Display the contents of a text file 46 | cd Show or change current directory 47 | clear or cls Clear the console screen 48 | cp or copy Copy files or directories 49 | date Show or change the system date 50 | echo Copy text directly to the console output 51 | eject Release removable disk device 52 | format Create filesystem on a disk device 53 | help or ? Show all built-in commands 54 | ls or dir List the contents of a directory 55 | mkdir Create a directory 56 | mount Engage removable disk device 57 | mv or rename Rename or move files and directories 58 | reboot Restart the FP/M kernel 59 | rm or erase Delete a file or set of files 60 | rmdir Remove a directory 61 | time Set or show the current system time 62 | ver Show the version of FP/M software 63 | vol Show the volume label of a disk device 64 | exit Close down the command interpreter 65 | 66 | Enter 'command -h' for more information on any of the above commands. 67 | ``` 68 | These commands are built into the command interpreter and always available. 69 | 70 | ## External commands 71 | 72 | Type `ls /bin`. You get a list of executables which reside in Flash memory. 73 | ``` 74 | flash:/ > ls bin 75 | cmd.exe free.exe hello.exe printf.exe 76 | ``` 77 | 78 | Try hello.exe: 79 | ``` 80 | flash:/ > hello 81 | Hello, World! 82 | ``` 83 | 84 | External commands can be executed only from `flash:/bin` directory. 85 | You can put more *.exe binaries there by copying from SD card. 86 | 87 | ## Line Editing 88 | 89 | Previous command can be recalled from history by Up Arrow key. 90 | The command line can be edited in place. Use Left/Right arrows 91 | and Home/End keys to navigate. ALternatively, use ^B/^F/^H/^E keys. 92 | 93 | ## Mounting SD Card 94 | 95 | After inserting the SD card, use the `mount` command to make it accessible. 96 | ``` 97 | mount sd: 98 | ``` 99 | View all mounted filesystems by running mount without any arguments. 100 | For example: 101 | ``` 102 | flash:/ > mount 103 | Drive Type 1k-blocks Used Available Capacity 104 | flash: FAT-12 16192 68 16124 0% 105 | sd: exFAT 15183872 6816 15177056 0% 106 | ``` 107 | To unmount a filesystem, use `eject` command: 108 | ``` 109 | eject sd: 110 | ``` 111 | 112 | ## Formatting the SD Card 113 | 114 | If your SD card does not currently have a FAT filesystem, you must format it before proceeding. 115 | Use the `format` command: This will erase all existing data on the SD card. 116 | For example: 117 | ``` 118 | flash:/ > format sd: 119 | Do you really want to format drive sd? 120 | Disk 'PH SD04G', size 3796.0 Mbytes. 121 | All data on the disk will be lost. 122 | Confirm? y/n [n] y 123 | Disk sd formatted. 124 | 125 | flash:/ > mount sd: 126 | flash:/ > mount 127 | Drive Type 1k-blocks Used Available Capacity 128 | flash: FAT-12 16192 68 16124 0% 129 | sd: FAT-32 3887072 544 3886528 0% 130 | ``` 131 | 132 | # Copying files 133 | 134 | Use `cp` command to copy files between `flash:` drive and `sd:` drive. 135 | With option `-r` it can copy directories recursively. 136 | -------------------------------------------------------------------------------- /kernel/cmd/cmd_rename.c: -------------------------------------------------------------------------------- 1 | // 2 | // Rename files or directories 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // 10 | // Options for renaming. 11 | // 12 | typedef struct { 13 | bool force; // do not ask for confirmation 14 | bool verbose; // show files as they are renamed 15 | } options_t; 16 | 17 | // 18 | // Rename one file. 19 | // 20 | static void rename_file(const char *source, const char *destination, const options_t *options) 21 | { 22 | fs_result_t result; 23 | 24 | // If file exists, ask user if it should be replaced. 25 | if (!options->force) { 26 | file_info_t info; 27 | result = f_stat(destination, &info); 28 | if (result == FR_OK) { 29 | 30 | // Prompt only if source exist. 31 | result = f_stat(source, &info); 32 | if (result != FR_OK) { 33 | fpm_printf("%s: %s\r\n", source, f_strerror(result)); 34 | return; 35 | } 36 | 37 | // Ask user. 38 | char prompt[32 + strlen(destination)]; 39 | uint16_t reply[32]; 40 | fpm_snprintf(prompt, sizeof(prompt), "Overwrite %s? y/n [n] ", destination); 41 | fpm_editline(reply, sizeof(reply), 1, prompt, 0); 42 | fpm_puts("\r\n"); 43 | 44 | if (reply[0] != 'y' && reply[0] != 'Y') { 45 | fpm_printf("Not overwritten.\r\n"); 46 | return; 47 | } 48 | } 49 | } 50 | 51 | result = f_rename(source, destination); 52 | if (result != FR_OK) { 53 | fpm_printf("%s -> %s: %s\r\n", source, destination, f_strerror(result)); 54 | return; 55 | } 56 | 57 | // Renamed successfully. 58 | if (options->verbose) 59 | fpm_printf("%s -> %s\r\n", source, destination); 60 | } 61 | 62 | // 63 | // Move a list of files into destination directory. 64 | // 65 | static void move_files_to_directory(const char *source[], unsigned num_sources, 66 | const char *dest_dir, const options_t *options) 67 | { 68 | unsigned baselen = strlen(dest_dir); 69 | char destination[baselen + FF_LFN_BUF + 2]; 70 | 71 | strcpy(destination, dest_dir); 72 | char *endp = &destination[baselen]; 73 | if (!baselen || endp[-1] != '/') { 74 | // Add trailing slash. 75 | *endp++ = '/'; 76 | ++baselen; 77 | } 78 | for (unsigned i = 0; i < num_sources; i++) { 79 | // 80 | // Find the last component of the source pathname. It 81 | // may have trailing slashes. 82 | // 83 | const char *p = source[i] + strlen(source[i]); 84 | while (p != source[i] && p[-1] == '/') 85 | --p; 86 | while (p != source[i] && p[-1] != '/') 87 | --p; 88 | 89 | strcpy(endp, p); 90 | rename_file(source[i], destination, options); 91 | } 92 | } 93 | 94 | void fpm_cmd_rename(int argc, char *argv[]) 95 | { 96 | static const struct fpm_option long_opts[] = { 97 | { "help", FPM_NO_ARG, NULL, 'h' }, 98 | {}, 99 | }; 100 | const char *destination = 0; 101 | const char *source[argc]; 102 | unsigned num_sources = 0; 103 | options_t options = {}; 104 | struct fpm_opt opt = {}; 105 | 106 | while (fpm_getopt(argc, argv, "fhv", long_opts, &opt) >= 0) { 107 | switch (opt.ret) { 108 | case 1: 109 | // Last argument is destination. 110 | if (destination != 0) { 111 | source[num_sources++] = destination; 112 | } 113 | destination = opt.arg; 114 | break; 115 | 116 | case 'f': 117 | options.force = true; 118 | break; 119 | 120 | case 'v': 121 | options.verbose = true; 122 | break; 123 | 124 | case '?': 125 | // Unknown option: message already printed. 126 | fpm_puts("\r\n"); 127 | return; 128 | 129 | case 'h': 130 | usage: fpm_puts("Usage:\r\n" 131 | " mv [options] filename ...\r\n" 132 | " rename [options] filename ...\r\n" 133 | "Options:\r\n" 134 | " -f Force, do not ask for confirmation to overwrite\r\n" 135 | " -v Verbose: show files as they are renamed\r\n" 136 | "\n"); 137 | return; 138 | } 139 | } 140 | 141 | if (destination == 0 || num_sources == 0) { 142 | goto usage; 143 | } 144 | 145 | // No disk name is allowed in destination. 146 | if (strchr(destination, ':') != 0) { 147 | fpm_printf("%s: cannot move across devices, sorry\r\n\n", destination); 148 | return; 149 | } 150 | 151 | file_info_t info = {}; 152 | fs_result_t result = f_stat(destination, &info); 153 | if (result == FR_INVALID_NAME) { 154 | // Cannot stat current directory - fake it. 155 | info.fattrib = AM_DIR; 156 | result = FR_OK; 157 | } 158 | if (result != FR_OK || !(info.fattrib & AM_DIR)) { 159 | // The destination doesn't exist or isn't a directory. 160 | // More than 2 arguments is an error in this case. 161 | if (num_sources != 1) { 162 | fpm_printf("%s is not a directory\r\n", destination); 163 | } else { 164 | // Rename one file. 165 | rename_file(source[0], destination, &options); 166 | } 167 | } else { 168 | move_files_to_directory(source, num_sources, destination, &options); 169 | } 170 | fpm_puts("\r\n"); 171 | } 172 | -------------------------------------------------------------------------------- /kernel/fpm_strlcpy.c: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 1998 Todd C. Miller 3 | // Copyright (c) 2023 Serge Vakulenko 4 | // 5 | // Permission to use, copy, modify, and distribute this software for any 6 | // purpose with or without fee is hereby granted, provided that the above 7 | // copyright notice and this permission notice appear in all copies. 8 | // 9 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | // 17 | #include 18 | 19 | // 20 | // Decode UTF-8 string. 21 | // 22 | size_t fpm_strlcpy_from_utf8(uint16_t *dst, const char *src, size_t nitems) 23 | { 24 | uint16_t *d = dst; 25 | const char *s = src; 26 | size_t n = nitems; 27 | 28 | // Copy as many bytes as will fit. 29 | if (n != 0) { 30 | while (--n != 0) { 31 | uint8_t c1 = *s++; 32 | if (c1 == '\0') { 33 | *d++ = 0; 34 | break; 35 | } 36 | 37 | // Decode utf-8 to unicode. 38 | if (! (c1 & 0x80)) { 39 | *d++ = c1; 40 | } else { 41 | // Read second byte. 42 | uint8_t c2 = *s++; 43 | if (! (c1 & 0x20)) { 44 | *d++ = (c1 & 0x1f) << 6 | (c2 & 0x3f); 45 | } else { 46 | // Read third byte. 47 | uint8_t c3 = *s++; 48 | *d++ = (c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 | (c3 & 0x3f); 49 | } 50 | } 51 | } 52 | } 53 | 54 | // Not enough room in dst, add NUL and traverse rest of src. 55 | if (n == 0) { 56 | if (nitems != 0) { 57 | *d = '\0'; // NUL-terminate dst 58 | } 59 | while (*s++) 60 | ; 61 | } 62 | 63 | return (s - src - 1); // count does not include NUL 64 | } 65 | 66 | // 67 | // Encode as UTF-8 string. 68 | // 69 | size_t fpm_strlcpy_to_utf8(char *dst, const uint16_t *src, size_t nitems) 70 | { 71 | char *d = dst; 72 | const uint16_t *s = src; 73 | size_t n = nitems; 74 | 75 | // Copy as many bytes as will fit. 76 | if (n != 0) { 77 | while (--n != 0) { 78 | uint16_t ch = *s++; 79 | if (ch == '\0') { 80 | *d++ = 0; 81 | break; 82 | } 83 | 84 | // Convert to UTF-8 encoding. 85 | if (ch < 0x80) { 86 | *d++ = ch; 87 | } else if (ch < 0x800) { 88 | if (n < 2) { 89 | *d++ = 0; 90 | break; 91 | } 92 | *d++ = ch >> 6 | 0xc0; 93 | *d++ = (ch & 0x3f) | 0x80; 94 | } else { 95 | if (n < 3) { 96 | *d++ = 0; 97 | break; 98 | } 99 | *d++ = ch >> 12 | 0xe0; 100 | *d++ = ((ch >> 6) & 0x3f) | 0x80; 101 | *d++ = (ch & 0x3f) | 0x80; 102 | } 103 | } 104 | } 105 | 106 | // Not enough room in dst, add NUL and traverse rest of src. 107 | if (n == 0) { 108 | if (nitems != 0) { 109 | *d = '\0'; // NUL-terminate dst 110 | } 111 | while (*s++) 112 | ; 113 | } 114 | 115 | return (s - src - 1); // count does not include NUL 116 | } 117 | 118 | // 119 | // Copy src to string dst of size nitems. At most nitems-1 characters 120 | // will be copied. Always NUL terminates (unless nitems == 0). 121 | // Returns strwlen(src); if retval >= nitems, truncation occurred. 122 | // 123 | size_t fpm_strlcpy_unicode(uint16_t *dst, const uint16_t *src, size_t nitems) 124 | { 125 | uint16_t *d = dst; 126 | const uint16_t *s = src; 127 | size_t n = nitems; 128 | 129 | // Copy as many bytes as will fit. 130 | if (n != 0) { 131 | while (--n != 0) { 132 | if ((*d++ = *s++) == '\0') { 133 | break; 134 | } 135 | } 136 | } 137 | 138 | // Not enough room in dst, add NUL and traverse rest of src. 139 | if (n == 0) { 140 | if (nitems != 0) { 141 | *d = '\0'; // NUL-terminate dst 142 | } 143 | while (*s++) 144 | ; 145 | } 146 | 147 | return (s - src - 1); // count does not include NUL 148 | } 149 | 150 | // 151 | // Copy src to string dst of size nitems. At most nitems-1 characters 152 | // will be copied. Always NUL terminates (unless nitems == 0). 153 | // Returns strlen(src); if retval >= nitems, truncation occurred. 154 | // 155 | size_t fpm_strlcpy(char *dst, const char *src, size_t nitems) 156 | { 157 | char *d = dst; 158 | const char *s = src; 159 | size_t n = nitems; 160 | 161 | // Copy as many bytes as will fit. 162 | if (n != 0) { 163 | while (--n != 0) { 164 | if ((*d++ = *s++) == '\0') { 165 | break; 166 | } 167 | } 168 | } 169 | 170 | // Not enough room in dst, add NUL and traverse rest of src. 171 | if (n == 0) { 172 | if (nitems != 0) { 173 | *d = '\0'; // NUL-terminate dst 174 | } 175 | while (*s++) 176 | ; 177 | } 178 | 179 | return (s - src - 1); // count does not include NUL 180 | } 181 | -------------------------------------------------------------------------------- /kernel/fpm_exec.c: -------------------------------------------------------------------------------- 1 | // 2 | // Execute a command or external program. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // 12 | // Debug options. 13 | // 14 | static const bool debug_trace = false; 15 | 16 | static const unsigned MIN_STACK_SIZE = 8*1024; 17 | 18 | static const char *find_exe(const char *cmdname, char *buf) 19 | { 20 | if (strchr(cmdname, '/') != NULL) { 21 | // Full path name or relative path name - use as is. 22 | return cmdname; 23 | } 24 | 25 | if (strchr(cmdname, '.') != NULL) { 26 | // Extension is explicitly present. 27 | return cmdname; 28 | } 29 | 30 | // Copy filename, append extension. 31 | strcpy(buf, cmdname); 32 | strcat(buf, ".exe"); 33 | 34 | // Check whether file exists. 35 | file_info_t info; 36 | fs_result_t result = f_stat(buf, &info); 37 | if (result == FR_OK) { 38 | return buf; 39 | } 40 | 41 | // Look in flash:/bin/ directory. 42 | strcpy(buf, "flash:/bin/"); 43 | strcat(buf, cmdname); 44 | strcat(buf, ".exe"); 45 | 46 | // Check whether file exists. 47 | result = f_stat(buf, &info); 48 | if (result == FR_OK) { 49 | return buf; 50 | } 51 | 52 | return NULL; 53 | } 54 | 55 | // 56 | // Execute internal command or external program with given arguments. 57 | // 58 | void fpm_exec(int argc, char *argv[]) 59 | { 60 | // Table of internal commands. 61 | typedef struct { 62 | const char *name; 63 | void (*func)(int argc, char *argv[]); 64 | } command_table_t; 65 | static const command_table_t cmd_tab[] = { 66 | { "?", fpm_cmd_help }, // also HELP 67 | { "cat", fpm_cmd_cat }, // also TYPE 68 | { "cd", fpm_cmd_cd }, // 69 | { "clear", fpm_cmd_clear }, // also CLS 70 | { "cls", fpm_cmd_clear }, // also CLEAR 71 | { "copy", fpm_cmd_copy }, // also CP 72 | { "cp", fpm_cmd_copy }, // also COPY 73 | { "date", fpm_cmd_date }, // 74 | { "dir", fpm_cmd_dir }, // also LS 75 | { "echo", fpm_cmd_echo }, // 76 | { "eject", fpm_cmd_eject }, // 77 | { "erase", fpm_cmd_remove }, // also RM 78 | { "format", fpm_cmd_format }, // 79 | { "help", fpm_cmd_help }, // also ? 80 | { "ls", fpm_cmd_dir }, // also DIR 81 | { "mkdir", fpm_cmd_mkdir }, // 82 | { "mount", fpm_cmd_mount }, // 83 | { "mv", fpm_cmd_rename }, // also RENAME 84 | { "reboot", fpm_cmd_reboot }, // 85 | { "rename", fpm_cmd_rename }, // also MV 86 | { "rm", fpm_cmd_remove }, // also ERASE 87 | { "rmdir", fpm_cmd_rmdir }, // 88 | { "time", fpm_cmd_time }, // 89 | { "type", fpm_cmd_cat }, // also CAT 90 | { "ver", fpm_cmd_ver }, // 91 | { "vol", fpm_cmd_vol }, // 92 | { 0, 0 }, 93 | }; 94 | 95 | if (debug_trace) { 96 | // Print command before execution. 97 | fpm_printf("[%d] ", argc); 98 | for (int i=0; i 0) 100 | fpm_putchar(' '); 101 | fpm_puts(argv[i]); 102 | } 103 | fpm_puts("\r\n"); 104 | } 105 | 106 | // Command ends with colon? 107 | if (argv[0][strlen(argv[0]) - 1] == ':') { 108 | // Switch to another drive. 109 | fs_result_t result = f_chdrive(argv[0]); 110 | if (result != FR_OK) { 111 | fpm_puts(f_strerror(result)); 112 | fpm_puts("\r\n\n"); 113 | } 114 | return; 115 | } 116 | 117 | // Find internal command. 118 | for (const command_table_t *p = cmd_tab; p->name; p++) { 119 | // Note: command name is case insensitive. 120 | if (strcasecmp(p->name, argv[0]) == 0) { 121 | p->func(argc, argv); 122 | return; 123 | } 124 | } 125 | 126 | // Find file path of external command. 127 | // Try .exe extension, search flash:/bin directory. 128 | unsigned const buf_size = strlen(argv[0]) + sizeof(".exe") + sizeof("flash:/bin"); 129 | char buf[buf_size]; 130 | const char *path = find_exe(argv[0], buf); 131 | if (!path) { 132 | fpm_puts(argv[0]); 133 | fpm_puts(": Command not found\r\n\n"); 134 | return; 135 | } 136 | 137 | // Allocate program context. 138 | fpm_context_t ctx; 139 | memset(&ctx, 0, sizeof(ctx)); 140 | if (!fpm_load(&ctx, path)) { 141 | // Failed: error message already printed. 142 | fpm_puts("\r\n"); 143 | return; 144 | } 145 | if (fpm_stack_available() < MIN_STACK_SIZE) { 146 | fpm_puts(argv[0]); 147 | fpm_puts(": No space for stack\r\n\n"); 148 | return; 149 | } 150 | if (!fpm_context_push(&ctx)) { 151 | fpm_puts(argv[0]); 152 | fpm_puts(": No space for heap\r\n\n"); 153 | return; 154 | } 155 | 156 | // Load external executable. 157 | bool success = fpm_invoke(&ctx, fpm_bindings, argc, argv); 158 | fpm_unload(&ctx); 159 | fpm_context_pop(); 160 | 161 | if (!success) { 162 | // Failed: error message already printed. 163 | fpm_puts("\r\n"); 164 | return; 165 | } 166 | 167 | // External binary successfully executed. 168 | //TODO: save ctx.exit_code somehow. 169 | fpm_puts("\r\n"); 170 | } 171 | 172 | #if 0 173 | //TODO: environment variables and commands 174 | PATH Set or show the search path 175 | SET Set or show environment variables 176 | PROMPT Change the command prompt 177 | 178 | //TODO: symbolic links 179 | MKLINK Create a symbolic link 180 | 181 | //TODO: scripts 182 | CHOICE Wait for an keypress from a selectable list 183 | PAUSE Suspend execution of a batch file 184 | #endif 185 | -------------------------------------------------------------------------------- /include/fpm/api.h: -------------------------------------------------------------------------------- 1 | // 2 | // API for FP/M. 3 | // 4 | #ifndef FPM_API_H 5 | #define FPM_API_H 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // 18 | // Size of command line in Unicode characters. 19 | // 20 | #define FPM_CMDLINE_SIZE 128 21 | 22 | // 23 | // Unicode symbols. 24 | // 25 | #define FPM_LEFTWARDS_ARROW 0x2190 // ← 26 | #define FPM_UPWARDS_ARROW 0x2191 // ↑ 27 | #define FPM_RIGHTWARDS_ARROW 0x2192 // → 28 | #define FPM_DOWNWARDS_ARROW 0x2193 // ↓ 29 | #define FPM_LEFTWARDS_TO_BAR 0x21E4 // ⇤ 30 | #define FPM_RIGHTWARDS_TO_BAR 0x21E5 // ⇥ 31 | #define FPM_DELETE_KEY 0x2421 // ␡ 32 | 33 | // 34 | // Last command line. 35 | // 36 | extern uint16_t fpm_history[FPM_CMDLINE_SIZE]; 37 | 38 | // 39 | // Output to the console (USB or Uart). 40 | // 41 | void fpm_putwch(uint16_t); 42 | void fpm_putchar(char); 43 | void fpm_puts(const char *); 44 | 45 | int fpm_printf(const char *, ...); 46 | int fpm_snprintf(char *, size_t, const char *, ...); 47 | int fpm_vprintf(const char *, va_list); 48 | int fpm_vsnprintf(char *, size_t, const char *, va_list); 49 | 50 | int fpm_sscanf(const char *, const char *, ...); 51 | int fpm_vsscanf(const char *, const char *, va_list); 52 | 53 | // 54 | // Get a Unicode character from console. 55 | // Decode escape sequences. 56 | // 57 | uint16_t fpm_getkey(void); 58 | 59 | // 60 | // Wait for a keycode character. 61 | // Returns: 62 | // - Unicode symbol, 16 bits 63 | // 64 | uint16_t fpm_getwch(void); 65 | 66 | // 67 | // Wait for a keycode character. 68 | // Returns: 69 | // - ASCII keycode 70 | // 71 | char fpm_getchar(void); 72 | 73 | // 74 | // Write the Unicode string to the console. 75 | // 76 | void fpm_wputs(const uint16_t *); 77 | 78 | // 79 | // The main line edit function 80 | // Parameters: 81 | // - buffer: Pointer to the line edit buffer 82 | // - buffer_length: Size of the buffer in bytes 83 | // - clear: Set to false to not clear, true to clear on entry 84 | // Returns: 85 | // - The exit key pressed (ESC or CR) 86 | // 87 | int fpm_editline(uint16_t *buffer, unsigned buffer_length, bool clear, 88 | const char *prompt, uint16_t *history); 89 | 90 | // 91 | // Parse a command line and split it into tokens (in place). 92 | // Return NULL on success. 93 | // Fill an argument vector. 94 | // On error, return a message. 95 | // 96 | const char *fpm_tokenize(char *argv[], int *argc, char *cmd_line); 97 | 98 | // 99 | // Compute length of Unicode string. 100 | // Return the number of characters that precede the terminating NUL character. 101 | // 102 | size_t fpm_strwlen(const uint16_t *str); 103 | 104 | // 105 | // Compute length of UTF-8 string. 106 | // Return the number of characters that precede the terminating NUL character. 107 | // 108 | size_t fpm_utf8len(const char *str); 109 | 110 | // 111 | // Size-bounded string copying. 112 | // 113 | size_t fpm_strlcpy(char *dst, const char *src, size_t nitems); 114 | size_t fpm_strlcpy_from_utf8(uint16_t *dst, const char *src, size_t nitems); 115 | size_t fpm_strlcpy_to_utf8(char *dst, const uint16_t *src, size_t nitems); 116 | size_t fpm_strlcpy_unicode(uint16_t *dst, const uint16_t *src, size_t nitems); 117 | 118 | // 119 | // Convert string to number. 120 | // Return true when value is out of range. 121 | // 122 | bool fpm_strtol(long *output, const char *str, char **endptr, int base); 123 | bool fpm_strtoul(unsigned long *output, const char *str, char **endptr, int base); 124 | bool fpm_strtod(double *output, const char *str, char **endptr); 125 | 126 | // 127 | // Print FP/M version. 128 | // 129 | void fpm_print_version(void); 130 | 131 | // 132 | // Get/set date and time. 133 | // 134 | void fpm_get_datetime(int *year, int *month, int *day, int *dotw, int *hour, int *min, int *sec); 135 | void fpm_set_datetime(int year, int month, int day, int hour, int min, int sec); 136 | 137 | // 138 | // Compute day of the week. 139 | // 140 | int fpm_get_dotw(int year, int month, int day); 141 | 142 | // 143 | // Reboot the processor. 144 | // 145 | void fpm_reboot(void); 146 | 147 | // 148 | // Execute internal command or external program with given arguments. 149 | // 150 | void fpm_exec(int argc, char *argv[]); 151 | 152 | // 153 | // Return the current 64-bit timestamp value in microseconds. 154 | // 155 | uint64_t fpm_time_usec(void); 156 | 157 | // 158 | // Busy wait for the given 64-bit number of microseconds. 159 | // 160 | void fpm_delay_usec(uint64_t delay_usec); 161 | 162 | // 163 | // Busy wait for the given number of milliseconds. 164 | // 165 | void fpm_delay_msec(unsigned delay_msec); 166 | 167 | // 168 | // Memory allocation. 169 | // 170 | void *fpm_alloc(size_t nbytes); 171 | void fpm_free(void *ptr); 172 | void *fpm_realloc(void *ptr, size_t nbytes); 173 | void *fpm_alloc_dirty(size_t nbytes); 174 | void fpm_truncate(void *ptr, size_t nbytes); 175 | size_t fpm_sizeof(void *ptr); 176 | size_t fpm_heap_available(void); 177 | size_t fpm_stack_available(void); 178 | 179 | // 180 | // Forbid standard routines. 181 | // 182 | long strtol(const char *str, char **endptr, int base) __attribute__((deprecated("use fpm_strtol() instead"))); 183 | unsigned long strtoul(const char *str, char **endptr, int base) __attribute__((deprecated("use fpm_strtoul() instead"))); 184 | double strtod(const char *str, char **endptr) __attribute__((deprecated("use fpm_strtod() instead"))); 185 | 186 | void *malloc(size_t size) __attribute__((deprecated("use fpm_alloc_dirty() instead"))); 187 | void free(void *ptr) __attribute__((deprecated("use fpm_free() instead"))); 188 | void *calloc(size_t count, size_t size) __attribute__((deprecated("use fpm_alloc() instead"))); 189 | void *realloc(void *ptr, size_t size) __attribute__((deprecated("use fpm_realloc() instead"))); 190 | 191 | // 192 | // Interactive shell. 193 | // 194 | void fpm_shell(void); 195 | 196 | #ifdef __cplusplus 197 | } 198 | #endif 199 | 200 | #endif // FPM_API_H 201 | --------------------------------------------------------------------------------