diff --git a/3rdPartyFiles.txt b/3rdPartyFiles.txt
new file mode 100644
index 0000000..69026bd
--- /dev/null
+++ b/3rdPartyFiles.txt
@@ -0,0 +1,73 @@
+Brian Sidebotham's - Raspberry-Pi Bare Metal Tutorials
+https://github.com/BrianSidebotham/arm-tutorial-rpi
+armc-cstartup.c
+armc-cstubs.c
+rpi-base.h
+rpi-interrupts.h
+rpi-interrupts.c
+rpi-mailbox.h
+rpi-mailbox.c
+
+David Banks' PiTubeDirect (GNU General Public License v3.0)
+https://github.com/hoglet67/PiTubeDirect
+defs.h
+cache.h
+cache.c
+armc-start.S
+startup.h
+rpi-aux.h
+rpi-aux.c
+rpi-gpio.h (added GpioDetect)
+rpi-gpio.c
+rpi-mailbox-interface.h (Added some missing ones)
+rpi-mailbox-interface.c
+linker.ld
+
+R Stange's USPi
+https://github.com/rsta2/uspi
+uspi\*.*
+exception.c
+interrupt.h
+interrupt.c
+Timer.h
+Timer.cpp
+
+R Stange's modified version of John Cronin's EMMC controller
+emmc.h
+emmc.cpp
+
+ChaN's Generic FAT file system R0.12b
+http://elm-chan.org/fsw/ff/00index_e.html
+ff.h
+ff.cpp
+diskio.h
+diskio.cpp
+
+Sean Barrett's stb_image (MIT) (used for loading PNGs)
+https://github.com/nothings/stb
+stb_image.h
+
+FFmpeg (LGPL v2.1+) (used for the font)
+https://github.com/FFmpeg/FFmpeg
+xga_font_data.h
+xga_font_data.c
+
+Pete Rittwage's (Markus Brenner's) NIB lib
+https://c64preservation.com/svn/nibtools/trunk
+gcr.h
+gcr.cpp
+prot.h
+prot.cpp
+
+Marcus Geelnard's LZ77 coder/decoder (GNU General Public License, version 2 or later)
+https://c64preservation.com/svn/nibtools/trunk/lz.c
+lz.h
+lz.c
+
+
+nbla000's CBM-FileBrowser_v1.6
+http://www.nightfallcrew.com/21/08/2013/cbm-filebrowser-v1-6-by-nbla000/
+
+
+
+
diff --git a/CBMFont.h b/CBMFont.h
new file mode 100644
index 0000000..229ae0f
--- /dev/null
+++ b/CBMFont.h
@@ -0,0 +1,259 @@
+const long int CMBFont_size = 4096;
+const unsigned char CMBFont[4096] = {
+ 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00, 0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00,
+ 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00,
+ 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00,
+ 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x00, 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00,
+ 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00,
+ 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00,
+ 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00,
+ 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E, 0x00,
+ 0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00, 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00,
+ 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00,
+ 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00,
+ 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00,
+ 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00,
+ 0x0C, 0x12, 0x30, 0x7C, 0x30, 0x62, 0xFC, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00,
+ 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00,
+ 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, 0x00,
+ 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, 0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00,
+ 0x3C, 0x66, 0x3C, 0x38, 0x67, 0x66, 0x3F, 0x00, 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00,
+ 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00,
+ 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00, 0x18, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7E, 0x00,
+ 0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E, 0x00, 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00,
+ 0x06, 0x0E, 0x1E, 0x66, 0x7F, 0x06, 0x06, 0x00, 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00,
+ 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 0x7E, 0x66, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x00,
+ 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00,
+ 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30,
+ 0x0E, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00,
+ 0x70, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x70, 0x00, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x08, 0x1C, 0x3E, 0x7F, 0x7F, 0x1C, 0x3E, 0x00,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+ 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0x38, 0x18, 0x18,
+ 0x18, 0x18, 0x1C, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x18, 0x18, 0x38, 0xF0, 0xE0, 0x00, 0x00, 0x00,
+ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0xFF, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03,
+ 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xC0, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
+ 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x36, 0x7F, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1C, 0x18, 0x18,
+ 0xC3, 0xE7, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x3C, 0x00,
+ 0x18, 0x18, 0x66, 0x66, 0x18, 0x18, 0x3C, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x08, 0x1C, 0x3E, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18,
+ 0xC0, 0xC0, 0x30, 0x30, 0xC0, 0xC0, 0x30, 0x30, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x03, 0x3E, 0x76, 0x36, 0x36, 0x00, 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
+ 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0x33, 0x33, 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18,
+ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F,
+ 0xC3, 0x99, 0x91, 0x91, 0x9F, 0x99, 0xC3, 0xFF, 0xE7, 0xC3, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF,
+ 0x83, 0x99, 0x99, 0x83, 0x99, 0x99, 0x83, 0xFF, 0xC3, 0x99, 0x9F, 0x9F, 0x9F, 0x99, 0xC3, 0xFF,
+ 0x87, 0x93, 0x99, 0x99, 0x99, 0x93, 0x87, 0xFF, 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x81, 0xFF,
+ 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x9F, 0xFF, 0xC3, 0x99, 0x9F, 0x91, 0x99, 0x99, 0xC3, 0xFF,
+ 0x99, 0x99, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, 0xC3, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF,
+ 0xE1, 0xF3, 0xF3, 0xF3, 0xF3, 0x93, 0xC7, 0xFF, 0x99, 0x93, 0x87, 0x8F, 0x87, 0x93, 0x99, 0xFF,
+ 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x81, 0xFF, 0x9C, 0x88, 0x80, 0x94, 0x9C, 0x9C, 0x9C, 0xFF,
+ 0x99, 0x89, 0x81, 0x81, 0x91, 0x99, 0x99, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF,
+ 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0x9F, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xF1, 0xFF,
+ 0x83, 0x99, 0x99, 0x83, 0x87, 0x93, 0x99, 0xFF, 0xC3, 0x99, 0x9F, 0xC3, 0xF9, 0x99, 0xC3, 0xFF,
+ 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, 0x9C, 0x9C, 0x9C, 0x94, 0x80, 0x88, 0x9C, 0xFF,
+ 0x99, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0x99, 0xFF, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xE7, 0xE7, 0xFF,
+ 0x81, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x81, 0xFF, 0xC3, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xC3, 0xFF,
+ 0xF3, 0xED, 0xCF, 0x83, 0xCF, 0x9D, 0x03, 0xFF, 0xC3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xC3, 0xFF,
+ 0xFF, 0xE7, 0xC3, 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xEF, 0xCF, 0x80, 0x80, 0xCF, 0xEF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF,
+ 0x99, 0x99, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x99, 0x99, 0x00, 0x99, 0x00, 0x99, 0x99, 0xFF,
+ 0xE7, 0xC1, 0x9F, 0xC3, 0xF9, 0x83, 0xE7, 0xFF, 0x9D, 0x99, 0xF3, 0xE7, 0xCF, 0x99, 0xB9, 0xFF,
+ 0xC3, 0x99, 0xC3, 0xC7, 0x98, 0x99, 0xC0, 0xFF, 0xF9, 0xF3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF3, 0xE7, 0xCF, 0xCF, 0xCF, 0xE7, 0xF3, 0xFF, 0xCF, 0xE7, 0xF3, 0xF3, 0xF3, 0xE7, 0xCF, 0xFF,
+ 0xFF, 0x99, 0xC3, 0x00, 0xC3, 0x99, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0x81, 0xE7, 0xE7, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xFC, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF,
+ 0xC3, 0x99, 0x91, 0x89, 0x99, 0x99, 0xC3, 0xFF, 0xE7, 0xE7, 0xC7, 0xE7, 0xE7, 0xE7, 0x81, 0xFF,
+ 0xC3, 0x99, 0xF9, 0xF3, 0xCF, 0x9F, 0x81, 0xFF, 0xC3, 0x99, 0xF9, 0xE3, 0xF9, 0x99, 0xC3, 0xFF,
+ 0xF9, 0xF1, 0xE1, 0x99, 0x80, 0xF9, 0xF9, 0xFF, 0x81, 0x9F, 0x83, 0xF9, 0xF9, 0x99, 0xC3, 0xFF,
+ 0xC3, 0x99, 0x9F, 0x83, 0x99, 0x99, 0xC3, 0xFF, 0x81, 0x99, 0xF3, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF,
+ 0xC3, 0x99, 0x99, 0xC3, 0x99, 0x99, 0xC3, 0xFF, 0xC3, 0x99, 0x99, 0xC1, 0xF9, 0x99, 0xC3, 0xFF,
+ 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF,
+ 0xF1, 0xE7, 0xCF, 0x9F, 0xCF, 0xE7, 0xF1, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0xFF, 0xFF,
+ 0x8F, 0xE7, 0xF3, 0xF9, 0xF3, 0xE7, 0x8F, 0xFF, 0xC3, 0x99, 0xF9, 0xF3, 0xE7, 0xFF, 0xE7, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF7, 0xE3, 0xC1, 0x80, 0x80, 0xE3, 0xC1, 0xFF,
+ 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF,
+ 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xFF, 0xFF, 0xFF, 0x1F, 0x0F, 0xC7, 0xE7, 0xE7,
+ 0xE7, 0xE7, 0xE3, 0xF0, 0xF8, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xC7, 0x0F, 0x1F, 0xFF, 0xFF, 0xFF,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x3F, 0x1F, 0x8F, 0xC7, 0xE3, 0xF1, 0xF8, 0xFC,
+ 0xFC, 0xF8, 0xF1, 0xE3, 0xC7, 0x8F, 0x1F, 0x3F, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x00, 0x00, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFF, 0xC3, 0x81, 0x81, 0x81, 0x81, 0xC3, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xC9, 0x80, 0x80, 0x80, 0xC1, 0xE3, 0xF7, 0xFF,
+ 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0xFF, 0xFF, 0xFF, 0xF8, 0xF0, 0xE3, 0xE7, 0xE7,
+ 0x3C, 0x18, 0x81, 0xC3, 0xC3, 0x81, 0x18, 0x3C, 0xFF, 0xC3, 0x81, 0x99, 0x99, 0x81, 0xC3, 0xFF,
+ 0xE7, 0xE7, 0x99, 0x99, 0xE7, 0xE7, 0xC3, 0xFF, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9,
+ 0xF7, 0xE3, 0xC1, 0x80, 0xC1, 0xE3, 0xF7, 0xFF, 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xE7, 0xE7, 0xE7,
+ 0x3F, 0x3F, 0xCF, 0xCF, 0x3F, 0x3F, 0xCF, 0xCF, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7,
+ 0xFF, 0xFF, 0xFC, 0xC1, 0x89, 0xC9, 0xC9, 0xFF, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0x33, 0xCC, 0xCC, 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F,
+ 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xE7, 0xE7, 0xE7,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
+ 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xFF, 0xFF, 0xFF,
+ 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00, 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00,
+ 0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x3C, 0x60, 0x60, 0x60, 0x3C, 0x00,
+ 0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00,
+ 0x00, 0x0E, 0x18, 0x3E, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x7C,
+ 0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x00, 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3C, 0x00,
+ 0x00, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3C, 0x00, 0x60, 0x60, 0x6C, 0x78, 0x6C, 0x66, 0x00,
+ 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x66, 0x7F, 0x7F, 0x6B, 0x63, 0x00,
+ 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00,
+ 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06,
+ 0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x00,
+ 0x00, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00,
+ 0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x3E, 0x36, 0x00,
+ 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x0C, 0x78,
+ 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00,
+ 0x0C, 0x12, 0x30, 0x7C, 0x30, 0x62, 0xFC, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00,
+ 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00,
+ 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, 0x00,
+ 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, 0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00,
+ 0x3C, 0x66, 0x3C, 0x38, 0x67, 0x66, 0x3F, 0x00, 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00,
+ 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00,
+ 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00, 0x18, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7E, 0x00,
+ 0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E, 0x00, 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00,
+ 0x06, 0x0E, 0x1E, 0x66, 0x7F, 0x06, 0x06, 0x00, 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00,
+ 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 0x7E, 0x66, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x00,
+ 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00,
+ 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30,
+ 0x0E, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00,
+ 0x70, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x70, 0x00, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00,
+ 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00,
+ 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00,
+ 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x00, 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00,
+ 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00,
+ 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00,
+ 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00,
+ 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00,
+ 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E, 0x00,
+ 0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00, 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00,
+ 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00,
+ 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00,
+ 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00,
+ 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18,
+ 0xC0, 0xC0, 0x30, 0x30, 0xC0, 0xC0, 0x30, 0x30, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
+ 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18,
+ 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x01, 0x03, 0x06, 0x6C, 0x78, 0x70, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0,
+ 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F,
+ 0xC3, 0x99, 0x91, 0x91, 0x9F, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0xC3, 0xF9, 0xC1, 0x99, 0xC1, 0xFF,
+ 0xFF, 0x9F, 0x9F, 0x83, 0x99, 0x99, 0x83, 0xFF, 0xFF, 0xFF, 0xC3, 0x9F, 0x9F, 0x9F, 0xC3, 0xFF,
+ 0xFF, 0xF9, 0xF9, 0xC1, 0x99, 0x99, 0xC1, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0x81, 0x9F, 0xC3, 0xFF,
+ 0xFF, 0xF1, 0xE7, 0xC1, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xFF, 0xC1, 0x99, 0x99, 0xC1, 0xF9, 0x83,
+ 0xFF, 0x9F, 0x9F, 0x83, 0x99, 0x99, 0x99, 0xFF, 0xFF, 0xE7, 0xFF, 0xC7, 0xE7, 0xE7, 0xC3, 0xFF,
+ 0xFF, 0xF9, 0xFF, 0xF9, 0xF9, 0xF9, 0xF9, 0xC3, 0xFF, 0x9F, 0x9F, 0x93, 0x87, 0x93, 0x99, 0xFF,
+ 0xFF, 0xC7, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF, 0xFF, 0xFF, 0x99, 0x80, 0x80, 0x94, 0x9C, 0xFF,
+ 0xFF, 0xFF, 0x83, 0x99, 0x99, 0x99, 0x99, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0xC3, 0xFF,
+ 0xFF, 0xFF, 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0xFF, 0xFF, 0xC1, 0x99, 0x99, 0xC1, 0xF9, 0xF9,
+ 0xFF, 0xFF, 0x83, 0x99, 0x9F, 0x9F, 0x9F, 0xFF, 0xFF, 0xFF, 0xC1, 0x9F, 0xC3, 0xF9, 0x83, 0xFF,
+ 0xFF, 0xE7, 0x81, 0xE7, 0xE7, 0xE7, 0xF1, 0xFF, 0xFF, 0xFF, 0x99, 0x99, 0x99, 0x99, 0xC1, 0xFF,
+ 0xFF, 0xFF, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0x9C, 0x94, 0x80, 0xC1, 0xC9, 0xFF,
+ 0xFF, 0xFF, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0xFF, 0xFF, 0xFF, 0x99, 0x99, 0x99, 0xC1, 0xF3, 0x87,
+ 0xFF, 0xFF, 0x81, 0xF3, 0xE7, 0xCF, 0x81, 0xFF, 0xC3, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xC3, 0xFF,
+ 0xF3, 0xED, 0xCF, 0x83, 0xCF, 0x9D, 0x03, 0xFF, 0xC3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xC3, 0xFF,
+ 0xFF, 0xE7, 0xC3, 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xEF, 0xCF, 0x80, 0x80, 0xCF, 0xEF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF,
+ 0x99, 0x99, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x99, 0x99, 0x00, 0x99, 0x00, 0x99, 0x99, 0xFF,
+ 0xE7, 0xC1, 0x9F, 0xC3, 0xF9, 0x83, 0xE7, 0xFF, 0x9D, 0x99, 0xF3, 0xE7, 0xCF, 0x99, 0xB9, 0xFF,
+ 0xC3, 0x99, 0xC3, 0xC7, 0x98, 0x99, 0xC0, 0xFF, 0xF9, 0xF3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xF3, 0xE7, 0xCF, 0xCF, 0xCF, 0xE7, 0xF3, 0xFF, 0xCF, 0xE7, 0xF3, 0xF3, 0xF3, 0xE7, 0xCF, 0xFF,
+ 0xFF, 0x99, 0xC3, 0x00, 0xC3, 0x99, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0x81, 0xE7, 0xE7, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xFC, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF,
+ 0xC3, 0x99, 0x91, 0x89, 0x99, 0x99, 0xC3, 0xFF, 0xE7, 0xE7, 0xC7, 0xE7, 0xE7, 0xE7, 0x81, 0xFF,
+ 0xC3, 0x99, 0xF9, 0xF3, 0xCF, 0x9F, 0x81, 0xFF, 0xC3, 0x99, 0xF9, 0xE3, 0xF9, 0x99, 0xC3, 0xFF,
+ 0xF9, 0xF1, 0xE1, 0x99, 0x80, 0xF9, 0xF9, 0xFF, 0x81, 0x9F, 0x83, 0xF9, 0xF9, 0x99, 0xC3, 0xFF,
+ 0xC3, 0x99, 0x9F, 0x83, 0x99, 0x99, 0xC3, 0xFF, 0x81, 0x99, 0xF3, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF,
+ 0xC3, 0x99, 0x99, 0xC3, 0x99, 0x99, 0xC3, 0xFF, 0xC3, 0x99, 0x99, 0xC1, 0xF9, 0x99, 0xC3, 0xFF,
+ 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF,
+ 0xF1, 0xE7, 0xCF, 0x9F, 0xCF, 0xE7, 0xF1, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0xFF, 0xFF,
+ 0x8F, 0xE7, 0xF3, 0xF9, 0xF3, 0xE7, 0x8F, 0xFF, 0xC3, 0x99, 0xF9, 0xF3, 0xE7, 0xFF, 0xE7, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF,
+ 0x83, 0x99, 0x99, 0x83, 0x99, 0x99, 0x83, 0xFF, 0xC3, 0x99, 0x9F, 0x9F, 0x9F, 0x99, 0xC3, 0xFF,
+ 0x87, 0x93, 0x99, 0x99, 0x99, 0x93, 0x87, 0xFF, 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x81, 0xFF,
+ 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x9F, 0xFF, 0xC3, 0x99, 0x9F, 0x91, 0x99, 0x99, 0xC3, 0xFF,
+ 0x99, 0x99, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, 0xC3, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF,
+ 0xE1, 0xF3, 0xF3, 0xF3, 0xF3, 0x93, 0xC7, 0xFF, 0x99, 0x93, 0x87, 0x8F, 0x87, 0x93, 0x99, 0xFF,
+ 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x81, 0xFF, 0x9C, 0x88, 0x80, 0x94, 0x9C, 0x9C, 0x9C, 0xFF,
+ 0x99, 0x89, 0x81, 0x81, 0x91, 0x99, 0x99, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF,
+ 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0x9F, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xF1, 0xFF,
+ 0x83, 0x99, 0x99, 0x83, 0x87, 0x93, 0x99, 0xFF, 0xC3, 0x99, 0x9F, 0xC3, 0xF9, 0x99, 0xC3, 0xFF,
+ 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, 0x9C, 0x9C, 0x9C, 0x94, 0x80, 0x88, 0x9C, 0xFF,
+ 0x99, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0x99, 0xFF, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xE7, 0xE7, 0xFF,
+ 0x81, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x81, 0xFF, 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xE7, 0xE7, 0xE7,
+ 0x3F, 0x3F, 0xCF, 0xCF, 0x3F, 0x3F, 0xCF, 0xCF, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7,
+ 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99,
+ 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xE7, 0xE7, 0xE7,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
+ 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
+ 0xFE, 0xFC, 0xF9, 0x93, 0x87, 0x8F, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F,
+ 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xFF, 0xFF, 0xFF,
+ 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0
+};
diff --git a/DiskCaddy.cpp b/DiskCaddy.cpp
new file mode 100644
index 0000000..e5998cc
--- /dev/null
+++ b/DiskCaddy.cpp
@@ -0,0 +1,202 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "DiskCaddy.h"
+#include "debug.h"
+#include
+#include
+#include "ff.h"
+extern "C"
+{
+#include "rpi-gpio.h" // For SetACTLed
+}
+
+static const u32 screenPosXCaddySelections = 240;
+static const u32 screenPosYCaddySelections = 280;
+static char buffer[256] = { 0 };
+static u32 white = RGBA(0xff, 0xff, 0xff, 0xff);
+static u32 red = RGBA(0xff, 0, 0, 0xff);
+
+bool DiskCaddy::Insert(const FILINFO* fileInfo, bool readOnly)
+{
+ bool success;
+ FIL fp;
+ FRESULT res = f_open(&fp, fileInfo->fname, FA_READ);
+ if (res == FR_OK)
+ {
+ if (screen)
+ {
+ int y = screenPosYCaddySelections;
+
+ snprintf(buffer, 256, "Loading %s\r\n", fileInfo->fname);
+ screen->PrintText(false, screenPosXCaddySelections, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), red);
+ }
+
+ u32 bytesRead;
+ SetACTLed(true);
+ f_read(&fp, DiskImage::readBuffer, READBUFFER_SIZE, &bytesRead);
+ SetACTLed(false);
+ f_close(&fp);
+
+ DiskImage::DiskType diskType = DiskImage::GetDiskImageTypeViaExtention(fileInfo->fname);
+ switch (diskType)
+ {
+ case DiskImage::D64:
+ success = InsertD64(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly);
+ break;
+ case DiskImage::G64:
+ success = InsertG64(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly);
+ break;
+ case DiskImage::NIB:
+ success = InsertNIB(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly);
+ break;
+ case DiskImage::NBZ:
+ success = InsertNBZ(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly);
+ break;
+ default:
+ success = false;
+ break;
+ }
+ if (success)
+ {
+ DEBUG_LOG("Mounted into caddy %s - %d\r\n", fileInfo->fname, bytesRead);
+ }
+ }
+ else
+ {
+ DEBUG_LOG("Failed to open %s\r\n", fileInfo->fname);
+ success = false;
+ }
+
+ oldCaddyIndex = 0;
+
+ return success;
+}
+
+bool DiskCaddy::InsertD64(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly)
+{
+ DiskImage diskImage;
+ if (diskImage.OpenD64(fileInfo, diskImageData, size))
+ {
+ diskImage.SetReadOnly(readOnly);
+ disks.push_back(diskImage);
+ selectedIndex = disks.size() - 1;
+ return true;
+ }
+ return false;
+}
+
+bool DiskCaddy::InsertG64(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly)
+{
+ DiskImage diskImage;
+ if (diskImage.OpenG64(fileInfo, diskImageData, size))
+ {
+ diskImage.SetReadOnly(readOnly);
+ disks.push_back(diskImage);
+ //DEBUG_LOG("Disks size = %d\r\n", disks.size());
+ selectedIndex = disks.size() - 1;
+ return true;
+ }
+ return false;
+}
+
+bool DiskCaddy::InsertNIB(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly)
+{
+ DiskImage diskImage;
+ if (diskImage.OpenNIB(fileInfo, diskImageData, size))
+ {
+ // At the moment we cannot write out NIB files.
+ diskImage.SetReadOnly(true);// readOnly);
+ disks.push_back(diskImage);
+ selectedIndex = disks.size() - 1;
+ return true;
+ }
+ return false;
+}
+
+bool DiskCaddy::InsertNBZ(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly)
+{
+ DiskImage diskImage;
+ if (diskImage.OpenNBZ(fileInfo, diskImageData, size))
+ {
+ // At the moment we cannot write out NIB files.
+ diskImage.SetReadOnly(true);// readOnly);
+ disks.push_back(diskImage);
+ selectedIndex = disks.size() - 1;
+ return true;
+ }
+ return false;
+}
+
+void DiskCaddy::Display()
+{
+ if (screen)
+ {
+ unsigned numberOfImages = GetNumberOfImages();
+ unsigned caddyIndex;
+ int y = screenPosYCaddySelections;
+
+ snprintf(buffer, 256, "Emulating\r\n");
+ screen->PrintText(false, screenPosXCaddySelections, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), red);
+ y += 16;
+
+ for (caddyIndex = 0; caddyIndex < numberOfImages; ++caddyIndex)
+ {
+ DiskImage* image = GetImage(caddyIndex);
+ const char* name = image->GetName();
+ if (name)
+ {
+ snprintf(buffer, 256, "%d %s\r\n", caddyIndex + 1, name);
+ screen->PrintText(false, screenPosXCaddySelections, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), red);
+ y += 16;
+ }
+ }
+
+ ShowSelectedImage(0);
+ }
+}
+
+void DiskCaddy::ShowSelectedImage(u32 index)
+{
+ u32 x = screenPosXCaddySelections - 16;
+ u32 y = screenPosYCaddySelections + 16 + 16 * index;
+ snprintf(buffer, 256, "*");
+ screen->PrintText(false, x, y, buffer, white, red);
+}
+
+bool DiskCaddy::Update()
+{
+ u32 y;
+ u32 x;
+ u32 caddyIndex = GetSelectedIndex();
+ if (caddyIndex != oldCaddyIndex)
+ {
+ if (screen)
+ {
+ x = screenPosXCaddySelections - 16;
+ y = screenPosYCaddySelections + 16 + 16 * oldCaddyIndex;
+ snprintf(buffer, 256, " ");
+ screen->PrintText(false, x, y, buffer, red, red);
+ oldCaddyIndex = caddyIndex;
+ ShowSelectedImage(oldCaddyIndex);
+ }
+
+ return true;
+ }
+ return false;
+}
diff --git a/DiskCaddy.h b/DiskCaddy.h
new file mode 100644
index 0000000..40a8a4d
--- /dev/null
+++ b/DiskCaddy.h
@@ -0,0 +1,111 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef DISKCADDY_H
+#define DISKCADDY_H
+
+#include
+#include "DiskImage.h"
+#include "Screen.h"
+
+class DiskCaddy
+{
+public:
+ DiskCaddy()
+ : selectedIndex(0)
+ , screen(0)
+ {
+ }
+
+ void SetScreen(Screen* screen) { this->screen = screen; }
+
+ void Empty()
+ {
+ int index;
+ for (index = 0; index < (int)disks.size(); ++index)
+ {
+ disks[index].Close();
+ }
+ disks.clear();
+ selectedIndex = 0;
+ }
+
+ bool Insert(const FILINFO* fileInfo, bool readOnly);
+
+ DiskImage* GetCurrentDisk()
+ {
+ if (selectedIndex < disks.size())
+ return &disks[selectedIndex];
+
+ return 0;
+ }
+
+ DiskImage* NextDisk()
+ {
+ selectedIndex = (selectedIndex + 1) % (u32)disks.size();
+ return GetCurrentDisk();
+ }
+
+ DiskImage* PrevDisk()
+ {
+ selectedIndex = (selectedIndex - 1) % (u32)disks.size();
+ return GetCurrentDisk();
+ }
+
+ u32 GetNumberOfImages() const { return disks.size(); }
+ u32 GetSelectedIndex() const { return selectedIndex; }
+
+ DiskImage* GetImage(unsigned index) { return &disks[index]; }
+ DiskImage* SelectImage(unsigned index)
+ {
+ if (selectedIndex != index && index < disks.size())
+ {
+ selectedIndex = index;
+ return GetCurrentDisk();
+ }
+ return 0;
+ }
+ DiskImage* SelectFirstImage()
+ {
+ if (disks.size())
+ {
+ selectedIndex = 0;
+ return GetCurrentDisk();
+ }
+ return 0;
+ }
+
+ void Display();
+ bool Update();
+
+private:
+ bool InsertD64(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
+ bool InsertG64(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
+ bool InsertNIB(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
+ bool InsertNBZ(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
+
+ void ShowSelectedImage(u32 index);
+
+ std::vector disks;
+ u32 selectedIndex;
+ u32 oldCaddyIndex;
+
+ Screen* screen;
+};
+
+#endif
\ No newline at end of file
diff --git a/DiskImage.cpp b/DiskImage.cpp
new file mode 100644
index 0000000..bd7e616
--- /dev/null
+++ b/DiskImage.cpp
@@ -0,0 +1,810 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+// Pete Rittwage and Markus Brenner's code was heavly referenced and functions converted to CPP
+// Used with Pete Rittwage's permission
+
+#include "DiskImage.h"
+#include "gcr.h"
+#include "debug.h"
+#include
+#include
+#include "lz.h"
+extern "C"
+{
+#include "rpi-gpio.h"
+}
+
+unsigned char DiskImage::readBuffer[READBUFFER_SIZE];
+
+static unsigned char compressionBuffer[HALF_TRACK_COUNT * NIB_TRACK_LENGTH];
+
+static const unsigned short SECTOR_LENGTH = 256;
+static const unsigned short SECTOR_LENGTH_WITH_CHECKSUM = 260;
+static const unsigned char GCR_SYNC_BYTE = 0xff;
+static const unsigned char GCR_GAP_BYTE = 0x55;
+static const int SECTOR_HEADER_LENGTH = 8;
+static const unsigned MAX_D64_SIZE = 0x30000;
+
+#define NIB_HEADER_SIZE 0xFF
+
+int gap_match_length = 7; // Used by gcr.cpp
+
+const unsigned char DiskImage::SectorsPerTrack[42] =
+{
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, // 1 - 17
+ 19, 19, 19, 19, 19, 19, 19, // 18 - 24
+ 18, 18, 18, 18, 18, 18, // 25 - 30
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, // 31 - 40
+ 17, 17 // 41 - 42
+ // total 683-768 sectors
+};
+
+DiskImage::DiskImage()
+ : readOnly(false)
+ , dirty(false)
+ , attachedImageSize(0)
+ , fileInfo(0)
+{
+ memset(tracks, 0x55, sizeof(tracks));
+}
+
+void DiskImage::Close()
+{
+ switch (diskType)
+ {
+ case D64:
+ CloseD64();
+ break;
+ case G64:
+ CloseG64();
+ break;
+ case NIB:
+ CloseNIB();
+ break;
+ case NBZ:
+ CloseNBZ();
+ break;
+ default:
+ break;
+ }
+ memset(tracks, 0x55, sizeof(tracks));
+ memset(trackLengths, 0, sizeof(trackLengths));
+ diskType = NONE;
+ fileInfo = 0;
+}
+
+void DiskImage::DumpTrack(unsigned track)
+{
+ unsigned char* src = tracks[track];
+ unsigned trackLength = trackLengths[track];
+ DEBUG_LOG("track = %d trackLength = %d\r\n", track, trackLength);
+ for (unsigned index = 0; index < trackLength; ++index)
+ {
+ DEBUG_LOG("%d %02x\r\n", index, src[index]);
+ }
+}
+
+bool DiskImage::OpenD64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
+{
+ Close();
+
+ this->fileInfo = fileInfo;
+
+ unsigned offset = 0;
+
+ if (size > MAX_D64_SIZE)
+ size = MAX_D64_SIZE;
+
+ attachedImageSize = size;
+
+ for (unsigned halfTrackIndex = 0; halfTrackIndex < HALF_TRACK_COUNT; ++halfTrackIndex)
+ {
+ unsigned char track = (halfTrackIndex >> 1);
+ unsigned char* dest = tracks[halfTrackIndex];
+
+ trackLengths[halfTrackIndex] = SectorsPerTrack[track] * GCR_SECTOR_LENGTH;
+
+ if ((halfTrackIndex & 1) == 0)
+ {
+ if (offset < size) // This will allow for >35 tracks.
+ {
+ trackUsed[halfTrackIndex] = true;
+ //DEBUG_LOG("Track %d used\r\n", halfTrackIndex);
+ for (unsigned sectorNo = 0; sectorNo < SectorsPerTrack[track]; ++sectorNo)
+ {
+ convert_sector_to_GCR(diskImage + offset, dest, track + 1, sectorNo, diskImage + 0x165A2, 0);
+ dest += 361;
+
+ offset += SECTOR_LENGTH;
+ }
+ }
+ else
+ {
+ trackUsed[halfTrackIndex] = false;
+ //DEBUG_LOG("Track %d not used\r\n", halfTrackIndex);
+ }
+ }
+ else
+ {
+ trackUsed[halfTrackIndex] = false;
+ //DEBUG_LOG("Track %d not used\r\n", halfTrackIndex);
+ }
+ }
+
+ diskType = D64;
+ return true;
+}
+
+bool DiskImage::WriteD64()
+{
+ if (readOnly)
+ return true;
+
+ FIL fp;
+ FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
+ if (res == FR_OK)
+ {
+ u32 bytesToWrite;
+ u32 bytesWritten;
+
+ int track, sector;
+ BYTE id[3];
+ BYTE d64data[MAXBLOCKSONDISK * 256], *d64ptr;
+ int blocks_to_save = 0;
+
+ DEBUG_LOG("Writing D64 file...\r\n");
+
+ memset(d64data, 0, sizeof(d64data));
+
+ if (!GetID(34, id))
+ {
+ DEBUG_LOG("Cannot find directory sector.\r\n");
+ return false;
+ }
+ d64ptr = d64data;
+ for (track = 0; track <= 40 * 2; track += 2)
+ {
+ if (trackUsed[track])
+ {
+ //printf("Track %d\n", track);
+
+ for (sector = 0; sector < SectorsPerTrack[track / 2]; sector++)
+ {
+ ConvertSector(track, sector, d64ptr);
+ d64ptr += 256;
+ blocks_to_save++;
+ }
+ }
+ }
+
+ bytesToWrite = blocks_to_save * 256;
+ SetACTLed(true);
+ if (f_write(&fp, d64data, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ {
+ SetACTLed(false);
+ DEBUG_LOG("Cannot write d64 data.\r\n");
+ f_close(&fp);
+ return false;
+ }
+
+ f_close(&fp);
+
+ f_utime(fileInfo->fname, fileInfo);
+ SetACTLed(false);
+
+ DEBUG_LOG("Converted %d blocks into D64 file\r\n", blocks_to_save);
+
+ return true;
+ }
+ else
+ {
+ DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
+ return false;
+ }
+}
+
+void DiskImage::CloseD64()
+{
+ if (dirty)
+ {
+ WriteD64();
+ dirty = false;
+ }
+ attachedImageSize = 0;
+}
+
+bool DiskImage::OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
+{
+ Close();
+
+ this->fileInfo = fileInfo;
+
+ attachedImageSize = size;
+
+ if (memcmp(diskImage, "GCR-1541", 8) == 0)
+ {
+ //DEBUG_LOG("Is G64\r\n");
+
+ unsigned char numTracks = diskImage[9];
+ //DEBUG_LOG("numTracks = %d\r\n", numTracks);
+
+ unsigned char* data = diskImage + 12;
+ unsigned char* speedZoneData = diskImage + 0x15c;
+ unsigned short trackLength = 0;
+
+ unsigned track;
+
+ for (track = 0; track < numTracks; ++track)
+ {
+ unsigned offset = *(unsigned*)data;
+ data += 4;
+
+ //DEBUG_LOG("Track = %d Offset = %x\r\n", track, offset);
+
+ trackDensity[track] = *(unsigned*)(speedZoneData + track * 4);
+
+ if (offset == 0)
+ {
+ trackLengths[track] = capacity_max[trackDensity[track]];
+ trackUsed[track] = false;
+ }
+ else
+ {
+ unsigned char* trackData = diskImage + offset;
+
+ trackLength = *(unsigned short*)(trackData);
+ //DEBUG_LOG("trackLength = %d offset = %d\r\n", trackLength, offset);
+ trackData += 2;
+ trackLengths[track] = trackLength;
+ memcpy(tracks[track], trackData, trackLength);
+ trackUsed[track] = true;
+ //DEBUG_LOG("%d has data\r\n", track);
+ }
+ }
+
+ diskType = G64;
+ return true;
+ }
+ return false;
+}
+
+static bool WriteDwords(FIL* fp, u32* values, u32 amount)
+{
+ u32 index;
+ u32 bytesToWrite = 4;
+ u32 bytesWritten;
+
+ for (index = 0; index < amount; ++index)
+ {
+ if (f_write(fp, &values[index], bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ return false;
+ }
+ return true;
+}
+
+bool DiskImage::WriteG64()
+{
+ if (readOnly)
+ return true;
+
+ FIL fp;
+ FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
+ if (res == FR_OK)
+ {
+ u32 bytesToWrite;
+ u32 bytesWritten;
+ int track_inc = 1;
+
+ BYTE header[12];
+ DWORD gcr_track_p[MAX_HALFTRACKS_1541] = { 0 };
+ DWORD gcr_speed_p[MAX_HALFTRACKS_1541] = { 0 };
+ BYTE gcr_track[NIB_TRACK_LENGTH + 2];
+ size_t track_len;
+ int index = 0, track;
+ BYTE buffer[NIB_TRACK_LENGTH], tempfillbyte;
+
+ DEBUG_LOG("Writing G64 file...\r\n");
+ //DEBUG_LOG("G64 Track Length = %d", G64_TRACK_MAXLEN);
+
+ strcpy((char *)header, "GCR-1541");
+ header[8] = 0;
+ header[9] = MAX_HALFTRACKS_1541;
+ header[10] = (BYTE)(G64_TRACK_MAXLEN % 256);
+ header[11] = (BYTE)(G64_TRACK_MAXLEN / 256);
+
+ bytesToWrite = sizeof(header);
+ SetACTLed(true);
+ if (f_write(&fp, header, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ {
+ SetACTLed(false);
+ DEBUG_LOG("Cannot write G64 header.\r\n");
+ f_close(&fp);
+ return false;
+ }
+ SetACTLed(false);
+
+ for (track = 0; track < MAX_HALFTRACKS_1541; track += track_inc)
+ {
+ if (trackLengths[track] == 0 || !trackUsed[track])
+ {
+ gcr_track_p[track] = 0;
+ gcr_speed_p[track] = 0;
+ }
+ else
+ {
+ gcr_track_p[track] = 0xc + (MAX_TRACKS_1541 * 16) + (index++ * (G64_TRACK_MAXLEN + 2));
+ gcr_speed_p[track] = trackDensity[track] & 3;
+ }
+ }
+
+ SetACTLed(true);
+ WriteDwords(&fp, (u32*)gcr_track_p, MAX_HALFTRACKS_1541);
+ WriteDwords(&fp, (u32*)gcr_speed_p, MAX_HALFTRACKS_1541);
+ SetACTLed(false);
+
+ for (track = 0; track < MAX_HALFTRACKS_1541; track += track_inc)
+ {
+ track_len = trackLengths[track];
+ if (track_len>G64_TRACK_MAXLEN) track_len = G64_TRACK_MAXLEN;
+
+ if (!track_len || !trackUsed[track]) continue;
+
+ tempfillbyte = 0x55;
+
+ memset(&gcr_track[2], tempfillbyte, G64_TRACK_MAXLEN);
+
+ gcr_track[0] = (BYTE)(track_len % 256);
+ gcr_track[1] = (BYTE)(track_len / 256);
+ memcpy(buffer, tracks[track], track_len);
+
+ memcpy(gcr_track + 2, buffer, track_len);
+ bytesToWrite = G64_TRACK_MAXLEN + 2;
+ SetACTLed(true);
+ if (f_write(&fp, gcr_track, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ {
+ SetACTLed(false);
+ DEBUG_LOG("Cannot write track data.\r\n");
+ f_close(&fp);
+ return false;
+ }
+ SetACTLed(false);
+ }
+
+ f_close(&fp);
+ DEBUG_LOG("nSuccessfully saved G64\r\n");
+
+ return true;
+ }
+ else
+ {
+ DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
+ return false;
+ }
+}
+
+void DiskImage::CloseG64()
+{
+ if (dirty)
+ {
+ WriteG64();
+
+ dirty = false;
+ }
+ attachedImageSize = 0;
+}
+
+bool DiskImage::OpenNIB(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
+{
+ int track, t_index = 0, h_index = 0;
+ Close();
+
+ this->fileInfo = fileInfo;
+
+ attachedImageSize = size;
+
+ if (memcmp(diskImage, "MNIB-1541-RAW", 13) == 0)
+ {
+
+ for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
+ {
+ trackLengths[track] = capacity_max[trackDensity[track]];
+ trackUsed[track] = false;
+ }
+
+ while (diskImage[0x10 + h_index])
+ {
+ track = diskImage[0x10 + h_index] - 2;
+ unsigned char v = diskImage[0x11 + h_index];
+ trackDensity[track] = (v & 0x03);
+
+ DEBUG_LOG("Converting NIB track %d (%d.%d)\r\n", track, track >> 1, track & 1 ? 5 : 0);
+
+ unsigned char* nibdata = diskImage + (t_index * NIB_TRACK_LENGTH) + 0x100;
+ int align;
+ trackLengths[track] = extract_GCR_track(tracks[track], nibdata, &align
+ //, ALIGN_GAP
+ , ALIGN_NONE
+ , capacity_min[trackDensity[track]],
+ capacity_max[trackDensity[track]]);
+
+ trackUsed[track] = true;
+
+ h_index += 2;
+ t_index++;
+ }
+
+
+ DEBUG_LOG("Successfully parsed NIB data for %d tracks\n", t_index);
+ diskType = NIB;
+ return true;
+ }
+ return false;
+}
+
+bool DiskImage::WriteNIB()
+{
+ if (readOnly)
+ return true;
+
+ FIL fp;
+ FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
+ if (res == FR_OK)
+ {
+ u32 bytesToWrite;
+ u32 bytesWritten;
+
+ int track;
+ char header[0x100];
+ int header_entry = 0;
+
+ DEBUG_LOG("Converting to NIB format...\n");
+
+ memset(header, 0, sizeof(header));
+
+ sprintf(header, "MNIB-1541-RAW%c%c%c", 1, 0, 0);
+
+ for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
+ {
+ if (trackUsed[track])
+ {
+ header[0x10 + (header_entry * 2)] = (BYTE)track + 2;
+ header[0x10 + (header_entry * 2) + 1] = trackDensity[track];
+
+ header_entry++;
+ }
+ }
+
+ bytesToWrite = sizeof(header);
+ SetACTLed(true);
+ if (f_write(&fp, header, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ {
+ DEBUG_LOG("Cannot write track data.\r\n");
+ }
+ else
+ {
+ bytesToWrite = NIB_TRACK_LENGTH;
+ for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
+ {
+ if (trackUsed[track])
+ {
+ if (f_write(&fp, tracks[track], bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ {
+ DEBUG_LOG("Cannot write track data.\r\n");
+ }
+ }
+ }
+ }
+ SetACTLed(false);
+
+ f_close(&fp);
+
+ DEBUG_LOG("nSuccessfully saved NIB\r\n");
+
+ return true;
+ }
+ else
+ {
+ DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
+ return false;
+ }
+}
+
+void DiskImage::CloseNIB()
+{
+ if (dirty)
+ {
+ WriteNIB();
+
+ dirty = false;
+ }
+ attachedImageSize = 0;
+}
+
+bool DiskImage::OpenNBZ(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
+{
+ Close();
+
+ if ((size = LZ_Uncompress(diskImage, compressionBuffer, size)))
+ {
+ if (OpenNIB(fileInfo, compressionBuffer, size))
+ {
+ diskType = NIB;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool DiskImage::WriteNBZ()
+{
+ bool success = false;
+
+ if (readOnly)
+ return true;
+
+ SetACTLed(true);
+ if (WriteNIB())
+ {
+ FIL fp;
+ FRESULT res = f_open(&fp, fileInfo->fname, FA_READ);
+ if (res == FR_OK)
+ {
+ u32 bytesRead;
+ f_read(&fp, readBuffer, READBUFFER_SIZE, &bytesRead);
+ f_close(&fp);
+ DEBUG_LOG("Reloaded %s - %d for compression\r\n", fileInfo->fname, bytesRead);
+ bytesRead = LZ_Compress(readBuffer, compressionBuffer, bytesRead);
+
+ if (bytesRead)
+ {
+ res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
+ if (res == FR_OK)
+ {
+ u32 bytesToWrite = bytesRead;
+ u32 bytesWritten;
+
+ if (f_write(&fp, compressionBuffer, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
+ {
+ DEBUG_LOG("Cannot write NBZ data.\r\n");
+ }
+ else
+ {
+ success = true;
+ }
+ f_close(&fp);
+ }
+ }
+ }
+ }
+ SetACTLed(false);
+ return success;
+}
+
+void DiskImage::CloseNBZ()
+{
+ if (dirty)
+ {
+ WriteNBZ();
+
+ dirty = false;
+ }
+ attachedImageSize = 0;
+}
+
+bool DiskImage::GetDecodedSector(u32 track, u32 sector, u8* buffer)
+{
+ if (track > 0)
+ {
+ track = (track - 1) * 2;
+ if (trackUsed[track])
+ return ConvertSector(track, sector, buffer);
+ }
+
+ return false;
+}
+
+DiskImage::DiskType DiskImage::GetDiskImageTypeViaExtention(const char* diskImageName)
+{
+ char* ext = strrchr((char*)diskImageName, '.');
+
+ if (ext)
+ {
+ if (toupper((char)ext[1]) == 'G' && ext[2] == '6' && ext[3] == '4')
+ return G64;
+ else if (toupper((char)ext[1]) == 'N' && toupper((char)ext[2]) == 'I' && toupper((char)ext[3]) == 'B')
+ return NIB;
+ else if (toupper((char)ext[1]) == 'N' && toupper((char)ext[2]) == 'B' && toupper((char)ext[3]) == 'Z')
+ return NBZ;
+ else if (toupper((char)ext[1]) == 'D' && ext[2] == '6' && ext[3] == '4')
+ return D64;
+ else if (toupper((char)ext[1]) == 'L' && toupper((char)ext[2]) == 'S' && toupper((char)ext[3]) == 'T')
+ return LST;
+ }
+ return NONE;
+}
+
+bool DiskImage::IsDiskImageExtention(const char* diskImageName)
+{
+ return GetDiskImageTypeViaExtention(diskImageName) != NONE;
+}
+
+bool DiskImage::IsLSTExtention(const char* diskImageName)
+{
+ char* ext = strrchr((char*)diskImageName, '.');
+
+ if (ext && toupper((char)ext[1]) == 'L' && toupper((char)ext[2]) == 'S' && toupper((char)ext[3]) == 'T')
+ return true;
+ return false;
+}
+
+bool DiskImage::ConvertSector(unsigned track, unsigned sector, unsigned char* data)
+{
+ unsigned char buffer[SECTOR_LENGTH_WITH_CHECKSUM];
+ unsigned char checkSum;
+ int index;
+ int bitIndex;
+
+ bitIndex = FindSectorHeader(track, sector, 0);
+ if (bitIndex < 0)
+ return false;
+
+ bitIndex = FindSync(track, bitIndex, (SECTOR_LENGTH_WITH_CHECKSUM * 2) * 8);
+ if (bitIndex < 0)
+ return false;
+
+ DecodeBlock(track, bitIndex, buffer, SECTOR_LENGTH_WITH_CHECKSUM / 4);
+
+ checkSum = buffer[257];
+ for (index = 0; index < SECTOR_LENGTH; ++index)
+ {
+ data[index] = buffer[index + 1];
+ checkSum ^= data[index];
+ }
+
+ if (buffer[0] != 0x07)
+ return false; // No data block
+
+ return checkSum == 0;
+}
+
+void DiskImage::DecodeBlock(unsigned track, int bitIndex, unsigned char* buf, int num)
+{
+ int shift, i, j;
+ unsigned char gcr[5];
+ unsigned char byte;
+ unsigned char* offset;
+ unsigned char* end = tracks[track] + trackLengths[track];
+
+ shift = bitIndex & 7;
+ offset = tracks[track] + (bitIndex >> 3);
+
+ byte = offset[0] << shift;
+ for (i = 0; i < num; i++, buf += 4)
+ {
+ for (j = 0; j < 5; j++)
+ {
+ offset++;
+ if (offset >= end)
+ offset = tracks[track];
+
+ if (shift)
+ {
+ gcr[j] = byte | ((offset[0] << shift) >> 8);
+ byte = offset[0] << shift;
+ }
+ else
+ {
+ gcr[j] = byte;
+ byte = offset[0];
+ }
+ }
+ convert_4bytes_from_GCR(gcr, buf);
+ }
+}
+
+int DiskImage::FindSync(unsigned track, int bitIndex, int maxBits, int* syncStartIndex)
+{
+ int readShiftRegister = 0;
+ unsigned char byte = tracks[track][bitIndex >> 3] << (bitIndex & 7);
+ bool prevBitZero = true;
+
+ while (maxBits--)
+ {
+ if (byte & 0x80)
+ {
+ if (syncStartIndex && prevBitZero)
+ *syncStartIndex = bitIndex;
+
+ prevBitZero = false;
+ readShiftRegister = (readShiftRegister << 1) | 1;
+ }
+ else
+ {
+ prevBitZero = true;
+
+ if (~readShiftRegister & 0x3ff)
+ readShiftRegister <<= 1;
+ else
+ return bitIndex;
+ }
+ if (~bitIndex & 7)
+ {
+ bitIndex++;
+ byte <<= 1;
+ }
+ else
+ {
+ bitIndex++;
+ if (bitIndex >= NIB_TRACK_LENGTH * 8)
+ bitIndex = 0;
+ byte = tracks[track][bitIndex >> 3];
+ }
+ }
+ return -1;
+}
+
+int DiskImage::FindSectorHeader(unsigned track, unsigned sector, unsigned char* id)
+{
+ unsigned char header[10];
+ int bitIndex;
+ int bitIndexPrev;
+
+ bitIndex = 0;
+ bitIndexPrev = -1;
+ for (;;)
+ {
+ bitIndex = FindSync(track, bitIndex, NIB_TRACK_LENGTH * 8);
+ if (bitIndexPrev == bitIndex)
+ break;
+ if (bitIndexPrev < 0)
+ bitIndexPrev = bitIndex;
+ DecodeBlock(track, bitIndex, header, 2);
+
+ if (header[0] == 0x08 && header[2] == sector)
+ {
+ if (id)
+ {
+ id[0] = header[5];
+ id[1] = header[4];
+ }
+ return bitIndex;
+ }
+ }
+ return -1;
+}
+
+unsigned DiskImage::GetID(unsigned track, unsigned char* id)
+{
+ if (FindSectorHeader(track, 0, id) >= 0)
+ return 1;
+ return 0;
+}
+
+unsigned DiskImage::LastTrackUsed()
+{
+ unsigned i;
+ unsigned lastTrackUsed = 0;
+
+ for (i = 0; i < HALF_TRACK_COUNT; ++i)
+ {
+ if (trackUsed[i])
+ lastTrackUsed = i;
+ }
+ return lastTrackUsed;
+}
diff --git a/DiskImage.h b/DiskImage.h
new file mode 100644
index 0000000..efe3b91
--- /dev/null
+++ b/DiskImage.h
@@ -0,0 +1,158 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef DISKIMAGE_H
+#define DISKIMAGE_H
+#include "types.h"
+#include "ff.h"
+
+#define READBUFFER_SIZE 1024 * 512
+
+#define NIB_TRACK_LENGTH 0x2000
+
+#define BAM_OFFSET 4
+#define BAM_ENTRY_SIZE 4
+
+#define DIR_ENTRY_OFFSET_TYPE 0
+#define DIR_ENTRY_OFFSET_NAME 3
+#define DIR_ENTRY_OFFSET_BLOCKS 28
+
+#define DIR_ENTRY_NAME_LENGTH 18-2
+
+static const unsigned char HALF_TRACK_COUNT = 84;
+static const unsigned short GCR_SYNC_LENGTH = 5;
+static const unsigned short GCR_HEADER_LENGTH = 10;
+static const unsigned short GCR_HEADER_GAP_LENGTH = 8;
+static const unsigned short GCR_SECTOR_DATA_LENGTH = 325;
+static const unsigned short GCR_SECTOR_GAP_LENGTH = 8;
+static const unsigned short GCR_SECTOR_LENGTH = GCR_SYNC_LENGTH + GCR_HEADER_LENGTH + GCR_HEADER_GAP_LENGTH + GCR_SYNC_LENGTH + GCR_SECTOR_DATA_LENGTH + GCR_SECTOR_GAP_LENGTH; //361
+
+static const unsigned short G64_MAX_TRACK_LENGTH = 7928;
+
+class DiskImage
+{
+public:
+ enum DiskType
+ {
+ NONE,
+ D64,
+ G64,
+ NIB,
+ NBZ,
+ LST,
+ RAW
+ };
+
+ DiskImage();
+
+ bool OpenD64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
+ bool OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
+ bool OpenNIB(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
+ bool OpenNBZ(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
+
+ void Close();
+
+ bool GetDecodedSector(u32 track, u32 sector, u8* buffer);
+
+ inline bool GetNextBit(u32 track, u32 byte, u32 bit)
+ {
+ if (attachedImageSize == 0)
+ return 0;
+
+ return ((tracks[track][byte] >> bit) & 1) != 0;
+ }
+
+ inline void SetBit(u32 track, u32 byte, u32 bit, bool value)
+ {
+ if (attachedImageSize == 0)
+ return;
+
+ u8 dataOld = tracks[track][byte];
+ u8 bitMask = 1 << bit;
+ if (value)
+ {
+ TestDirty(track, (dataOld & bitMask) == 0);
+ tracks[track][byte] |= bitMask;
+ }
+ else
+ {
+ TestDirty(track, (dataOld & bitMask) != 0);
+ tracks[track][byte] &= ~bitMask;
+ }
+ }
+
+ static const unsigned char SectorsPerTrack[42];
+
+ void DumpTrack(unsigned track);
+
+ const char* GetName() { return fileInfo->fname; }
+
+ inline unsigned BitsInTrack(unsigned track) const { return trackLengths[track] << 3; }
+
+ static DiskType GetDiskImageTypeViaExtention(const char* diskImageName);
+ static bool IsDiskImageExtention(const char* diskImageName);
+ static bool IsLSTExtention(const char* diskImageName);
+
+ bool GetReadOnly() const { return readOnly; }
+ void SetReadOnly(bool readOnly) { this->readOnly = readOnly; }
+
+ unsigned LastTrackUsed();
+
+ static unsigned char readBuffer[READBUFFER_SIZE];
+
+private:
+ void CloseD64();
+ void CloseG64();
+ void CloseNIB();
+ void CloseNBZ();
+
+ bool WriteD64();
+ bool WriteG64();
+ bool WriteNIB();
+ bool WriteNBZ();
+
+ inline void TestDirty(u32 track, bool isDirty)
+ {
+ if (isDirty)
+ {
+ trackDirty[track] = true;
+ trackUsed[track] = true;
+ dirty = true;
+ }
+ }
+
+ bool ConvertSector(unsigned track, unsigned sector, unsigned char* buffer);
+ void DecodeBlock(unsigned track, int bitIndex, unsigned char* buf, int num);
+ unsigned GetID(unsigned track, unsigned char* id);
+ int FindSectorHeader(unsigned track, unsigned sector, unsigned char* id);
+ int FindSync(unsigned track, int bitIndex, int maxBits, int* syncStartIndex = 0);
+
+ bool readOnly;
+ bool dirty;
+ unsigned attachedImageSize;
+ DiskType diskType;
+ const FILINFO* fileInfo;
+
+ unsigned char tracks[HALF_TRACK_COUNT][NIB_TRACK_LENGTH];
+ unsigned short trackLengths[HALF_TRACK_COUNT];
+ unsigned char trackDensity[HALF_TRACK_COUNT];
+ bool trackDirty[HALF_TRACK_COUNT];
+ bool trackUsed[HALF_TRACK_COUNT];
+};
+
+#endif
\ No newline at end of file
diff --git a/Drive.cpp b/Drive.cpp
new file mode 100644
index 0000000..8ff7b6a
--- /dev/null
+++ b/Drive.cpp
@@ -0,0 +1,530 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "Drive.h"
+#include "m6522.h"
+#include "debug.h"
+
+// There is a lot going on even though the emulation code is extremely small.
+// A few counters, shift registers and the occasional logic gate takes a surprisingly small amount of code to implement.
+
+// Note: A 1541 is an upgraded (cost reduced) version of the 1540.
+// In a 1541 a lot of the drive logic is implemented in a custom asic chip 235572. Internally, this chip contains essentially the same logic that was used in the 1540.
+// Commodore chose to reduce the chip count to also reduce power consumption, heat, and production costs.
+// Schematics for the 1540 drive logic made this emulator possible. The 1540 logic was emulated hence chip names reference the 1540.
+// I have a VIC-1541 drive with a 1540 type board in it. The chips are laid out in columns A through to H.
+
+// Considering how prolific magnetic storage has been for many decades there is surprisingly little information (retaliative to other computer hardware like CPUs) into how disk drives actually work.
+// This is how I understand it (and I apologise for the verbosity but it helped me come to that understanding).
+//
+// Magnetic media can provide simple, inexpensive and reliable storage medium.
+// Reliability being a challenge considering that disks can vary between different disk manufactures and brands.
+// Physical properties of drives, like alignment and rotation speed can vary slightly from drive to drive. These properties can even vary overtime or due to temperature fluctuations within the same drive.
+// Digital information needs to be stored via an analogue system that can tolerate minor inconsistencies between theses physical properties.
+//
+// A magnetic field has two important characteristics flux density, and polarity (North and South Poles).
+// At first glance, the two magnetic polarities, N and S, look ideal to nicely to represent a 1 or 0.
+// Hall effect sensors can detect flux density of a magnetic field (even a constant one) but generally require stronger flux densities than found on the coating of floppy media can provide, so are not used to read data.
+// And unfortunately, hall effect sensors have no way of writing data only reading it.
+//
+// Maxwell–Faraday law/equation states that a time varying magnetic field will induce a time varing electric field and visa versa.
+// Disks drive heads use this principal, and can therefore, read and write data.
+// Yet they can only detect/read and create/write changes in the disk's analogue magnetic field over time.
+// The larger and quicker the change the easier it is to detect and read.
+// Writing works in reverse where a changing electric signal creates a changing magnetic field.
+//
+// So how are digital 1s and 0s represented using this analogue magnetic field?
+// A 1 is represented by the changing analogue magnetic field changing from N to S and visa versa.
+// But what about the 0s? A 0 is represented by how quickly the magnetic field changes between any two 1 bits.
+// So technically only the 1s are written to the disk. 0s are infered from the rate at which the magnetic field changes between two 1 bits.
+// The slower the analogue change between N-S/S-N change the more 0s there will be in between the 1s.
+// Long groups of consecutive 0s will eventually slow the rate of change down to a point where the magnetic field becomes almost constant.
+// A constant magnetic field will stop induction.
+// Due to the way the amplification circuits operate (explained below), an almost constant magnetic field can become problematic producing false readings.
+//
+// To solve the problem of preventing long groups of 0s and to keep the magnetic field varing over time various data encoding schemes were created.
+// These data encoding schemes sacrifice disk density for maintaining a rapidly changing, easy to read magnetic field.
+// Originally, up to 50% of a disk's density was sacrificed for software encoding schemes. Interleaving clock bits with data bits.
+// Then some bright sparks invented other more efficient schemes, almost doubling the density of the of the disk (as space for the clock bits could now be used for data).
+// Some marketing genius picked up on the words “double density” and the name stuck, even though the disk was always capable of storing the exact same number of bits!
+// A encoding scheme commonly used at the time of the 1541 is called GCR.
+// GCR encodes 4 bits into 5 where any combination cannot produce no more than two 0s in a row. This sacrifices only 20% of the disk's density.
+// There is no drive hardware that dictates that any specific encoding scheme nees to be used.
+// Software is free to do whatever it wants, even change the encoding schemes used over different parts of the disk or even within a single track.
+// But any coding scheme that allows long groups of 0s will experience side effects.
+// Side effects of long groups of 0s can cause a random 1 to be read incorrectly (explained below).
+// Many copy protection schemes rely on these side effects occurring.
+// The decoder in the 1541 is also hard coded to output a 1 after every three 0s even if there is no magnetic field change (flux reversal). So a 1541 cannot read more than three 0s in a row.
+//
+// A read/write head is a coil with a centre tap effectively creating two coils. One coil detects/creates magnetic field changes in one direction (N-S) and the other coil in the other direction (S-N).
+// There is also a third coil use to erase.
+// The difference between the electric signals in each of the two coils is amplified.
+// Remember that we are only interested in the rate in which the magnetic field is changing not its relative amplitude.
+// Drives vary slightly and even the same drive can vary with temperature.
+// Because of this and to increase alignment tolerance the relative amplitude of the magnetic field can be ignored. The rate of change is important.
+// The amplifier therefore amplifies the signal with a varying gain. This is called 'Automatic Gain Control'.
+// It is always trying to amplify the signal as much as it needs. For a strong signal not as much as for a weak signal.
+//
+// The signal is then filtered. A low-pass filter attenuates noise and unwanted harmonics.
+//
+// In a 1540;-
+// This is done by L8-L11, and C16.
+//
+// To detect the all important change in magnetic field direction (ie a flux reversal) the signal is again amplified.
+// But this time the amplifier has been configured as a differentiator as well.
+// A differentiator amplifier produces an output signal that is the first derivative of the input signal.
+// That is, the output signal is directly proportional to the rate of change of the input signal.
+// This means that every place in the input signal where there is a peak or trough (ie the signal changes direction, namely, a flux reversal) the new signal will cross the zero point (in the analogue waveform).
+//
+// The differentiated signal can now be analysed. A zero crossing represents a 1. The absence of a zero crossing over a certain time threshold represents a 0.
+// The problem then becomes how to detect zero crossings and time them.
+// Detecting zero crossings is solved using another operational amplifier configured as a comparator with a reference voltage of zero.
+// The comparator output toggles each time its inputs sense a zero crossing. The signal now being analysed is a simple square wave.
+//
+// In a 1540;-
+// Heads read 7mv.
+// First amp UH7 amplifies this to 350mv.
+// Second amp UH5 amplifies this and differentiates it to 3.5v.
+// Comparator UH4 and the pulse generator output 5v TTL.
+//
+// The comparator output is then applied to the time domain filter.
+// The primary purpose of the time domain filter is to generate an output pulse for each transition of the comparator output.
+// These pulses correspond to valid 1s being read off the disk and the absence of pulses the infered 0s.
+// The secondary purpose is to filter out false zero crossings which can be caused by noise aggravated by media defect and contamination.
+// Filtering out false information involves the use of an "ignore window", during which time any zero crossings are ignored.
+//
+// The time domain filter consists of a two one shots, and a D type flip flop.
+// Each transition of the comparator output triggers the first one shot, which provides the ignore window.
+// When the output of the first one shot returns to the normal level, the D type flip flop clocks through the new data (from the comparator), causing the Q and !Q outputs to switch.
+// This in turn triggers the second one shot, to produce an output pulse.
+// However, the second one shot will only be triggered if the comparator output has changed since the last clocking of the D type flip flop, and remained at that new level.
+// If the comparator changed level due to noise on a slowly changing magnetic field and returned to its original level within the first one shot's period, the D type flip flop outputs will not change, and the second one shot will not generate a new output pulse.
+//
+// Thus, the zero crossing detector and the pulse generator result in pulses occurring at the original input signal's peak points (ie flux reversals).
+//
+// So in a 1540;-
+// The output of the comparator is applied to the valid pulse detector(UG2D, UG3A, and UF6A).
+// UG2D, along with C27, R24, and R25, forms an edge detector.
+// Pin 11 of UG2D will produce a 500 nanosecond active high pulse on rising edges of the comparator's output and will produce a 150 nanosecond high pulse on falling edges of the comparator's output.
+// The pulse out of UG2D is applied to UG3A, a single shot multivibrator.
+// UG3A produces an active low pulse which is approximately 2.5 microseconds wide.
+// Flip flop UF6A is clocked on the trailing edge of the output pulse of UG3A.
+// The output of the comparator must be maintained for at least 2.5 microseconds in order to be latched into the flip flop.
+// If a narrow noise pulse triggers this circuit, the output of the flip flop will not change since the noise pulse will terminate before UG3A triggers UF6A.
+// The output of the flip flop (UF6A) will reflect the data delayed by approximately 2.5 microseconds.
+// The valid data out of the valid pulse detector is applied to an edge detector consisting of UG2A and UG3B.
+// UG2A operates the same as UG2D above.
+// The pulses out of UG2A trigger UG3B, a single shot multivibrator.
+// UG3B produces a narrow pulse, which represents transitions of the data.
+// This output is applied to the timing circuit to synchronise the encoder / decoder clock
+//
+//
+// The problem of zero crossing (hence flux reversals) is solved, now 1s can be detetected. Detecting 0s now becomes a task of timing the gap between the 1s.
+//
+// After all this explanation, NONE of this needs to be emulated!
+//
+// Up to this point the hardware explained so far is responsible for converting the analogue siganls into clean digital signals.
+// Emulated disk images already consist of digital information and hence the hardware explained so far has to some degree already been emulated.
+// What does need to be emulated though (at least simulated) is the side effects the hardware can produce.
+//
+//
+// Side effects
+// When software uses a valid encoding scheme long runs of 0 are avoided.
+// Even if the disk is old or the drive is slightly miss alligned or varying in rotation speed (within a certain range) there should not be a problem reading the data.
+// But there is still nothing that to prevent the magnetic field on a disk changing slowly.
+// So what happens when the magnetic field is changing slowly but just fast enough for induction to be detected?
+// The first amplifier will detect the weak induced signal and maximise its gain. With a maximised gain noise can become a problem.
+// The slope of the slowly changing signal will be differentiated to produce a signal that will be near zero.
+// A low amplitude signal varying near zero is fine and valid but a low amplitude signal varying around zero is bad as each zero crossing will be detected incorrectly as a 1.
+// So any noise on the differentiated signal could cause it to cross the zero point causing the comparator to switch.
+// The time domain filter will normally filter most of the high frequency false crossings out.
+// But if these random zero crossing just happen to occur at the normally expected rate of valid flux reversals, random 1s, along with the groups of 0s, will be read.
+// On a signal that is near zero these random noise frequencies can and do occur at the normally valid ranges.
+// Copy protection schemes rely on this and deliberately place a slow changing magnetic field on the disk.
+// The software then reads that section of the disk multiple times. Each time comparing the values, looking for the random 1s.
+// If the 1s are consistent then it knows that the field on the disk is no longer the slowly changing one of the original but that of a faster changing one of a copy.
+// This side effect is simulated by the code below.
+//
+// Now all that is left to solve is the problem of timing.
+// The data on the disk is divided into cells. If a transition occurs at the beginning of a cell, the cell is interpreted as a logic 1. If no transition occurs, the cell is interpreted as a logic 0.
+// We just need to time the cell. The cell handling is the responsibility of the encoder/decoder (for writing/reading).
+//
+// For the standard CBM format more information (hence more cells) are stored on the outside of a disk than the inside.
+// This variation keeps the bit density relatively constant over the surface of the disk.
+// The disk spins at a near to constant rate.
+// So for a round disk there is more surface area on the outside tracks than the inside.
+// The timing of the cells and hence the encoder/decoder's clock can vary upto four levels (set via 2 pins in the VIA($1C00) PB5 and PB6).
+// The encoder/decoder is usually clocked at a fester rate when on the outer tracks than when on the inner tracks.
+// It is the encoder/decoder clock which determines how many bits will fit on any one track.
+//
+// The core clock generator inside a 1541 is a 16Mz crystal. All timings including the encoder/decoder clock is derived from this.
+//
+// The encoder/decoder clock is simply a counter. UE7 a 74LS193 Synchronous 4-Bit Up/Down Counter.
+// If counting at 16Mz it will take 16 cycles to count to 16 and therefore overflow/trigger a carry every 1us (ie 1MHz)
+// It always counts at the same rate but can be made to trigger quicker if it is preloaded with an initial count each time it begins a new count.
+// This preloading value comes from the CLOCK_SEL_AB pins of the VIA's PB5 and PB6 (set via the CPU) and can therefore be preloaded with a value of 0 to 3.
+// The programmable divider is controlled by the CLOCK SEL A and CLOCK SEL B lines and selects the density level.
+// Density/Bit Rate Index 3 2 1 0
+// CLOCK SEL A 1 0 1 0
+// CLOCK SEL B 1 1 0 0
+// 16MHz Division Factor 13 14 15 16
+// Encoder / Decoder Clock Freq (MHz) 1.2307 1.1428 1.0666 1.00
+// Sectors per Track 21 20 18 17
+// Track Numbers 1 - 17 18 - 24 25 - 30 31 - 42
+// Division Factor/16 0.8125 0.875 0.9375 1
+// Bit cell length in us (MAX) 3.25 3.5 3.75 4
+// (4xDivision Factor/16)
+//
+//
+// Nothing in the hardware limits what timings are required for any section of the disk. Software is free to change the timing at any time for any section of the disk (even within the same track).
+//
+// The encoder/decoder itself is made up of a four bit counter (UE4) and a NOR gate (UE5A).
+// The counter is configured to always count up, clocked by the encoder/decoder clock.
+// Any flux reversal either true or induced by noise will reset the encoder/decoder's counter (UE4) back to zero and encoder/decoder clock's counter (UE7) back to its preload density values.
+// The encoder/decoder counter has 4 outputs A, B, C and D. They represent the bit value of the UE4's current count where A is the least significant bit and D the most.
+// The B output forms the serial data clock.
+// The C and D are NORed together and the output forms the serial data.
+// The serial data is fed into the Read Shift Register (UD2) where it is converted from serial to parallel and can be gated onto the VIA's portA via the read latch UC3.
+//
+// A bit cell is four encoder/decoder clock pulses wide, as the 2nd bit (output B) in UF4's count controls the serial clock. Based on the VIA's density preload values a cell is at a maximum 3.25-4us long (shorter if it contains a flux reversal).
+// If a flux reversal occurs at the beginning of a cell, that cell is a 1 else that cell is a 0.
+// If a flux reversal occurs, UF4's counter is cleared and the timing circuit is reset to start the encoder/decoder clock at the beginning of the VIA's current density setting.
+// To phase lock onto data we must always begin with a 1 hence the need for a soft SYNC marker full of 1s.
+//
+// When B becomes high the NORed value of C and D (serial data) is sent to the shift register.
+// If there is a flux reversal the counter resets. Two counts later a one is always shifted in.
+// If after the first flux reversal there is an absence of others, UE4 continues to count up and each time B goes high (counts 6 10 & 14) only 0s will be shifted in (at the rate dictated by the encoder/decoders's clock and its density preload setting).
+// If UE4's count is left to cycle (ie no flux reversals for 16 counts) a 1 will be shifted in regardless.
+// This limits the hardware to only read three consecutive 0s between any two 1s. (with GCR this will never occur)
+//
+// The 1541 uses soft sectors. The hardware can only detect the sync sequence of then one bits in a row and everything must then be phase locked to that.
+// The sync sequence phase locks to blocks. Blocks can really be any size, even the entire track.
+// UC2 monitors the parallel output of UD2/UE4, when all 10 bits are 1, the output pin 9 goes low indicating a SYNC sequence has been read.
+// Whenever the SYNC is asserted UE3 is reset and we are also phase locked to bytes.
+// UE3 continues the phase lock to bytes as it is another counter responsible for counting 8 serial clocks.
+// When 8 bits have been counted, UF3 pin 12 goes low, UC1 pin 10 goes high, and UF3 pin 8 goes low indicating a byte is ready to be read by the CPU.
+//
+// And as we have already seen the flux reversals phase lock to bits.
+//
+// So now the analogue signal can be phase locked onto and the data read/written in a bit stream, despite minor physical inconsistencies between disks and drives.
+
+
+// The Encoder/Decoder Clock
+// UE7 74LS193 Synchronous 4-Bit Up/Down Counter
+// - clocks UF4
+// - counts up on pin 5 and this is connected to 16Mhz clock
+// - never counts down as pin 4 is tied to VCC
+// - inputs A and B are connected to VIA's PB5/6 CLOCK_SEL_AB input C and D to GND
+// - pin 12 !CO (!carry out) is output to UE5C (NOR gate used as an inverter) and the inverted signal is called "Encoder/Decoder Clock"
+// - carry out is the only output used.
+// - pin 14 CLR is grounded so the count is never cleared this way
+// - pin 11 LOAD is NORed (UE5D) with our inverted !CO (pin 12) and "Bit Sync" signal. "Bit Sync" is data from the read amplifier, differentiator, compatitor, time domain filter and pulse generator.
+// - the counter resets to VIA's current PB5/6 CLOCK_SEL_AB
+//
+// The Encoder/Decoder
+// UF4 74LS193 Synchronous 4-Bit Up/Down Counter (used to provide clocks for the UD2 serial to parallel shifter and the UE3 bit counter)
+// - counts up on pin 5 and this is connected to the inverted UE7's carry out ie "Encoder/Decoder Clock"
+// - never counts down as pin 4 is tied to VCC
+// - pin 11 !LOAD is tied to VCC so the count will never be altered this way
+// - pin 14 CLR receives data from the read amplifiers (ie "Bit Sync")
+// - so a 1 will reset the count
+// - outputs D and C are connected to a NOR gate UE5A
+// - output A
+// - output B ie every 2nd count
+// drives UD2 shifter
+//
+// Byte Phase Lock
+// UE3 74LS191 Presettable 4-Bit Up/Down Counter
+// - pin 14 CLOCK clocked by
+// - when UF4 counts 2, 6, 10 and 14 (2 is the only count that outputs a 1 into readShiftRegister)
+// - Will count 8 bits everytime UF4 cycles twice
+// - pins 4 (!ENABLE) and 5 (DOWN/UP) connected to GND
+// - puts it in count up mode constantly
+// - pin 7 (OUTPUT D) is NC
+// - eventhough it can count to 16 the high bit is ignored and system treats it as a don't care state
+// - low 3 bits only makes it a 3 bit counter that has 8 counts
+// - pin 11 !LOAD
+// - this is connected to the output of UC2 74ls133 a 13 input NAND gate
+// - 2 inputs are connected to VCC
+// - 8 inputs are connected to
+// - 2 inputs are connected to
+// - 1 input is coneeced to read/write signal
+// - UC2 output can only asserted when reading
+// - the output of UC2 is then called !BLOCK SYNC
+// - pins 15, 1 and 10 inputs A,B and C are connected to GND. Pin 0 input D NC and as a don't care state
+// - so, resets back to zero when !LOAD is asserted
+// - ie when in SYNC will always reset until the first 0 bit
+// - pins 3, 2 and 6 outputs A, B and C (pin 7 output D is NC)
+// - NANDed together by UF3A and this is inverted by UC1E 74ls06
+// - this then forms part of UF3B's NAND inputs
+// - along with UF4 output A and B
+// - this will only activate when (ie UD3 Parallel to serial shift register will only latch when)
+// - UE3 counts 7 AND UF4 counts 3, 7, 11, 15 (outputs A and B)
+// - UF3B output goes to UD3 pin 1 LOAD(LATCH)
+// - Also this then forms part of UF3C's NAND inputs
+// - along with "Byte Sync Enable" (from the VIA) and
+// - UC5B (NOR used to invert UF4's output B) output high when UF4 counts 0,1,4,5,8,9,12 and 13
+// - (IS THIS AN OVERSIGHT IN VICE? They don't emulate it and trigger BYTE SYNC when the serial clock is high?)
+// - UF3C output is then called !BYTE SYNC
+//
+// Read Shift Register
+// UD2 74LS164 8-Bit Serial-In Parallel-Out Shift Register
+// - Clocking occurs on the LOW-to-HIGH level transition of the clock input and when so, the value at the inputs is entered
+// - pin 8 CLOCK - shifts the data when UF4 Counter output B (rising edge)
+// - ie when UF4 counts 2, 6, 10 and 14 (2 is the only count that outputs a 1 into readShiftRegister)
+// - so will only shift in a 1 on UF4 counts 2
+// - on counts 6, 10 and 14 the UE5A NOR will output a 0 to pin 2 input B so no transition
+// - pin 2 input B from UF4's NORed outputs C and D
+// - pin 1 input A connected to VCC
+// - so has no impact on the data shifted in via input B
+// - On counts 2, 6, 10 and 14 of UF4 (2 is the only count that outputs a 1 into readShiftRegister)
+// - will shift the data
+// ie but ony a 1 will be shifed in on count 2
+// - outputs Q0-Q7
+//
+// Sync Phase Lock
+// UC2 74LS133 13 input Nand Gate
+// UE4A/B 74LS74 Flop flops (These are used as an extention to UD2 shift register to increase the number of bits to 10)
+// - UD2 feeds in UC2 with 8 lines
+// - UE4A/B another 2
+// - R/!W another 1
+// - the other 2 inputs of UC2 are pulled high so SYNC is triggered only when reading and 10 consecutive 1 have been detected.
+//
+// Read Latch
+// UC3 74LS245 Octal Bus Transceiver
+// - Data read and shifted into UD2 is latched here.
+// - Reading PortA of the VIA will read the values in this latch.
+// - Hardware requires this latch to isolate the output of UD2 from VIA PortA when VIA PortA is set to output ie writing.
+// (Note: the emulater does not need to emulate this component as its value will always reflected in the read shift register UD2)
+//
+// Write Shift Register
+// UD3 74LS165 8-BIT Parallel to serial shift register
+// - pin 2 CLOCK from UF4 Counter output B (rising edge)
+// - ie when UF4 counts 2, 6, 10 and 14 (2 is the only count that outputs a 1 into readShiftRegister)
+// - pin 1 LOAD form UF3B output
+// - this will only activate when
+// - UE3 counts 7 AND UF4 counts 3, 7, 11, 15 (outputs A and B)
+//
+
+// For some insane reason some demo coders use the write protect sensor to detect disk swapping! It is such a lame way to do it. Just use the disk ID in the sector header that's what it is for!
+// I don't think I have seen any games that do this only demos. And the most popular ones tend to do it.
+// When you eject a disk the disk will start to move out of the drive. Its write protect notch will no longer line up with the write protect sensor and the VIA will signal that the write protection came on.
+// When the disk becomes totally ejected there is now nothing blocking the write protect sensor and the VIA will signal that the write protection was turned off.
+// Now when the next disk is inserted the top of the disk will obscure the write protect sensor's LED and again the VIA will signal that the write protection came on.
+// When the newly inserted disk finally comes to rest its write protect notch will now line up over the write protect sensor and again the VIA will signal that the write protection was turned off.
+//
+// Because of a few demos using this ridiculous technique we need to emulate the changing write protect status using a timer.
+// Using a real drive you can actually break some of these demos (during the disk swap) just by the manual way you eject the disks.
+#define DISK_SWAP_CYCLES_DISK_EJECTING 400000
+#define DISK_SWAP_CYCLES_NO_DISK 200000
+#define DISK_SWAP_CYCLES_DISK_INSERTING 400000
+
+Drive::Drive() : m_pVIA(0)
+{
+ Reset();
+ srand(0x811c9dc5U);
+}
+
+void Drive::Reset()
+{
+ headTrackPos = 34; // Start with the head over track 18
+ CLOCK_SEL_AB = 3; // Track 18 will use speed zone 3 (encoder/decoder (ie UE7Counter) clocked at 1.2307Mhz)
+ UpdateHeadSectorPosition();
+ lastHeadDirection = 0;
+ motor = false;
+ SO = false;
+ readShiftRegister = 0;
+ writeShiftRegister = 0;
+ UE3Counter = 0;
+ ResetEncoderDecoder(18.0f, 22.0f);
+ newDiskImageQueuedCylesRemaining = DISK_SWAP_CYCLES_DISK_EJECTING + DISK_SWAP_CYCLES_NO_DISK + DISK_SWAP_CYCLES_DISK_INSERTING;
+ m_pVIA->InputCA1(true); // Reset in read mode
+ m_pVIA->InputCB1(true);
+ m_pVIA->InputCA2(true);
+ m_pVIA->InputCB2(true);
+}
+
+void Drive::Insert(DiskImage* diskImage)
+{
+ Eject();
+ this->diskImage = diskImage;
+ newDiskImageQueuedCylesRemaining = DISK_SWAP_CYCLES_DISK_EJECTING + DISK_SWAP_CYCLES_NO_DISK + DISK_SWAP_CYCLES_DISK_INSERTING;
+}
+
+void Drive::Eject()
+{
+ if (diskImage) diskImage = 0;
+}
+
+void Drive::DumpTrack(unsigned track)
+{
+ if (diskImage) diskImage->DumpTrack(track);
+}
+
+// Signals from the VIA.
+void Drive::OnPortOut(void* pThis, unsigned char status)
+{
+ Drive* pDrive = (Drive*)pThis;
+ pDrive->motor = (status & 4) != 0;
+ pDrive->MoveHead(status & 3);
+ pDrive->CLOCK_SEL_AB = ((status >> 5) & 3);
+ pDrive->LED = (status & 8) != 0;
+}
+
+u32 Drive::AdvanceSectorPositionR(int& byteOffset)
+{
+ ++headBitOffset %= bitsInTrack;
+ byteOffset = headBitOffset >> 3;
+ return (~headBitOffset) & 7;
+}
+
+u32 Drive::AdvanceSectorPositionW(int& byteOffset)
+{
+ byteOffset = headBitOffset >> 3;
+ u32 bit = (~headBitOffset) & 7;
+ ++headBitOffset %= bitsInTrack;
+ return bit;
+}
+
+bool Drive::GetNextBit()
+{
+ int byteOffset;
+ int bit = AdvanceSectorPositionR(byteOffset);
+ return diskImage->GetNextBit(headTrackPos, byteOffset, bit);
+}
+
+void Drive::SetNextBit(bool value)
+{
+ int byteOffset;
+ int bit = AdvanceSectorPositionW(byteOffset);
+ diskImage->SetBit(headTrackPos, byteOffset, bit, value);
+}
+
+bool Drive::Update()
+{
+ bool dataReady = false;
+
+ // When swapping some lame loaders monitor the write protect flag.
+ // Bit 4 of PortB (WP - write protect) should be;
+ // X Write protect status of D1
+ // 0 Write protected (D1 ejecting)
+ // 1 Not write protected (no disk)
+ // 0 Write protected (D2 inserting)
+ // X Write protect status of D2
+ if (newDiskImageQueuedCylesRemaining > 0)
+ {
+ newDiskImageQueuedCylesRemaining--;
+ if (newDiskImageQueuedCylesRemaining == 0) m_pVIA->GetPortB()->SetInput(0x10, !diskImage->GetReadOnly()); // X Write protect status of D2
+ else if (newDiskImageQueuedCylesRemaining > DISK_SWAP_CYCLES_NO_DISK + DISK_SWAP_CYCLES_DISK_INSERTING) m_pVIA->GetPortB()->SetInput(0x10, false); // 0 Write protected (D1 ejecting)
+ else if (newDiskImageQueuedCylesRemaining > DISK_SWAP_CYCLES_DISK_INSERTING) m_pVIA->GetPortB()->SetInput(0x10, true); // 1 Not write protected (no disk)
+ else m_pVIA->GetPortB()->SetInput(0x10, false); // 0 Write protected (D2 inserting)
+ }
+ else if (diskImage && motor)
+ {
+ bool writing = (m_pVIA->GetFCR() & m6522::FCR_CB2_OUTPUT_MODE0) == 0;
+
+ if (SO)
+ {
+ dataReady = true;
+ SO = false;
+ }
+ // UE6 provides the CPU's clock by dividing the 16Mhz clock by 16.
+ // UE7 (a 74ls193 4bit counter) counts up on the falling edge of the 16Mhz clock. UE7 drives the Encoder/Decoder clock.
+ // So we need to simulate 16 cycles for every 1 CPU cycle
+ for (int cycles = 0; cycles < 16; ++cycles)
+ {
+ if (!writing)
+ {
+ if (++cyclesForBit >= cyclesPerBit)
+ {
+ cyclesForBit -= cyclesPerBit;
+ // Any 1 bit coming from the disk will come in the form of a flux reversal. (Non return to zero inverted emulation.)
+ if (GetNextBit())
+ {
+ // We have a genuine flux reversal.
+ // Pin 12 of UE5D is the BIT SYNC Input. When a positive pulse is applied to pin 12, the output of UE5D(pin 13) is applied to the load line (of UE7),
+ // causing the encoder/decoder clock to terminate the current cycle early and begin a new one.
+ ResetEncoderDecoder(18.0f, 20.0f); // Start seeing random flux reversals 18us-20us from now (ie since the last real flux reversal).
+ }
+ }
+ // The video amplifiers will often oscillate with no data in, but these oscillations are high enough in frequency that they "seldom" get past the valid pulse detector.
+ // Some do and some copy protections rely on this random behaviour so we need to emultate it.
+ // For example, 720 will read a byte from the disk multiple times and check that the values read each time were infact different. It does not matter what the values are just that they are different.
+ randomFluxReversalTime -= 0.0625f; // One 16th of a micro second.
+ if (randomFluxReversalTime <= 0) ResetEncoderDecoder(2.0f, 25.0f); // Trigger a random noise generated zero crossing and start seeing more anywhere between 2us and 25us after this one.
+ }
+ if (++UE7Counter == 0x10) // The count carry (bit 4) clocks UF4.
+ {
+ UE7Counter = CLOCK_SEL_AB; // A and B inputs of UE7 come from the VIA's CLOCK SEL A/B outputs (ie PB5/6) ie preload the encoder/decoder clock for the current density settings.
+ // The decoder consists of UF4 and UE5A. The ecoder has two outputs, Pin 1 of UE5A is the serial data output and pin 2 of UF4 (output B) is the serial clock output.
+ ++UF4Counter &= 0xf; // Clock and clamp UF4.
+ // The UD2 read shift register is clocked by serial clock (the rising edge of encoder/decoder's UF4 B output (serial clock))
+ // - ie on counts 2, 6, 10 and 14 (2 is the only count that outputs a 1 into readShiftRegister as the MSB bits of the count NORed together for other values are 0)
+ if ((UF4Counter & 0x3) == 2)
+ {
+ // A bit cell is four encoder/decoder clock pulses wide, as the 2nd bit of UF4 controls the serial clock (and takes 4 cycles to loop a two bit counter).
+ // If a flux reversal (or pulse into the decoder) occurs at the beginning of a cell, that cell is a 1 else that cell is a 0.
+ // If a flux reversal occurs, UF4's counter is cleared and the timing circuit is reset to start the encoder/decoder clock at the beginning of the VIA's current density setting.
+ // Pins 6 (output C) and 7 (output D) of UF4 are low, causing the output of UE5A, the serial data line, to go high.
+ // 2 encoder/decoder clock pulses later, the serial clock(pin 2 of UF4) goes high. When the serial clock line is high, the serial data line is valid and the shift register will shift in the data.
+ // The serial clock line remains high for another clock cycle.
+ // After four encoder/decoder clocks a bit cell is now complete.
+ // At this time, pins 2 (output A) and 3 (output B) of UF4 will again be low but as the count is counting up pin 6 (output C) will now be high.
+ // The high on pin 6 (output C) of UF4 causes the serial data line (pin 1 of UE5A) to go low as this is NORed with the low on pin 7 (output D).
+ // If a flux reversal occurs at the beginning of the next cell then everything resets and again we see a 1 on the serial data line 2 encoder/decoder cycles into that cell.
+ // If no flux reversal occurs at the beginning of the next cell, the serial data line will remain low when the serial clock line goes high again (two encoder/decoder clock cycles into the new cell).
+ // If there are no flux reversals for 2 cells then we see 0 on pin 6 (output C) and 1 on pin 7 (output D) of UF4 and this causes the serial data line (pin 1 of UE5A) to remain at 0.
+ // If there are no flux reversals for 3 cells then we see 1 on pin 6 (output C) and 1 on pin 7 (output D) of UF4 and this causes the serial data line (pin 1 of UE5A) to also remain at 0, after all, UE5A is a NOR gate.
+ // After 4 cells the counter inside UF4 loops back to 0 and we again see 0 on pin 6 (output C) and 0 on pin 7 (output C), causing the output of UE5A, the serial data line, to go to a 1, regardless of a true flux reversal!
+ readShiftRegister <<= 1;
+ readShiftRegister |= (UF4Counter == 2); // Emulate UE5A and only shift in a 1 when pins 6 (output C) and 7 (output D) (bits 2 and 3 of UF4Counter are 0. ie the first count of the bit cell)
+ if (writing) SetNextBit((writeShiftRegister & 0x80));
+ writeShiftRegister <<= 1;
+ // Note: SYNC can only trigger during reading as R/!W line is one of UC2's inputs.
+ if (!writing && ((readShiftRegister & 0x3ff) == 0x3ff)) // if the last 10 bits are 1s then SYNC
+ {
+ UE3Counter = 0; // Phase lock on to byte boundary
+ m_pVIA->GetPortB()->SetInput(0x80, false); // PB7 active low SYNC
+ }
+ else
+ {
+ if (!writing) m_pVIA->GetPortB()->SetInput(0x80, true); // SYNC not asserted if not following the SYNC bits
+ UE3Counter++;
+ }
+ }
+ // UC5B (NOR used to invert UF4's output B serial clock) output high when UF4 counts 0,1,4,5,8,9,12 and 13
+ else if (((UF4Counter & 2) == 0) && (UE3Counter == 8)) // Phase locked on to byte boundary
+ {
+ UE3Counter = 0;
+ SO = (m_pVIA->GetFCR() & m6522::FCR_CA2_OUTPUT_MODE0) != 0; // bit 2 of the FCR indicates "Byte Ready Active" turned on or not.
+ if (writing)
+ {
+ writeShiftRegister = m_pVIA->GetPortA()->GetOutput();
+ }
+ else
+ {
+ writeShiftRegister = (u8)(readShiftRegister & 0xff);
+ m_pVIA->GetPortA()->SetInput(writeShiftRegister);
+ }
+ }
+ }
+ }
+ }
+ m_pVIA->InputCA1(!SO);
+ return dataReady;
+}
diff --git a/Drive.h b/Drive.h
new file mode 100644
index 0000000..db28eaf
--- /dev/null
+++ b/Drive.h
@@ -0,0 +1,128 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef DRIVE_H
+#define DRIVE_H
+
+#include "m6522.h"
+#include "DiskImage.h"
+#include
+
+class Drive
+{
+public:
+ Drive();
+
+ void SetVIA(m6522* pVIA)
+ {
+ m_pVIA = pVIA;
+ pVIA->GetPortB()->SetPortOut(this, OnPortOut);
+ }
+
+ static void OnPortOut(void*, unsigned char status);
+
+ bool Update();
+ void Insert(DiskImage* diskImage);
+ inline const DiskImage* GetDiskImage() const { return diskImage; }
+ void Eject();
+ void Reset();
+ inline unsigned Track() const { return headTrackPos; }
+ inline unsigned SectorPos() const { return headBitOffset >> 3; }
+ inline unsigned GetHeadBitOffset() const { return headBitOffset; }
+ inline bool IsMotorOn() const { return motor; }
+ inline bool IsLEDOn() const { return LED; }
+
+ inline unsigned char GetLastHeadDirection() const { return lastHeadDirection; } // For simulated head movement sounds
+private:
+ inline float GenerateRandomFluxReversalTime(float min, float max) { return ((max - min) * ((float)rand() / RAND_MAX)) + min; } // Inputs in micro seconds
+
+ inline void ResetEncoderDecoder(float min, float max)
+ {
+ UE7Counter = CLOCK_SEL_AB; // A and B inputs of UE7 come from the VIA's CLOCK SEL A/B outputs (ie PB5/6)
+ UF4Counter = 0;
+ randomFluxReversalTime = GenerateRandomFluxReversalTime(min, max);
+ }
+ inline void UpdateHeadSectorPosition()
+ {
+ // Disk spins at 300rpm = 5rps so to calculate how many 16Mhz cycles one rotation takes;-
+ // 16000000 / 5 = 3200000;
+ static const float CYCLES_16Mhz_PER_ROTATION = 3200000.0f;
+
+ bitsInTrack = diskImage->BitsInTrack(headTrackPos);
+ headBitOffset %= bitsInTrack;
+ cyclesPerBit = CYCLES_16Mhz_PER_ROTATION / (float)bitsInTrack;
+ }
+
+ inline void MoveHead(unsigned char headDirection)
+ {
+ if (lastHeadDirection != headDirection)
+ {
+ if (((lastHeadDirection - 1) & 3) == headDirection)
+ {
+ if (headTrackPos > 0) headTrackPos--;
+ // else head bang
+ }
+ else if (((lastHeadDirection + 1) & 3) == headDirection)
+ {
+ if (headTrackPos < HALF_TRACK_COUNT - 1) headTrackPos++;
+ }
+ lastHeadDirection = headDirection;
+ UpdateHeadSectorPosition();
+ }
+ }
+
+ void DumpTrack(unsigned track); // Used for debugging disk images.
+
+ u32 AdvanceSectorPositionR(int& byte_offset); // No reason why I seperate these into individual read and write versions. I was just trying to get the bit stream to line up when rewriting over existing data.
+ u32 AdvanceSectorPositionW(int& byte_offset);
+ bool GetNextBit();
+ void SetNextBit(bool value);
+
+ DiskImage* diskImage;
+ // When swapping disks some code waits for the write protect signal to go high which will happen if a human ejects a disk.
+ // Emulate this by asserting the write protect signal for a few cycles before inserting the new disk image.
+ u32 newDiskImageQueuedCylesRemaining;
+
+ // CA1 (input)
+ // - !BYTE SYNC
+ // CA2 (output)
+ // - BYTE SYNC enable
+ // CB1 (NC)
+ // - check pulled H/L
+ // CB2 (output)
+ // - R/!W
+ m6522* m_pVIA;
+
+ float cyclesForBit;
+ u32 readShiftRegister;
+ unsigned headTrackPos;
+ u32 headBitOffset;
+ float randomFluxReversalTime;
+ int UE7Counter;
+ int UF4Counter;
+ int UE3Counter;
+ int CLOCK_SEL_AB;
+ bool SO;
+ unsigned char lastHeadDirection;
+ u32 bitsInTrack;
+ float cyclesPerBit;
+ bool motor;
+ bool LED;
+ u8 writeShiftRegister;
+};
+#endif
diff --git a/FileBrowser.cpp b/FileBrowser.cpp
new file mode 100644
index 0000000..740bc78
--- /dev/null
+++ b/FileBrowser.cpp
@@ -0,0 +1,932 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "FileBrowser.h"
+#include
+#include
+#include
+#include
+#include "Screen.h"
+#include "debug.h"
+#include "Keyboard.h"
+#include "options.h"
+#include "InputMappings.h"
+#include "stb_image.h"
+#include "Petscii.h"
+extern "C"
+{
+#include "rpi-gpio.h"
+}
+
+extern Screen screen;
+
+#define PNG_WIDTH 320
+#define PNG_HEIGHT 200
+
+
+unsigned char FileBrowser::LSTBuffer[FileBrowser::LSTBuffer_size];
+
+const unsigned FileBrowser::SwapKeys[30] =
+{
+ KEY_F1, KEY_KP1, KEY_1,
+ KEY_F2, KEY_KP2, KEY_2,
+ KEY_F3, KEY_KP3, KEY_3,
+ KEY_F4, KEY_KP4, KEY_4,
+ KEY_F5, KEY_KP5, KEY_5,
+ KEY_F6, KEY_KP6, KEY_6,
+ KEY_F7, KEY_KP7, KEY_7,
+ KEY_F8, KEY_KP8, KEY_8,
+ KEY_F9, KEY_KP9, KEY_9,
+ KEY_F10, KEY_KP0, KEY_0
+};
+
+static const u32 palette[] =
+{
+ RGBA(0x00, 0x00, 0x00, 0xFF),
+ RGBA(0xFF, 0xFF, 0xFF, 0xFF),
+ RGBA(0x88, 0x39, 0x32, 0xFF),
+ RGBA(0x67, 0xB6, 0xBD, 0xFF),
+ RGBA(0x8B, 0x4F, 0x96, 0xFF),
+ RGBA(0x55, 0xA0, 0x49, 0xFF),
+ RGBA(0x40, 0x31, 0x8D, 0xFF),
+ RGBA(0xBF, 0xCE, 0x72, 0xFF),
+ RGBA(0x8B, 0x54, 0x29, 0xFF),
+ RGBA(0x57, 0x42, 0x00, 0xFF),
+ RGBA(0xB8, 0x69, 0x62, 0xFF),
+ RGBA(0x50, 0x50, 0x50, 0xFF),
+ RGBA(0x78, 0x78, 0x78, 0xFF),
+ RGBA(0x94, 0xE0, 0x89, 0xFF),
+ RGBA(0x78, 0x69, 0xC4, 0xFF),
+ RGBA(0x9F, 0x9F, 0x9F, 0xFF)
+};
+
+FileBrowser::BrowsableList::Entry* FileBrowser::BrowsableList::FindEntry(const char* name)
+{
+ int index;
+ int len = (int)entries.size();
+
+ for (index = 0; index < len; ++index)
+ {
+ Entry* entry = &entries[index];
+ if (!(entry->filImage.fattrib & AM_DIR) && strcasecmp(name, entry->filImage.fname) == 0)
+ return entry;
+ }
+ return 0;
+}
+
+FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, unsigned deviceID, bool displayPNGIcons)
+ : state(State_Folders)
+ , maxOnScreen(38)
+ , diskCaddy(diskCaddy)
+ , selectionsMade(false)
+ , roms(roms)
+ , deviceID(deviceID)
+ , displayPNGIcons(displayPNGIcons)
+{
+}
+
+u32 FileBrowser::Colour(int index)
+{
+ return palette[index & 0xf];
+}
+
+struct greater
+{
+ bool operator()(const FileBrowser::BrowsableList::Entry& lhs, const FileBrowser::BrowsableList::Entry& rhs) const
+ {
+ if (strcasecmp(lhs.filImage.fname, "..") == 0)
+ return true;
+ else if (strcasecmp(rhs.filImage.fname, "..") == 0)
+ return false;
+ else if (((lhs.filImage.fattrib & AM_DIR) && (rhs.filImage.fattrib & AM_DIR)) || (!(lhs.filImage.fattrib & AM_DIR) && !(rhs.filImage.fattrib & AM_DIR)))
+ return strcasecmp(lhs.filImage.fname, rhs.filImage.fname) < 0;
+ else if ((lhs.filImage.fattrib & AM_DIR) && !(rhs.filImage.fattrib & AM_DIR))
+ return true;
+ else
+ return false;
+ }
+};
+
+void FileBrowser::RefreshFolderEntries()
+{
+ DIR dir;
+ FileBrowser::BrowsableList::Entry entry;
+ FRESULT res;
+ char* ext;
+
+ folder.Clear();
+ res = f_opendir(&dir, ".");
+ if (res == FR_OK)
+ {
+ do
+ {
+ res = f_readdir(&dir, &entry.filImage);
+ ext = strrchr(entry.filImage.fname, '.');
+ if (res == FR_OK && entry.filImage.fname[0] != 0 && !(ext && strcasecmp(ext, ".png") == 0))
+ folder.entries.push_back(entry);
+ }
+ while (res == FR_OK && entry.filImage.fname[0] != 0);
+ f_closedir(&dir);
+
+ // Now check for icons
+ res = f_opendir(&dir, ".");
+ if (res == FR_OK)
+ {
+ do
+ {
+ res = f_readdir(&dir, &entry.filIcon);
+ ext = strrchr(entry.filIcon.fname, '.');
+ if (ext)
+ {
+ int length = ext - entry.filIcon.fname;
+ if (res == FR_OK && entry.filIcon.fname[0] != 0 && strcasecmp(ext, ".png") == 0)
+ {
+ for (unsigned index = 0; index < folder.entries.size(); ++index)
+ {
+ FileBrowser::BrowsableList::Entry* entryAtIndex = &folder.entries[index];
+ if (strncasecmp(entry.filIcon.fname, entryAtIndex->filImage.fname, length) == 0)
+ entryAtIndex->filIcon = entry.filIcon;
+ }
+ }
+ }
+ }
+ while (res == FR_OK && entry.filIcon.fname[0] != 0);
+ }
+ f_closedir(&dir);
+
+ strcpy(entry.filImage.fname, "..");
+ entry.filIcon.fname[0] = 0;
+ folder.entries.push_back(entry);
+
+ std::sort(folder.entries.begin(), folder.entries.end(), greater());
+
+ if (folder.entries.size() > 0) folder.current = &folder.entries[0];
+ else folder.current = 0;
+
+ folder.currentIndex = 0;
+ }
+ else
+ {
+ //DEBUG_LOG("Cannot open dir");
+ }
+
+ // incase they deleted something selected in the caddy
+ caddySelections.Clear();
+}
+
+void FileBrowser::FolderChanged()
+{
+ RefreshFolderEntries();
+ RefeshDisplay();
+}
+
+void FileBrowser::DisplayRoot()
+{
+ f_chdir("\\1541");
+ FolderChanged();
+}
+
+void FileBrowser::RefeshDisplayForBrowsableList(FileBrowser::BrowsableList* browsableList, int xOffset, bool showSelected)
+{
+ char buffer1[128] = { 0 };
+ char buffer2[128] = { 0 };
+ u32 index;
+ u32 entryIndex;
+ u32 x = xOffset;
+ u32 y = 17;
+ u32 colour;
+ bool terminal = false;
+ RGBA BkColour = RGBA(0, 0, 0, 0xFF); //palette[VIC2_COLOUR_INDEX_BLUE];
+
+ if (terminal)
+ printf("\E[2J\E[f");
+
+ for (index = 0; index < maxOnScreen; ++index)
+ {
+ entryIndex = browsableList->offset + index;
+
+ if (entryIndex < browsableList->entries.size())
+ {
+ FileBrowser::BrowsableList::Entry* entry = &browsableList->entries[entryIndex];
+ snprintf(buffer2, 81, "%s", entry->filImage.fname);
+ memset(buffer1, ' ', 80);
+ buffer1[127] = 0;
+ strncpy(buffer1, buffer2, strlen(buffer2));
+ if (showSelected && browsableList->currentIndex == entryIndex)
+ {
+ if (entry->filImage.fattrib & AM_DIR)
+ {
+ if (terminal)
+ printf("\E[34;47m%s\E[0m\r\n", buffer1);
+ screen.PrintText(false, x, y, buffer1, palette[VIC2_COLOUR_INDEX_LBLUE], RGBA(0xff, 0xff, 0xff, 0xff));
+ }
+ else
+ {
+ colour = RGBA(0xff, 0, 0, 0xff);
+ if (entry->filImage.fattrib & AM_RDO)
+ colour = palette[VIC2_COLOUR_INDEX_RED];
+
+ screen.PrintText(false, x, y, buffer1, colour, RGBA(0xff, 0xff, 0xff, 0xff));
+ if (terminal)
+ printf("\E[31;47m%s\E[0m\r\n", buffer1);
+ }
+ }
+ else
+ {
+ if (entry->filImage.fattrib & AM_DIR)
+ {
+ screen.PrintText(false, x, y, buffer1, palette[VIC2_COLOUR_INDEX_LBLUE], BkColour);
+ if (terminal)
+ printf("\E[34m%s\E[0m\r\n", buffer1);
+ }
+ else
+ {
+ colour = palette[VIC2_COLOUR_INDEX_LGREY];
+ if (entry->filImage.fattrib & AM_RDO)
+ colour = palette[VIC2_COLOUR_INDEX_PINK];
+ screen.PrintText(false, x, y, buffer1, colour, BkColour);
+ if (terminal)
+ printf("\E[0;m%s\E[0m\r\n", buffer1);
+ }
+ }
+ }
+ else
+ {
+ memset(buffer1, ' ', 80);
+ screen.PrintText(false, x, y, buffer1, BkColour, BkColour);
+ if (terminal)
+ printf("%s\r\n", buffer1);
+ }
+ y += 16;
+ }
+}
+
+void FileBrowser::RefeshDisplay()
+{
+ char buffer[1024];
+ if (f_getcwd(buffer, 1024) == FR_OK)
+ {
+ u32 textColour = Colour(VIC2_COLOUR_INDEX_LGREEN);
+ u32 bgColour = Colour(VIC2_COLOUR_INDEX_GREY);
+
+ screen.ClearArea(0, 0, (int)screen.Width(), 17, bgColour);
+ screen.PrintText(false, 0, 0, buffer, textColour, bgColour);
+ }
+
+ RefeshDisplayForBrowsableList(&folder, 0);
+ RefeshDisplayForBrowsableList(&caddySelections, 1024 - 320, false);
+
+ DisplayPNG();
+ DisplayStatusBar();
+}
+
+bool FileBrowser::CheckForPNG(const char* filename, FILINFO& filIcon)
+{
+ bool foundValid = false;
+
+ filIcon.fname[0] = 0;
+
+ if (DiskImage::IsDiskImageExtention(filename))
+ {
+ char fileName[256];
+ char* ptr = strrchr(filename, '.');
+ if (ptr)
+ {
+ int len = ptr - filename;
+ strncpy(fileName, filename, len);
+ fileName[len] = 0;
+
+ strcat(fileName, ".png");
+
+ if (f_stat(fileName, &filIcon) == FR_OK && filIcon.fsize < FILEBROWSER_MAX_PNG_SIZE)
+ foundValid = true;
+ }
+ }
+ return foundValid;
+}
+
+void FileBrowser::DisplayPNG(FILINFO& filIcon, int x, int y)
+{
+ if (filIcon.fname[0] != 0 && filIcon.fsize < FILEBROWSER_MAX_PNG_SIZE)
+ {
+ FIL fp;
+ FRESULT res;
+
+ res = f_open(&fp, filIcon.fname, FA_READ);
+ if (res == FR_OK)
+ {
+ u32 bytesRead;
+ SetACTLed(true);
+ f_read(&fp, PNG, FILEBROWSER_MAX_PNG_SIZE, &bytesRead);
+ SetACTLed(false);
+ f_close(&fp);
+
+ int w;
+ int h;
+ int channels_in_file;
+ stbi_uc* image = stbi_load_from_memory((stbi_uc const*)PNG, bytesRead, &w, &h, &channels_in_file, 4);
+ if (image && (w == PNG_WIDTH && h == PNG_HEIGHT))
+ {
+ //DEBUG_LOG("Opened PNG %s w = %d h = %d cif = %d\r\n", fileName, w, h, channels_in_file);
+ screen.PlotImage((u32*)image, x, y, w, h);
+ }
+ else
+ {
+ //DEBUG_LOG("Invalid PNG size %d x %d\r\n", w, h);
+ }
+ }
+ }
+ else
+ {
+ //DEBUG_LOG("Cannot find PNG %s\r\n", fileName);
+ }
+}
+
+void FileBrowser::DisplayPNG()
+{
+ if (displayPNGIcons && folder.current)
+ {
+ FileBrowser::BrowsableList::Entry* current = folder.current;
+ DisplayPNG(current->filIcon, 1024 - 320, 426);
+ }
+}
+
+void FileBrowser::PopFolder()
+{
+ f_chdir("..");
+ //{
+ // char buffer[1024];
+ // if (f_getcwd(buffer, 1024) == FR_OK)
+ // {
+ // DEBUG_LOG("CWD = %s\r\n", buffer);
+ // }
+ //}
+ RefreshFolderEntries();
+ caddySelections.Clear();
+ RefeshDisplay();
+}
+
+void FileBrowser::UpdateInput()
+{
+ InputMappings* inputMappings = InputMappings::Instance();
+ Keyboard* keyboard = Keyboard::Instance();
+ bool dirty = false;
+
+ if (keyboard->CheckChanged())
+ dirty = inputMappings->CheckKeyboardBrowseMode();
+ else
+ dirty = inputMappings->CheckButtonsBrowseMode();
+
+ if (dirty)
+ {
+ //if (state == State_Folders)
+ UpdateInputFolders();
+ //else
+ // UpdateInputDiskCaddy();
+ }
+}
+
+FileBrowser::Folder* FileBrowser::GetCurrentFolder()
+{
+ return &folder;
+}
+
+bool FileBrowser::FillCaddyWithSelections()
+{
+ if (caddySelections.entries.size())
+ {
+ for (auto it = caddySelections.entries.begin(); it != caddySelections.entries.end();)
+ {
+ bool readOnly = ((*it).filImage.fattrib & AM_RDO) != 0;
+ if (diskCaddy->Insert(&(*it).filImage, readOnly) == false)
+ caddySelections.entries.erase(it);
+ else
+ it++;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+bool FileBrowser::AddToCaddy(FileBrowser::BrowsableList::Entry* current)
+{
+ bool added = false;
+
+ if (current && !(current->filImage.fattrib & AM_DIR) && DiskImage::IsDiskImageExtention(current->filImage.fname))
+ {
+ bool canAdd = true;
+ unsigned i;
+ for (i = 0; i < caddySelections.entries.size(); ++i)
+ {
+ if (strcmp(current->filImage.fname, caddySelections.entries[i].filImage.fname) == 0)
+ {
+ canAdd = false;
+ break;
+ }
+ }
+ if (canAdd)
+ {
+ caddySelections.entries.push_back(*current);
+ added = true;
+ }
+ }
+ return added;
+}
+
+void FileBrowser::UpdateInputFolders()
+{
+ Keyboard* keyboard = Keyboard::Instance();
+ InputMappings* inputMappings = InputMappings::Instance();
+
+ if (folder.entries.size() > 0)
+ {
+ u32 numberOfEntriesMinus1 = folder.entries.size() - 1;
+ bool dirty = false;
+
+ if (inputMappings->BrowseSelect())
+ {
+ FileBrowser::BrowsableList::Entry* current = folder.current;
+ if (current)
+ {
+ if (current->filImage.fattrib & AM_DIR)
+ {
+ if (strcmp(current->filImage.fname, "..") == 0)
+ {
+ PopFolder();
+ }
+ else if (strcmp(current->filImage.fname, ".") != 0)
+ {
+ f_chdir(current->filImage.fname);
+ RefreshFolderEntries();
+ }
+ dirty = true;
+ }
+ else
+ {
+ if (strcmp(current->filImage.fname, "..") == 0)
+ {
+ PopFolder();
+ }
+ else if (DiskImage::IsDiskImageExtention(current->filImage.fname))
+ {
+ DiskImage::DiskType diskType = DiskImage::GetDiskImageTypeViaExtention(current->filImage.fname);
+
+ // Should also be able to create a LST file from all the images currently selected in the caddy
+ if (diskType == DiskImage::LST)
+ {
+ selectionsMade = SelectLST(current->filImage.fname);
+ }
+ else
+ {
+ // Add the current selected
+ AddToCaddy(current);
+ selectionsMade = FillCaddyWithSelections();
+ }
+
+ if (selectionsMade)
+ lastSelectionName = current->filImage.fname;
+
+ dirty = true;
+ }
+ }
+ }
+ }
+ else if (inputMappings->BrowseDone())
+ {
+ selectionsMade = FillCaddyWithSelections();
+ }
+ //else if (keyboard->KeyPressed(KEY_TAB))
+ //{
+ // state = State_DiskCaddy;
+ // dirty = true;
+ //}
+ else if (inputMappings->BrowseBack())
+ {
+ PopFolder();
+ dirty = true;
+ }
+ else if (inputMappings->Exit())
+ {
+ caddySelections.Clear();
+ dirty = true;
+ }
+ else if (inputMappings->BrowseInsert())
+ {
+ FileBrowser::BrowsableList::Entry* current = folder.current;
+ if (current)
+ {
+ dirty = AddToCaddy(current);
+ }
+ }
+ else
+ {
+ unsigned keySetIndex;
+ for (keySetIndex = 0; keySetIndex < ROMs::MAX_ROMS; ++keySetIndex)
+ {
+ unsigned keySetIndexBase = keySetIndex * 3;
+ if (keyboard->KeyPressed(FileBrowser::SwapKeys[keySetIndexBase]) || keyboard->KeyPressed(FileBrowser::SwapKeys[keySetIndexBase + 1]) || keyboard->KeyPressed(FileBrowser::SwapKeys[keySetIndexBase + 2]))
+ {
+ if (roms->ROMValid[keySetIndex])
+ {
+ roms->currentROMIndex = keySetIndex;
+ roms->lastManualSelectedROMIndex = keySetIndex;
+ DEBUG_LOG("Swap ROM %d %s\r\n", keySetIndex, roms->ROMNames[keySetIndex]);
+ ShowDeviceAndROM();
+ }
+ }
+ }
+
+ if (inputMappings->BrowseDown())
+ {
+ if (folder.currentIndex < numberOfEntriesMinus1)
+ {
+ folder.currentIndex++;
+ folder.current = &folder.entries[folder.currentIndex];
+ if (folder.currentIndex >= (folder.offset + maxOnScreen) && (folder.currentIndex < folder.entries.size()))
+ folder.offset++;
+ dirty = true;
+ }
+ }
+ if (inputMappings->BrowseUp())
+ {
+ if (folder.currentIndex > 0)
+ {
+ folder.currentIndex--;
+ folder.current = &folder.entries[folder.currentIndex];
+ if ((folder.offset > 0) && (folder.currentIndex < folder.offset))
+ folder.offset--;
+ dirty = true;
+ }
+ }
+ if (inputMappings->BrowsePageDown())
+ {
+ u32 maxOnScreenMinus1 = maxOnScreen - 1;
+
+ if (folder.currentIndex == folder.offset + maxOnScreenMinus1)
+ {
+ // Need to move the screen window down so that the currentIndex is now at the top of the screen
+ folder.offset = folder.currentIndex;
+
+ // Current index now becomes the bottom one
+ if (folder.offset + maxOnScreenMinus1 > numberOfEntriesMinus1)
+ folder.currentIndex = numberOfEntriesMinus1; // Not enough entries to move another page just move to the last entry
+ else // Move the window down a page
+ folder.currentIndex = folder.offset + maxOnScreenMinus1;
+ }
+ else
+ {
+ // Need to move to folder.offset + maxOnScreenMinus1
+ if (folder.offset + maxOnScreenMinus1 > numberOfEntriesMinus1)
+ folder.currentIndex = numberOfEntriesMinus1; // Run out of entries before we hit the bottom. Just move to the bottom.
+ else
+ folder.currentIndex = folder.offset + maxOnScreenMinus1; // Move the bottom of the screen
+ }
+ folder.current = &folder.entries[folder.currentIndex];
+ dirty = true;
+ }
+ if (inputMappings->BrowsePageUp())
+ {
+ if (folder.currentIndex == folder.offset)
+ {
+ // If the cursor is already at the top of the window then page up
+ int offset = (int)folder.currentIndex - (int)maxOnScreen;
+ if (offset < 0) folder.offset = 0;
+ else folder.offset = (u32)offset;
+ folder.currentIndex = folder.offset;
+ }
+ else
+ {
+ folder.currentIndex = folder.offset; // Move the cursor to the top of the window
+ }
+ folder.current = &folder.entries[folder.currentIndex];
+ dirty = true;
+ }
+ }
+
+ if (dirty) RefeshDisplay();
+ }
+ else
+ {
+ if (inputMappings->BrowseBack())
+ PopFolder();
+ }
+}
+
+bool FileBrowser::SelectLST(const char* filenameLST)
+{
+ bool validImage = false;
+ //DEBUG_LOG("Selected %s\r\n", filenameLST);
+ if (DiskImage::IsLSTExtention(filenameLST))
+ {
+ FileBrowser::Folder* currentFolder = GetCurrentFolder();
+ if (currentFolder)
+ {
+ DiskImage::DiskType diskType;
+ FIL fp;
+ FRESULT res;
+ res = f_open(&fp, filenameLST, FA_READ);
+ if (res == FR_OK)
+ {
+ u32 bytesRead;
+ SetACTLed(true);
+ f_read(&fp, FileBrowser::LSTBuffer, FileBrowser::LSTBuffer_size, &bytesRead);
+ SetACTLed(false);
+ f_close(&fp);
+
+ TextParser textParser;
+
+ textParser.SetData((char*)FileBrowser::LSTBuffer);
+ char* token = textParser.GetToken(true);
+ while (token)
+ {
+ //DEBUG_LOG("LST token = %s\r\n", token);
+ diskType = DiskImage::GetDiskImageTypeViaExtention(token);
+ if (diskType == DiskImage::D64 || diskType == DiskImage::G64 || diskType == DiskImage::NIB || diskType == DiskImage::NBZ)
+ {
+ FileBrowser::BrowsableList::Entry* entry = currentFolder->FindEntry(token);
+ if (entry && !(entry->filImage.fattrib & AM_DIR))
+ {
+ bool readOnly = (entry->filImage.fattrib & AM_RDO) != 0;
+ if (diskCaddy->Insert(&entry->filImage, readOnly))
+ validImage = true;
+ }
+ }
+ else
+ {
+ roms->SelectROM(token);
+ }
+ token = textParser.GetToken(true);
+ }
+ }
+ }
+ }
+ return validImage;
+}
+
+// Not used
+void FileBrowser::UpdateInputDiskCaddy()
+{
+ bool dirty = false;
+ Keyboard* keyboard = Keyboard::Instance();
+
+ if (keyboard->KeyPressed(KEY_DELETE))
+ {
+ }
+ else if (keyboard->KeyPressed(KEY_TAB))
+ {
+ state = State_Folders;
+ dirty = true;
+ }
+ else
+ {
+ if (keyboard->KeyHeld(KEY_DOWN))
+ {
+ }
+ if (keyboard->KeyHeld(KEY_UP))
+ {
+ }
+ }
+
+ if (dirty) RefeshDisplay();
+}
+
+void FileBrowser::DisplayStatusBar()
+{
+ u32 x = 0;
+ u32 y = STATUS_BAR_POSITION_Y;
+
+ char bufferOut[128];
+ snprintf(bufferOut, 256, "LED 0 Motor 0 Track 00.0 ATN 0 DAT 0 CLK 0");
+ screen.PrintText(false, x, y, bufferOut, RGBA(0, 0, 0, 0xff), RGBA(0xff, 0xff, 0xff, 0xff));
+}
+
+void FileBrowser::ClearScreen()
+{
+ u32 bgColour = palette[VIC2_COLOUR_INDEX_BLUE];
+ screen.Clear(bgColour);
+}
+
+void FileBrowser::ShowDeviceAndROM()
+{
+ char buffer[256];
+ u32 textColour = RGBA(0, 0, 0, 0xff);
+ u32 bgColour = RGBA(0xff, 0xff, 0xff, 0xff);
+ u32 y = STATUS_BAR_POSITION_Y;
+
+ snprintf(buffer, 256, "Device %d %s \r\n", deviceID, roms->ROMNames[roms->currentROMIndex]);
+ screen.PrintText(false, 43 * 8, y, buffer, textColour, bgColour);
+}
+
+void FileBrowser::DisplayDiskInfo(DiskImage* diskImage, const char* filenameForIcon)
+{
+ // Ideally we should not have to load the entire disk to read the directory.
+ static const char* fileTypes[]=
+ {
+ "DEL", "SEQ", "PRG", "USR", "REL", "UKN", "UKN", "UKN"
+ };
+ // Decode the BAM
+ unsigned track = 18;
+ unsigned sectorNo = 0;
+ char name[17] = { 0 };
+ unsigned char buffer[260] = { 0 };
+ int charIndex;
+
+ u32 x = 0;
+ u32 y = 0;
+ char bufferOut[128] = { 0 };
+ u32 textColour = palette[VIC2_COLOUR_INDEX_LBLUE];
+ u32 bgColour = palette[VIC2_COLOUR_INDEX_BLUE];
+
+ u32 usedColour = palette[VIC2_COLOUR_INDEX_RED];
+ u32 freeColour = palette[VIC2_COLOUR_INDEX_LGREEN];
+
+ ClearScreen();
+
+ if (diskImage->GetDecodedSector(track, sectorNo, buffer))
+ {
+ track = buffer[0];
+ sectorNo = buffer[1];
+
+ //144-161 ($90-Al) Name of the disk (padded with "shift space")
+ //162,163 ($A2,$A3) Disk ID marker
+ //164 ($A4) $A0 Shift Space
+ //165,166 ($A5,$A6) $32,$41 ASCII chars "2A" DOS indicator
+ //167-170 ($A7-$AA) $A0 Shift Space
+ //171-255 ($AB-$FF) $00 Not used, filled with zero (The bytes 180 to 191 can have the contents "blocks free" on many disks.)
+
+ strncpy(name, (char*)&buffer[144], 16);
+
+ int blocksFree = 0;
+ int bamTrack;
+ int lastTrackUsed = (int)diskImage->LastTrackUsed() >> 1;
+ for (bamTrack = 0; bamTrack < 35; ++bamTrack)
+ {
+ if ((bamTrack + 1) != 18)
+ blocksFree += buffer[BAM_OFFSET + bamTrack * BAM_ENTRY_SIZE];
+
+ x = 400;
+ for (int bit = 0; bit < DiskImage::SectorsPerTrack[bamTrack]; bit++)
+ {
+ u32 bits = buffer[BAM_OFFSET + 1 + (bit >> 3) + bamTrack * BAM_ENTRY_SIZE];
+ bool used = (bits & (1 << (bit & 0x7))) == 0;
+
+ if (!used)
+ {
+ snprintf(bufferOut, 128, "%c", screen2petscii(87));
+ screen.PrintText(true, x, y, bufferOut, usedColour, bgColour);
+ }
+ else
+ {
+ snprintf(bufferOut, 128, "%c", screen2petscii(81));
+ screen.PrintText(true, x, y, bufferOut, freeColour, bgColour);
+ }
+ x += 8;
+ bits <<= 1;
+ }
+ y += 8;
+ }
+ for (; bamTrack < lastTrackUsed; ++bamTrack)
+ {
+ x = 400;
+ for (int bit = 0; bit < DiskImage::SectorsPerTrack[bamTrack]; bit++)
+ {
+ snprintf(bufferOut, 128, "%c", screen2petscii(87));
+ screen.PrintText(true, x, y, bufferOut, usedColour, bgColour);
+ x += 8;
+ }
+ y += 8;
+ }
+ x = 0;
+ y = 0;
+ snprintf(bufferOut, 128, "0");
+ screen.PrintText(true, x, y, bufferOut, textColour, bgColour);
+ x = 16;
+ snprintf(bufferOut, 128, "\"%s\" %c%c%c%c%c%c", name, buffer[162], buffer[163], buffer[164], buffer[165], buffer[166], buffer[167]);
+ screen.PrintText(true, x, y, bufferOut, bgColour, textColour);
+ x = 0;
+ y += 8;
+
+ if (track != 0)
+ {
+ bool complete = false;
+ // Blocks 1 through 19 on track 18 contain the file entries. The first two bytes of a block point to the next directory block with file entries. If no more directory blocks follow, these bytes contain $00 and $FF, respectively.
+ while (!complete)
+ {
+ //DEBUG_LOG("track %d sector %d\r\n", track, sectorNo);
+ if (diskImage->GetDecodedSector(track, sectorNo, buffer))
+ {
+ unsigned trackNext = buffer[0];
+ unsigned sectorNoNext = buffer[1];
+
+ complete = (track == trackNext) && (sectorNo == sectorNoNext); // Detect looping directory entries (raid over moscow ntsc)
+ complete |= (trackNext == 00) || (sectorNoNext == 0xff);
+ complete |= (trackNext == 18) && (sectorNoNext == 1);
+ track = trackNext;
+ sectorNo = sectorNoNext;
+
+ int entry;
+ int entryOffset = 2;
+ for (entry = 0; entry < 8; ++entry)
+ {
+ bool done = true;
+ for (int i = 0; i < 0x1d; ++i)
+ {
+ if (buffer[i + entryOffset])
+ done = false;
+ }
+
+ if (!done)
+ {
+ u8 fileType = buffer[DIR_ENTRY_OFFSET_TYPE + entryOffset];
+ u16 blocks = (buffer[DIR_ENTRY_OFFSET_BLOCKS + entryOffset + 1] << 8) | buffer[DIR_ENTRY_OFFSET_BLOCKS + entryOffset];
+
+ x = 0;
+ for (charIndex = 0; charIndex < DIR_ENTRY_NAME_LENGTH; ++charIndex)
+ {
+ char c = buffer[DIR_ENTRY_OFFSET_NAME + entryOffset + charIndex];
+ if (c == 0xa0) c = 0x20;
+ name[charIndex] = c;
+ }
+ name[charIndex] = 0;
+
+ //DEBUG_LOG("%d name = %s %x\r\n", blocks, name, fileType);
+ snprintf(bufferOut, 128, "%d", blocks);
+ screen.PrintText(true, x, y, bufferOut, textColour, bgColour);
+ x += 5 * 8;
+ snprintf(bufferOut, 128, "\"%s\"", name);
+ screen.PrintText(true, x, y, bufferOut, textColour, bgColour);
+ x += 19 * 8;
+ char modifier = 0x20;
+ if ((fileType & 0x80) == 0)
+ modifier = screen2petscii(42);
+ else if (fileType & 0x40)
+ modifier = screen2petscii(60);
+ snprintf(bufferOut, 128, "%s%c", fileTypes[fileType & 7], modifier);
+ screen.PrintText(true, x, y, bufferOut, textColour, bgColour);
+ y += 8;
+ }
+ entryOffset += 32;
+ }
+ }
+ else
+ {
+ // Error, just abort
+ complete = true;
+ }
+ }
+ }
+ x = 0;
+ //DEBUG_LOG("%d blocks free\r\n", blocksFree);
+ snprintf(bufferOut, 128, "%d BLOCKS FREE.\r\n", blocksFree);
+ screen.PrintText(true, x, y, bufferOut, textColour, bgColour);
+ y += 8;
+ }
+
+ DisplayStatusBar();
+
+ if (filenameForIcon)
+ {
+ FILINFO filIcon;
+ if (CheckForPNG(filenameForIcon, filIcon))
+ DisplayPNG(filIcon, 1024 - 320, 0);
+ }
+}
+
+void FileBrowser::AutoSelectTestImage()
+{
+ FileBrowser::BrowsableList::Entry* current = 0;
+ int index;
+ int maxEntries = folder.entries.size();
+
+ for (index = 0; index < maxEntries; ++index)
+ {
+ current = &folder.entries[index];
+ if (strcmp(current->filImage.fname, "someimage.g64") == 0)
+ {
+ break;
+ }
+ }
+
+ if (index != maxEntries)
+ {
+ caddySelections.Clear();
+ caddySelections.entries.push_back(*current);
+ selectionsMade = FillCaddyWithSelections();
+ }
+}
diff --git a/FileBrowser.h b/FileBrowser.h
new file mode 100644
index 0000000..107b74e
--- /dev/null
+++ b/FileBrowser.h
@@ -0,0 +1,169 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef FileBrowser_H
+#define FileBrowser_H
+#include
+#include "ff.h"
+#include
+#include "types.h"
+#include "DiskImage.h"
+#include "DiskCaddy.h"
+#include "ROMs.h"
+
+#define VIC2_COLOUR_INDEX_BLACK 0
+#define VIC2_COLOUR_INDEX_WHITE 1
+#define VIC2_COLOUR_INDEX_RED 2
+#define VIC2_COLOUR_INDEX_CYAN 3
+#define VIC2_COLOUR_INDEX_MAGENTA 4
+#define VIC2_COLOUR_INDEX_GREEN 5
+#define VIC2_COLOUR_INDEX_BLUE 6
+#define VIC2_COLOUR_INDEX_YELLOW 7
+#define VIC2_COLOUR_INDEX_ORANGE 8
+#define VIC2_COLOUR_INDEX_BROWN 9
+#define VIC2_COLOUR_INDEX_PINK 10
+#define VIC2_COLOUR_INDEX_DGREY 11
+#define VIC2_COLOUR_INDEX_GREY 12
+#define VIC2_COLOUR_INDEX_LGREEN 13
+#define VIC2_COLOUR_INDEX_LBLUE 14
+#define VIC2_COLOUR_INDEX_LGREY 15
+
+#define FILEBROWSER_MAX_PNG_SIZE 0x10000
+
+#define STATUS_BAR_POSITION_Y (40 * 16 + 10)
+
+class FileBrowser
+{
+public:
+
+ class BrowsableList
+ {
+ public:
+ BrowsableList()
+ : current(0)
+ , currentIndex(0)
+ , offset(0)
+ {
+ }
+
+ void Clear()
+ {
+ entries.clear();
+ current = 0;
+ currentIndex = 0;
+ offset = 0;
+ }
+
+ struct Entry
+ {
+ FILINFO filImage;
+ FILINFO filIcon;
+ };
+
+ Entry* FindEntry(const char* name);
+
+ std::vector entries;
+ Entry* current;
+ u32 currentIndex;
+ u32 offset;
+ };
+
+ class Folder : public BrowsableList
+ {
+ public:
+ Folder()
+ : BrowsableList()
+ , name(0)
+ {
+ }
+
+ FILINFO* name;
+ };
+
+ FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, unsigned deviceID, bool displayPNGIcons);
+
+ void AutoSelectTestImage();
+ void DisplayRoot();
+ void UpdateInput();
+
+ void RefeshDisplay();
+ void DisplayDiskInfo(DiskImage* diskImage, const char* filenameForIcon);
+
+ void DisplayStatusBar();
+
+ void FolderChanged();
+ void PopFolder();
+
+ bool SelectionsMade() { return selectionsMade; }
+ const char* LastSelectionName() { return lastSelectionName; }
+ void ClearSelections() { selectionsMade = false; caddySelections.Clear(); }
+
+ void ShowDeviceAndROM();
+
+ void ClearScreen();
+
+ void SetDeviceID(u8 id) { deviceID = id; }
+
+ Folder* GetCurrentFolder();
+
+ static const long int LSTBuffer_size = 1024 * 8;
+ static unsigned char LSTBuffer[];
+
+ static const unsigned SwapKeys[];
+
+ static u32 Colour(int index);
+
+
+ bool SelectLST(const char* filenameLST);
+
+private:
+ void DisplayPNG(FILINFO& filIcon, int x, int y);
+ void RefreshFolderEntries();
+
+ void UpdateInputFolders();
+ void UpdateInputDiskCaddy();
+
+ void RefeshDisplayForBrowsableList(FileBrowser::BrowsableList* browsableList, int xOffset, bool showSelected = true);
+
+ bool FillCaddyWithSelections();
+
+ bool AddToCaddy(FileBrowser::BrowsableList::Entry* current);
+
+ bool CheckForPNG(const char* filename, FILINFO& filIcon);
+ void DisplayPNG();
+
+ enum State
+ {
+ State_Folders,
+ State_DiskCaddy
+ } state;
+
+ Folder folder;
+ u32 maxOnScreen;
+ DiskCaddy* diskCaddy;
+ bool selectionsMade;
+ const char* lastSelectionName;
+ ROMs* roms;
+ unsigned deviceID;
+ bool displayPNGIcons;
+
+ BrowsableList caddySelections;
+
+ char PNG[FILEBROWSER_MAX_PNG_SIZE];
+};
+#endif
diff --git a/IOPort.h b/IOPort.h
new file mode 100644
index 0000000..37f9016
--- /dev/null
+++ b/IOPort.h
@@ -0,0 +1,49 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef IOPort_H
+#define IOPort_H
+#include
+
+typedef void(*PortOutFn)(void*, unsigned char status);
+
+class IOPort
+{
+public:
+ IOPort() : stateOut(0), stateIn(0), direction(0), portOutFn(0) {}
+
+ inline void SetInput(unsigned char pin, bool state)
+ {
+ if (state) stateIn |= pin;
+ else stateIn &= ~pin;
+ }
+ inline unsigned char GetInput() { return stateIn; }
+ inline void SetInput(unsigned char value) { stateIn = value; }
+ inline unsigned char GetOutput() { return stateOut; }
+ inline void SetOutput(unsigned char value) { stateOut = value; if (portOutFn) (portOutFn)(portOutFnThis, stateOut & direction); }
+ inline unsigned char GetDirection() { return direction; }
+ inline void SetDirection(unsigned char value) { direction = value; if (portOutFn) (portOutFn)(portOutFnThis, stateOut & direction); }
+ inline void SetPortOut(void* data, PortOutFn fn) { portOutFnThis = data; portOutFn = fn; }
+private:
+ unsigned char stateOut;
+ unsigned char stateIn;
+ unsigned char direction;
+ PortOutFn portOutFn;
+ void* portOutFnThis;
+};
+#endif
diff --git a/InputMappings.cpp b/InputMappings.cpp
new file mode 100644
index 0000000..e09a880
--- /dev/null
+++ b/InputMappings.cpp
@@ -0,0 +1,195 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "InputMappings.h"
+#include "FileBrowser.h"
+#include "iec_bus.h"
+#include "debug.h"
+extern "C"
+{
+#include "rpi-aux.h"
+}
+
+// If disk swaps can be done via multiple cores then directDiskSwapRequest needs to be volatile. WARNING: volatile acesses can be very expensive.
+//volatile unsigned InputMappings::directDiskSwapRequest = 0;
+unsigned InputMappings::directDiskSwapRequest = 0;
+//volatile unsigned InputMappings::uartFlags = 0;
+//unsigned InputMappings::escapeSequenceIndex = 0;
+
+InputMappings::InputMappings()
+{
+}
+
+bool InputMappings::CheckButtonsBrowseMode()
+{
+ buttonFlags = 0;
+ if (IEC_Bus::GetInputButtonPressed(0))
+ SetButtonFlag(ENTER_FLAG);
+ else if (IEC_Bus::GetInputButtonRepeating(1))
+ SetButtonFlag(UP_FLAG);
+ else if (IEC_Bus::GetInputButtonRepeating(2))
+ SetButtonFlag(DOWN_FLAG);
+ else if (IEC_Bus::GetInputButtonPressed(3))
+ SetButtonFlag(BACK_FLAG);
+ else if (IEC_Bus::GetInputButtonPressed(4))
+ SetButtonFlag(INSERT_FLAG);
+
+ return buttonFlags != 0;
+}
+
+void InputMappings::CheckButtonsEmulationMode()
+{
+ buttonFlags = 0;
+
+ if (IEC_Bus::GetInputButtonPressed(0))
+ SetButtonFlag(ESC_FLAG);
+ else if (IEC_Bus::GetInputButtonPressed(1))
+ SetButtonFlag(NEXT_FLAG);
+ else if (IEC_Bus::GetInputButtonPressed(2))
+ SetButtonFlag(PREV_FLAG);
+ //else if (IEC_Bus::GetInputButtonPressed(3))
+ // SetButtonFlag(BACK_FLAG);
+}
+
+
+//void InputMappings::CheckUart()
+//{
+// char charReceived;
+//
+// uartFlags = 0;
+//
+// if (RPI_AuxMiniUartRead(&charReceived))
+// {
+// DEBUG_LOG("charReceived=%c %02x\r\n", charReceived, charReceived);
+// if (charReceived == '[')
+// {
+// escapeSequenceIndex++;
+// }
+// else
+// {
+// if (escapeSequenceIndex == 0)
+// {
+// if (charReceived == 27)
+// SetUartFlag(ESC_FLAG);
+// else if (charReceived == 13)
+// SetUartFlag(ENTER_FLAG);
+// else if (charReceived == ' ')
+// SetUartFlag(SPACE_FLAG);
+// else if (charReceived == 0x7f)
+// SetUartFlag(BACK_FLAG);
+// //else if (charReceived == 'u')
+// // SetUartFlag(UP_FLAG);
+// //else if (charReceived == 'U')
+// // SetUartFlag(PAGEUP_FLAG);
+// //else if (charReceived == 'd')
+// // SetUartFlag(DOWN_FLAG);
+// //else if (charReceived == 'D')
+// // SetUartFlag(PAGEDOWN_FLAG);
+// else
+// {
+// char number = charReceived - '0';
+// if (number >= 0 && number <= 9)
+// {
+// if (number == 0)
+// number = 10;
+// directDiskSwapRequest |= (1 << (number - 1));
+// printf("SWAP %d\r\n", number);
+// }
+// }
+// }
+// else if (escapeSequenceIndex == 1)
+// {
+// if (charReceived == 'A')
+// SetUartFlag(UP_FLAG);
+// else if (charReceived == 'B')
+// SetUartFlag(DOWN_FLAG);
+// else if (charReceived == 'C')
+// SetUartFlag(PAGEDOWN_FLAG);
+// else if (charReceived == 'D')
+// SetUartFlag(PAGEUP_FLAG);
+// else if (charReceived == '2')
+// SetUartFlag(INSERT_FLAG);
+// escapeSequenceIndex = 0;
+// }
+// }
+// }
+//}
+
+bool InputMappings::CheckKeyboardBrowseMode()
+{
+ Keyboard* keyboard = Keyboard::Instance();
+
+ keyboardFlags = 0;
+
+ if (keyboard->KeyHeld(KEY_ESC))
+ SetKeyboardFlag(ESC_FLAG);
+ else if (keyboard->KeyHeld(KEY_ENTER))
+ SetKeyboardFlag(ENTER_FLAG);
+ else if (keyboard->KeyHeld(KEY_BACKSPACE))
+ SetKeyboardFlag(BACK_FLAG);
+ else if (keyboard->KeyHeld(KEY_SPACE))
+ SetKeyboardFlag(SPACE_FLAG);
+ else if (keyboard->KeyHeld(KEY_INSERT))
+ SetKeyboardFlag(INSERT_FLAG);
+ else if (keyboard->KeyHeld(KEY_UP))
+ SetKeyboardFlag(UP_FLAG);
+ else if (keyboard->KeyHeld(KEY_PAGEUP) || keyboard->KeyHeld(KEY_LEFT))
+ SetKeyboardFlag(PAGEUP_FLAG);
+ else if (keyboard->KeyHeld(KEY_DOWN))
+ SetKeyboardFlag(DOWN_FLAG);
+ else if (keyboard->KeyHeld(KEY_PAGEDOWN) || keyboard->KeyHeld(KEY_RIGHT))
+ SetKeyboardFlag(PAGEDOWN_FLAG);
+ else
+ {
+ unsigned index;
+ for (index = 0; index < 10; ++index)
+ {
+ unsigned keySetIndexBase = index * 3;
+ if (keyboard->KeyHeld(FileBrowser::SwapKeys[keySetIndexBase]) || keyboard->KeyHeld(FileBrowser::SwapKeys[keySetIndexBase + 1]) || keyboard->KeyHeld(FileBrowser::SwapKeys[keySetIndexBase + 2]))
+ keyboardFlags |= NUMBER_FLAG;
+ }
+ }
+
+ return keyboardFlags != 0;
+}
+
+void InputMappings::CheckKeyboardEmulationMode(unsigned numberOfImages, unsigned numberOfImagesMax)
+{
+ Keyboard* keyboard = Keyboard::Instance();
+
+ keyboardFlags = 0;
+ if (keyboard->CheckChanged())
+ {
+ if (keyboard->KeyHeld(KEY_ESC))
+ SetKeyboardFlag(ESC_FLAG);
+ else if (keyboard->KeyHeld(KEY_PAGEUP))
+ SetKeyboardFlag(PREV_FLAG);
+ else if (keyboard->KeyHeld(KEY_PAGEDOWN))
+ SetKeyboardFlag(NEXT_FLAG);
+ else if (numberOfImages > 1)
+ {
+ unsigned index;
+ for (index = 0; index < numberOfImagesMax; ++index)
+ {
+ unsigned keySetIndexBase = index * 3;
+ if (keyboard->KeyHeld(FileBrowser::SwapKeys[keySetIndexBase]) || keyboard->KeyHeld(FileBrowser::SwapKeys[keySetIndexBase + 1]) || keyboard->KeyHeld(FileBrowser::SwapKeys[keySetIndexBase + 2]))
+ directDiskSwapRequest |= (1 << index);
+ }
+ }
+ }
+}
diff --git a/InputMappings.h b/InputMappings.h
new file mode 100644
index 0000000..f17a48d
--- /dev/null
+++ b/InputMappings.h
@@ -0,0 +1,129 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef InputMappings_H
+#define InputMappings_H
+#include "Singleton.h"
+#include "Keyboard.h"
+
+#define ESC_FLAG (1 << 0)
+#define NEXT_FLAG (1 << 1)
+#define PREV_FLAG (1 << 2)
+#define ENTER_FLAG (1 << 3)
+#define UP_FLAG (1 << 4)
+#define PAGEUP_FLAG (1 << 5)
+#define DOWN_FLAG (1 << 6)
+#define PAGEDOWN_FLAG (1 << 7)
+#define SPACE_FLAG (1 << 8)
+#define BACK_FLAG (1 << 9)
+#define INSERT_FLAG (1 << 10)
+#define NUMBER_FLAG (1 << 11)
+
+class InputMappings : public Singleton
+{
+protected:
+ friend Singleton;
+
+ unsigned keyboardFlags;
+ unsigned buttonFlags;
+
+ //inline void SetUartFlag(unsigned flag) { uartFlags |= flag; }
+ //inline bool UartFlag(unsigned flag) { return (uartFlags & flag) != 0; }
+ inline void SetKeyboardFlag(unsigned flag) { keyboardFlags |= flag; }
+ inline bool KeyboardFlag(unsigned flag) { return (keyboardFlags & flag) != 0; }
+ inline void SetButtonFlag(unsigned flag) { buttonFlags |= flag; }
+ inline bool ButtonFlag(unsigned flag) { return (buttonFlags & flag) != 0; }
+
+public:
+ InputMappings();
+
+ //void CheckUart(); // One core will call this
+ bool CheckKeyboardBrowseMode();
+ void CheckKeyboardEmulationMode(unsigned numberOfImages, unsigned numberOfImagesMax); // The other core will call this
+ bool CheckButtonsBrowseMode();
+ void CheckButtonsEmulationMode();
+
+ void Reset()
+ {
+ keyboardFlags = 0;
+ buttonFlags = 0;
+ }
+
+ inline bool Exit()
+ {
+ return KeyboardFlag(ESC_FLAG)/* | UartFlag(ESC_FLAG)*/ | ButtonFlag(ESC_FLAG);
+ }
+
+ inline bool NextDisk()
+ {
+ return KeyboardFlag(NEXT_FLAG)/* | UartFlag(NEXT_FLAG)*/ | ButtonFlag(NEXT_FLAG);
+ }
+
+ inline bool PrevDisk()
+ {
+ return KeyboardFlag(PREV_FLAG)/* | UartFlag(PREV_FLAG)*/ | ButtonFlag(PREV_FLAG);
+ }
+
+ inline bool BrowseSelect()
+ {
+ return KeyboardFlag(ENTER_FLAG)/* | UartFlag(ENTER_FLAG)*/ | ButtonFlag(ENTER_FLAG);
+ }
+
+ inline bool BrowseDone()
+ {
+ return KeyboardFlag(SPACE_FLAG)/* | UartFlag(SPACE_FLAG)*/;
+ }
+
+ inline bool BrowseBack()
+ {
+ return KeyboardFlag(BACK_FLAG)/* | UartFlag(BACK_FLAG)*/ | ButtonFlag(BACK_FLAG);
+ }
+
+ inline bool BrowseUp()
+ {
+ return KeyboardFlag(UP_FLAG)/* | UartFlag(UP_FLAG)*/ | ButtonFlag(UP_FLAG);
+ }
+
+ inline bool BrowsePageUp()
+ {
+ return KeyboardFlag(PAGEUP_FLAG)/* | UartFlag(PAGEUP_FLAG)*/;
+ }
+
+ inline bool BrowseDown()
+ {
+ return KeyboardFlag(DOWN_FLAG)/* | UartFlag(DOWN_FLAG)*/ | ButtonFlag(DOWN_FLAG);
+ }
+
+ inline bool BrowsePageDown()
+ {
+ return KeyboardFlag(PAGEDOWN_FLAG)/* | UartFlag(PAGEDOWN_FLAG)*/;
+ }
+
+ inline bool BrowseInsert()
+ {
+ return KeyboardFlag(INSERT_FLAG)/* | UartFlag(INSERT_FLAG)*/ | ButtonFlag(INSERT_FLAG);
+ }
+
+ // Used by the 2 cores so need to be volatile
+ //volatile static unsigned directDiskSwapRequest;
+ static unsigned directDiskSwapRequest;
+ //volatile static unsigned uartFlags; // WARNING uncached volatile accessed across cores can be very expensive and may cause the emulation to exceed the 1us time frame and a realtime cycle will not be emulated correctly!
+//private:
+// static unsigned escapeSequenceIndex;
+};
+#endif
diff --git a/Keyboard.cpp b/Keyboard.cpp
new file mode 100644
index 0000000..55dcff9
--- /dev/null
+++ b/Keyboard.cpp
@@ -0,0 +1,155 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "Keyboard.h"
+#include
+#include
+#include "timer.h"
+#include "debug.h"
+extern "C"
+{
+#include "uspi\devicenameservice.h"
+}
+
+#define REPEAT_RATE 8
+#define REPEAT_DELAY 3
+
+void Keyboard::KeyPressedHandlerRaw(TUSBKeyboardDevice* device, unsigned char modifiers, const unsigned char RawKeys[6])
+{
+ // byte 0 - modifires
+ // bit 0: left control
+ // bit 1 : left shift
+ // bit 2 : left alt
+ // bit 3 : left GUI(Win / Apple / Meta key)
+ // bit 4 : right control
+ // bit 5 : right shift
+ // bit 6 : right alt
+ // bit 7 : right GUI
+ // byte 1 - reserved
+ // bytes 2-7 - represent the keys that are concurrently pressed (up to 6 in this case)
+ // ranges from 0x04 to 0xE7
+ // If no keys are currently pressed then all 6 bytes should contain 0x00
+ // The key repeat delay and rate is purely a host function.
+ //DEBUG_LOG("KeyPressedHandlerRaw: %x %d %d %d %d %d %d\r\n", modifiers, RawKeys[0], RawKeys[1], RawKeys[2], RawKeys[3], RawKeys[4], RawKeys[5]);
+
+ Keyboard* keyboard = Keyboard::Instance();
+ bool anyDown = false;
+
+ keyboard->keyStatusPrev[0] = keyboard->keyStatus[0];
+ keyboard->keyStatusPrev[1] = keyboard->keyStatus[1];
+ keyboard->keyStatus[0] = 0;
+ keyboard->keyStatus[1] = 0;
+
+ keyboard->modifier = modifiers;
+
+ int index;
+ for (index = 0; index < 6; ++index)
+ {
+ u8 rawKey = RawKeys[index];
+ if (rawKey >= 4)
+ {
+ int keyStatusIndex = (rawKey >= 64) ? 1 : 0;
+
+ //DEBUG_LOG("%x %d\r\n", rawKey, keyStatusIndex);
+
+ u64 keyBit = 1ULL << (u64)(rawKey & 0x3f);
+ if (keyboard->keyStatusPrev[keyStatusIndex] & keyBit)
+ {
+ }
+ else
+ {
+ keyboard->keyRepeatCount[rawKey] = 0;
+ }
+ keyboard->keyStatus[keyStatusIndex] |= keyBit;
+ anyDown = true;
+ }
+ }
+
+ if (anyDown)
+ {
+ // Only need the timer if a key was held down
+ if (keyboard->timer == 0)
+ {
+ keyboard->timer = TimerStartKernelTimer(REPEAT_RATE, USBKeyboardDeviceTimerHandler, 0, device);
+ //DEBUG_LOG("Timer started\r\n");
+ }
+ }
+ else
+ {
+ if (keyboard->timer != 0)
+ {
+ TimerCancelKernelTimer(keyboard->timer);
+ keyboard->timer = 0;
+ }
+ }
+ keyboard->updateCount++;
+}
+
+void Keyboard::USBKeyboardDeviceTimerHandler(unsigned hTimer, void *pParam, void *pContext)
+{
+ Keyboard* keyboard = Keyboard::Instance();
+ bool anyDown = false;
+
+ int keyStatusIndex;
+ int keyIndex;
+ for (keyStatusIndex = 0; keyStatusIndex < 2; ++keyStatusIndex)
+ {
+ for (keyIndex = 0; keyIndex < 64; ++keyIndex)
+ {
+ int keyCodeIndex = keyStatusIndex * 64 + keyIndex;
+
+ if (keyboard->keyStatus[keyStatusIndex] & (1ULL << keyIndex))
+ {
+ anyDown = true;
+
+ keyboard->keyRepeatCount[keyCodeIndex]++;
+ if (keyboard->keyRepeatCount[keyCodeIndex] > REPEAT_DELAY)
+ keyboard->updateCount++;
+ }
+ else
+ {
+ keyboard->keyRepeatCount[keyCodeIndex] = 0;
+ }
+ }
+ }
+
+ keyboard->keyStatusPrev[0] = keyboard->keyStatus[0];
+ keyboard->keyStatusPrev[1] = keyboard->keyStatus[1];
+
+ if (keyboard->timer != 0)
+ {
+ TimerCancelKernelTimer(keyboard->timer);
+ keyboard->timer = 0;
+ }
+
+ if (anyDown) // Only need the timer if a key was held down
+ keyboard->timer = TimerStartKernelTimer(REPEAT_RATE, USBKeyboardDeviceTimerHandler, 0, pContext);
+}
+
+Keyboard::Keyboard()
+ : modifier(0)
+ , timer(0)
+ , updateCount(0)
+ , updateCountLastRead(-1)
+{
+ keyStatus[0] = 0;
+ keyStatus[1] = 0;
+
+ memset(keyRepeatCount, 0, sizeof(keyRepeatCount));
+ USPiKeyboardRegisterKeyStatusHandlerRaw(KeyPressedHandlerRaw);
+}
diff --git a/Keyboard.h b/Keyboard.h
new file mode 100644
index 0000000..21cde5f
--- /dev/null
+++ b/Keyboard.h
@@ -0,0 +1,348 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef Keyboard_H
+#define Keyboard_H
+#include "Singleton.h"
+#include
+
+extern "C"
+{
+#include
+}
+
+#define MAX_KEYS 0x7f
+
+#define KEY_MOD_LCTRL 0x01
+#define KEY_MOD_LSHIFT 0x02
+#define KEY_MOD_LALT 0x04
+#define KEY_MOD_LMETA 0x08
+#define KEY_MOD_RCTRL 0x10
+#define KEY_MOD_RSHIFT 0x20
+#define KEY_MOD_RALT 0x40
+#define KEY_MOD_RMETA 0x80
+
+#define KEY_A 0x04 // Keyboard a and A
+#define KEY_B 0x05 // Keyboard b and B
+#define KEY_C 0x06 // Keyboard c and C
+#define KEY_D 0x07 // Keyboard d and D
+#define KEY_E 0x08 // Keyboard e and E
+#define KEY_F 0x09 // Keyboard f and F
+#define KEY_G 0x0a // Keyboard g and G
+#define KEY_H 0x0b // Keyboard h and H
+#define KEY_I 0x0c // Keyboard i and I
+#define KEY_J 0x0d // Keyboard j and J
+#define KEY_K 0x0e // Keyboard k and K
+#define KEY_L 0x0f // Keyboard l and L
+#define KEY_M 0x10 // Keyboard m and M
+#define KEY_N 0x11 // Keyboard n and N
+#define KEY_O 0x12 // Keyboard o and O
+#define KEY_P 0x13 // Keyboard p and P
+#define KEY_Q 0x14 // Keyboard q and Q
+#define KEY_R 0x15 // Keyboard r and R
+#define KEY_S 0x16 // Keyboard s and S
+#define KEY_T 0x17 // Keyboard t and T
+#define KEY_U 0x18 // Keyboard u and U
+#define KEY_V 0x19 // Keyboard v and V
+#define KEY_W 0x1a // Keyboard w and W
+#define KEY_X 0x1b // Keyboard x and X
+#define KEY_Y 0x1c // Keyboard y and Y
+#define KEY_Z 0x1d // Keyboard z and Z
+
+#define KEY_1 0x1e // Keyboard 1 and !
+#define KEY_2 0x1f // Keyboard 2 and @
+#define KEY_3 0x20 // Keyboard 3 and #
+#define KEY_4 0x21 // Keyboard 4 and $
+#define KEY_5 0x22 // Keyboard 5 and %
+#define KEY_6 0x23 // Keyboard 6 and ^
+#define KEY_7 0x24 // Keyboard 7 and &
+#define KEY_8 0x25 // Keyboard 8 and *
+#define KEY_9 0x26 // Keyboard 9 and (
+#define KEY_0 0x27 // Keyboard 0 and )
+
+#define KEY_ENTER 0x28 // Keyboard Return (ENTER)
+#define KEY_ESC 0x29 // Keyboard ESCAPE
+#define KEY_BACKSPACE 0x2a // Keyboard DELETE (Backspace)
+#define KEY_TAB 0x2b // Keyboard Tab
+#define KEY_SPACE 0x2c // Keyboard Spacebar
+#define KEY_MINUS 0x2d // Keyboard - and _
+#define KEY_EQUAL 0x2e // Keyboard = and +
+#define KEY_LEFTBRACE 0x2f // Keyboard [ and {
+#define KEY_RIGHTBRACE 0x30 // Keyboard ] and }
+#define KEY_BACKSLASH 0x31 // Keyboard \ and |
+#define KEY_HASHTILDE 0x32 // Keyboard Non-US # and ~
+#define KEY_SEMICOLON 0x33 // Keyboard ; and :
+#define KEY_APOSTROPHE 0x34 // Keyboard ' and "
+#define KEY_GRAVE 0x35 // Keyboard ` and ~
+#define KEY_COMMA 0x36 // Keyboard , and <
+#define KEY_DOT 0x37 // Keyboard . and >
+#define KEY_SLASH 0x38 // Keyboard / and ?
+#define KEY_CAPSLOCK 0x39 // Keyboard Caps Lock
+
+#define KEY_F1 0x3a // Keyboard F1
+#define KEY_F2 0x3b // Keyboard F2
+#define KEY_F3 0x3c // Keyboard F3
+#define KEY_F4 0x3d // Keyboard F4
+#define KEY_F5 0x3e // Keyboard F5
+#define KEY_F6 0x3f // Keyboard F6
+#define KEY_F7 0x40 // Keyboard F7
+#define KEY_F8 0x41 // Keyboard F8
+#define KEY_F9 0x42 // Keyboard F9
+#define KEY_F10 0x43 // Keyboard F10
+#define KEY_F11 0x44 // Keyboard F11
+#define KEY_F12 0x45 // Keyboard F12
+
+#define KEY_SYSRQ 0x46 // Keyboard Print Screen
+#define KEY_SCROLLLOCK 0x47 // Keyboard Scroll Lock
+#define KEY_PAUSE 0x48 // Keyboard Pause
+#define KEY_INSERT 0x49 // Keyboard Insert
+#define KEY_HOME 0x4a // Keyboard Home
+#define KEY_PAGEUP 0x4b // Keyboard Page Up
+#define KEY_DELETE 0x4c // Keyboard Delete Forward
+#define KEY_END 0x4d // Keyboard End
+#define KEY_PAGEDOWN 0x4e // Keyboard Page Down
+#define KEY_RIGHT 0x4f // Keyboard Right Arrow
+#define KEY_LEFT 0x50 // Keyboard Left Arrow
+#define KEY_DOWN 0x51 // Keyboard Down Arrow
+#define KEY_UP 0x52 // Keyboard Up Arrow
+
+#define KEY_NUMLOCK 0x53 // Keyboard Num Lock and Clear
+#define KEY_KPSLASH 0x54 // Keypad /
+#define KEY_KPASTERISK 0x55 // Keypad *
+#define KEY_KPMINUS 0x56 // Keypad -
+#define KEY_KPPLUS 0x57 // Keypad +
+#define KEY_KPENTER 0x58 // Keypad ENTER
+#define KEY_KP1 0x59 // Keypad 1 and End
+#define KEY_KP2 0x5a // Keypad 2 and Down Arrow
+#define KEY_KP3 0x5b // Keypad 3 and PageDn
+#define KEY_KP4 0x5c // Keypad 4 and Left Arrow
+#define KEY_KP5 0x5d // Keypad 5
+#define KEY_KP6 0x5e // Keypad 6 and Right Arrow
+#define KEY_KP7 0x5f // Keypad 7 and Home
+#define KEY_KP8 0x60 // Keypad 8 and Up Arrow
+#define KEY_KP9 0x61 // Keypad 9 and Page Up
+#define KEY_KP0 0x62 // Keypad 0 and Insert
+#define KEY_KPDOT 0x63 // Keypad . and Delete
+
+#define KEY_102ND 0x64 // Keyboard Non-US \ and |
+#define KEY_COMPOSE 0x65 // Keyboard Application
+#define KEY_POWER 0x66 // Keyboard Power
+#define KEY_KPEQUAL 0x67 // Keypad =
+
+#define KEY_F13 0x68 // Keyboard F13
+#define KEY_F14 0x69 // Keyboard F14
+#define KEY_F15 0x6a // Keyboard F15
+#define KEY_F16 0x6b // Keyboard F16
+#define KEY_F17 0x6c // Keyboard F17
+#define KEY_F18 0x6d // Keyboard F18
+#define KEY_F19 0x6e // Keyboard F19
+#define KEY_F20 0x6f // Keyboard F20
+#define KEY_F21 0x70 // Keyboard F21
+#define KEY_F22 0x71 // Keyboard F22
+#define KEY_F23 0x72 // Keyboard F23
+#define KEY_F24 0x73 // Keyboard F24
+
+#define KEY_OPEN 0x74 // Keyboard Execute
+#define KEY_HELP 0x75 // Keyboard Help
+#define KEY_PROPS 0x76 // Keyboard Menu
+#define KEY_FRONT 0x77 // Keyboard Select
+#define KEY_STOP 0x78 // Keyboard Stop
+#define KEY_AGAIN 0x79 // Keyboard Again
+#define KEY_UNDO 0x7a // Keyboard Undo
+#define KEY_CUT 0x7b // Keyboard Cut
+#define KEY_COPY 0x7c // Keyboard Copy
+#define KEY_PASTE 0x7d // Keyboard Paste
+#define KEY_FIND 0x7e // Keyboard Find
+#define KEY_MUTE 0x7f // Keyboard Mute
+#define KEY_VOLUMEUP 0x80 // Keyboard Volume Up
+#define KEY_VOLUMEDOWN 0x81 // Keyboard Volume Down
+// 0x82 Keyboard Locking Caps Lock
+// 0x83 Keyboard Locking Num Lock
+// 0x84 Keyboard Locking Scroll Lock
+#define KEY_KPCOMMA 0x85 // Keypad Comma
+// 0x86 Keypad Equal Sign
+#define KEY_RO 0x87 // Keyboard International1
+#define KEY_KATAKANAHIRAGANA 0x88 // Keyboard International2
+#define KEY_YEN 0x89 // Keyboard International3
+#define KEY_HENKAN 0x8a // Keyboard International4
+#define KEY_MUHENKAN 0x8b // Keyboard International5
+#define KEY_KPJPCOMMA 0x8c // Keyboard International6
+// 0x8d Keyboard International7
+// 0x8e Keyboard International8
+// 0x8f Keyboard International9
+#define KEY_HANGEUL 0x90 // Keyboard LANG1
+#define KEY_HANJA 0x91 // Keyboard LANG2
+#define KEY_KATAKANA 0x92 // Keyboard LANG3
+#define KEY_HIRAGANA 0x93 // Keyboard LANG4
+#define KEY_ZENKAKUHANKAKU 0x94 // Keyboard LANG5
+// 0x95 Keyboard LANG6
+// 0x96 Keyboard LANG7
+// 0x97 Keyboard LANG8
+// 0x98 Keyboard LANG9
+// 0x99 Keyboard Alternate Erase
+// 0x9a Keyboard SysReq/Attention
+// 0x9b Keyboard Cancel
+// 0x9c Keyboard Clear
+// 0x9d Keyboard Prior
+// 0x9e Keyboard Return
+// 0x9f Keyboard Separator
+// 0xa0 Keyboard Out
+// 0xa1 Keyboard Oper
+// 0xa2 Keyboard Clear/Again
+// 0xa3 Keyboard CrSel/Props
+// 0xa4 Keyboard ExSel
+
+// 0xb0 Keypad 00
+// 0xb1 Keypad 000
+// 0xb2 Thousands Separator
+// 0xb3 Decimal Separator
+// 0xb4 Currency Unit
+// 0xb5 Currency Sub-unit
+#define KEY_KPLEFTPAREN 0xb6 // Keypad (
+#define KEY_KPRIGHTPAREN 0xb7 // Keypad )
+// 0xb8 Keypad {
+// 0xb9 Keypad }
+// 0xba Keypad Tab
+// 0xbb Keypad Backspace
+// 0xbc Keypad A
+// 0xbd Keypad B
+// 0xbe Keypad C
+// 0xbf Keypad D
+// 0xc0 Keypad E
+// 0xc1 Keypad F
+// 0xc2 Keypad XOR
+// 0xc3 Keypad ^
+// 0xc4 Keypad %
+// 0xc5 Keypad <
+// 0xc6 Keypad >
+// 0xc7 Keypad &
+// 0xc8 Keypad &&
+// 0xc9 Keypad |
+// 0xca Keypad ||
+// 0xcb Keypad :
+// 0xcc Keypad #
+// 0xcd Keypad Space
+// 0xce Keypad @
+// 0xcf Keypad !
+// 0xd0 Keypad Memory Store
+// 0xd1 Keypad Memory Recall
+// 0xd2 Keypad Memory Clear
+// 0xd3 Keypad Memory Add
+// 0xd4 Keypad Memory Subtract
+// 0xd5 Keypad Memory Multiply
+// 0xd6 Keypad Memory Divide
+// 0xd7 Keypad +/-
+// 0xd8 Keypad Clear
+// 0xd9 Keypad Clear Entry
+// 0xda Keypad Binary
+// 0xdb Keypad Octal
+// 0xdc Keypad Decimal
+// 0xdd Keypad Hexadecimal
+
+#define KEY_LEFTCTRL 0xe0 // Keyboard Left Control
+#define KEY_LEFTSHIFT 0xe1 // Keyboard Left Shift
+#define KEY_LEFTALT 0xe2 // Keyboard Left Alt
+#define KEY_LEFTMETA 0xe3 // Keyboard Left GUI
+#define KEY_RIGHTCTRL 0xe4 // Keyboard Right Control
+#define KEY_RIGHTSHIFT 0xe5 // Keyboard Right Shift
+#define KEY_RIGHTALT 0xe6 // Keyboard Right Alt
+#define KEY_RIGHTMETA 0xe7 // Keyboard Right GUI
+
+#define KEY_MEDIA_PLAYPAUSE 0xe8
+#define KEY_MEDIA_STOPCD 0xe9
+#define KEY_MEDIA_PREVIOUSSONG 0xea
+#define KEY_MEDIA_NEXTSONG 0xeb
+#define KEY_MEDIA_EJECTCD 0xec
+#define KEY_MEDIA_VOLUMEUP 0xed
+#define KEY_MEDIA_VOLUMEDOWN 0xee
+#define KEY_MEDIA_MUTE 0xef
+#define KEY_MEDIA_WWW 0xf0
+#define KEY_MEDIA_BACK 0xf1
+#define KEY_MEDIA_FORWARD 0xf2
+#define KEY_MEDIA_STOP 0xf3
+#define KEY_MEDIA_FIND 0xf4
+#define KEY_MEDIA_SCROLLUP 0xf5
+#define KEY_MEDIA_SCROLLDOWN 0xf6
+#define KEY_MEDIA_EDIT 0xf7
+#define KEY_MEDIA_SLEEP 0xf8
+#define KEY_MEDIA_COFFEE 0xf9
+#define KEY_MEDIA_REFRESH 0xfa
+#define KEY_MEDIA_CALC 0xfb
+
+class Keyboard : public Singleton
+{
+protected:
+ friend Singleton;
+
+ u8 modifier;
+ volatile u64 keyStatus[2];
+ volatile u64 keyStatusPrev[2];
+ u32 keyRepeatCount[MAX_KEYS];
+ u32 timer;
+ //volatile bool dirty;
+ volatile u32 updateCount;
+ u32 updateCountLastRead;
+
+ static void KeyPressedHandlerRaw(TUSBKeyboardDevice* device, unsigned char modifiers, const unsigned char RawKeys[6]);
+ static void USBKeyboardDeviceTimerHandler(unsigned hTimer, void *pParam, void *pContext);
+
+public:
+ Keyboard();
+
+ //inline u32 UpdateCount() const { return updateCount; }
+
+ inline bool CheckChanged()
+ {
+ if (updateCountLastRead == updateCount)
+ {
+ return false;
+ }
+ else
+ {
+ updateCountLastRead = updateCount;
+ return true;
+ }
+ // bool dirtyOld = dirty;
+ // dirty = false;
+ // return dirtyOld;
+ }
+
+ inline bool KeyPressed(u32 rawKey)
+ {
+ int keyStatusIndex = rawKey >= 64 ? 1 : 0;
+ u64 mask = 1ULL << (rawKey & 0x3f);
+ return ((keyStatus[keyStatusIndex] & mask) && !(keyStatusPrev[keyStatusIndex] & mask));
+ }
+ inline bool KeyReleased(u32 rawKey)
+ {
+ int keyStatusIndex = rawKey >= 64 ? 1 : 0;
+ u64 mask = 1ULL << (rawKey & 0x3f);
+ return (!(keyStatus[keyStatusIndex] & mask) && (keyStatusPrev[keyStatusIndex] & mask));
+ }
+ inline bool KeyHeld(u32 rawKey)
+ {
+ int keyStatusIndex = rawKey >= 64 ? 1 : 0;
+ u64 mask = 1ULL << (rawKey & 0x3f);
+ return (keyStatus[keyStatusIndex] & mask);
+ }
+ inline bool KeyAnyHeld()
+ {
+ return (keyStatus[0] | keyStatus[1]);
+ }
+};
+#endif
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d96845f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,81 @@
+#
+# Makefile
+#
+
+OBJS = armc-start.o armc-cstartup.o armc-cstubs.o armc-cppstubs.o exception.o main.o rpi-aux.o rpi-mailbox-interface.o rpi-mailbox.o rpi-gpio.o rpi-interrupts.o cache.o ff.o interrupt.o keyboard.o Pi1541.o DiskImage.o iec_bus.o iec_commands.o m6502.o m6522.o drive.o gcr.o prot.o lz.o emmc.o diskio.o options.o Screen.o Timer.o FileBrowser.o DiskCaddy.o ROMs.o InputMappings.o xga_font_data.o
+
+kernel.img: $(OBJS) $(OBJSS)
+ $(LD) -o kernel.elf -Map kernel.map -T linker.ld $(OBJS) $(LIBS)
+ $(PREFIX)objdump -d kernel.elf | $(PREFIX)c++filt > kernel.lst
+ $(PREFIX)objcopy kernel.elf -O binary kernel.img
+ wc -c kernel.img
+
+GCC_BASE = "C:/Program Files (x86)/GNU Tools ARM Embedded/5.4 2016q2"
+
+RASPPI ?= 3
+PREFIX ?= arm-none-eabi-
+
+CC = $(PREFIX)gcc
+CPP = $(PREFIX)g++
+AS = $(CC)
+LD = $(PREFIX)ld
+AR = $(PREFIX)ar
+
+ifeq ($(strip $(RASPPI)),0)
+#ARCH ?= -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -DRPIZERO=1 -DDEBUG
+ARCH ?= -march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard -DRPIZERO=1
+CFLAGS += -DRPIZERO=1
+endif
+ifeq ($(strip $(RASPPI)),1)
+ARCH ?= -march=armv6zk -mtune=arm1176jzf-s -mfloat-abi=hard -DRPIZERO=1
+CFLAGS += -DRPIBPLUS=1
+endif
+ifeq ($(strip $(RASPPI)),2)
+ARCH ?= -march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -marm -DRPI2=1
+CFLAGS += -DRPI2=1
+endif
+ifeq ($(strip $(RASPPI)),3)
+ARCH ?= -march=armv8-a -mtune=cortex-a53 -mfpu=neon-fp-armv8 -mfloat-abi=hard -marm -DRPI3=1 -DDEBUG -DNDEBUG
+#ARCH ?= -march=armv8-a -mtune=cortex-a53 -mfpu=neon-fp-armv8 -mfloat-abi=hard -marm -DRPI3=1
+CFLAGS += -DRPI3=1
+endif
+
+LIBS = $(GCC_BASE)/arm-none-eabi/lib/fpu/libc.a $(GCC_BASE)/lib/gcc/arm-none-eabi/5.4.1/fpu/libgcc.a
+LIBS += uspi/lib/libuspi.a -lstdc++
+
+INCLUDE += -I $(GCC_BASE)/arm-none-eabi/include -I $(GCC_BASE)/lib/gcc/arm-none-eabi/5.4.1/include
+#INCLUDE += -I USB/include
+INCLUDE += -I uspi/include
+#INCLUDE += -I uspi/include/uspi
+
+AFLAGS += $(ARCH) $(INCLUDE)
+CFLAGS += $(ARCH) -Wall -Wno-psabi -fsigned-char -fno-builtin $(INCLUDE)
+#-Wno-packed-bitfield-compat
+#CFLAGS += -O3
+#CFLAGS += -O4
+CFLAGS += -Ofast
+CPPFLAGS+= $(CFLAGS) -fno-exceptions -fno-rtti -std=c++0x -Wno-write-strings
+
+CFLAGS += -fno-delete-null-pointer-checks -fdata-sections -ffunction-sections -u _printf_float
+
+%.o: %.S
+ $(AS) $(AFLAGS) -c -o $@ $<
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+#assm add -S
+%.o: %.cpp
+ $(CPP) $(CPPFLAGS) -c -o $@ $<
+
+clean:
+# rm -f *.o *.a *.elf *.lst *.img *.cir *.map *~ $(EXTRACLEAN)
+ del *.o
+ del *.a
+ del *.elf
+ del *.img
+
+
+
+
+#arm-none-eabi-objdump -D kernel.elf > kernel.elf.asm
diff --git a/Petscii.h b/Petscii.h
new file mode 100644
index 0000000..f780896
--- /dev/null
+++ b/Petscii.h
@@ -0,0 +1,54 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef PETSCII
+#define PETSCII
+
+static inline u8 ascii2petscii(u8 ch)
+{
+ if (ch > 64 && ch < 91) ch += 128;
+ else if (ch > 96 && ch < 123) ch -= 32;
+ else if (ch > 192 && ch < 219) ch -= 128;
+ return ch;
+}
+static inline u8 petscii2ascii(u8 ch)
+{
+ if (ch >(64 + 128) && ch < (91 + 128)) ch -= 128;
+ else if (ch >(96 - 32) && ch < (123 - 32)) ch += 32;
+ else if (ch >(192 - 128) && ch < (219 - 128)) ch += 128;
+ return ch;
+}
+
+static inline u8 petscii2screen(u8 ch)
+{
+ if ((ch >= 0x40 && ch <= 0x5F) || (ch >= 0xa0 && ch <= 0xbf)) ch -= 0x40;
+ else if (ch >= 0xc0 && ch <= 0xdf) ch -= 0x80;
+ else if (ch >= 0 && ch <= 0x1f) ch += 0x80;
+ else if ((ch >= 0x60 && ch <= 0x7F) || (ch >= 0x90 && ch <= 0x9f)) ch += 0x40;
+ return ch;
+}
+
+static inline u8 screen2petscii(u8 ch)
+{
+ if ((ch >= 0 && ch <= 0x1F) || (ch >= 0x60 && ch <= 0x7f)) ch += 0x40;
+ else if (ch >= 0x40 && ch <= 0x5f) ch += 0x80;
+ else if (ch >= 0x80 && ch <= 0x9f) ch -= 0x80;
+ else if ((ch >= 0xa0 && ch <= 0xbF) || (ch >= 0xd0 && ch <= 0xdf)) ch -= 0x40;
+ return ch;
+}
+#endif
diff --git a/Pi1541.cpp b/Pi1541.cpp
new file mode 100644
index 0000000..799898f
--- /dev/null
+++ b/Pi1541.cpp
@@ -0,0 +1,77 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "Pi1541.h"
+#include "debug.h"
+
+Pi1541::Pi1541()
+{
+ VIA[0].ConnectIRQ(&m6502.IRQ);
+ VIA[1].ConnectIRQ(&m6502.IRQ);
+}
+
+void Pi1541::Initialise()
+{
+ VIA[0].ConnectIRQ(&m6502.IRQ);
+ VIA[1].ConnectIRQ(&m6502.IRQ);
+}
+
+//void Pi1541::ConfigureOfExtraRAM(bool extraRAM)
+//{
+// if (extraRAM)
+// m6502.SetBusFunctions(this, Read6502ExtraRAM, Write6502ExtraRAM);
+// else
+// m6502.SetBusFunctions(this, Read6502, Write6502);
+//}
+
+void Pi1541::Update()
+{
+ if (drive.Update())
+ {
+ //This pin sets the overflow flag on a negative transition from TTL one to TTL zero.
+ // SO is sampled at the trailing edge of P1, the cpu V flag is updated at next P1.
+ m6502.SO();
+ }
+
+ VIA[1].Execute();
+ VIA[0].Execute();
+}
+
+void Pi1541::Reset()
+{
+ IOPort* VIABortB;
+
+ // Must reset the VIAs first as the devices will initialise inputs (eg CA1 ports etc)
+ // - VIAs will reset the inputs to a default value
+ // - devices will then set the inputs
+ // - could cause a IR_CXX IRQ
+ // - possibilities
+ // - reset while an ATN
+ // - reset while !BYTE SYNC
+ // - should be fine as VIA's functionControlRegister is reset to 0 and IRQs will be turned off
+ VIA[0].Reset();
+ VIA[1].Reset();
+ drive.Reset();
+ IEC_Bus::Reset();
+ // On a real drive the outputs look like they are being pulled high (when set to inputs) (Taking an input from the front end of an inverter)
+ VIABortB = VIA[0].GetPortB();
+ VIABortB->SetInput(VIAPORTPINS_DATAOUT, true);
+ VIABortB->SetInput(VIAPORTPINS_CLOCKOUT, true);
+ VIABortB->SetInput(VIAPORTPINS_ATNAOUT, true);
+}
+
diff --git a/Pi1541.h b/Pi1541.h
new file mode 100644
index 0000000..5e688cf
--- /dev/null
+++ b/Pi1541.h
@@ -0,0 +1,55 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef PI1541_H
+#define PI1541_H
+
+#include "Drive.h"
+#include "m6502.h"
+#include "iec_bus.h"
+
+class Pi1541
+{
+
+public:
+ Pi1541();
+
+ void Initialise();
+
+ void Update();
+
+ void Reset();
+
+ //void ConfigureOfExtraRAM(bool extraRAM);
+
+ Drive drive;
+ m6522 VIA[2];
+
+ M6502 m6502;
+
+private:
+ //u8 Memory[0xc000];
+
+ //static u8 Read6502(u16 address, void* data);
+ //static u8 Read6502ExtraRAM(u16 address, void* data);
+ //static u8 Peek6502(u16 address, void* data);
+ //static void Write6502(u16 address, const u8 value, void* data);
+ //static void Write6502ExtraRAM(u16 address, const u8 value, void* data);
+};
+
+#endif
\ No newline at end of file
diff --git a/README.md b/README.md
index 7b13587..56404ef 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,29 @@
# Pi1541
+
Commodore 1541 emulator for the Raspberry Pi
+
+Pi1541 is a real-time, cycle exact, Commodore 1541 disk drive emulator that can run on a Raspberry Pi 3B (or 3B+). The software is free and I have endeavored to make the hardware as simple and inexpensive as possible.
+
+Pi1541 provides you with an SD card solution for using D64, G64, NIB and NBZ Commodore disk images on real Commodore 8 bit computers such as;-
+Commodore 64
+Commodore 128
+Commodore Vic20
+Commodore 16
+Commodore Plus4
+
+See www.pi1541.com for SD card and hardware configurations.
+
+Building
+--------
+
+I use GNU Tools ARM Embedded tool chain 5.4.1 on Windows using make. https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads/5-2016-q2-update
+There are two make files.
+One in uspi\lib and Pi1541's make file the root folder.
+You will need to edit the make files to set GCC_BASE to the location of your GNU tools.
+(If anyone knows how to fix this requirement then please fix it. arm-none-eabi-gcc can find the include paths why can't arm-none-eabi-ld find the library paths?)
+
+You need to build uspi\lib first.
+Change to uspi\lib and make.
+Change back to the root folder of the project and again make.
+This will build kernel.img
+
diff --git a/ROMs.cpp b/ROMs.cpp
new file mode 100644
index 0000000..4d7ca2f
--- /dev/null
+++ b/ROMs.cpp
@@ -0,0 +1,45 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "ROMs.h"
+#include "debug.h"
+#include
+
+void ROMs::ResetCurrentROMIndex()
+{
+ currentROMIndex = lastManualSelectedROMIndex;
+
+ DEBUG_LOG("Reset ROM back to %d %s\r\n", currentROMIndex, ROMNames[currentROMIndex]);
+}
+
+void ROMs::SelectROM(const char* ROMName)
+{
+ unsigned index;
+
+ for (index = 0; index < MAX_ROMS; ++index)
+ {
+ if (ROMNames[index] && strcasecmp(ROMNames[index], ROMName) == 0)
+ {
+ DEBUG_LOG("LST switching ROM %d %s\r\n", index, ROMNames[index]);
+
+ currentROMIndex = index;
+ break;
+ }
+ }
+}
+
diff --git a/ROMs.h b/ROMs.h
new file mode 100644
index 0000000..7f438bc
--- /dev/null
+++ b/ROMs.h
@@ -0,0 +1,46 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef ROMs_H
+#define ROMs_H
+
+#include "types.h"
+
+class ROMs
+{
+public:
+ void SelectROM(const char* ROMName);
+
+ inline u8 Read(u16 address)
+ {
+ return ROMImages[currentROMIndex][address & 0x3fff];
+ }
+
+ void ResetCurrentROMIndex();
+
+ static const int ROM_SIZE = 16384;
+ static const int MAX_ROMS = 8;
+
+ unsigned char ROMImages[MAX_ROMS][ROM_SIZE];
+ char ROMNames[MAX_ROMS][256];
+ bool ROMValid[MAX_ROMS];
+
+ unsigned currentROMIndex;
+ unsigned lastManualSelectedROMIndex;
+};
+#endif
diff --git a/Screen.cpp b/Screen.cpp
new file mode 100644
index 0000000..538c618
--- /dev/null
+++ b/Screen.cpp
@@ -0,0 +1,311 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "Screen.h"
+#include "CBMFont.h"
+#include
+#include
+#include
+#include "debug.h"
+#include "Petscii.h"
+#include "stb_image_config.h"
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+extern "C"
+{
+ #include "rpi-mailbox-interface.h"
+ #include "xga_font_data.h"
+}
+
+extern u32 RPi_CpuId;
+
+static const int BitFontHt = 16;
+static const int BitFontWth = 8;
+
+void Screen::Open(u32 widthDesired, u32 heightDesired, u32 colourDepth)
+{
+ rpi_mailbox_property_t* mp;
+ //int width = 0;
+ //int height = 0;
+ //int depth = 0;
+
+ RPI_PropertyInit();
+ RPI_PropertyAddTag(TAG_GET_PHYSICAL_SIZE);
+ RPI_PropertyAddTag(TAG_GET_VIRTUAL_SIZE);
+ RPI_PropertyAddTag(TAG_GET_DEPTH);
+ RPI_PropertyProcess();
+
+ //if ((mp = RPI_PropertyGet(TAG_GET_PHYSICAL_SIZE)))
+ //{
+ // width = mp->data.buffer_32[0];
+ // height = mp->data.buffer_32[1];
+ //}
+
+ //if ((mp = RPI_PropertyGet(TAG_GET_DEPTH)))
+ // depth = mp->data.buffer_32[0];
+
+ //DEBUG_LOG("width = %d height = %d depth = %d\r\n", width, height, depth);
+
+ do
+ {
+ RPI_PropertyInit();
+ RPI_PropertyAddTag(TAG_ALLOCATE_BUFFER);
+ RPI_PropertyAddTag(TAG_SET_PHYSICAL_SIZE, widthDesired, heightDesired);
+ RPI_PropertyAddTag(TAG_SET_VIRTUAL_SIZE, widthDesired, heightDesired); // Don't need to double buffer (yet).
+ RPI_PropertyAddTag(TAG_SET_DEPTH, colourDepth);
+ RPI_PropertyAddTag(TAG_GET_PITCH);
+ RPI_PropertyAddTag(TAG_GET_PHYSICAL_SIZE);
+ RPI_PropertyAddTag(TAG_GET_DEPTH);
+ RPI_PropertyProcess();
+
+ if ((mp = RPI_PropertyGet(TAG_GET_PHYSICAL_SIZE)))
+ {
+ width = mp->data.buffer_32[0];
+ height = mp->data.buffer_32[1];
+ }
+
+ if ((mp = RPI_PropertyGet(TAG_GET_DEPTH)))
+ bpp = mp->data.buffer_32[0];
+
+ if ((mp = RPI_PropertyGet(TAG_GET_PITCH)))
+ pitch = mp->data.buffer_32[0];
+
+ if ((mp = RPI_PropertyGet(TAG_ALLOCATE_BUFFER)))
+ framebuffer = (unsigned char*)(mp->data.buffer_32[0] & 0x3FFFFFFF);
+ }
+ while (framebuffer == 0);
+
+
+ //RPI_PropertyInit();
+ //RPI_PropertyAddTag(TAG_SET_PALETTE, palette);
+ //RPI_PropertyProcess();
+
+ switch (bpp)
+ {
+ case 32:
+ plotPixelFn = &Screen::PlotPixel32;
+ break;
+ case 24:
+ plotPixelFn = &Screen::PlotPixel24;
+ break;
+ default:
+ case 16:
+ plotPixelFn = &Screen::PlotPixel16;
+ break;
+ case 8:
+ plotPixelFn = &Screen::PlotPixel8;
+ break;
+ }
+
+ opened = true;
+}
+
+void Screen::PlotPixel32(u32 pixel_offset, RGBA Colour)
+{
+ *((volatile RGBA*)&framebuffer[pixel_offset]) = Colour;
+}
+void Screen::PlotPixel24(u32 pixel_offset, RGBA Colour)
+{
+ framebuffer[pixel_offset++] = BLUE(Colour);
+ framebuffer[pixel_offset++] = GREEN(Colour);
+ framebuffer[pixel_offset++] = RED(Colour);
+}
+void Screen::PlotPixel16(u32 pixel_offset, RGBA Colour)
+{
+ *(unsigned short*)&framebuffer[pixel_offset] = ((RED(Colour) >> 3) << 11) | ((GREEN(Colour) >> 2) << 5) | (BLUE(Colour) >> 3);
+}
+void Screen::PlotPixel8(u32 pixel_offset, RGBA Colour)
+{
+ framebuffer[pixel_offset++] = RED(Colour);
+}
+
+void Screen::ClipRect(u32& x1, u32& y1, u32& x2, u32& y2)
+{
+ if (x1 > width) x1 = width;
+ if (y1 > height) y1 = height;
+ if (x2 > width) x2 = width;
+ if (y2 > height) y2 = height;
+}
+
+void Screen::ClearArea(u32 x1, u32 y1, u32 x2, u32 y2, RGBA colour)
+{
+ ClipRect(x1, y1, x2, y2);
+
+ for (u32 y = y1; y < y2; y++)
+ {
+ u32 line = y * pitch;
+ for (u32 x = x1; x < x2; x++)
+ {
+ u32 pixel_offset = (x * (bpp >> 3)) + line;
+ (this->*Screen::plotPixelFn)(pixel_offset, colour);
+ }
+ }
+}
+
+void Screen::ScrollArea(u32 x1, u32 y1, u32 x2, u32 y2)
+{
+ ClipRect(x1, y1, x2, y2);
+
+ if (x2 - 1 <= x1)
+ return;
+
+ for (u32 y = y1; y < y2; y++)
+ {
+ u32 line = y * pitch;
+ for (u32 x = x1; x < (x2 - 1); x++)
+ {
+ u32 pixel_offset = ((x + 1) * (bpp >> 3)) + line;
+ u32 pixel_offsetDest = (x * (bpp >> 3)) + line;
+ *(unsigned short*)&framebuffer[pixel_offsetDest] = *(unsigned short*)&framebuffer[pixel_offset];
+ }
+ }
+}
+
+void Screen::Clear(RGBA colour)
+{
+ ClearArea(0, 0, width, height, colour);
+}
+
+void Screen::WriteChar(bool petscii, u32 x, u32 y, unsigned char c, RGBA colour)
+{
+ if (opened)
+ {
+ u32 fontHeight;
+ const unsigned char* fontBitMap;
+ if (petscii)
+ {
+ fontBitMap = CMBFont;
+ fontHeight = 8;
+ c = petscii2screen(c);
+ }
+ else
+ {
+ fontBitMap = avpriv_vga16_font;
+ fontHeight = BitFontHt;
+ }
+ for (u32 py = 0; py < fontHeight; ++py)
+ {
+ if (y + py > height)
+ return;
+
+ unsigned char b = fontBitMap[c * fontHeight + py];
+ int yoffs = (y + py) * pitch;
+ for (int px = 0; px < 8; ++px)
+ {
+ if (x + px > width)
+ continue;
+
+ int pixel_offset = ((px + x) * (bpp >> 3)) + yoffs;
+ if ((b & 0x80) == 0x80)
+ (this->*Screen::plotPixelFn)(pixel_offset, colour);
+ b = b << 1;
+ }
+ }
+ }
+}
+
+void Screen::PlotPixel(u32 x, u32 y, RGBA colour)
+{
+ int pixel_offset = (x * (bpp >> 3)) + (y * pitch);
+ (this->*Screen::plotPixelFn)(pixel_offset, colour);
+}
+
+void Screen::DrawLine(u32 x1, u32 y1, u32 x2, u32 y2, RGBA colour)
+{
+ ClipRect(x1, y1, x2, y2);
+
+ int dx0, dy0, ox, oy, eulerMax;
+ dx0 = (int)(x2 - x1);
+ dy0 = (int)(y2 - y1);
+ eulerMax = abs(dx0);
+ if (abs(dy0) > eulerMax) eulerMax = abs(dy0);
+ for (int i = 0; i <= eulerMax; i++)
+ {
+ ox = ((dx0 * i) / eulerMax) + x1;
+ oy = ((dy0 * i) / eulerMax) + y1;
+ int pixel_offset = (ox * (bpp >> 3)) + (oy * pitch);
+ (this->*Screen::plotPixelFn)(pixel_offset, colour);
+ }
+}
+
+void Screen::DrawLineV(u32 x, u32 y1, u32 y2, RGBA colour)
+{
+ //ClipRect(x, y1, x, y2);
+ for (u32 y = y1; y <= y2; ++y)
+ {
+ int pixel_offset = (x * (bpp >> 3)) + (y * pitch);
+ (this->*Screen::plotPixelFn)(pixel_offset, colour);
+ }
+}
+
+u32 Screen::PrintText(bool petscii, u32 x, u32 y, char *ptr, RGBA TxtColour, RGBA BkColour, bool measureOnly, u32* width, u32* height)
+{
+ int xCursor = x;
+ int yCursor = y;
+ int len = 0;
+ u32 fontHeight;
+
+ if (petscii) fontHeight = 8;
+ else fontHeight = BitFontHt;
+
+ if (width) *width = 0;
+
+ while (*ptr != 0)
+ {
+ char c = *ptr++;
+ if ((c != '\r') && (c != '\n'))
+ {
+ if (!measureOnly)
+ {
+ ClearArea(xCursor, yCursor, xCursor + BitFontWth, yCursor + fontHeight, BkColour);
+ WriteChar(petscii, xCursor, yCursor, c, TxtColour);
+ }
+ xCursor += BitFontWth;
+ if (width) *width = MAX(*width, (u32)MAX(0, xCursor));
+ }
+ else
+ {
+ xCursor = x;
+ yCursor += fontHeight;
+ }
+ len++;
+ }
+ if (height) *height = yCursor;
+
+ return len;
+}
+
+u32 Screen::MeasureText(bool petscii, char *ptr, u32* width, u32* height)
+{
+ return PrintText(petscii, 0, 0, ptr, 0, 0, true, width, height);
+}
+
+void Screen::PlotImage(u32* image, int x, int y, int w, int h)
+{
+ int px;
+ int py;
+ int i = 0;
+ for (py = 0; py < h; ++py)
+ {
+ for (px = 0; px < w; ++px)
+ {
+ PlotPixel(x + px, y + py, image[i++]);
+ }
+ }
+}
diff --git a/Screen.h b/Screen.h
new file mode 100644
index 0000000..13fcb10
--- /dev/null
+++ b/Screen.h
@@ -0,0 +1,88 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef SCREEN_H
+#define SCREEN_H
+
+#include "types.h"
+
+typedef u32 RGBA;
+
+#define RED(colour) ( (u8)(((u32)colour) & 0xFF) )
+#define GREEN(colour) ( (u8)(((u32)colour >> 8) & 0xFF) )
+#define BLUE(colour) ( (u8)(((u32)colour >> 16) & 0xFF) )
+#define ALPHA(colour) ( (u8)(((u32)colour >> 24) & 0xFF) )
+
+#define RGBA(r, g, b, a) ( ((u32)((u8)(r))) | ((u32)((u8)(g)) << 8) | ((u32)((u8)(b)) << 16) | ((u32)((u8)(a)) << 24) )
+
+class Screen
+{
+
+public:
+ Screen()
+ : opened(false)
+ , width(0)
+ , height(0)
+ , bpp(0)
+ , pitch(0)
+ , framebuffer(0)
+ {
+ }
+
+ void Open(u32 width, u32 height, u32 colourDepth);
+
+ void ClearArea(u32 x1, u32 y1, u32 x2, u32 y2, RGBA colour);
+ void Clear(RGBA colour);
+
+ void ScrollArea(u32 x1, u32 y1, u32 x2, u32 y2);
+
+ void WriteChar(bool petscii, u32 x, u32 y, unsigned char c, RGBA colour);
+ u32 PrintText(bool petscii, u32 xPos, u32 yPos, char *ptr, RGBA TxtColour = RGBA(0xff, 0xff, 0xff, 0xff), RGBA BkColour = RGBA(0, 0, 0, 0xFF), bool measureOnly = false, u32* width = 0, u32* height = 0);
+ u32 MeasureText(bool petscii, char *ptr, u32* width = 0, u32* height = 0);
+
+ void DrawLine(u32 x1, u32 y1, u32 x2, u32 y2, RGBA colour);
+ void DrawLineV(u32 x, u32 y1, u32 y2, RGBA colour);
+ void PlotPixel(u32 x, u32 y, RGBA colour);
+
+ void PlotImage(u32* image, int x, int y, int w, int h);
+
+ u32 Width() const { return width; }
+ u32 Height() const { return height; }
+
+private:
+
+ typedef void (Screen::*PlotPixelFunction)(u32 pixel_offset, RGBA Colour);
+
+ void PlotPixel32(u32 pixel_offset, RGBA Colour);
+ void PlotPixel24(u32 pixel_offset, RGBA Colour);
+ void PlotPixel16(u32 pixel_offset, RGBA Colour);
+ void PlotPixel8(u32 pixel_offset, RGBA Colour);
+
+ void ClipRect(u32& x1, u32& y1, u32& x2, u32& y2);
+
+ PlotPixelFunction plotPixelFn;
+
+ bool opened;
+ u32 width;
+ u32 height;
+ u32 bpp;
+ u32 pitch;
+ volatile u8* framebuffer;
+};
+
+#endif
\ No newline at end of file
diff --git a/Singleton.h b/Singleton.h
new file mode 100644
index 0000000..bc72b3e
--- /dev/null
+++ b/Singleton.h
@@ -0,0 +1,51 @@
+#ifndef _Singleton_h_
+#define _Singleton_h_
+
+#include "Types.h"
+
+template
+class Singleton
+{
+public:
+ static T* Instance()
+ {
+ if (m_pInstance == 0)
+ m_pInstance = new T;
+
+ //assert(m_pInstance != 0);
+
+ return m_pInstance;
+ };
+
+ static void DestroyInstance()
+ {
+ delete m_pInstance;
+ m_pInstance = 0;
+ };
+
+protected:
+
+ Singleton()
+ {
+ };
+
+ virtual ~Singleton()
+ {
+ };
+
+ static T* m_pInstance;
+
+private:
+
+ Singleton(const Singleton& source)
+ {
+ };
+
+};
+
+template T* Singleton::m_pInstance = 0;
+
+#endif
+
+
+
diff --git a/Timer.c b/Timer.c
new file mode 100644
index 0000000..ba37641
--- /dev/null
+++ b/Timer.c
@@ -0,0 +1,121 @@
+#include "timer.h"
+#include "bcm2835int.h"
+#include "rpiHardware.h"
+
+// Support functions for uspi
+// Based on env\include\uspienv\timer.h
+
+// The number of 1MHz ticks for 10ms
+#define TEN_MILLISECS (CLOCKHZ / HZ)
+
+static unsigned Ticks = 0;
+
+static volatile TKernelTimer m_KernelTimer[KERNEL_TIMERS]; // TODO: should be linked list
+
+static void TimerPollKernelTimers()
+{
+ //EnterCritical();
+
+ for (unsigned hTimer = 0; hTimer < KERNEL_TIMERS; hTimer++)
+ {
+ volatile TKernelTimer* pTimer = &m_KernelTimer[hTimer];
+
+ TKernelTimerHandler* pHandler = pTimer->m_pHandler;
+ if (pHandler != 0)
+ {
+ if ((int)(pTimer->m_nElapsesAt - Ticks) <= 0)
+ {
+ pTimer->m_pHandler = 0;
+
+ (*pHandler)(hTimer + 1, pTimer->m_pParam, pTimer->m_pContext);
+ }
+ }
+ }
+
+ //LeaveCritical();
+}
+
+static void TimerInterruptHandler(void* pParam)
+{
+ DataMemBarrier();
+
+ //assert(read32(ARM_SYSTIMER_CS) & (1 << 3));
+
+ u32 nCompare = read32(ARM_SYSTIMER_C3) + TEN_MILLISECS;
+ write32(ARM_SYSTIMER_C3, nCompare);
+ if (nCompare < read32(ARM_SYSTIMER_CLO)) // time may drift
+ {
+ nCompare = read32(ARM_SYSTIMER_CLO) + TEN_MILLISECS;
+ write32(ARM_SYSTIMER_C3, nCompare);
+ }
+
+ write32(ARM_SYSTIMER_CS, 1 << 3);
+
+ DataMemBarrier();
+
+ ++Ticks;
+
+ TimerPollKernelTimers();
+}
+
+void TimerSystemInitialize()
+{
+ InterruptSystemConnectIRQ(ARM_IRQ_TIMER3, TimerInterruptHandler, 0);
+
+ DataMemBarrier();
+
+ write32(ARM_SYSTIMER_CLO, -(30 * CLOCKHZ)); // timer wraps soon, to check for problems
+
+ // Interrupt every 10ms
+ write32(ARM_SYSTIMER_C3, read32(ARM_SYSTIMER_CLO) + TEN_MILLISECS);
+
+ DataMemBarrier();
+
+ for (unsigned hTimer = 0; hTimer < KERNEL_TIMERS; hTimer++)
+ {
+ m_KernelTimer[hTimer].m_pHandler = 0;
+ }
+
+}
+
+unsigned TimerStartKernelTimer(unsigned nDelay, TKernelTimerHandler* pHandler, void* pParam, void* pContext)
+{
+ //EnterCritical();
+
+// DEBUG_LOG("Timer started\r\n");
+
+ unsigned hTimer;
+ for (hTimer = 0; hTimer < KERNEL_TIMERS; hTimer++)
+ {
+ if (m_KernelTimer[hTimer].m_pHandler == 0)
+ {
+ break;
+ }
+ }
+
+ if (hTimer >= KERNEL_TIMERS)
+ {
+ //LeaveCritical();
+
+ DEBUG_LOG("System limit of kernel timers exceeded\r\n");
+
+ return 0;
+ }
+
+ //assert(pHandler != 0);
+ m_KernelTimer[hTimer].m_pHandler = pHandler;
+ m_KernelTimer[hTimer].m_nElapsesAt = Ticks+nDelay;
+ m_KernelTimer[hTimer].m_pParam = pParam;
+ m_KernelTimer[hTimer].m_pContext = pContext;
+
+ //LeaveCritical();
+
+ return hTimer+1;
+}
+
+void TimerCancelKernelTimer(unsigned hTimer)
+{
+ //assert(1 <= hTimer && hTimer <= KERNEL_TIMERS);
+ m_KernelTimer[hTimer-1].m_pHandler = 0;
+}
+
diff --git a/Timer.h b/Timer.h
new file mode 100644
index 0000000..1378b8b
--- /dev/null
+++ b/Timer.h
@@ -0,0 +1,46 @@
+#ifndef Timer_H
+#define Timer_H
+#include
+#include "interrupt.h"
+
+// Support functions for uspi
+// Based on env\include\uspienv\timer.h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KERNEL_TIMERS 20
+
+#define HZ 100 // ticks per second
+
+#define MSEC2HZ(msec) ((msec) * HZ / 1000)
+
+ typedef void TKernelTimerHandler(unsigned hTimer, void* pParam, void* pContext);
+
+ typedef struct TKernelTimer
+ {
+ TKernelTimerHandler* m_pHandler;
+ unsigned m_nElapsesAt;
+ void* m_pParam;
+ void* m_pContext;
+ }
+ TKernelTimer;
+
+ void TimerSystemInitialize();
+
+#define CLOCKHZ 1000000
+
+ unsigned TimerStartKernelTimer(
+ unsigned nDelay, // in HZ units
+ TKernelTimerHandler* pHandler,
+ void* pParam,
+ void* pContext);
+ void TimerCancelKernelTimer(unsigned hTimer);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/armc-cppstubs.cpp b/armc-cppstubs.cpp
new file mode 100644
index 0000000..e1ea9c5
--- /dev/null
+++ b/armc-cppstubs.cpp
@@ -0,0 +1,42 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include
+
+extern "C" void *__dso_handle = 0;
+
+void* operator new(size_t size) noexcept
+{
+ return malloc(size);
+}
+
+void operator delete (void* ptr)
+{
+ free(ptr);
+}
+
+namespace std
+{
+ void __throw_bad_alloc()
+ {
+ }
+
+ void __throw_length_error(char const*e)
+ {
+ }
+}
\ No newline at end of file
diff --git a/armc-cstartup.c b/armc-cstartup.c
new file mode 100644
index 0000000..742c5c8
--- /dev/null
+++ b/armc-cstartup.c
@@ -0,0 +1,69 @@
+/*
+
+ Part of the Raspberry-Pi Bare Metal Tutorials
+ Copyright (c) 2013, Brian Sidebotham
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+extern int __bss_start__;
+extern int __bss_end__;
+
+extern void kernel_main( unsigned int r0, unsigned int r1, unsigned int atags );
+
+extern void __libc_init_array();
+
+void _init(void)
+{
+}
+
+void _cstartup( unsigned int r0, unsigned int r1, unsigned int r2 )
+{
+ int* bss = &__bss_start__;
+ int* bss_end = &__bss_end__;
+
+ /*
+ Clear the BSS section
+
+ See http://en.wikipedia.org/wiki/.bss for further information on the
+ BSS section
+
+ See https://sourceware.org/newlib/libc.html#Stubs for further
+ information on the c-library stubs
+ */
+ while( bss < bss_end )
+ *bss++ = 0;
+
+ __libc_init_array();
+
+ /* We should never return from main ... */
+ kernel_main( r0, r1, r2 );
+
+ /* ... but if we do, safely trap here */
+ while(1)
+ {
+ /* EMPTY! */
+ }
+}
diff --git a/armc-cstubs.c b/armc-cstubs.c
new file mode 100644
index 0000000..46b6891
--- /dev/null
+++ b/armc-cstubs.c
@@ -0,0 +1,246 @@
+/*
+
+ Part of the Raspberry-Pi Bare Metal Tutorials
+ Copyright (c) 2013-2015, Brian Sidebotham
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+/* For more information about this file, please visit:
+ https://sourceware.org/newlib/libc.html#Stubs
+
+ These are the newlib C-Library stubs for the valvers Raspberry-Pi bare-metal
+ tutorial */
+
+/*
+ Graceful failure is permitted by returning an error code. A minor
+ complication arises here: the C library must be compatible with development
+ environments that supply fully functional versions of these subroutines.
+ Such environments usually return error codes in a global errno. However,
+ the Red Hat newlib C library provides a macro definition for errno in the
+ header file errno.h, as part of its support for reentrant routines (see
+ Reentrancy).
+
+ The bridge between these two interpretations of errno is straightforward:
+ the C library routines with OS interface calls capture the errno values
+ returned globally, and record them in the appropriate field of the
+ reentrancy structure (so that you can query them using the errno macro from
+ errno.h).
+
+ This mechanism becomes visible when you write stub routines for OS
+ interfaces. You must include errno.h, then disable the macro, like this:
+ */
+#include
+#undef errno
+extern int errno;
+
+/* Required include for fstat() */
+#include
+
+/* Required include for times() */
+#include
+
+/* Prototype for the UART write function */
+#include "rpi-aux.h"
+
+/* A pointer to a list of environment variables and their values. For a minimal
+ environment, this empty list is adequate: */
+char *__env[1] =
+{ 0 };
+char **environ = __env;
+
+/* A helper function written in assembler to aid us in allocating memory */
+//extern caddr_t _get_stack_pointer(void);
+
+/* Never return from _exit as there's no OS to exit to, so instead we trap
+ here */
+void _exit(int status)
+{
+ /* Stop the compiler complaining about unused variables by "using" it */
+ (void) status;
+
+ while (1)
+ {
+ /* TRAP HERE */
+ }
+}
+
+/* There's currently no implementation of a file system because there's no
+ file system! */
+int _close(int file)
+{
+ return -1;
+}
+
+/* Transfer control to a new process. Minimal implementation (for a system
+ without processes): */
+int execve(char *name, char **argv, char **env)
+{
+ errno = ENOMEM;
+ return -1;
+}
+
+/* Create a new process. Minimal implementation (for a system without
+ processes): */
+int fork(void)
+{
+ errno = EAGAIN;
+ return -1;
+}
+
+/* Status of an open file. For consistency with other minimal implementations
+ in these examples, all files are regarded as character special devices. The
+ sys/stat.h header file required is distributed in the include subdirectory
+ for this C library. */
+int _fstat(int file, struct stat *st)
+{
+ st->st_mode = S_IFCHR;
+ return 0;
+}
+
+/* Process-ID; this is sometimes used to generate strings unlikely to conflict
+ with other processes. Minimal implementation, for a system without
+ processes: */
+int getpid(void)
+{
+ return 1;
+}
+int _getpid(void)
+{
+ return 1;
+}
+
+/* Query whether output stream is a terminal. For consistency with the other
+ minimal implementations, which only support output to stdout, this minimal
+ implementation is suggested: */
+int _isatty(int file)
+{
+ return 1;
+}
+
+/* Send a signal. Minimal implementation: */
+int kill(int pid, int sig)
+{
+ errno = EINVAL;
+ return -1;
+}
+int _kill(int pid, int sig)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+/* Establish a new name for an existing file. Minimal implementation: */
+int link( char *old, char *pnew )
+{
+ errno = EMLINK;
+ return -1;
+}
+
+/* Set position in a file. Minimal implementation: */
+int _lseek(int file, int ptr, int dir)
+{
+ return 0;
+}
+
+/* Open a file. Minimal implementation: */
+int open(const char *name, int flags, int mode)
+{
+ return -1;
+}
+
+/* Read from a file. Minimal implementation: */
+int _read(int file, char *ptr, int len)
+{
+ return 0;
+}
+
+/* Increase program data space. As malloc and related functions depend on this,
+ it is useful to have a working implementation. The following suffices for a
+ standalone system; it exploits the symbol _end automatically defined by the
+ GNU linker. */
+caddr_t _sbrk(int incr)
+{
+ extern char _end;
+ static char* heap_end = 0;
+ char* prev_heap_end;
+
+ if (heap_end == 0)
+ heap_end = &_end;
+
+ prev_heap_end = heap_end;
+ heap_end += incr;
+
+ return (caddr_t) prev_heap_end;
+}
+
+/* Status of a file (by name). Minimal implementation: */
+int stat(const char *file, struct stat *st)
+{
+ st->st_mode = S_IFCHR;
+ return 0;
+}
+
+/* Timing information for current process. Minimal implementation: */
+clock_t times(struct tms *buf)
+{
+ return -1;
+}
+
+/* Remove a file's directory entry. Minimal implementation: */
+int unlink(char *name)
+{
+ errno = ENOENT;
+ return -1;
+}
+
+/* Wait for a child process. Minimal implementation: */
+int wait(int *status)
+{
+ errno = ECHILD;
+ return -1;
+}
+
+void outbyte(char b)
+{
+ RPI_AuxMiniUartWrite(b);
+}
+
+/* Write to a file. libc subroutines will use this system routine for output to
+ all files, including stdout—so if you need to generate any output, for
+ example to a serial port for debugging, you should make your minimal write
+ capable of doing this. The following minimal implementation is an
+ incomplete example; it relies on a outbyte subroutine (not shown; typically,
+ you must write this in assembler from examples provided by your hardware
+ manufacturer) to actually perform the output. */
+int _write(int file, char *ptr, int len)
+{
+ int todo;
+
+ for (todo = 0; todo < len; todo++)
+ outbyte(*ptr++);
+
+ return len;
+}
diff --git a/armc-start.S b/armc-start.S
new file mode 100644
index 0000000..578bfcd
--- /dev/null
+++ b/armc-start.S
@@ -0,0 +1,510 @@
+
+// Part of the Raspberry-Pi Bare Metal Tutorials
+// Copyright (c) 2013-2015, Brian Sidebotham
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+
+// Relocate to just below 32MB
+
+#include "defs.h"
+
+.equ STACK_SIZE, 0x00100000
+
+.equ C0_SVR_STACK, 0
+.equ C0_IRQ_STACK, (STACK_SIZE*1)
+.equ C0_FIQ_STACK, STACK_SIZE*2
+.equ C0_USER_STACK, STACK_SIZE*4
+.equ C0_ABORT_STACK, STACK_SIZE*5
+.equ C0_UNDEFINED_STACK, STACK_SIZE*6
+
+#if defined(RPI2) || defined(RPI3)
+.equ C1_SVR_STACK, STACK_SIZE*7
+.equ C1_IRQ_STACK, STACK_SIZE*8
+.equ C1_FIQ_STACK, STACK_SIZE*9
+.equ C1_USER_STACK, STACK_SIZE*10
+.equ C1_ABORT_STACK, STACK_SIZE*11
+.equ C1_UNDEFINED_STACK, STACK_SIZE*12
+#endif
+
+.equ SCTLR_ENABLE_DATA_CACHE, 0x4
+.equ SCTLR_ENABLE_BRANCH_PREDICTION, 0x800
+.equ SCTLR_ENABLE_INSTRUCTION_CACHE, 0x1000
+
+.section ".text.startup"
+
+.global _start
+.global _get_cpsr
+.global _get_stack_pointer
+.global _exception_table
+.global _enable_interrupts
+.global _disable_interrupts
+.global _enable_unaligned_access
+.global _enable_l1_cache
+.global _invalidate_icache
+.global _invalidate_dcache
+.global _clean_invalidate_dcache
+.global _invalidate_dcache_mva
+.global _clean_invalidate_dcache_mva
+.global _invalidate_dtlb
+.global _invalidate_dtlb_mva
+.global _data_memory_barrier
+
+#ifdef HAS_MULTICORE
+.global _get_core
+.global _init_core
+.global _spin_core
+#endif
+
+#if defined(HAS_40PINS)
+.global _toggle_test_pin
+#endif
+
+// From the ARM ARM (Architecture Reference Manual). Make sure you get the
+// ARMv5 documentation which includes the ARMv6 documentation which is the
+// correct processor type for the Broadcom BCM2835. The ARMv6-M manuals
+// available on the ARM website are for Cortex-M parts only and are very
+// different.
+//
+// See ARM section A2.2 (Processor Modes)
+
+.equ CPSR_MODE_USER, 0x10
+.equ CPSR_MODE_FIQ, 0x11
+.equ CPSR_MODE_IRQ, 0x12
+.equ CPSR_MODE_SVR, 0x13
+.equ CPSR_MODE_ABORT, 0x17
+.equ CPSR_MODE_HYP, 0x1A
+.equ CPSR_MODE_UNDEFINED, 0x1B
+.equ CPSR_MODE_SYSTEM, 0x1F
+
+.equ CPSR_MODE_MASK, 0x1F
+
+// See ARM section A2.5 (Program status registers)
+.equ CPSR_A_BIT, 0x100
+.equ CPSR_IRQ_INHIBIT, 0x80
+.equ CPSR_FIQ_INHIBIT, 0x40
+.equ CPSR_THUMB, 0x20
+
+_start:
+ ldr pc, _reset_h
+ ldr pc, _undefined_instruction_vector_h
+ ldr pc, _software_interrupt_vector_h
+ ldr pc, _prefetch_abort_vector_h
+ ldr pc, _data_abort_vector_h
+ ldr pc, _unused_handler_h
+ ldr pc, _interrupt_vector_h
+ ldr pc, _fast_interrupt_vector_h
+
+_reset_h: .word _reset_
+_undefined_instruction_vector_h: .word _undefined_instruction_handler_
+_software_interrupt_vector_h: .word _swi_handler_
+_prefetch_abort_vector_h: .word _prefetch_abort_handler_
+_data_abort_vector_h: .word _data_abort_handler_
+_unused_handler_h: .word _reset_
+_interrupt_vector_h: .word arm_irq_handler
+_fast_interrupt_vector_h: .word arm_fiq_handler
+
+.section ".text._reset_"
+_reset_:
+ BL _enable_l1_cache
+#ifdef HAS_MULTICORE
+
+#ifdef KERNEL_OLD
+
+ // if kernel_old=1 all cores are running and we need to sleep 1-3
+ // if kernel_old=0 then just core0 is running, and core 1-3 are waiting
+ // on a mailbox write to be woken up.
+ //
+ // Test which core we are running on
+ mrc p15, 0, r0, c0, c0, 5
+ ands r0, #3
+ beq _core_continue
+ // Put cores 1-3 into a tight loop
+_core_loop:
+ wfi
+ b _core_loop
+_core_continue:
+
+#else
+
+ // if kernel_old=0 enter in HYP mode and need to force a switch to SVC mode
+ //
+ // for now we assume kernel_old=1 and don't execute this core
+ //
+ // The logs show:
+ // SVC mode: cpsr ends with 1d3
+ // HYP mode: cpsr ends with 1a3
+ mrs r0, cpsr
+ eor r0, r0, #CPSR_MODE_HYP
+ tst r0, #CPSR_MODE_MASK
+ bic r0 , r0 , #CPSR_MODE_MASK
+ orr r0 , r0 , #CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT | CPSR_MODE_SVR
+ bne _not_in_hyp_mode
+ orr r0, r0, #CPSR_A_BIT
+ adr lr, _reset_continue
+ msr spsr_cxsf, r0
+ .word 0xE12EF30E // msr_elr_hyp lr
+ .word 0xE160006E // eret
+_not_in_hyp_mode:
+ msr cpsr_c, r0
+_reset_continue:
+#endif
+
+#endif
+
+ // We enter execution in supervisor mode. For more information on
+ // processor modes see ARM Section A2.2 (Processor Modes)
+
+ ldr r0,=_start
+ mov r1, #0x00000000
+ ldmia r0!,{r2, r3, r4, r5, r6, r7, r8, r9}
+ stmia r1!,{r2, r3, r4, r5, r6, r7, r8, r9}
+ ldmia r0!,{r2, r3, r4, r5, r6, r7, r8, r9}
+ stmia r1!,{r2, r3, r4, r5, r6, r7, r8, r9}
+
+ // Initialise Stack Pointers ---------------------------------------------
+ ldr r4,=_start
+
+ // We're going to use interrupt mode, so setup the interrupt mode
+ // stack pointer which differs to the application stack pointer:
+ mov r0, #(CPSR_MODE_IRQ | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, #C0_IRQ_STACK
+
+ // Also setup the stack used for FIQs
+ mov r0, #(CPSR_MODE_FIQ | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, #C0_FIQ_STACK
+
+ // Also setup the stack used for undefined exceptions
+ mov r0, #(CPSR_MODE_UNDEFINED | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, #C0_UNDEFINED_STACK
+
+ // Also setup the stack used for prefetch and data abort exceptions
+ mov r0, #(CPSR_MODE_ABORT | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, #C0_ABORT_STACK
+
+ // Finally, a user/system mode stack, although the application will likely reset this
+ mov r0, #(CPSR_MODE_SYSTEM | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, #C0_USER_STACK
+
+ // Switch back to supervisor mode (our application mode) and
+ // set the stack pointer. Remember that the stack works its way
+ // down memory, our heap will work it's way up from after the
+ // application.
+ mov r0, #(CPSR_MODE_SVR | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, #C0_SVR_STACK
+
+ // Enable VFP ------------------------------------------------------------
+
+#ifdef HAS_MULTICORE
+ //1. Set the CPACR for access to CP10 and CP11, and clear the ASEDIS and D32DIS bits:
+ ldr r0, =(0xf << 20)
+ mcr p15, 0, r0, c1, c0, 2
+
+ // 2. Set the FPEXC EN bit to enable the NEON MPE:
+ mov r0, #0x40000000
+ vmsr fpexc, r0
+
+#else
+ // r1 = Access Control Register
+ MRC p15, #0, r1, c1, c0, #2
+ // enable full access for p10,11
+ ORR r1, r1, #(0xf << 20)
+ // ccess Control Register = r1
+ MCR p15, #0, r1, c1, c0, #2
+ MOV r1, #0
+ // flush prefetch buffer because of FMXR below
+ MCR p15, #0, r1, c7, c5, #4
+ // and CP 10 & 11 were only just enabled
+ // Enable VFP itself
+ MOV r0,#0x40000000
+ // FPEXC = r0
+ FMXR FPEXC, r0
+#endif
+
+ // The c-startup function which we never return from. This function will
+ // initialise the ro data section (most things that have the const
+ // declaration) and initialise the bss section variables to 0 (generally
+ // known as automatics). It'll then call main
+
+ b _cstartup
+
+
+arm_fiq_handler:
+arm_irq_handler:
+ //subs pc, lr, #4
+ sub lr, lr, #4 /* lr: return address */
+ stmfd sp!, {r0-r12, lr} /* save r0-r12 and return address */
+ bl InterruptHandler
+ ldmfd sp!, {r0-r12, pc}^ /* restore registers and return */
+
+.section ".text._get_stack_pointer"
+_get_stack_pointer:
+ mov r0, sp
+ mov pc, lr
+
+.section ".text._get_cpsr"
+_get_cpsr:
+ mrs r0, cpsr
+ mov pc, lr
+
+.section ".text._enable_interrupts"
+_enable_interrupts:
+ mrs r0, cpsr
+ bic r0, r0, #CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT
+ msr cpsr_c, r0
+ mov pc, lr
+
+.section ".text._disable_interrupts"
+_disable_interrupts:
+ mrs r0, cpsr
+ orr r1, r0, #CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT
+ msr cpsr_c, r1
+ mov pc, lr
+
+.section ".text._undefined_instruction_handler_"
+_undefined_instruction_handler_:
+ stmfd sp!, {r0-r12, lr}
+ mrs r0, spsr // Get spsr.
+ stmfd sp!, {r0} // Store spsr onto stack.
+ mov r0, sp
+ bl undefined_instruction_handler
+
+.section ".text._prefetch_abort_handler_"
+_prefetch_abort_handler_:
+ stmfd sp!, {r0-r12, lr}
+ mrs r0, spsr // Get spsr.
+ stmfd sp!, {r0} // Store spsr onto stack.
+ mov r0, sp
+ bl prefetch_abort_handler
+
+.section ".text._data_abort_handler_"
+_data_abort_handler_:
+ stmfd sp!, {r0-r12, lr}
+ mrs r0, spsr // Get spsr.
+ stmfd sp!, {r0} // Store spsr onto stack.
+ mov r0, sp
+ bl data_abort_handler
+
+.section ".text._swi_handler_"
+_swi_handler_:
+ stmfd sp!, {r0-r12, lr}
+ mrs r0, spsr // Get spsr.
+ stmfd sp!, {r0} // Store spsr onto stack.
+ mov r0, sp
+ bl swi_handler
+
+.section ".text._enable_unaligned_access"
+_enable_unaligned_access:
+ mrc p15, 0, r0, c1, c0, 0 // read SCTLR
+ bic r0, r0, #2 // A (no unaligned access fault)
+ orr r0, r0, #1 << 22 // U (v6 unaligned access model)
+ mcr p15, 0, r0, c1, c0, 0 // write SCTLR
+ mov pc, lr
+
+ // Enable L1 Cache -------------------------------------------------------
+.section ".text._enable_l1_cache"
+_enable_l1_cache:
+
+ // R0 = System Control Register
+ mrc p15,0,r0,c1,c0,0
+
+ // Enable caches and branch prediction
+ orr r0,#SCTLR_ENABLE_BRANCH_PREDICTION
+ orr r0,#SCTLR_ENABLE_DATA_CACHE
+ orr r0,#SCTLR_ENABLE_INSTRUCTION_CACHE
+
+ // System Control Register = R0
+ mcr p15,0,r0,c1,c0,0
+
+ mov pc, lr
+
+.section ".text._invalidate_icache"
+_invalidate_icache:
+ mov r0, #0
+ mcr p15, 0, r0, c7, c5, 0
+ mov pc, lr
+
+.section ".text._invalidate_dcache"
+_invalidate_dcache:
+ mov r0, #0
+ mcr p15, 0, r0, c7, c6, 0
+ mov pc, lr
+
+.section ".text._clean_invalidate_dcache"
+_clean_invalidate_dcache:
+ mov r0, #0
+ mcr p15, 0, r0, c7, c14, 0
+ mov pc, lr
+
+.section ".text._invalidate_dcache_mva"
+_invalidate_dcache_mva:
+ mcr p15, 0, r0, c7, c6, 1
+ mov pc, lr
+
+.section ".text._clean_invalidate_dcache_mva"
+_clean_invalidate_dcache_mva:
+ mcr p15, 0, r0, c7, c14, 1
+ mov pc, lr
+
+.section ".text._invalidate_dtlb"
+_invalidate_dtlb:
+ mov r0, #0
+ mcr p15, 0, r0, c8, c6, 0
+ mov pc, lr
+
+.section ".text._invalidate_dtlb_mva"
+_invalidate_dtlb_mva:
+ mcr p15, 0, r0, c8, c6, 1
+ mov pc, lr
+
+.section ".text._data_memory_barrier"
+_data_memory_barrier:
+#if defined(RPI2) || defined(RPI3)
+ dmb
+#else
+ mov r0, #0
+ mcr p15, 0, r0, c7, c10, 5
+#endif
+ mov pc, lr
+
+#ifdef USE_MULTICORE
+
+_init_core:
+ // On a Raspberry Pi 2 we enter in HYP mode, and need to force a switch to supervisor mode
+ mrs r0, cpsr
+ eor r0, r0, #CPSR_MODE_HYP
+ tst r0, #CPSR_MODE_MASK
+ bic r0 , r0 , #CPSR_MODE_MASK
+ orr r0 , r0 , #CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT | CPSR_MODE_SVR
+ bne _init_not_in_hyp_mode
+ orr r0, r0, #CPSR_A_BIT
+ adr lr, _init_continue
+ msr spsr_cxsf, r0
+ .word 0xE12EF30E // msr_elr_hyp lr
+ .word 0xE160006E // eret
+_init_not_in_hyp_mode:
+ msr cpsr_c, r0
+
+_init_continue:
+ ldr r4,=_start
+ // Initialise Stack Pointers ---------------------------------------------
+
+ // We're going to use interrupt mode, so setup the interrupt mode
+ // stack pointer which differs to the application stack pointer:
+ mov r0, #(CPSR_MODE_IRQ | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, # C1_IRQ_STACK
+
+ // Also setup the stack used for FIQs
+ mov r0, #(CPSR_MODE_FIQ | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, # C1_FIQ_STACK
+
+ // Also setup the stack used for undefined exceptions
+ mov r0, #(CPSR_MODE_UNDEFINED | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, # C1_UNDEFINED_STACK
+
+ // Also setup the stack used for prefetch and data abort exceptions
+ mov r0, #(CPSR_MODE_ABORT | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, # C1_ABORT_STACK
+
+ // Finally, a user/system mode stack, although the application will likely reset this
+ mov r0, #(CPSR_MODE_SYSTEM | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, # C1_USER_STACK
+
+ // Switch back to supervisor mode (our application mode) and
+ // set the stack pointer. Remember that the stack works its way
+ // down memory, our heap will work it's way up from after the
+ // application.
+ mov r0, #(CPSR_MODE_SVR | CPSR_IRQ_INHIBIT | CPSR_FIQ_INHIBIT )
+ msr cpsr_c, r0
+ sub sp, r4, # C1_SVR_STACK
+
+ // Enable VFP ------------------------------------------------------------
+
+ //1. Set the CPACR for access to CP10 and CP11, and clear the ASEDIS and D32DIS bits:
+ ldr r0, =(0xf << 20)
+ mcr p15, 0, r0, c1, c0, 2
+
+ // 2. Set the FPEXC EN bit to enable the NEON MPE:
+ mov r0, #0x40000000
+ vmsr fpexc, r0
+
+ bl run_core
+#endif
+
+#ifdef HAS_MULTICORE
+
+ // If main does return for some reason, just catch it and stay here.
+_spin_core:
+#ifdef DEBUG
+ mov r0, #'S'
+ bl RPI_AuxMiniUartWrite
+ mov r0, #'P'
+ bl RPI_AuxMiniUartWrite
+ mov r0, #'I'
+ bl RPI_AuxMiniUartWrite
+ mov r0, #'N'
+ bl RPI_AuxMiniUartWrite
+ bl _get_core
+ add r0, r0, #'0'
+ bl RPI_AuxMiniUartWrite
+ mov r0, #'\r'
+ bl RPI_AuxMiniUartWrite
+ mov r0, #'\n'
+ bl RPI_AuxMiniUartWrite
+#endif
+_spin_core1:
+ wfi
+ b _spin_core1
+
+_get_core:
+ mrc p15, 0, r0, c0, c0, 5
+ and r0, #3
+ mov pc, lr
+
+#endif
+
+#ifdef HAS_40PINS
+.section ".text._toggle_test_pin"
+_toggle_test_pin:
+ mov r1, #TEST_MASK
+_toggle_test_pin_loop:
+ ldr r2, =GPSET0
+ str r1, [r2]
+ ldr r2, =GPCLR0
+ str r1, [r2]
+ subs r0, r0, #1
+ bne _toggle_test_pin_loop
+ mov pc, lr
+#endif
+
diff --git a/bcm2835int.h b/bcm2835int.h
new file mode 100644
index 0000000..6f38120
--- /dev/null
+++ b/bcm2835int.h
@@ -0,0 +1,109 @@
+//
+// bcm2835int.h
+//
+// The IRQ list is taken from Linux and is:
+// Copyright (C) 2010 Broadcom
+// Copyright (C) 2003 ARM Limited
+// Copyright (C) 2000 Deep Blue Solutions Ltd.
+// Licensed under GPL2
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#ifndef _uspienv_bcm2835int_h
+#define _uspienv_bcm2835int_h
+
+#define ARM_IRQS_PER_REG 32
+
+#define ARM_IRQ1_BASE 0
+#define ARM_IRQ2_BASE (ARM_IRQ1_BASE + ARM_IRQS_PER_REG) // Bitmask of pending shared IRQs 0-31
+#define ARM_IRQBASIC_BASE (ARM_IRQ2_BASE + ARM_IRQS_PER_REG) // Bitmask of pending shared IRQs 32-63
+
+#define ARM_IRQ_TIMER0 (ARM_IRQ1_BASE + 0)
+#define ARM_IRQ_TIMER1 (ARM_IRQ1_BASE + 1)
+#define ARM_IRQ_TIMER2 (ARM_IRQ1_BASE + 2)
+#define ARM_IRQ_TIMER3 (ARM_IRQ1_BASE + 3)
+#define ARM_IRQ_CODEC0 (ARM_IRQ1_BASE + 4)
+#define ARM_IRQ_CODEC1 (ARM_IRQ1_BASE + 5)
+#define ARM_IRQ_CODEC2 (ARM_IRQ1_BASE + 6)
+#define ARM_IRQ_JPEG (ARM_IRQ1_BASE + 7)
+#define ARM_IRQ_ISP (ARM_IRQ1_BASE + 8)
+#define ARM_IRQ_USB (ARM_IRQ1_BASE + 9)
+#define ARM_IRQ_3D (ARM_IRQ1_BASE + 10)
+#define ARM_IRQ_TRANSPOSER (ARM_IRQ1_BASE + 11)
+#define ARM_IRQ_MULTICORESYNC0 (ARM_IRQ1_BASE + 12)
+#define ARM_IRQ_MULTICORESYNC1 (ARM_IRQ1_BASE + 13)
+#define ARM_IRQ_MULTICORESYNC2 (ARM_IRQ1_BASE + 14)
+#define ARM_IRQ_MULTICORESYNC3 (ARM_IRQ1_BASE + 15)
+#define ARM_IRQ_DMA0 (ARM_IRQ1_BASE + 16)
+#define ARM_IRQ_DMA1 (ARM_IRQ1_BASE + 17)
+#define ARM_IRQ_DMA2 (ARM_IRQ1_BASE + 18)
+#define ARM_IRQ_DMA3 (ARM_IRQ1_BASE + 19)
+#define ARM_IRQ_DMA4 (ARM_IRQ1_BASE + 20)
+#define ARM_IRQ_DMA5 (ARM_IRQ1_BASE + 21)
+#define ARM_IRQ_DMA6 (ARM_IRQ1_BASE + 22)
+#define ARM_IRQ_DMA7 (ARM_IRQ1_BASE + 23)
+#define ARM_IRQ_DMA8 (ARM_IRQ1_BASE + 24)
+#define ARM_IRQ_DMA9 (ARM_IRQ1_BASE + 25)
+#define ARM_IRQ_DMA10 (ARM_IRQ1_BASE + 26)
+#define ARM_IRQ_DMA11 (ARM_IRQ1_BASE + 27)
+#define ARM_IRQ_DMA12 (ARM_IRQ1_BASE + 28)
+#define ARM_IRQ_AUX (ARM_IRQ1_BASE + 29)
+#define ARM_IRQ_ARM (ARM_IRQ1_BASE + 30)
+#define ARM_IRQ_VPUDMA (ARM_IRQ1_BASE + 31)
+
+#define ARM_IRQ_HOSTPORT (ARM_IRQ2_BASE + 0)
+#define ARM_IRQ_VIDEOSCALER (ARM_IRQ2_BASE + 1)
+#define ARM_IRQ_CCP2TX (ARM_IRQ2_BASE + 2)
+#define ARM_IRQ_SDC (ARM_IRQ2_BASE + 3)
+#define ARM_IRQ_DSI0 (ARM_IRQ2_BASE + 4)
+#define ARM_IRQ_AVE (ARM_IRQ2_BASE + 5)
+#define ARM_IRQ_CAM0 (ARM_IRQ2_BASE + 6)
+#define ARM_IRQ_CAM1 (ARM_IRQ2_BASE + 7)
+#define ARM_IRQ_HDMI0 (ARM_IRQ2_BASE + 8)
+#define ARM_IRQ_HDMI1 (ARM_IRQ2_BASE + 9)
+#define ARM_IRQ_PIXELVALVE1 (ARM_IRQ2_BASE + 10)
+#define ARM_IRQ_I2CSPISLV (ARM_IRQ2_BASE + 11)
+#define ARM_IRQ_DSI1 (ARM_IRQ2_BASE + 12)
+#define ARM_IRQ_PWA0 (ARM_IRQ2_BASE + 13)
+#define ARM_IRQ_PWA1 (ARM_IRQ2_BASE + 14)
+#define ARM_IRQ_CPR (ARM_IRQ2_BASE + 15)
+#define ARM_IRQ_SMI (ARM_IRQ2_BASE + 16)
+#define ARM_IRQ_GPIO0 (ARM_IRQ2_BASE + 17)
+#define ARM_IRQ_GPIO1 (ARM_IRQ2_BASE + 18)
+#define ARM_IRQ_GPIO2 (ARM_IRQ2_BASE + 19)
+#define ARM_IRQ_GPIO3 (ARM_IRQ2_BASE + 20)
+#define ARM_IRQ_I2C (ARM_IRQ2_BASE + 21)
+#define ARM_IRQ_SPI (ARM_IRQ2_BASE + 22)
+#define ARM_IRQ_I2SPCM (ARM_IRQ2_BASE + 23)
+#define ARM_IRQ_SDIO (ARM_IRQ2_BASE + 24)
+#define ARM_IRQ_UART (ARM_IRQ2_BASE + 25)
+#define ARM_IRQ_SLIMBUS (ARM_IRQ2_BASE + 26)
+#define ARM_IRQ_VEC (ARM_IRQ2_BASE + 27)
+#define ARM_IRQ_CPG (ARM_IRQ2_BASE + 28)
+#define ARM_IRQ_RNG (ARM_IRQ2_BASE + 29)
+#define ARM_IRQ_ARASANSDIO (ARM_IRQ2_BASE + 30)
+#define ARM_IRQ_AVSPMON (ARM_IRQ2_BASE + 31)
+
+#define ARM_IRQ_ARM_TIMER (ARM_IRQBASIC_BASE + 0)
+#define ARM_IRQ_ARM_MAILBOX (ARM_IRQBASIC_BASE + 1)
+#define ARM_IRQ_ARM_DOORBELL_0 (ARM_IRQBASIC_BASE + 2)
+#define ARM_IRQ_ARM_DOORBELL_1 (ARM_IRQBASIC_BASE + 3)
+#define ARM_IRQ_VPU0_HALTED (ARM_IRQBASIC_BASE + 4)
+#define ARM_IRQ_VPU1_HALTED (ARM_IRQBASIC_BASE + 5)
+#define ARM_IRQ_ILLEGAL_TYPE0 (ARM_IRQBASIC_BASE + 6)
+#define ARM_IRQ_ILLEGAL_TYPE1 (ARM_IRQBASIC_BASE + 7)
+
+#define IRQ_LINES (ARM_IRQS_PER_REG * 2 + 8)
+
+#endif
diff --git a/cache.c b/cache.c
new file mode 100644
index 0000000..d8348b2
--- /dev/null
+++ b/cache.c
@@ -0,0 +1,302 @@
+// Part of PiTubeDirect
+// https://github.com/hoglet67/PiTubeDirect
+
+#include
+#include
+#include "startup.h"
+#include "rpi-base.h"
+#include "cache.h"
+#include "defs.h"
+
+// Historical Note:
+// Were seeing core 3 crashes if inner *and* outer both set to some flavour of WB (i.e. 1 or 3)
+// The point of crashing is when the data cache is enabled
+// At that point, the stack appears to vanish and the data read back is 0x55555555
+// Reason turned out to be failure to correctly invalidate the entire data cache
+
+const static unsigned l1_cached_threshold = L2_CACHED_MEM_BASE >> 20;
+const static unsigned l2_cached_threshold = UNCACHED_MEM_BASE >> 20;
+const static unsigned uncached_threshold = PERIPHERAL_BASE >> 20;
+
+volatile __attribute__ ((aligned (0x4000))) unsigned PageTable[4096];
+volatile __attribute__ ((aligned (0x4000))) unsigned PageTable2[NUM_4K_PAGES];
+
+const static int aa = 1;
+const static int bb = 1;
+const static int shareable = 1;
+
+#if defined(RPI2) || defined (RPI3)
+
+#define SETWAY_LEVEL_SHIFT 1
+
+// 4 ways x 128 sets x 64 bytes per line 32KB
+#define L1_DATA_CACHE_SETS 128
+#define L1_DATA_CACHE_WAYS 4
+#define L1_SETWAY_WAY_SHIFT 30 // 32-Log2(L1_DATA_CACHE_WAYS)
+#define L1_SETWAY_SET_SHIFT 6 // Log2(L1_DATA_CACHE_LINE_LENGTH)
+
+#if defined(RPI2)
+// 8 ways x 1024 sets x 64 bytes per line = 512KB
+#define L2_CACHE_SETS 1024
+#define L2_CACHE_WAYS 8
+#define L2_SETWAY_WAY_SHIFT 29 // 32-Log2(L2_CACHE_WAYS)
+#else
+// 16 ways x 512 sets x 64 bytes per line = 512KB
+#define L2_CACHE_SETS 512
+#define L2_CACHE_WAYS 16
+#define L2_SETWAY_WAY_SHIFT 28 // 32-Log2(L2_CACHE_WAYS)
+#endif
+
+#define L2_SETWAY_SET_SHIFT 6 // Log2(L2_CACHE_LINE_LENGTH)
+
+// The origin of this function is:
+// https://github.com/rsta2/uspi/blob/master/env/lib/synchronize.c
+
+void InvalidateDataCache (void)
+{
+ unsigned nSet;
+ unsigned nWay;
+ uint32_t nSetWayLevel;
+ // invalidate L1 data cache
+ for (nSet = 0; nSet < L1_DATA_CACHE_SETS; nSet++) {
+ for (nWay = 0; nWay < L1_DATA_CACHE_WAYS; nWay++) {
+ nSetWayLevel = nWay << L1_SETWAY_WAY_SHIFT
+ | nSet << L1_SETWAY_SET_SHIFT
+ | 0 << SETWAY_LEVEL_SHIFT;
+ asm volatile ("mcr p15, 0, %0, c7, c6, 2" : : "r" (nSetWayLevel) : "memory"); // DCISW
+ }
+ }
+
+ // invalidate L2 unified cache
+ for (nSet = 0; nSet < L2_CACHE_SETS; nSet++) {
+ for (nWay = 0; nWay < L2_CACHE_WAYS; nWay++) {
+ nSetWayLevel = nWay << L2_SETWAY_WAY_SHIFT
+ | nSet << L2_SETWAY_SET_SHIFT
+ | 1 << SETWAY_LEVEL_SHIFT;
+ asm volatile ("mcr p15, 0, %0, c7, c6, 2" : : "r" (nSetWayLevel) : "memory"); // DCISW
+ }
+ }
+}
+
+void CleanDataCache (void)
+{
+ unsigned nSet;
+ unsigned nWay;
+ uint32_t nSetWayLevel;
+ // clean L1 data cache
+ for (nSet = 0; nSet < L1_DATA_CACHE_SETS; nSet++) {
+ for (nWay = 0; nWay < L1_DATA_CACHE_WAYS; nWay++) {
+ nSetWayLevel = nWay << L1_SETWAY_WAY_SHIFT
+ | nSet << L1_SETWAY_SET_SHIFT
+ | 0 << SETWAY_LEVEL_SHIFT;
+ asm volatile ("mcr p15, 0, %0, c7, c10, 2" : : "r" (nSetWayLevel) : "memory");
+ }
+ }
+
+ // clean L2 unified cache
+ for (nSet = 0; nSet < L2_CACHE_SETS; nSet++) {
+ for (nWay = 0; nWay < L2_CACHE_WAYS; nWay++) {
+ nSetWayLevel = nWay << L2_SETWAY_WAY_SHIFT
+ | nSet << L2_SETWAY_SET_SHIFT
+ | 1 << SETWAY_LEVEL_SHIFT;
+ asm volatile ("mcr p15, 0, %0, c7, c10, 2" : : "r" (nSetWayLevel) : "memory");
+ }
+ }
+}
+#endif
+
+// TLB 4KB Section Descriptor format
+// 31..12 Section Base Address
+// 11..9 - unused, set to zero
+// 8..6 TEX - type extension- TEX, C, B used together, see below
+// 5..4 AP - access ctrl - set to 11 for full access from user and super modes
+// 3 C - cacheable - TEX, C, B used together, see below
+// 2 B - bufferable - TEX, C, B used together, see below
+// 1 1
+// 0 1
+
+void map_4k_page(int logical, int physical) {
+ // Invalidate the data TLB before changing mapping
+ _invalidate_dtlb_mva((void *)(logical << 12));
+ // Setup the 4K page table entry
+ // Second level descriptors use extended small page format so inner/outer cacheing can be controlled
+ // Pi 0/1:
+ // XP (bit 23) in SCTRL is 0 so descriptors use ARMv4/5 backwards compatible format
+ // Pi 2/3:
+ // XP (bit 23) in SCTRL no longer exists, and we see to be using ARMv6 table formats
+ // this means bit 0 of the page table is actually XN and must be clear to allow native ARM code to execute
+ // (this was the cause of issue #27)
+#if defined(RPI2) || defined (RPI3)
+ PageTable2[logical] = (physical<<12) | 0x132 | (bb << 6) | (aa << 2);
+#else
+ PageTable2[logical] = (physical<<12) | 0x133 | (bb << 6) | (aa << 2);
+#endif
+}
+
+void enable_MMU_and_IDCaches(void)
+{
+
+ //DEBUG_LOG("enable_MMU_and_IDCaches\r\n");
+ //DEBUG_LOG("cpsr = %08x\r\n", _get_cpsr());
+
+ unsigned i;
+ unsigned base;
+
+ // TLB 1MB Sector Descriptor format
+ // 31..20 Section Base Address
+ // 19 NS - ? - set to 0
+ // 18 0 - - set to 0
+ // 17 nG - ? - set to 0
+ // 16 S - ? - set to 0
+ // 15 APX - access ctrl - set to 0 for full access from user and super modes
+ // 14..12 TEX - type extension- TEX, C, B used together, see below
+ // 11..10 AP - access ctrl - set to 11 for full access from user and super modes
+ // 9 P - - set to 0
+ // 8..5 Domain- access domain - set to 0000 as nor using access ctrl
+ // 4 XN - eXecute Never - set to 1 for I/O devices
+ // 3 C - cacheable - set to 1 for cachable RAM i
+ // 2 B - bufferable - set to 1 for cachable RAM
+ // 1 1 - TEX, C, B used together, see below
+ // 0 0 - TEX, C, B used together, see below
+
+ // For I/O devices
+ // TEX = 000; C=0; B=1 (Shared device)
+
+ // For cacheable RAM
+ // TEX = 001; C=1; B=1 (Outer and inner write back, write allocate)
+
+ // For non-cachable RAM
+ // TEX = 001; C=0; B=0 (Outer and inner non-cacheable)
+
+ // For individual control
+ // TEX = 1BB CB=AA
+ // AA = inner policy
+ // BB = outer policy
+ // 00 = NC (non-cacheable)
+ // 01 = WBWA (write-back, write allocate)
+ // 10 = WT (write-through
+ // 11 = WBNWA (write-back, no write allocate)
+ /// TEX = 100; C=0; B=1 (outer non cacheable, inner write-back, write allocate)
+
+ for (base = 0; base < l1_cached_threshold; base++)
+ {
+ // Value from my original RPI code = 11C0E (outer and inner write back, write allocate, shareable)
+ // bits 11..10 are the AP bits, and setting them to 11 enables user mode access as well
+ // Values from RPI2 = 11C0E (outer and inner write back, write allocate, shareable (fast but unsafe)); works on RPI
+ // Values from RPI2 = 10C0A (outer and inner write through, no write allocate, shareable)
+ // Values from RPI2 = 15C0A (outer write back, write allocate, inner write through, no write allocate, shareable)
+ PageTable[base] = base << 20 | 0x04C02 | (shareable << 16) | (bb << 12) | (aa << 2);
+ }
+ for (; base < l2_cached_threshold; base++)
+ {
+ PageTable[base] = base << 20 | 0x04C02 | (shareable << 16) | (bb << 12);
+ }
+ for (; base < uncached_threshold; base++)
+ {
+ PageTable[base] = base << 20 | 0x01C02;
+ }
+ for (; base < 4096; base++)
+ {
+ // shared device, never execute
+ PageTable[base] = base << 20 | 0x10C16;
+ }
+
+ // suppress a warning as we really do want to copy from src address 0!
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnonnull"
+ // copy vectors from virtual address zero to a higher unused location
+ memcpy((void *)HIGH_VECTORS_BASE, (void *)0, 0x1000);
+#pragma GCC diagnostic pop
+
+ // replace the first N 1MB entries with second level page tables, giving N x 256 4K pages
+ for (i = 0; i < NUM_4K_PAGES >> 8; i++)
+ {
+ PageTable[i] = (unsigned int) (&PageTable2[i << 8]);
+ PageTable[i] +=1;
+ }
+
+ // populate the second level page tables
+ for (base = 0; base < NUM_4K_PAGES; base++)
+ {
+ map_4k_page(base, base);
+ }
+
+ // relocate the vector pointer to the moved page
+ asm volatile("mcr p15, 0, %[addr], c12, c0, 0" : : [addr] "r" (HIGH_VECTORS_BASE));
+
+#if defined(RPI3)
+ unsigned cpuextctrl0, cpuextctrl1;
+ asm volatile ("mrrc p15, 1, %0, %1, c15" : "=r" (cpuextctrl0), "=r" (cpuextctrl1));
+ //DEBUG_LOG("extctrl = %08x %08x\r\n", cpuextctrl1, cpuextctrl0);
+#else
+ // RPI: bit 6 of auxctrl is restrict cache size to 16K (no page coloring)
+ // RPI2: bit 6 of auxctrl is set SMP bit, otherwise all caching disabled
+ unsigned auxctrl;
+ asm volatile ("mrc p15, 0, %0, c1, c0, 1" : "=r" (auxctrl));
+ auxctrl |= 1 << 6;
+ asm volatile ("mcr p15, 0, %0, c1, c0, 1" :: "r" (auxctrl));
+ asm volatile ("mrc p15, 0, %0, c1, c0, 1" : "=r" (auxctrl));
+ //DEBUG_LOG("auxctrl = %08x\r\n", auxctrl);
+#endif
+
+ // set domain 0 to client
+ asm volatile ("mcr p15, 0, %0, c3, c0, 0" :: "r" (1));
+
+ // always use TTBR0
+ asm volatile ("mcr p15, 0, %0, c2, c0, 2" :: "r" (0));
+
+ unsigned ttbcr;
+ asm volatile ("mrc p15, 0, %0, c2, c0, 2" : "=r" (ttbcr));
+ //DEBUG_LOG("ttbcr = %08x\r\n", ttbcr);
+
+#if defined(RPI2) || defined(RPI3)
+ // set TTBR0 - page table walk memory cacheability/shareable
+ // [Bit 0, Bit 6] indicates inner cachability: 01 = normal memory, inner write-back write-allocate cacheable
+ // [Bit 4, Bit 3] indicates outer cachability: 01 = normal memory, outer write-back write-allocate cacheable
+ // Bit 1 indicates sharable
+ // 4A = 0100 1010
+ int attr = ((aa & 1) << 6) | (bb << 3) | (shareable << 1) | ((aa & 2) >> 1);
+ asm volatile ("mcr p15, 0, %0, c2, c0, 0" :: "r" (attr | (unsigned) &PageTable));
+#else
+ // set TTBR0 (page table walk inner cacheable, outer non-cacheable, shareable memory)
+ asm volatile ("mcr p15, 0, %0, c2, c0, 0" :: "r" (0x03 | (unsigned) &PageTable));
+#endif
+ unsigned ttbr0;
+ asm volatile ("mrc p15, 0, %0, c2, c0, 0" : "=r" (ttbr0));
+ //DEBUG_LOG("ttbr0 = %08x\r\n", ttbr0);
+
+
+ // Invalidate entire data cache
+#if defined(RPI2) || defined(RPI3)
+ asm volatile ("isb" ::: "memory");
+ InvalidateDataCache();
+#else
+ // invalidate data cache and flush prefetch buffer
+ // NOTE: The below code seems to cause a Pi 2 to crash
+ asm volatile ("mcr p15, 0, %0, c7, c5, 4" :: "r" (0) : "memory");
+ asm volatile ("mcr p15, 0, %0, c7, c6, 0" :: "r" (0) : "memory");
+#endif
+
+ // enable MMU, L1 cache and instruction cache, L2 cache, write buffer,
+ // branch prediction and extended page table on
+ unsigned sctrl;
+ asm volatile ("mrc p15,0,%0,c1,c0,0" : "=r" (sctrl));
+ // Bit 13 enable vector relocation
+ // Bit 12 enables the L1 instruction cache
+ // Bit 11 enables branch pre-fetching
+ // Bit 2 enables the L1 data cache
+ // Bit 0 enabled the MMU
+ // The L1 instruction cache can be used independently of the MMU
+ // The L1 data cache will one be enabled if the MMU is enabled
+
+ sctrl |= 0x00001805;
+ asm volatile ("mcr p15,0,%0,c1,c0,0" :: "r" (sctrl) : "memory");
+ asm volatile ("mrc p15,0,%0,c1,c0,0" : "=r" (sctrl));
+ //DEBUG_LOG("sctrl = %08x\r\n", sctrl);
+
+ // For information, show the cache type register
+ // From this you can tell what type of cache is implemented
+ unsigned ctype;
+ asm volatile ("mrc p15,0,%0,c0,c0,1" : "=r" (ctype));
+ //DEBUG_LOG("ctype = %08x\r\n", ctype);
+}
diff --git a/cache.h b/cache.h
new file mode 100644
index 0000000..5e58e9e
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,32 @@
+// Part of PiTubeDirect
+// https://github.com/hoglet67/PiTubeDirect
+
+// cache.h
+
+#ifndef CACHE_H
+#define CACHE_H
+
+// Memory below 64MB is L1 and L2 cached (inner and outer)
+
+// Mark the memory above 64MB to 128MB as L2 cached only (outer)
+#define L2_CACHED_MEM_BASE 0x04000000
+
+// Mark the memory above 128MB as uncachable
+#define UNCACHED_MEM_BASE 0x08000000
+
+// Location of the high vectors (last page of L1 cached memory)
+#define HIGH_VECTORS_BASE (L2_CACHED_MEM_BASE - 0x1000)
+
+// The first 2MB of memory is mapped at 4K pages so the 6502 Co Pro
+// can play tricks with banks selection
+#define NUM_4K_PAGES 512
+
+#ifndef __ASSEMBLER__
+
+void map_4k_page(int logical, int physical);
+
+void enable_MMU_and_IDCaches(void);
+
+#endif
+
+#endif
diff --git a/debug.h b/debug.h
new file mode 100644
index 0000000..4df0123
--- /dev/null
+++ b/debug.h
@@ -0,0 +1,16 @@
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#ifdef __ASSEMBLER__
+#else
+#include
+#endif
+
+#ifdef DEBUG
+#define DEBUG_LOG(...) printf(__VA_ARGS__)
+#else
+#define DEBUG_LOG(...)
+#endif
+
+
+#endif
\ No newline at end of file
diff --git a/defs.h b/defs.h
new file mode 100644
index 0000000..7462bdb
--- /dev/null
+++ b/defs.h
@@ -0,0 +1,71 @@
+#ifndef DEFS_H
+#define DEFS_H
+
+#include "debug.h"
+
+// Indicates a Pi with the 40 pin GPIO connector
+// so that additional functionality (e.g. test pins) can be enabled
+#if defined(RPIZERO) || defined(RPIBPLUS) || defined(RPI2) || defined(RPI3)
+#define HAS_40PINS
+#endif
+
+// Pi 2/3 Multicore options
+#if defined(RPI2) || defined(RPI3)
+
+// Indicate the platform has multiple cores
+#define HAS_MULTICORE
+
+#define USE_GPU
+
+#define USE_HW_MAILBOX
+
+// Indicates we want to make active use of multiple cores
+#define USE_MULTICORE
+
+// Needs to match kernel_old setting in config.txt
+//#define KERNEL_OLD
+
+// Include instruction histogram in multi core 65tube
+//#define HISTOGRAM
+
+#else
+
+#define USE_GPU
+
+#define USE_HW_MAILBOX
+
+#endif
+
+#include "rpi-base.h"
+
+#ifdef USE_HW_MAILBOX
+#define MBOX0_READ (PERIPHERAL_BASE + 0x00B880)
+#define MBOX0_STATUS (PERIPHERAL_BASE + 0x00B898)
+#define MBOX0_CONFIG (PERIPHERAL_BASE + 0x00B89C)
+#define MBOX0_EMPTY (0x40000000)
+#define MBOX0_DATAIRQEN (0x00000001)
+#endif
+
+#ifdef __ASSEMBLER__
+
+#define GPFSEL0 (PERIPHERAL_BASE + 0x200000) // controls GPIOs 0..9
+#define GPFSEL1 (PERIPHERAL_BASE + 0x200004) // controls GPIOs 10..19
+#define GPFSEL2 (PERIPHERAL_BASE + 0x200008) // controls GPIOs 20..29
+#define GPSET0 (PERIPHERAL_BASE + 0x20001C)
+#define GPCLR0 (PERIPHERAL_BASE + 0x200028)
+#define GPLEV0 (PERIPHERAL_BASE + 0x200034)
+#define GPEDS0 (PERIPHERAL_BASE + 0x200040)
+#define FIQCTRL (PERIPHERAL_BASE + 0x00B20C)
+
+#endif // __ASSEMBLER__
+
+#ifdef HAS_40PINS
+#define TEST_PIN (21)
+#define TEST_MASK (1 << TEST_PIN)
+#define TEST2_PIN (20)
+#define TEST2_MASK (1 << TEST2_PIN)
+#define TEST3_PIN (16)
+#define TEST3_MASK (1 << TEST3_PIN)
+#endif
+
+#endif
diff --git a/diskio.cpp b/diskio.cpp
new file mode 100644
index 0000000..25dc6ef
--- /dev/null
+++ b/diskio.cpp
@@ -0,0 +1,275 @@
+/*-----------------------------------------------------------------------*/
+/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
+/*-----------------------------------------------------------------------*/
+/* If a working storage control module is available, it should be */
+/* attached to the FatFs via a glue function rather than modifying it. */
+/* This is an example of glue functions to attach various exsisting */
+/* storage control modules to the FatFs module with a defined API. */
+/*-----------------------------------------------------------------------*/
+
+#include "diskio.h" /* FatFs lower layer API */
+
+/* Definitions of physical drive number for each drive */
+#define DEV_MMC 0 /* Example: Map MMC/SD card to physical drive 0 */
+
+//static struct emmc_block_dev *emmc_dev;
+static CEMMCDevice* pEMMC;
+
+#define SD_BLOCK_SIZE 512
+
+void disk_setEMM(CEMMCDevice* pEMMCDevice)
+{
+ pEMMC = pEMMCDevice;
+}
+
+int sd_card_init(struct block_device **dev)
+{
+ return 0;
+}
+
+size_t sd_read(uint8_t *buf, size_t buf_size, uint32_t block_no)
+{
+// g_pLogger->Write("", LogNotice, "sd_read %d", block_no);
+
+ return pEMMC->DoRead(buf, buf_size, block_no);
+}
+
+size_t sd_write(uint8_t *buf, size_t buf_size, uint32_t block_no)
+{
+ return pEMMC->DoWrite(buf, buf_size, block_no);
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Get Drive Status */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_status (
+ BYTE pdrv /* Physical drive nmuber to identify the drive */
+)
+{
+ //DSTATUS stat;
+ //int result;
+
+ //switch (pdrv) {
+ ////case DEV_RAM :
+ //// result = RAM_disk_status();
+
+ //// // translate the reslut code here
+
+ //// return stat;
+
+ //case DEV_MMC :
+
+ // result = MMC_disk_status();
+
+ // // translate the reslut code here
+
+ // return stat;
+
+ ////case DEV_USB :
+ //// result = USB_disk_status();
+
+ //// // translate the reslut code here
+
+ //// return stat;
+ //}
+ //return STA_NOINIT;
+ return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Inidialize a Drive */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_initialize (
+ BYTE pdrv /* Physical drive nmuber to identify the drive */
+)
+{
+ //DSTATUS stat;
+ //int result;
+
+ //switch (pdrv) {
+ ////case DEV_RAM :
+ //// result = RAM_disk_initialize();
+
+ //// // translate the reslut code here
+
+ //// return stat;
+
+ ////case DEV_MMC :
+ //// result = MMC_disk_initialize();
+
+ //// // translate the reslut code here
+
+ //// return stat;
+
+ ////case DEV_USB :
+ //// result = USB_disk_initialize();
+
+ //// // translate the reslut code here
+
+ //// return stat;
+ //}
+ //return STA_NOINIT;
+ return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read Sector(s) */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_read (
+ BYTE pdrv, /* Physical drive nmuber to identify the drive */
+ BYTE *buff, /* Data buffer to store read data */
+ DWORD sector, /* Start sector in LBA */
+ UINT count /* Number of sectors to read */
+)
+{
+ //DRESULT res;
+ //int result;
+
+// g_pLogger->Write("", LogNotice, "disk_read pdrv = %d", pdrv);
+
+ switch (pdrv) {
+ //case DEV_RAM :
+ // // translate the arguments here
+
+ // result = RAM_disk_read(buff, sector, count);
+
+ // // translate the reslut code here
+
+ // return res;
+
+ case DEV_MMC :
+ // translate the arguments here
+
+ //result = MMC_disk_read(buff, sector, count);
+
+// g_pLogger->Write("", LogNotice, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!disk_read %d %d buf_size = 0x%x", sector, count, buf_size);
+
+ for (UINT s = 0; s < count; ++s)
+ {
+ if (sd_read(buff, SD_BLOCK_SIZE, sector+s) < SD_BLOCK_SIZE)
+ {
+ return RES_ERROR;
+ }
+ buff += SD_BLOCK_SIZE;
+ }
+ return RES_OK;
+
+ //case DEV_USB :
+ // // translate the arguments here
+
+ // result = USB_disk_read(buff, sector, count);
+
+ // // translate the reslut code here
+
+ // return res;
+ }
+
+ return RES_PARERR;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Write Sector(s) */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_write (
+ BYTE pdrv, /* Physical drive nmuber to identify the drive */
+ const BYTE *buff, /* Data to be written */
+ DWORD sector, /* Start sector in LBA */
+ UINT count /* Number of sectors to write */
+)
+{
+ //DRESULT res;
+ //int result;
+
+ switch (pdrv) {
+ //case DEV_RAM :
+ // // translate the arguments here
+
+ // result = RAM_disk_write(buff, sector, count);
+
+ // // translate the reslut code here
+
+ // return res;
+
+ case DEV_MMC :
+ // translate the arguments here
+
+ //result = MMC_disk_write(buff, sector, count);
+ //size_t buf_size = count * SD_BLOCK_SIZE;
+ //if (sd_write((uint8_t *)buff, buf_size, sector) < buf_size)
+ //{
+ // return RES_ERROR;
+ //}
+
+ for (UINT s = 0; s < count; ++s)
+ {
+ if (sd_write((uint8_t *)buff, SD_BLOCK_SIZE, sector+s) < SD_BLOCK_SIZE)
+ {
+ return RES_ERROR;
+ }
+ buff += SD_BLOCK_SIZE;
+ }
+
+ return RES_OK;
+
+ //case DEV_USB :
+ // // translate the arguments here
+
+ // result = USB_disk_write(buff, sector, count);
+
+ // // translate the reslut code here
+
+ // return res;
+ }
+
+ return RES_PARERR;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Miscellaneous Functions */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_ioctl (
+ BYTE pdrv, /* Physical drive nmuber (0..) */
+ BYTE cmd, /* Control code */
+ void *buff /* Buffer to send/receive control data */
+)
+{
+ //DRESULT res;
+ //int result;
+
+ //switch (pdrv) {
+ //case DEV_RAM :
+
+ // // Process of the command for the RAM drive
+
+ // return res;
+
+ //case DEV_MMC :
+
+ // // Process of the command for the MMC/SD card
+
+ // return res;
+
+ //case DEV_USB :
+
+ // // Process of the command the USB drive
+
+ // return res;
+ //}
+
+ return RES_PARERR;
+}
+
diff --git a/diskio.h b/diskio.h
new file mode 100644
index 0000000..e09564c
--- /dev/null
+++ b/diskio.h
@@ -0,0 +1,83 @@
+/*-----------------------------------------------------------------------/
+/ Low level disk interface modlue include file (C)ChaN, 2014 /
+/-----------------------------------------------------------------------*/
+
+#ifndef _DISKIO_DEFINED
+#define _DISKIO_DEFINED
+
+#include "integer.h"
+#include "emmc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Status of Disk Functions */
+typedef BYTE DSTATUS;
+
+/* Results of Disk Functions */
+typedef enum {
+ RES_OK = 0, /* 0: Successful */
+ RES_ERROR, /* 1: R/W Error */
+ RES_WRPRT, /* 2: Write Protected */
+ RES_NOTRDY, /* 3: Not Ready */
+ RES_PARERR /* 4: Invalid Parameter */
+} DRESULT;
+
+
+/*---------------------------------------*/
+/* Prototypes for disk control functions */
+
+
+void disk_setEMM(CEMMCDevice* pEMMCDevice);
+
+DSTATUS disk_initialize (BYTE pdrv);
+DSTATUS disk_status (BYTE pdrv);
+DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
+DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
+DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
+
+
+/* Disk Status Bits (DSTATUS) */
+
+#define STA_NOINIT 0x01 /* Drive not initialized */
+#define STA_NODISK 0x02 /* No medium in the drive */
+#define STA_PROTECT 0x04 /* Write protected */
+
+
+/* Command code for disk_ioctrl fucntion */
+
+/* Generic command (Used by FatFs) */
+#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */
+#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */
+#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */
+#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */
+#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) */
+
+/* Generic command (Not used by FatFs) */
+#define CTRL_POWER 5 /* Get/Set power status */
+#define CTRL_LOCK 6 /* Lock/Unlock media removal */
+#define CTRL_EJECT 7 /* Eject media */
+#define CTRL_FORMAT 8 /* Create physical format on the media */
+
+/* MMC/SDC specific ioctl command */
+#define MMC_GET_TYPE 10 /* Get card type */
+#define MMC_GET_CSD 11 /* Get CSD */
+#define MMC_GET_CID 12 /* Get CID */
+#define MMC_GET_OCR 13 /* Get OCR */
+#define MMC_GET_SDSTAT 14 /* Get SD status */
+#define ISDIO_READ 55 /* Read data form SD iSDIO register */
+#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
+#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
+
+/* ATA/CF specific ioctl command */
+#define ATA_GET_REV 20 /* Get F/W revision */
+#define ATA_GET_MODEL 21 /* Get model name */
+#define ATA_GET_SN 22 /* Get serial number */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/emmc.cpp b/emmc.cpp
new file mode 100644
index 0000000..4b00d2b
--- /dev/null
+++ b/emmc.cpp
@@ -0,0 +1,1998 @@
+//
+// emmc.cpp
+//
+// Provides an interface to the EMMC controller and commands for interacting
+// with an sd card
+//
+// Copyright(C) 2013 by John Cronin
+//
+// Modified for Circle by R. Stange
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// References:
+//
+// PLSS - SD Group Physical Layer Simplified Specification ver 3.00
+// HCSS - SD Group Host Controller Simplified Specification ver 3.00
+//
+// Broadcom BCM2835 ARM Peripherals Guide
+//
+
+#include "emmc.h"
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+#include
+extern "C"
+{
+ #include "rpiHardware.h"
+}
+
+//
+// Configuration options
+//
+
+//#define EMMC_DEBUG
+//#define EMMC_DEBUG2
+
+//
+// According to the BCM2835 ARM Peripherals Guide the EMMC STATUS register
+// should not be used for polling. The original driver does not meet this
+// specification in this point but the modified driver should do so.
+// Define EMMC_POLL_STATUS_REG if you want the original function!
+//
+//#define EMMC_POLL_STATUS_REG
+
+// Enable 1.8V support
+//#define SD_1_8V_SUPPORT
+
+// Enable 4-bit support
+#define SD_4BIT_DATA
+
+// SD Clock Frequencies(in Hz)
+#define SD_CLOCK_ID 400000
+#define SD_CLOCK_NORMAL 25000000
+#define SD_CLOCK_HIGH 50000000
+#define SD_CLOCK_100 100000000
+#define SD_CLOCK_208 208000000
+
+// Enable SDXC maximum performance mode
+// Requires 150 mA power so disabled on the RPi for now
+#define SDXC_MAXIMUM_PERFORMANCE
+
+// Enable card interrupts
+//#define SD_CARD_INTERRUPTS
+
+#define EMMC_ARG2 (ARM_EMMC_BASE + 0x00)
+#define EMMC_BLKSIZECNT (ARM_EMMC_BASE + 0x04)
+#define EMMC_ARG1 (ARM_EMMC_BASE + 0x08)
+#define EMMC_CMDTM (ARM_EMMC_BASE + 0x0C)
+#define EMMC_RESP0 (ARM_EMMC_BASE + 0x10)
+#define EMMC_RESP1 (ARM_EMMC_BASE + 0x14)
+#define EMMC_RESP2 (ARM_EMMC_BASE + 0x18)
+#define EMMC_RESP3 (ARM_EMMC_BASE + 0x1C)
+#define EMMC_DATA (ARM_EMMC_BASE + 0x20)
+#define EMMC_STATUS (ARM_EMMC_BASE + 0x24)
+#define EMMC_CONTROL0 (ARM_EMMC_BASE + 0x28)
+#define EMMC_CONTROL1 (ARM_EMMC_BASE + 0x2C)
+#define EMMC_INTERRUPT (ARM_EMMC_BASE + 0x30)
+#define EMMC_IRPT_MASK (ARM_EMMC_BASE + 0x34)
+#define EMMC_IRPT_EN (ARM_EMMC_BASE + 0x38)
+#define EMMC_CONTROL2 (ARM_EMMC_BASE + 0x3C)
+#define EMMC_CAPABILITIES_0 (ARM_EMMC_BASE + 0x40)
+#define EMMC_CAPABILITIES_1 (ARM_EMMC_BASE + 0x44)
+#define EMMC_FORCE_IRPT (ARM_EMMC_BASE + 0x50)
+#define EMMC_BOOT_TIMEOUT (ARM_EMMC_BASE + 0x70)
+#define EMMC_DBG_SEL (ARM_EMMC_BASE + 0x74)
+#define EMMC_EXRDFIFO_CFG (ARM_EMMC_BASE + 0x80)
+#define EMMC_EXRDFIFO_EN (ARM_EMMC_BASE + 0x84)
+#define EMMC_TUNE_STEP (ARM_EMMC_BASE + 0x88)
+#define EMMC_TUNE_STEPS_STD (ARM_EMMC_BASE + 0x8C)
+#define EMMC_TUNE_STEPS_DDR (ARM_EMMC_BASE + 0x90)
+#define EMMC_SPI_INT_SPT (ARM_EMMC_BASE + 0xF0)
+#define EMMC_SLOTISR_VER (ARM_EMMC_BASE + 0xFC)
+
+#define SD_CMD_INDEX(a) ((a) << 24)
+#define SD_CMD_TYPE_NORMAL 0
+#define SD_CMD_TYPE_SUSPEND (1 << 22)
+#define SD_CMD_TYPE_RESUME (2 << 22)
+#define SD_CMD_TYPE_ABORT (3 << 22)
+#define SD_CMD_TYPE_MASK (3 << 22)
+#define SD_CMD_ISDATA (1 << 21)
+#define SD_CMD_IXCHK_EN (1 << 20)
+#define SD_CMD_CRCCHK_EN (1 << 19)
+#define SD_CMD_RSPNS_TYPE_NONE 0 // For no response
+#define SD_CMD_RSPNS_TYPE_136 (1 << 16) // For response R2(with CRC), R3,4(no CRC)
+#define SD_CMD_RSPNS_TYPE_48 (2 << 16) // For responses R1, R5, R6, R7(with CRC)
+#define SD_CMD_RSPNS_TYPE_48B (3 << 16) // For responses R1b, R5b(with CRC)
+#define SD_CMD_RSPNS_TYPE_MASK (3 << 16)
+#define SD_CMD_MULTI_BLOCK (1 << 5)
+#define SD_CMD_DAT_DIR_HC 0
+#define SD_CMD_DAT_DIR_CH (1 << 4)
+#define SD_CMD_AUTO_CMD_EN_NONE 0
+#define SD_CMD_AUTO_CMD_EN_CMD12 (1 << 2)
+#define SD_CMD_AUTO_CMD_EN_CMD23 (2 << 2)
+#define SD_CMD_BLKCNT_EN (1 << 1)
+#define SD_CMD_DMA 1
+
+#define SD_ERR_CMD_TIMEOUT 0
+#define SD_ERR_CMD_CRC 1
+#define SD_ERR_CMD_END_BIT 2
+#define SD_ERR_CMD_INDEX 3
+#define SD_ERR_DATA_TIMEOUT 4
+#define SD_ERR_DATA_CRC 5
+#define SD_ERR_DATA_END_BIT 6
+#define SD_ERR_CURRENT_LIMIT 7
+#define SD_ERR_AUTO_CMD12 8
+#define SD_ERR_ADMA 9
+#define SD_ERR_TUNING 10
+#define SD_ERR_RSVD 11
+
+#define SD_ERR_MASK_CMD_TIMEOUT (1 <<(16 + SD_ERR_CMD_TIMEOUT))
+#define SD_ERR_MASK_CMD_CRC (1 <<(16 + SD_ERR_CMD_CRC))
+#define SD_ERR_MASK_CMD_END_BIT (1 <<(16 + SD_ERR_CMD_END_BIT))
+#define SD_ERR_MASK_CMD_INDEX (1 <<(16 + SD_ERR_CMD_INDEX))
+#define SD_ERR_MASK_DATA_TIMEOUT (1 <<(16 + SD_ERR_CMD_TIMEOUT))
+#define SD_ERR_MASK_DATA_CRC (1 <<(16 + SD_ERR_CMD_CRC))
+#define SD_ERR_MASK_DATA_END_BIT (1 <<(16 + SD_ERR_CMD_END_BIT))
+#define SD_ERR_MASK_CURRENT_LIMIT (1 <<(16 + SD_ERR_CMD_CURRENT_LIMIT))
+#define SD_ERR_MASK_AUTO_CMD12 (1 <<(16 + SD_ERR_CMD_AUTO_CMD12))
+#define SD_ERR_MASK_ADMA (1 <<(16 + SD_ERR_CMD_ADMA))
+#define SD_ERR_MASK_TUNING (1 <<(16 + SD_ERR_CMD_TUNING))
+
+#define SD_COMMAND_COMPLETE 1
+#define SD_TRANSFER_COMPLETE (1 << 1)
+#define SD_BLOCK_GAP_EVENT (1 << 2)
+#define SD_DMA_INTERRUPT (1 << 3)
+#define SD_BUFFER_WRITE_READY (1 << 4)
+#define SD_BUFFER_READ_READY (1 << 5)
+#define SD_CARD_INSERTION (1 << 6)
+#define SD_CARD_REMOVAL (1 << 7)
+#define SD_CARD_INTERRUPT (1 << 8)
+
+#define SD_RESP_NONE SD_CMD_RSPNS_TYPE_NONE
+#define SD_RESP_R1 (SD_CMD_RSPNS_TYPE_48 | SD_CMD_CRCCHK_EN)
+#define SD_RESP_R1b (SD_CMD_RSPNS_TYPE_48B | SD_CMD_CRCCHK_EN)
+#define SD_RESP_R2 (SD_CMD_RSPNS_TYPE_136 | SD_CMD_CRCCHK_EN)
+#define SD_RESP_R3 SD_CMD_RSPNS_TYPE_48
+#define SD_RESP_R4 SD_CMD_RSPNS_TYPE_136
+#define SD_RESP_R5 (SD_CMD_RSPNS_TYPE_48 | SD_CMD_CRCCHK_EN)
+#define SD_RESP_R5b (SD_CMD_RSPNS_TYPE_48B | SD_CMD_CRCCHK_EN)
+#define SD_RESP_R6 (SD_CMD_RSPNS_TYPE_48 | SD_CMD_CRCCHK_EN)
+#define SD_RESP_R7 (SD_CMD_RSPNS_TYPE_48 | SD_CMD_CRCCHK_EN)
+
+#define SD_DATA_READ (SD_CMD_ISDATA | SD_CMD_DAT_DIR_CH)
+#define SD_DATA_WRITE (SD_CMD_ISDATA | SD_CMD_DAT_DIR_HC)
+
+#define SD_CMD_RESERVED(a) 0xffffffff
+
+#define SUCCESS (m_last_cmd_success)
+#define FAIL (m_last_cmd_success == 0)
+#define TIMEOUT (FAIL &&(m_last_error == 0))
+#define CMD_TIMEOUT (FAIL &&(m_last_error &(1 << 16)))
+#define CMD_CRC (FAIL &&(m_last_error &(1 << 17)))
+#define CMD_END_BIT (FAIL &&(m_last_error &(1 << 18)))
+#define CMD_INDEX (FAIL &&(m_last_error &(1 << 19)))
+#define DATA_TIMEOUT (FAIL &&(m_last_error &(1 << 20)))
+#define DATA_CRC (FAIL &&(m_last_error &(1 << 21)))
+#define DATA_END_BIT (FAIL &&(m_last_error &(1 << 22)))
+#define CURRENT_LIMIT (FAIL &&(m_last_error &(1 << 23)))
+#define ACMD12_ERROR (FAIL &&(m_last_error &(1 << 24)))
+#define ADMA_ERROR (FAIL &&(m_last_error &(1 << 25)))
+#define TUNING_ERROR (FAIL &&(m_last_error &(1 << 26)))
+
+#define SD_VER_UNKNOWN 0
+#define SD_VER_1 1
+#define SD_VER_1_1 2
+#define SD_VER_2 3
+#define SD_VER_3 4
+#define SD_VER_4 5
+
+const char *CEMMCDevice::sd_versions[] =
+{
+ "unknown",
+ "1.0 or 1.01",
+ "1.10",
+ "2.00",
+ "3.0x",
+ "4.xx"
+};
+
+#ifdef EMMC_DEBUG2
+const char *CEMMCDevice::err_irpts[] =
+{
+ "CMD_TIMEOUT",
+ "CMD_CRC",
+ "CMD_END_BIT",
+ "CMD_INDEX",
+ "DATA_TIMEOUT",
+ "DATA_CRC",
+ "DATA_END_BIT",
+ "CURRENT_LIMIT",
+ "AUTO_CMD12",
+ "ADMA",
+ "TUNING",
+ "RSVD"
+};
+#endif
+
+const u32 CEMMCDevice::sd_commands[] =
+{
+ SD_CMD_INDEX(0),
+ SD_CMD_RESERVED(1),
+ SD_CMD_INDEX(2) | SD_RESP_R2,
+ SD_CMD_INDEX(3) | SD_RESP_R6,
+ SD_CMD_INDEX(4),
+ SD_CMD_INDEX(5) | SD_RESP_R4,
+ SD_CMD_INDEX(6) | SD_RESP_R1,
+ SD_CMD_INDEX(7) | SD_RESP_R1b,
+ SD_CMD_INDEX(8) | SD_RESP_R7,
+ SD_CMD_INDEX(9) | SD_RESP_R2,
+ SD_CMD_INDEX(10) | SD_RESP_R2,
+ SD_CMD_INDEX(11) | SD_RESP_R1,
+ SD_CMD_INDEX(12) | SD_RESP_R1b | SD_CMD_TYPE_ABORT,
+ SD_CMD_INDEX(13) | SD_RESP_R1,
+ SD_CMD_RESERVED(14),
+ SD_CMD_INDEX(15),
+ SD_CMD_INDEX(16) | SD_RESP_R1,
+ SD_CMD_INDEX(17) | SD_RESP_R1 | SD_DATA_READ,
+ SD_CMD_INDEX(18) | SD_RESP_R1 | SD_DATA_READ | SD_CMD_MULTI_BLOCK | SD_CMD_BLKCNT_EN | SD_CMD_AUTO_CMD_EN_CMD12, // SD_CMD_AUTO_CMD_EN_CMD12 not in original driver
+ SD_CMD_INDEX(19) | SD_RESP_R1 | SD_DATA_READ,
+ SD_CMD_INDEX(20) | SD_RESP_R1b,
+ SD_CMD_RESERVED(21),
+ SD_CMD_RESERVED(22),
+ SD_CMD_INDEX(23) | SD_RESP_R1,
+ SD_CMD_INDEX(24) | SD_RESP_R1 | SD_DATA_WRITE,
+ SD_CMD_INDEX(25) | SD_RESP_R1 | SD_DATA_WRITE | SD_CMD_MULTI_BLOCK | SD_CMD_BLKCNT_EN | SD_CMD_AUTO_CMD_EN_CMD12, // SD_CMD_AUTO_CMD_EN_CMD12 not in original driver
+ SD_CMD_RESERVED(26),
+ SD_CMD_INDEX(27) | SD_RESP_R1 | SD_DATA_WRITE,
+ SD_CMD_INDEX(28) | SD_RESP_R1b,
+ SD_CMD_INDEX(29) | SD_RESP_R1b,
+ SD_CMD_INDEX(30) | SD_RESP_R1 | SD_DATA_READ,
+ SD_CMD_RESERVED(31),
+ SD_CMD_INDEX(32) | SD_RESP_R1,
+ SD_CMD_INDEX(33) | SD_RESP_R1,
+ SD_CMD_RESERVED(34),
+ SD_CMD_RESERVED(35),
+ SD_CMD_RESERVED(36),
+ SD_CMD_RESERVED(37),
+ SD_CMD_INDEX(38) | SD_RESP_R1b,
+ SD_CMD_RESERVED(39),
+ SD_CMD_RESERVED(40),
+ SD_CMD_RESERVED(41),
+ SD_CMD_RESERVED(42) | SD_RESP_R1,
+ SD_CMD_RESERVED(43),
+ SD_CMD_RESERVED(44),
+ SD_CMD_RESERVED(45),
+ SD_CMD_RESERVED(46),
+ SD_CMD_RESERVED(47),
+ SD_CMD_RESERVED(48),
+ SD_CMD_RESERVED(49),
+ SD_CMD_RESERVED(50),
+ SD_CMD_RESERVED(51),
+ SD_CMD_RESERVED(52),
+ SD_CMD_RESERVED(53),
+ SD_CMD_RESERVED(54),
+ SD_CMD_INDEX(55) | SD_RESP_R1,
+ SD_CMD_INDEX(56) | SD_RESP_R1 | SD_CMD_ISDATA,
+ SD_CMD_RESERVED(57),
+ SD_CMD_RESERVED(58),
+ SD_CMD_RESERVED(59),
+ SD_CMD_RESERVED(60),
+ SD_CMD_RESERVED(61),
+ SD_CMD_RESERVED(62),
+ SD_CMD_RESERVED(63)
+};
+
+const u32 CEMMCDevice::sd_acommands[] =
+{
+ SD_CMD_RESERVED(0),
+ SD_CMD_RESERVED(1),
+ SD_CMD_RESERVED(2),
+ SD_CMD_RESERVED(3),
+ SD_CMD_RESERVED(4),
+ SD_CMD_RESERVED(5),
+ SD_CMD_INDEX(6) | SD_RESP_R1,
+ SD_CMD_RESERVED(7),
+ SD_CMD_RESERVED(8),
+ SD_CMD_RESERVED(9),
+ SD_CMD_RESERVED(10),
+ SD_CMD_RESERVED(11),
+ SD_CMD_RESERVED(12),
+ SD_CMD_INDEX(13) | SD_RESP_R1,
+ SD_CMD_RESERVED(14),
+ SD_CMD_RESERVED(15),
+ SD_CMD_RESERVED(16),
+ SD_CMD_RESERVED(17),
+ SD_CMD_RESERVED(18),
+ SD_CMD_RESERVED(19),
+ SD_CMD_RESERVED(20),
+ SD_CMD_RESERVED(21),
+ SD_CMD_INDEX(22) | SD_RESP_R1 | SD_DATA_READ,
+ SD_CMD_INDEX(23) | SD_RESP_R1,
+ SD_CMD_RESERVED(24),
+ SD_CMD_RESERVED(25),
+ SD_CMD_RESERVED(26),
+ SD_CMD_RESERVED(27),
+ SD_CMD_RESERVED(28),
+ SD_CMD_RESERVED(29),
+ SD_CMD_RESERVED(30),
+ SD_CMD_RESERVED(31),
+ SD_CMD_RESERVED(32),
+ SD_CMD_RESERVED(33),
+ SD_CMD_RESERVED(34),
+ SD_CMD_RESERVED(35),
+ SD_CMD_RESERVED(36),
+ SD_CMD_RESERVED(37),
+ SD_CMD_RESERVED(38),
+ SD_CMD_RESERVED(39),
+ SD_CMD_RESERVED(40),
+ SD_CMD_INDEX(41) | SD_RESP_R3,
+ SD_CMD_INDEX(42) | SD_RESP_R1,
+ SD_CMD_RESERVED(43),
+ SD_CMD_RESERVED(44),
+ SD_CMD_RESERVED(45),
+ SD_CMD_RESERVED(46),
+ SD_CMD_RESERVED(47),
+ SD_CMD_RESERVED(48),
+ SD_CMD_RESERVED(49),
+ SD_CMD_RESERVED(50),
+ SD_CMD_INDEX(51) | SD_RESP_R1 | SD_DATA_READ,
+ SD_CMD_RESERVED(52),
+ SD_CMD_RESERVED(53),
+ SD_CMD_RESERVED(54),
+ SD_CMD_RESERVED(55),
+ SD_CMD_RESERVED(56),
+ SD_CMD_RESERVED(57),
+ SD_CMD_RESERVED(58),
+ SD_CMD_RESERVED(59),
+ SD_CMD_RESERVED(60),
+ SD_CMD_RESERVED(61),
+ SD_CMD_RESERVED(62),
+ SD_CMD_RESERVED(63)
+};
+
+// The actual command indices
+#define GO_IDLE_STATE 0
+#define ALL_SEND_CID 2
+#define SEND_RELATIVE_ADDR 3
+#define SET_DSR 4
+#define IO_SET_OP_COND 5
+#define SWITCH_FUNC 6
+#define SELECT_CARD 7
+#define DESELECT_CARD 7
+#define SELECT_DESELECT_CARD 7
+#define SEND_IF_COND 8
+#define SEND_CSD 9
+#define SEND_CID 10
+#define VOLTAGE_SWITCH 11
+#define STOP_TRANSMISSION 12
+#define SEND_STATUS 13
+#define GO_INACTIVE_STATE 15
+#define SET_BLOCKLEN 16
+#define READ_SINGLE_BLOCK 17
+#define READ_MULTIPLE_BLOCK 18
+#define SEND_TUNING_BLOCK 19
+#define SPEED_CLASS_CONTROL 20
+#define SET_BLOCK_COUNT 23
+#define WRITE_BLOCK 24
+#define WRITE_MULTIPLE_BLOCK 25
+#define PROGRAM_CSD 27
+#define SET_WRITE_PROT 28
+#define CLR_WRITE_PROT 29
+#define SEND_WRITE_PROT 30
+#define ERASE_WR_BLK_START 32
+#define ERASE_WR_BLK_END 33
+#define ERASE 38
+#define LOCK_UNLOCK 42
+#define APP_CMD 55
+#define GEN_CMD 56
+
+#define IS_APP_CMD 0x80000000
+#define ACMD(a) (a | IS_APP_CMD)
+#define SET_BUS_WIDTH (6 | IS_APP_CMD)
+#define SD_STATUS (13 | IS_APP_CMD)
+#define SEND_NUM_WR_BLOCKS (22 | IS_APP_CMD)
+#define SET_WR_BLK_ERASE_COUNT (23 | IS_APP_CMD)
+#define SD_SEND_OP_COND (41 | IS_APP_CMD)
+#define SET_CLR_CARD_DETECT (42 | IS_APP_CMD)
+#define SEND_SCR (51 | IS_APP_CMD)
+
+#define SD_RESET_CMD (1 << 25)
+#define SD_RESET_DAT (1 << 26)
+#define SD_RESET_ALL (1 << 24)
+
+#define SD_GET_CLOCK_DIVIDER_FAIL 0xffffffff
+
+#define SD_BLOCK_SIZE 512
+
+CEMMCDevice::CEMMCDevice()
+: m_ullOffset(0),
+ m_hci_ver(0)
+{
+}
+
+CEMMCDevice::~CEMMCDevice(void)
+{
+}
+
+bool CEMMCDevice::Initialize(void)
+{
+ //DEBUG_LOG("CEMMCDevice::Initialize\r\n");
+ PowerOn();
+ //if (PowerOn() == false)
+ //{
+ // DEBUG_LOG("BCM2708 controller did not power on successfully");
+ // return false;
+ //}
+
+ // Check the sanity of the sd_commands and sd_acommands structures
+ assert(sizeof(sd_commands) == (64 * sizeof(u32)));
+ assert(sizeof(sd_acommands) == (64 * sizeof(u32)));
+
+ // Read the controller version
+ u32 ver = read32(EMMC_SLOTISR_VER);
+ u32 sdversion = (ver >> 16) & 0xff;
+#ifdef EMMC_DEBUG2
+ u32 vendor = ver >> 24;
+ u32 slot_status = ver & 0xff;
+ DEBUG_LOG("Vendor %x, SD version %x, slot status %x", vendor, sdversion, slot_status);
+#endif
+ m_hci_ver = sdversion;
+ if (m_hci_ver < 2)
+ {
+ DEBUG_LOG("Only SDHCI versions >= 3.0 are supported");
+ return false;
+ }
+
+ if (CardReset() != 0)
+ return false;
+
+ return true;
+}
+
+int CEMMCDevice::Read(void *pBuffer, unsigned nCount)
+{
+ if (m_ullOffset % SD_BLOCK_SIZE != 0)
+ {
+ return -1;
+ }
+ unsigned nBlock = m_ullOffset / SD_BLOCK_SIZE;
+
+ //if (m_pActLED != 0)
+ // m_pActLED->On();
+
+ DataMemBarrier();
+
+ if (DoRead((u8 *) pBuffer, nCount, nBlock) !=(int) nCount)
+ {
+ DataMemBarrier();
+
+ //if (m_pActLED != 0)
+ // m_pActLED->Off();
+
+ return -1;
+ }
+
+ DataMemBarrier();
+
+ //if (m_pActLED != 0)
+ // m_pActLED->Off();
+
+ return nCount;
+}
+
+int CEMMCDevice::Write(const void *pBuffer, unsigned nCount)
+{
+ if (m_ullOffset % SD_BLOCK_SIZE != 0)
+ {
+ return -1;
+ }
+ unsigned nBlock = m_ullOffset / SD_BLOCK_SIZE;
+
+ //if (m_pActLED != 0)
+ // m_pActLED->On();
+
+ DataMemBarrier();
+
+ if (DoWrite((u8 *) pBuffer, nCount, nBlock) !=(int) nCount)
+ {
+ DataMemBarrier();
+
+ //if (m_pActLED != 0)
+ // m_pActLED->Off();
+
+ return -1;
+ }
+
+ DataMemBarrier();
+
+ //if (m_pActLED != 0)
+ // m_pActLED->Off();
+
+ return nCount;
+}
+
+unsigned long long CEMMCDevice::Seek(unsigned long long ullOffset)
+{
+ m_ullOffset = ullOffset;
+
+ return m_ullOffset;
+}
+
+bool CEMMCDevice::PowerOn(void)
+{
+ rpi_mailbox_property_t *buf;
+ RPI_PropertyInit();
+ RPI_PropertyAddTag(TAG_SET_POWER_STATE, DEVICE_ID_SD_CARD, POWER_STATE_ON | POWER_STATE_WAIT);
+ RPI_PropertyProcess();
+
+ //RPI_PropertyInit();
+ //RPI_PropertyAddTag(TAG_GET_POWER_STATE);
+ //RPI_PropertyProcess();
+ //buf = RPI_PropertyGet(TAG_GET_POWER_STATE);
+ buf = RPI_PropertyGet(TAG_SET_POWER_STATE);
+ if (buf)
+ {
+ u32 state = buf->data.buffer_32[0];
+
+ //DEBUG_LOG("state = %x\r\n, state");
+ //DEBUG_LOG("state = %x %x %x\r\n", buf->data.buffer_32[0], buf->data.buffer_32[1], buf->data.buffer_32[2]);
+
+ if ((state & POWER_STATE_DEVICE_DOESNT_EXIST) || ((state & 1) == POWER_STATE_OFF))
+ {
+ //DEBUG_LOG("Device did not power on successfully\r\n");
+ return false;
+ }
+ }
+ else
+ {
+ //DEBUG_LOG("RPI_PropertyGet(TAG_GET_POWER_STATE) failed\r\n");
+ return false;
+ }
+ return true;
+}
+
+void CEMMCDevice::PowerOff(void)
+{
+ // Power off the SD card
+ u32 control0 = read32(EMMC_CONTROL0);
+ control0 &= ~(1 << 8); // Set SD Bus Power bit off in Power Control Register
+ write32(EMMC_CONTROL0, control0);
+}
+
+// Set the clock dividers to generate a target value
+u32 CEMMCDevice::GetClockDivider(u32 base_clock, u32 target_rate)
+{
+ // TODO: implement use of preset value registers
+
+ u32 targetted_divisor = 1;
+ if (target_rate <= base_clock)
+ {
+ targetted_divisor = base_clock / target_rate;
+ if (base_clock % target_rate)
+ {
+ targetted_divisor--;
+ }
+ }
+
+ // Decide on the clock mode to use
+ // Currently only 10-bit divided clock mode is supported
+
+ if (m_hci_ver >= 2)
+ {
+ // HCI version 3 or greater supports 10-bit divided clock mode
+ // This requires a power-of-two divider
+
+ // Find the first bit set
+ int divisor = -1;
+ for(int first_bit = 31; first_bit >= 0; first_bit--)
+ {
+ u32 bit_test =(1 << first_bit);
+ if (targetted_divisor & bit_test)
+ {
+ divisor = first_bit;
+ targetted_divisor &= ~bit_test;
+ if (targetted_divisor)
+ {
+ // The divisor is not a power-of-two, increase it
+ divisor++;
+ }
+
+ break;
+ }
+ }
+
+ if (divisor == -1)
+ {
+ divisor = 31;
+ }
+ if (divisor >= 32)
+ {
+ divisor = 31;
+ }
+
+ if (divisor != 0)
+ {
+ divisor =(1 <<(divisor - 1));
+ }
+
+ if (divisor >= 0x400)
+ {
+ divisor = 0x3ff;
+ }
+
+ u32 freq_select = divisor & 0xff;
+ u32 upper_bits =(divisor >> 8) & 0x3;
+ u32 ret =(freq_select << 8) |(upper_bits << 6) |(0 << 5);
+
+#ifdef EMMC_DEBUG2
+ int denominator = 1;
+ if (divisor != 0)
+ {
+ denominator = divisor * 2;
+ }
+ int actual_clock = base_clock / denominator;
+ DEBUG_LOG("base_clock: %d, target_rate: %d, divisor: %08x, actual_clock: %d, ret: %08x", base_clock, target_rate, divisor, actual_clock, ret);
+#endif
+
+ return ret;
+ }
+ else
+ {
+ DEBUG_LOG("Unsupported host version");
+
+ return SD_GET_CLOCK_DIVIDER_FAIL;
+ }
+}
+
+// Switch the clock rate whilst running
+int CEMMCDevice::SwitchClockRate(u32 base_clock, u32 target_rate)
+{
+ // Decide on an appropriate divider
+ u32 divider = GetClockDivider(base_clock, target_rate);
+ if (divider == SD_GET_CLOCK_DIVIDER_FAIL)
+ {
+ DEBUG_LOG("Couldn't get a valid divider for target rate %d Hz\r\n", target_rate);
+
+ return -1;
+ }
+
+ // Wait for the command inhibit(CMD and DAT) bits to clear
+ while(read32(EMMC_STATUS) & 3)
+ {
+ delay_us(1000);
+ }
+
+ // Set the SD clock off
+ u32 control1 = read32(EMMC_CONTROL1);
+ control1 &= ~(1 << 2);
+ write32(EMMC_CONTROL1, control1);
+ delay_us(2000);
+
+ // Write the new divider
+ control1 &= ~0xffe0; // Clear old setting + clock generator select
+ control1 |= divider;
+ write32(EMMC_CONTROL1, control1);
+ delay_us(2000);
+
+ // Enable the SD clock
+ control1 |=(1 << 2);
+ write32(EMMC_CONTROL1, control1);
+ delay_us(2000);
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Successfully set clock rate to %d Hz\r\n", target_rate);
+#endif
+
+ return 0;
+}
+
+int CEMMCDevice::ResetCmd(void)
+{
+ u32 control1 = read32(EMMC_CONTROL1);
+ control1 |= SD_RESET_CMD;
+ write32(EMMC_CONTROL1, control1);
+
+ if (TimeoutWait(EMMC_CONTROL1, SD_RESET_CMD, 0, 1000000) < 0)
+ {
+ DEBUG_LOG("CMD line did not reset properly");
+
+ return -1;
+ }
+ return 0;
+}
+
+int CEMMCDevice::ResetDat(void)
+{
+ u32 control1 = read32(EMMC_CONTROL1);
+ control1 |= SD_RESET_DAT;
+ write32(EMMC_CONTROL1, control1);
+
+ if (TimeoutWait(EMMC_CONTROL1, SD_RESET_DAT, 0, 1000000) < 0)
+ {
+ DEBUG_LOG("DAT line did not reset properly");
+
+ return -1;
+ }
+ return 0;
+}
+
+void CEMMCDevice::IssueCommandInt(u32 cmd_reg, u32 argument, int timeout)
+{
+ m_last_cmd_reg = cmd_reg;
+ m_last_cmd_success = 0;
+
+ // This is as per HCSS 3.7.1.1/3.7.2.2
+
+#ifdef EMMC_POLL_STATUS_REG
+ // Check Command Inhibit
+ while(read32(EMMC_STATUS) & 1)
+ {
+ delay_us(1000);
+ }
+
+ // Is the command with busy?
+ if ((cmd_reg & SD_CMD_RSPNS_TYPE_MASK) == SD_CMD_RSPNS_TYPE_48B)
+ {
+ // With busy
+
+ // Is is an abort command?
+ if ((cmd_reg & SD_CMD_TYPE_MASK) != SD_CMD_TYPE_ABORT)
+ {
+ // Not an abort command
+
+ // Wait for the data line to be free
+ while(read32(EMMC_STATUS) & 2)
+ {
+ delay_us(1000);
+ }
+ }
+ }
+#endif
+
+ // Set block size and block count
+ // For now, block size = 512 bytes, block count = 1,
+ if (m_blocks_to_transfer > 0xffff)
+ {
+ DEBUG_LOG("blocks_to_transfer too great(%d)\r\n", m_blocks_to_transfer);
+ m_last_cmd_success = 0;
+ return;
+ }
+ u32 blksizecnt = m_block_size |(m_blocks_to_transfer << 16);
+ write32(EMMC_BLKSIZECNT, blksizecnt);
+
+ // Set argument 1 reg
+ write32(EMMC_ARG1, argument);
+
+ // Set command reg
+ write32(EMMC_CMDTM, cmd_reg);
+
+ //delay_us(2000);
+
+ // Wait for command complete interrupt
+ TimeoutWait(EMMC_INTERRUPT, 0x8001, 1, timeout);
+ u32 irpts = read32(EMMC_INTERRUPT);
+
+ // Clear command complete status
+ write32(EMMC_INTERRUPT, 0xffff0001);
+
+ // Test for errors
+ if ((irpts & 0xffff0001) != 1)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Error occured whilst waiting for command complete interrupt");
+#endif
+ m_last_error = irpts & 0xffff0000;
+ m_last_interrupt = irpts;
+
+ return;
+ }
+
+ //delay_us(2000);
+
+ // Get response data
+ switch(cmd_reg & SD_CMD_RSPNS_TYPE_MASK)
+ {
+ case SD_CMD_RSPNS_TYPE_48:
+ case SD_CMD_RSPNS_TYPE_48B:
+ m_last_r0 = read32(EMMC_RESP0);
+ break;
+
+ case SD_CMD_RSPNS_TYPE_136:
+ m_last_r0 = read32(EMMC_RESP0);
+ m_last_r1 = read32(EMMC_RESP1);
+ m_last_r2 = read32(EMMC_RESP2);
+ m_last_r3 = read32(EMMC_RESP3);
+ break;
+ }
+
+ // If with data, wait for the appropriate interrupt
+ if (cmd_reg & SD_CMD_ISDATA)
+ {
+ u32 wr_irpt;
+ int is_write = 0;
+ if (cmd_reg & SD_CMD_DAT_DIR_CH)
+ {
+ wr_irpt =(1 << 5); // read
+ }
+ else
+ {
+ is_write = 1;
+ wr_irpt =(1 << 4); // write
+ }
+
+#ifdef EMMC_DEBUG2
+ if (m_blocks_to_transfer > 1)
+ {
+ DEBUG_LOG("Multi block transfer");
+ }
+#endif
+ TimeoutWait(EMMC_INTERRUPT, wr_irpt | 0x8000, 1, timeout);
+ irpts = read32(EMMC_INTERRUPT);
+ write32(EMMC_INTERRUPT, 0xffff0000 | wr_irpt);
+
+ if ((irpts &(0xffff0000 | wr_irpt)) != wr_irpt)
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("Error occured whilst waiting for data ready interrupt");
+#endif
+ m_last_error = irpts & 0xffff0000;
+ m_last_interrupt = irpts;
+
+ return;
+ }
+
+ // Transfer the block
+ assert(m_block_size <= 1024); // internal FIFO size of EMMC
+ size_t length = m_block_size * m_blocks_to_transfer;
+
+ assert(((u32) m_buf & 3) == 0);
+ assert((length & 3) == 0);
+
+ u32 *pData =(u32 *) m_buf;
+ if (is_write)
+ {
+ for(; length > 0; length -= 4)
+ {
+ write32(EMMC_DATA, *pData++);
+ }
+ }
+ else
+ {
+ for(; length > 0; length -= 4)
+ {
+ *pData++ = read32(EMMC_DATA);
+ }
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Block transfer complete");
+#endif
+ }
+
+ // Wait for transfer complete(set if read/write transfer or with busy)
+ if ( (cmd_reg & SD_CMD_RSPNS_TYPE_MASK) == SD_CMD_RSPNS_TYPE_48B
+ ||(cmd_reg & SD_CMD_ISDATA))
+ {
+#ifdef EMMC_POLL_STATUS_REG
+ // First check command inhibit(DAT) is not already 0
+ if ((read32(EMMC_STATUS) & 2) == 0)
+ {
+ write32(EMMC_INTERRUPT, 0xffff0002);
+ }
+ else
+#endif
+ {
+ TimeoutWait(EMMC_INTERRUPT, 0x8002, 1, timeout);
+ irpts = read32(EMMC_INTERRUPT);
+ write32(EMMC_INTERRUPT, 0xffff0002);
+
+ // Handle the case where both data timeout and transfer complete
+ // are set - transfer complete overrides data timeout: HCSS 2.2.17
+ if ( ((irpts & 0xffff0002) != 2)
+ &&((irpts & 0xffff0002) != 0x100002))
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("Error occured whilst waiting for transfer complete interrupt");
+#endif
+ m_last_error = irpts & 0xffff0000;
+ m_last_interrupt = irpts;
+
+ return;
+ }
+
+ write32(EMMC_INTERRUPT, 0xffff0002);
+ }
+ }
+
+ // Return success
+ m_last_cmd_success = 1;
+}
+
+void CEMMCDevice::HandleCardInterrupt(void)
+{
+ // Handle a card interrupt
+
+#ifdef EMMC_DEBUG2
+ u32 status = read32(EMMC_STATUS);
+
+ DEBUG_LOG("Card interrupt");
+ DEBUG_LOG("controller status: %08x\r\n", status);
+#endif
+
+ // Get the card status
+ if (m_card_rca)
+ {
+ IssueCommandInt(sd_commands[SEND_STATUS], m_card_rca << 16, 500000);
+ if (FAIL)
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("Unable to get card status");
+#endif
+ }
+ else
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("card status: %08x\r\n", m_last_r0);
+#endif
+ }
+ }
+ else
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("no card currently selected");
+#endif
+ }
+}
+
+void CEMMCDevice::HandleInterrupts(void)
+{
+ u32 irpts = read32(EMMC_INTERRUPT);
+ u32 reset_mask = 0;
+
+ if (irpts & SD_COMMAND_COMPLETE)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious command complete interrupt");
+#endif
+ reset_mask |= SD_COMMAND_COMPLETE;
+ }
+
+ if (irpts & SD_TRANSFER_COMPLETE)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious transfer complete interrupt");
+#endif
+ reset_mask |= SD_TRANSFER_COMPLETE;
+ }
+
+ if (irpts & SD_BLOCK_GAP_EVENT)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious block gap event interrupt");
+#endif
+ reset_mask |= SD_BLOCK_GAP_EVENT;
+ }
+
+ if (irpts & SD_DMA_INTERRUPT)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious DMA interrupt");
+#endif
+ reset_mask |= SD_DMA_INTERRUPT;
+ }
+
+ if (irpts & SD_BUFFER_WRITE_READY)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious buffer write ready interrupt");
+#endif
+ reset_mask |= SD_BUFFER_WRITE_READY;
+ ResetDat();
+ }
+
+ if (irpts & SD_BUFFER_READ_READY)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious buffer read ready interrupt");
+#endif
+ reset_mask |= SD_BUFFER_READ_READY;
+ ResetDat();
+ }
+
+ if (irpts & SD_CARD_INSERTION)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("card insertion detected");
+#endif
+ reset_mask |= SD_CARD_INSERTION;
+ }
+
+ if (irpts & SD_CARD_REMOVAL)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("card removal detected");
+#endif
+ reset_mask |= SD_CARD_REMOVAL;
+ m_card_removal = 1;
+ }
+
+ if (irpts & SD_CARD_INTERRUPT)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("card interrupt detected");
+#endif
+ HandleCardInterrupt();
+ reset_mask |= SD_CARD_INTERRUPT;
+ }
+
+ if (irpts & 0x8000)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("spurious error interrupt: %08x\r\n", irpts);
+#endif
+ reset_mask |= 0xffff0000;
+ }
+
+ write32(EMMC_INTERRUPT, reset_mask);
+}
+
+bool CEMMCDevice::IssueCommand(u32 command, u32 argument, int timeout)
+{
+ // First, handle any pending interrupts
+ HandleInterrupts();
+
+ // Stop the command issue if it was the card remove interrupt that was handled
+ if (m_card_removal)
+ {
+ m_last_cmd_success = 0;
+ return false;
+ }
+
+ // Now run the appropriate commands by calling IssueCommandInt()
+ if (command & IS_APP_CMD)
+ {
+ command &= 0xff;
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Issuing command ACMD%d\r\n", command);
+#endif
+
+ if (sd_acommands[command] == SD_CMD_RESERVED(0))
+ {
+ DEBUG_LOG("Invalid command ACMD%d\r\n", command);
+ m_last_cmd_success = 0;
+
+ return false;
+ }
+ m_last_cmd = APP_CMD;
+
+ u32 rca = 0;
+ if (m_card_rca)
+ {
+ rca = m_card_rca << 16;
+ }
+ IssueCommandInt(sd_commands[APP_CMD], rca, timeout);
+ if (m_last_cmd_success)
+ {
+ m_last_cmd = command | IS_APP_CMD;
+ IssueCommandInt(sd_acommands[command], argument, timeout);
+ }
+ }
+ else
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Issuing command CMD%d\r\n", command);
+#endif
+
+ if (sd_commands[command] == SD_CMD_RESERVED(0))
+ {
+ DEBUG_LOG("Invalid command CMD%d\r\n", command);
+ m_last_cmd_success = 0;
+
+ return false;
+ }
+
+ m_last_cmd = command;
+ IssueCommandInt(sd_commands[command], argument, timeout);
+ }
+
+#ifdef EMMC_DEBUG2
+ if (FAIL)
+ {
+ DEBUG_LOG("Error issuing %s%u(intr %08x)\r\n", m_last_cmd & IS_APP_CMD ? "ACMD" : "CMD\r\n", m_last_cmd & 0xff, m_last_interrupt);
+
+ if (m_last_error == 0)
+ {
+ DEBUG_LOG("TIMEOUT");
+ }
+ else
+ {
+ for(int i = 0; i < SD_ERR_RSVD; i++)
+ {
+ if (m_last_error &(1 <<(i + 16)))
+ {
+ DEBUG_LOG(err_irpts[i]);
+ }
+ }
+ }
+ }
+ else
+ {
+ DEBUG_LOG("command completed successfully");
+ }
+#endif
+
+ return m_last_cmd_success;
+}
+
+int CEMMCDevice::CardReset(void)
+{
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Resetting controller");
+#endif
+
+ u32 control1 = read32(EMMC_CONTROL1);
+ control1 |=(1 << 24);
+ // Disable clock
+ control1 &= ~(1 << 2);
+ control1 &= ~(1 << 0);
+ write32(EMMC_CONTROL1, control1);
+ if (TimeoutWait(EMMC_CONTROL1, 7 << 24, 0, 1000000) < 0)
+ {
+ DEBUG_LOG("Controller did not reset properly");
+
+ return -1;
+ }
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("control0: %08x, control1: %08x, control2: %08x\r\n",
+ read32(EMMC_CONTROL0), read32(EMMC_CONTROL1), read32(EMMC_CONTROL2));
+#endif
+
+ // Check for a valid card
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("checking for an inserted card");
+#endif
+ TimeoutWait(EMMC_STATUS, 1 << 16, 1, 500000);
+ u32 status_reg = read32(EMMC_STATUS);
+ if ((status_reg &(1 << 16)) == 0)
+ {
+ DEBUG_LOG("no card inserted");
+
+ return -1;
+ }
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("status: %08x\r\n", status_reg);
+#endif
+
+ // Clear control2
+ write32(EMMC_CONTROL2, 0);
+
+ // Get the base clock rate
+ u32 base_clock = get_clock_rate(CLOCK_ID_EMMC);
+ if (base_clock == 0)
+ {
+ DEBUG_LOG("assuming clock rate to be 100MHz");
+ base_clock = 100000000;
+ }
+
+ // Set clock rate to something slow
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("setting clock rate");
+#endif
+ control1 = read32(EMMC_CONTROL1);
+ control1 |= 1; // enable clock
+
+ // Set to identification frequency(400 kHz)
+ u32 f_id = GetClockDivider(base_clock, SD_CLOCK_ID);
+ if (f_id == SD_GET_CLOCK_DIVIDER_FAIL)
+ {
+ DEBUG_LOG("unable to get a valid clock divider for ID frequency");
+
+ return -1;
+ }
+ control1 |= f_id;
+
+ // was not masked out and or'd with(7 << 16) in original driver
+ control1 &= ~(0xF << 16);
+ control1 |=(11 << 16); // data timeout = TMCLK * 2^24
+
+ write32(EMMC_CONTROL1, control1);
+
+ if (TimeoutWait(EMMC_CONTROL1, 2, 1, 1000000) < 0)
+ {
+ DEBUG_LOG("Clock did not stabilise within 1 second");
+
+ return -1;
+ }
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("control0: %08x, control1: %08x\r\n",
+ read32(EMMC_CONTROL0), read32(EMMC_CONTROL1));
+#endif
+
+ // Enable the SD clock
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("enabling SD clock");
+#endif
+ delay_us(2000);
+ control1 = read32(EMMC_CONTROL1);
+ control1 |= 4;
+ write32(EMMC_CONTROL1, control1);
+ delay_us(2000);
+
+ // Mask off sending interrupts to the ARM
+ write32(EMMC_IRPT_EN, 0);
+ // Reset interrupts
+ write32(EMMC_INTERRUPT, 0xffffffff);
+ // Have all interrupts sent to the INTERRUPT register
+ u32 irpt_mask = 0xffffffff &(~SD_CARD_INTERRUPT);
+#ifdef SD_CARD_INTERRUPTS
+ irpt_mask |= SD_CARD_INTERRUPT;
+#endif
+ write32(EMMC_IRPT_MASK, irpt_mask);
+
+ delay_us(2000);
+
+ // >> Prepare the device structure
+ m_device_id[0] = 0;
+ m_device_id[1] = 0;
+ m_device_id[2] = 0;
+ m_device_id[3] = 0;
+
+ m_card_supports_sdhc = 0;
+ m_card_supports_18v = 0;
+ m_card_ocr = 0;
+ m_card_rca = 0;
+ m_last_interrupt = 0;
+ m_last_error = 0;
+
+ m_failed_voltage_switch = 0;
+
+ m_last_cmd_reg = 0;
+ m_last_cmd = 0;
+ m_last_cmd_success = 0;
+ m_last_r0 = 0;
+ m_last_r1 = 0;
+ m_last_r2 = 0;
+ m_last_r3 = 0;
+
+ m_buf = 0;
+ m_blocks_to_transfer = 0;
+ m_block_size = 0;
+ m_card_removal = 0;
+ m_base_clock = 0;
+ // << Prepare the device structure
+
+ m_base_clock = base_clock;
+
+ // Send CMD0 to the card(reset to idle state)
+ if (!IssueCommand(GO_IDLE_STATE, 0))
+ {
+ DEBUG_LOG("no CMD0 response");
+
+ return -1;
+ }
+
+ // Send CMD8 to the card
+ // Voltage supplied = 0x1 = 2.7-3.6V(standard)
+ // Check pattern = 10101010b(as per PLSS 4.3.13) = 0xAA
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Note a timeout error on the following command(CMD8) is normal and expected if the SD card version is less than 2.0");
+#endif
+ IssueCommand(SEND_IF_COND, 0x1aa);
+ int v2_later = 0;
+ if (TIMEOUT)
+ {
+ v2_later = 0;
+ }
+ else if (CMD_TIMEOUT)
+ {
+ if (ResetCmd() == -1)
+ {
+ return -1;
+ }
+ write32(EMMC_INTERRUPT, SD_ERR_MASK_CMD_TIMEOUT);
+ v2_later = 0;
+ }
+ else if (FAIL)
+ {
+ DEBUG_LOG("failure sending CMD8(%08x)\r\n", m_last_interrupt);
+
+ return -1;
+ }
+ else
+ {
+ if ((m_last_r0 & 0xfff) != 0x1aa)
+ {
+ DEBUG_LOG("unusable card");
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("CMD8 response %08x\r\n", m_last_r0);
+#endif
+
+ return -1;
+ }
+ else
+ {
+ v2_later = 1;
+ }
+ }
+
+ // Here we are supposed to check the response to CMD5(HCSS 3.6)
+ // It only returns if the card is a SDIO card
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Note that a timeout error on the following command(CMD5) is normal and expected if the card is not a SDIO card.");
+#endif
+ IssueCommand(IO_SET_OP_COND, 0, 10000);
+ if (!TIMEOUT)
+ {
+ if (CMD_TIMEOUT)
+ {
+ if (ResetCmd() == -1)
+ {
+ return -1;
+ }
+
+ write32(EMMC_INTERRUPT, SD_ERR_MASK_CMD_TIMEOUT);
+ }
+ else
+ {
+ DEBUG_LOG("SDIO card detected - not currently supported");
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("CMD5 returned %08x\r\n", m_last_r0);
+#endif
+
+ return -1;
+ }
+ }
+
+ // Call an inquiry ACMD41(voltage window = 0) to get the OCR
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("sending inquiry ACMD41");
+#endif
+ if (!IssueCommand(ACMD(41), 0))
+ {
+ DEBUG_LOG("Inquiry ACMD41 failed");
+ return -1;
+ }
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("inquiry ACMD41 returned %08x\r\n", m_last_r0);
+#endif
+
+ // Call initialization ACMD41
+ int card_is_busy = 1;
+ while(card_is_busy)
+ {
+ u32 v2_flags = 0;
+ if (v2_later)
+ {
+ // Set SDHC support
+ v2_flags |=(1 << 30);
+
+ // Set 1.8v support
+#ifdef SD_1_8V_SUPPORT
+ if (!m_failed_voltage_switch)
+ {
+ v2_flags |=(1 << 24);
+ }
+#endif
+#ifdef SDXC_MAXIMUM_PERFORMANCE
+ // Enable SDXC maximum performance
+ v2_flags |=(1 << 28);
+#endif
+ }
+
+ if (!IssueCommand(ACMD(41), 0x00ff8000 | v2_flags))
+ {
+ DEBUG_LOG("Error issuing ACMD41");
+
+ return -1;
+ }
+
+ if ((m_last_r0 >> 31) & 1)
+ {
+ // Initialization is complete
+ m_card_ocr =(m_last_r0 >> 8) & 0xffff;
+ m_card_supports_sdhc =(m_last_r0 >> 30) & 0x1;
+#ifdef SD_1_8V_SUPPORT
+ if (!m_failed_voltage_switch)
+ {
+ m_card_supports_18v =(m_last_r0 >> 24) & 0x1;
+ }
+#endif
+
+ card_is_busy = 0;
+ }
+ else
+ {
+ // Card is still busy
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Card is busy, retrying");
+#endif
+ delay_us(500000);
+ }
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("card identified: OCR: %04x, 1.8v support: %d, SDHC support: %d\r\n", m_card_ocr, m_card_supports_18v, m_card_supports_sdhc);
+#endif
+
+ // At this point, we know the card is definitely an SD card, so will definitely
+ // support SDR12 mode which runs at 25 MHz
+ SwitchClockRate(base_clock, SD_CLOCK_NORMAL);
+
+ // A small wait before the voltage switch
+ delay_us(5000);
+
+ // Switch to 1.8V mode if possible
+ if (m_card_supports_18v)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("switching to 1.8V mode");
+#endif
+ // As per HCSS 3.6.1
+
+ // Send VOLTAGE_SWITCH
+ if (!IssueCommand(VOLTAGE_SWITCH, 0))
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("error issuing VOLTAGE_SWITCH");
+#endif
+ m_failed_voltage_switch = 1;
+ PowerOff();
+
+ return CardReset();
+ }
+
+ // Disable SD clock
+ control1 = read32(EMMC_CONTROL1);
+ control1 &= ~(1 << 2);
+ write32(EMMC_CONTROL1, control1);
+
+ // Check DAT[3:0]
+ status_reg = read32(EMMC_STATUS);
+ u32 dat30 =(status_reg >> 20) & 0xf;
+ if (dat30 != 0)
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("DAT[3:0] did not settle to 0");
+#endif
+ m_failed_voltage_switch = 1;
+ PowerOff();
+
+ return CardReset();
+ }
+
+ // Set 1.8V signal enable to 1
+ u32 control0 = read32(EMMC_CONTROL0);
+ control0 |=(1 << 8);
+ write32(EMMC_CONTROL0, control0);
+
+ // Wait 5 ms
+ delay_us(5000);
+
+ // Check the 1.8V signal enable is set
+ control0 = read32(EMMC_CONTROL0);
+ if (((control0 >> 8) & 1) == 0)
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("controller did not keep 1.8V signal enable high");
+#endif
+ m_failed_voltage_switch = 1;
+ PowerOff();
+
+ return CardReset();
+ }
+
+ // Re-enable the SD clock
+ control1 = read32(EMMC_CONTROL1);
+ control1 |=(1 << 2);
+ write32(EMMC_CONTROL1, control1);
+
+ delay_us(10000);
+
+ // Check DAT[3:0]
+ status_reg = read32(EMMC_STATUS);
+ dat30 =(status_reg >> 20) & 0xf;
+ if (dat30 != 0xf)
+ {
+#ifdef EMMC_DEBUG
+ DEBUG_LOG("DAT[3:0] did not settle to 1111b(%01x)\r\n", dat30);
+#endif
+ m_failed_voltage_switch = 1;
+ PowerOff();
+
+ return CardReset();
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("voltage switch complete");
+#endif
+ }
+
+ // Send CMD2 to get the cards CID
+ if (!IssueCommand(ALL_SEND_CID, 0))
+ {
+ DEBUG_LOG("error sending ALL_SEND_CID");
+
+ return -1;
+ }
+ m_device_id[0] = m_last_r0;
+ m_device_id[1] = m_last_r1;
+ m_device_id[2] = m_last_r2;
+ m_device_id[3] = m_last_r3;
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Card CID: %08x%08x%08x%08x",
+ m_device_id[3], m_device_id[2], m_device_id[1], m_device_id[0]);
+#endif
+
+ // Send CMD3 to enter the data state
+ if (!IssueCommand(SEND_RELATIVE_ADDR, 0))
+ {
+ DEBUG_LOG("error sending SEND_RELATIVE_ADDR");
+
+ return -1;
+ }
+
+ u32 cmd3_resp = m_last_r0;
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("CMD3 response: %08x\r\n", cmd3_resp);
+#endif
+
+ m_card_rca =(cmd3_resp >> 16) & 0xffff;
+ u32 crc_error =(cmd3_resp >> 15) & 0x1;
+ u32 illegal_cmd =(cmd3_resp >> 14) & 0x1;
+ u32 error =(cmd3_resp >> 13) & 0x1;
+ u32 status =(cmd3_resp >> 9) & 0xf;
+ u32 ready =(cmd3_resp >> 8) & 0x1;
+
+ if (crc_error)
+ {
+ DEBUG_LOG("CRC error");
+
+ return -1;
+ }
+
+ if (illegal_cmd)
+ {
+ DEBUG_LOG("illegal command");
+
+ return -1;
+ }
+
+ if (error)
+ {
+ DEBUG_LOG("generic error");
+
+ return -1;
+ }
+
+ if (!ready)
+ {
+ DEBUG_LOG("not ready for data");
+
+ return -1;
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("RCA: %04x\r\n", m_card_rca);
+#endif
+
+ // Now select the card(toggles it to transfer state)
+ if (!IssueCommand(SELECT_CARD, m_card_rca << 16))
+ {
+ DEBUG_LOG("error sending CMD7");
+
+ return -1;
+ }
+
+ u32 cmd7_resp = m_last_r0;
+ status =(cmd7_resp >> 9) & 0xf;
+
+ if ((status != 3) &&(status != 4))
+ {
+ DEBUG_LOG("Invalid status(%d)\r\n", status);
+
+ return -1;
+ }
+
+ // If not an SDHC card, ensure BLOCKLEN is 512 bytes
+ if (!m_card_supports_sdhc)
+ {
+ if (!IssueCommand(SET_BLOCKLEN, SD_BLOCK_SIZE))
+ {
+ DEBUG_LOG("Error sending SET_BLOCKLEN");
+
+ return -1;
+ }
+ }
+ u32 controller_block_size = read32(EMMC_BLKSIZECNT);
+ controller_block_size &=(~0xfff);
+ controller_block_size |= 0x200;
+ write32(EMMC_BLKSIZECNT, controller_block_size);
+
+ // Get the cards SCR register
+ m_buf = &m_SCR.scr[0];
+ m_block_size = 8;
+ m_blocks_to_transfer = 1;
+ IssueCommand(SEND_SCR, 0);
+ m_block_size = SD_BLOCK_SIZE;
+ if (FAIL)
+ {
+ DEBUG_LOG("Error sending SEND_SCR");
+
+ return -1;
+ }
+
+ // Determine card version
+ // Note that the SCR is big-endian
+ u32 scr0 = __builtin_bswap32(m_SCR.scr[0]);
+ m_SCR.sd_version = SD_VER_UNKNOWN;
+ u32 sd_spec =(scr0 >>(56 - 32)) & 0xf;
+ u32 sd_spec3 =(scr0 >>(47 - 32)) & 0x1;
+ u32 sd_spec4 =(scr0 >>(42 - 32)) & 0x1;
+ m_SCR.sd_bus_widths =(scr0 >>(48 - 32)) & 0xf;
+ if (sd_spec == 0)
+ {
+ m_SCR.sd_version = SD_VER_1;
+ }
+ else if (sd_spec == 1)
+ {
+ m_SCR.sd_version = SD_VER_1_1;
+ }
+ else if (sd_spec == 2)
+ {
+ if (sd_spec3 == 0)
+ {
+ m_SCR.sd_version = SD_VER_2;
+ }
+ else if (sd_spec3 == 1)
+ {
+ if (sd_spec4 == 0)
+ {
+ m_SCR.sd_version = SD_VER_3;
+ }
+ else if (sd_spec4 == 1)
+ {
+ m_SCR.sd_version = SD_VER_4;
+ }
+ }
+ }
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("SCR[0]: %08x, SCR[1]: %08x\r\n", m_SCR.scr[0], m_SCR.scr[1]);;
+ DEBUG_LOG("SCR: %08x%08x\r\n", __builtin_bswap32(m_SCR.scr[0]), __builtin_bswap32(m_SCR.scr[1]));
+ DEBUG_LOG("SCR: version %s, bus_widths %01x\r\n", sd_versions[m_SCR.sd_version], m_SCR.sd_bus_widths);
+#endif
+
+ if (m_SCR.sd_bus_widths & 4)
+ {
+ // Set 4-bit transfer mode(ACMD6)
+ // See HCSS 3.4 for the algorithm
+#ifdef SD_4BIT_DATA
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Switching to 4-bit data mode");
+#endif
+
+ // Disable card interrupt in host
+ u32 old_irpt_mask = read32(EMMC_IRPT_MASK);
+ u32 new_iprt_mask = old_irpt_mask & ~(1 << 8);
+ write32(EMMC_IRPT_MASK, new_iprt_mask);
+
+ // Send ACMD6 to change the card's bit mode
+ if (!IssueCommand(SET_BUS_WIDTH, 2))
+ {
+ DEBUG_LOG("Switch to 4-bit data mode failed");
+ }
+ else
+ {
+ // Change bit mode for Host
+ u32 control0 = read32(EMMC_CONTROL0);
+ control0 |= 0x2;
+ write32(EMMC_CONTROL0, control0);
+
+ // Re-enable card interrupt in host
+ write32(EMMC_IRPT_MASK, old_irpt_mask);
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("switch to 4-bit complete");
+#endif
+ }
+#endif
+ }
+
+ DEBUG_LOG("Found a valid version %s SD card\r\n", sd_versions[m_SCR.sd_version]);
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("setup successful(status %d)\r\n", status);
+#endif
+
+ // Reset interrupt register
+ write32(EMMC_INTERRUPT, 0xffffffff);
+
+ return 0;
+}
+
+int CEMMCDevice::EnsureDataMode(void)
+{
+ if (m_card_rca == 0)
+ {
+ // Try again to initialise the card
+ int ret = CardReset();
+ if (ret != 0)
+ {
+ return ret;
+ }
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("EnsureDataMode() obtaining status register for card_rca %08x: \r\n", m_card_rca);
+#endif
+
+ if (!IssueCommand(SEND_STATUS, m_card_rca << 16))
+ {
+ DEBUG_LOG("EnsureDataMode() error sending CMD13");
+ m_card_rca = 0;
+
+ return -1;
+ }
+
+ u32 status = m_last_r0;
+ u32 cur_state =(status >> 9) & 0xf;
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("status %d\r\n", cur_state);
+#endif
+ if (cur_state == 3)
+ {
+ // Currently in the stand-by state - select it
+ if (!IssueCommand(SELECT_CARD, m_card_rca << 16))
+ {
+ DEBUG_LOG("EnsureDataMode() no response from CMD17");
+ m_card_rca = 0;
+
+ return -1;
+ }
+ }
+ else if (cur_state == 5)
+ {
+ // In the data transfer state - cancel the transmission
+ if (!IssueCommand(STOP_TRANSMISSION, 0))
+ {
+ DEBUG_LOG("EnsureDataMode() no response from CMD12");
+ m_card_rca = 0;
+
+ return -1;
+ }
+
+ // Reset the data circuit
+ ResetDat();
+ }
+ else if (cur_state != 4)
+ {
+ // Not in the transfer state - re-initialise
+ int ret = CardReset();
+ if (ret != 0)
+ {
+ return ret;
+ }
+ }
+
+ // Check again that we're now in the correct mode
+ if (cur_state != 4)
+ {
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("EnsureDataMode() rechecking status: ");
+#endif
+ if (!IssueCommand(SEND_STATUS, m_card_rca << 16))
+ {
+ DEBUG_LOG("EnsureDataMode() no response from CMD13");
+ m_card_rca = 0;
+
+ return -1;
+ }
+ status = m_last_r0;
+ cur_state =(status >> 9) & 0xf;
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("status %d\r\n", cur_state);
+#endif
+
+ if (cur_state != 4)
+ {
+ DEBUG_LOG("unable to initialise SD card to data mode(state %d)\r\n", cur_state);
+ m_card_rca = 0;
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int CEMMCDevice::DoDataCommand(int is_write, u8 *buf, size_t buf_size, u32 block_no)
+{
+ // PLSS table 4.20 - SDSC cards use byte addresses rather than block addresses
+ if (!m_card_supports_sdhc)
+ {
+ block_no *= SD_BLOCK_SIZE;
+ }
+
+ // This is as per HCSS 3.7.2.1
+ if (buf_size < m_block_size)
+ {
+ DEBUG_LOG("DoDataCommand() called with buffer size(%d) less than block size(%d)\r\n", buf_size, m_block_size);
+
+ return -1;
+ }
+
+ m_blocks_to_transfer = buf_size / m_block_size;
+ if (buf_size % m_block_size)
+ {
+ DEBUG_LOG("DoDataCommand() called with buffer size(%d) not an exact multiple of block size(%d)\r\n", buf_size, m_block_size);
+
+ return -1;
+ }
+ m_buf = buf;
+
+ // Decide on the command to use
+ int command;
+ if (is_write)
+ {
+ if (m_blocks_to_transfer > 1)
+ {
+ command = WRITE_MULTIPLE_BLOCK;
+ }
+ else
+ {
+ command = WRITE_BLOCK;
+ }
+ }
+ else
+ {
+ if (m_blocks_to_transfer > 1)
+ {
+ command = READ_MULTIPLE_BLOCK;
+ }
+ else
+ {
+ command = READ_SINGLE_BLOCK;
+ }
+ }
+
+ int retry_count = 0;
+ int max_retries = 3;
+ while(retry_count < max_retries)
+ {
+ if (IssueCommand(command, block_no, 5000000))
+ {
+ break;
+ }
+ else
+ {
+ DEBUG_LOG("error sending CMD%d\r\n", command);
+ DEBUG_LOG("error = %08x\r\n", m_last_error);
+
+ if (++retry_count < max_retries)
+ {
+ DEBUG_LOG("Retrying");
+ }
+ else
+ {
+ DEBUG_LOG("Giving up");
+ }
+ }
+ }
+
+ if (retry_count == max_retries)
+ {
+ m_card_rca = 0;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+int CEMMCDevice::DoRead(u8 *buf, size_t buf_size, u32 block_no)
+{
+// g_pLogger->Write("\r\n", LogNotice, "DoRead %d\r\n", block_no);
+
+ // Check the status of the card
+ if (EnsureDataMode() != 0)
+ {
+ return -1;
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Reading from block %u %08x\r\n", block_no,(unsigned)buf);
+#endif
+
+ if (DoDataCommand(0, buf, buf_size, block_no) < 0)
+ {
+ return -1;
+ }
+
+ //int y = 0;
+ //int index = 0;
+ //for(y = 0; y <(512 / 8); ++y)
+ //{
+ // g_pLogger->Write("\r\n", LogNotice, "%04x, %02x %02x %02x %02x %02x %02x %02x %02x"
+ // , index
+ // , buf[index]
+ // , buf[index + 1]
+ // , buf[index + 2]
+ // , buf[index + 3]
+ // , buf[index + 4]
+ // , buf[index + 5]
+ // , buf[index + 6]
+ // , buf[index + 7]
+ // );
+ // index += 8;
+ //}
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Data read successful");
+#endif
+
+ return buf_size;
+}
+
+int CEMMCDevice::DoWrite(u8 *buf, size_t buf_size, u32 block_no)
+{
+ // Check the status of the card
+ if (EnsureDataMode() != 0)
+ {
+ return -1;
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Writing to block %u\r\n", block_no);
+#endif
+
+ if (DoDataCommand(1, buf, buf_size, block_no) < 0)
+ {
+ return -1;
+ }
+
+#ifdef EMMC_DEBUG2
+ DEBUG_LOG("Data write successful");
+#endif
+
+ return buf_size;
+}
+
+int CEMMCDevice::TimeoutWait(unsigned reg, unsigned mask, int value, unsigned usec)
+{
+ unsigned nCount = usec / 1000;
+
+ do
+ {
+ delay_us(1);
+
+ if ((read32(reg) & mask) ? value : !value)
+ {
+ return 0;
+ }
+
+ delay_us(999);
+ }
+ while(nCount--);
+
+ return -1;
+}
diff --git a/emmc.h b/emmc.h
new file mode 100644
index 0000000..2b6ec3c
--- /dev/null
+++ b/emmc.h
@@ -0,0 +1,108 @@
+#ifndef _SDCard_emmc_h
+#define _SDCard_emmc_h
+
+#define PACKED __attribute__ ((packed))
+
+#include "types.h"
+
+/*
+#include
+//#include
+#include
+#include
+#include
+#include
+#include
+*/
+
+#define BOOT_SIGNATURE 0xAA55
+
+struct TSCR // SD configuration register
+{
+ u32 scr[2];
+ u32 sd_bus_widths;
+ int sd_version;
+};
+
+class CEMMCDevice
+{
+public:
+ CEMMCDevice();
+ ~CEMMCDevice();
+
+ bool Initialize(void);
+
+ int Read(void *pBuffer, unsigned nCount);
+ int Write(const void *pBuffer, unsigned nCount);
+
+ unsigned long long Seek(unsigned long long ullOffset);
+
+ int DoRead2(u8 *buf, size_t buf_size, u32 block_no);
+ int DoRead(u8 *buf, size_t buf_size, u32 block_no);
+ int DoWrite(u8 *buf, size_t buf_size, u32 block_no);
+
+private:
+ bool PowerOn(void);
+ void PowerOff(void);
+
+ u32 GetBaseClock(void);
+ u32 GetClockDivider(u32 base_clock, u32 target_rate);
+ int SwitchClockRate(u32 base_clock, u32 target_rate);
+
+ int ResetCmd(void);
+ int ResetDat(void);
+
+ void IssueCommandInt(u32 cmd_reg, u32 argument, int timeout);
+ void HandleCardInterrupt(void);
+ void HandleInterrupts(void);
+ bool IssueCommand(u32 command, u32 argument, int timeout = 500000);
+
+ int CardReset(void);
+ int CardInit(void);
+
+ int EnsureDataMode(void);
+ int DoDataCommand(int is_write, u8 *buf, size_t buf_size, u32 block_no);
+
+ int TimeoutWait(unsigned reg, unsigned mask, int value, unsigned usec);
+
+ void usDelay(unsigned usec);
+
+private:
+ u64 m_ullOffset;
+
+ u32 m_hci_ver;
+
+ // was: struct emmc_block_dev
+ u32 m_device_id[4];
+
+ u32 m_card_supports_sdhc;
+ u32 m_card_supports_18v;
+ u32 m_card_ocr;
+ u32 m_card_rca;
+ u32 m_last_interrupt;
+ u32 m_last_error;
+
+ TSCR m_SCR;
+
+ int m_failed_voltage_switch;
+
+ u32 m_last_cmd_reg;
+ u32 m_last_cmd;
+ u32 m_last_cmd_success;
+ u32 m_last_r0;
+ u32 m_last_r1;
+ u32 m_last_r2;
+ u32 m_last_r3;
+
+ void *m_buf;
+ int m_blocks_to_transfer;
+ size_t m_block_size;
+ int m_card_removal;
+ u32 m_base_clock;
+
+ static const char *sd_versions[];
+ static const char *err_irpts[];
+ static const u32 sd_commands[];
+ static const u32 sd_acommands[];
+};
+#endif
diff --git a/exception.c b/exception.c
new file mode 100644
index 0000000..3ac5d7a
--- /dev/null
+++ b/exception.c
@@ -0,0 +1,179 @@
+#include "rpi-base.h"
+#include "rpi-aux.h"
+#include "rpi-gpio.h"
+#include "rpi-interrupts.h"
+#include "defs.h"
+#include "startup.h"
+
+// From here: https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=53862
+void reboot_now(void)
+{
+ const int PM_PASSWORD = 0x5a000000;
+ const int PM_RSTC_WRCFG_FULL_RESET = 0x00000020;
+ unsigned int *PM_WDOG = (unsigned int *) (PERIPHERAL_BASE + 0x00100024);
+ unsigned int *PM_RSTC = (unsigned int *) (PERIPHERAL_BASE + 0x0010001c);
+
+ // timeout = 1/16th of a second? (whatever)
+ *PM_WDOG = PM_PASSWORD | 1;
+ *PM_RSTC = PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
+ while(1);
+}
+
+void dump_digit(unsigned int c) {
+ c &= 15;
+ if (c < 10) {
+ c = '0' + c;
+ } else {
+ c = 'A' + c - 10;
+ }
+ RPI_AuxMiniUartWrite(c);
+}
+
+void dump_hex(unsigned int value) {
+ int i;
+ for (i = 0; i < 8; i++) {
+ int c = value >> 28;
+ if (c < 10) {
+ c = '0' + c;
+ } else {
+ c = 'A' + c - 10;
+ }
+ RPI_AuxMiniUartWrite(c);
+ value <<= 4;
+ }
+}
+
+void dump_binary(unsigned int value) {
+ int i;
+ for (i = 0; i < 32; i++) {
+ int c = '0' + (value >> 31);
+ RPI_AuxMiniUartWrite(c);
+ value <<= 1;
+ }
+}
+
+void dump_string(char *string) {
+ char c;
+ while ((c = *string++) != 0) {
+ RPI_AuxMiniUartWrite(c);
+ }
+}
+
+// For some reason printf generally doesn't work here
+void dump_info(unsigned int *context, int offset, char *type) {
+ unsigned int *addr;
+ unsigned int *reg;
+ unsigned int flags;
+ int i, j;
+ //int rstlow;
+ //int led;
+
+ // Make sure we avoid unaligned accesses
+ context = (unsigned int *)(((unsigned int) context) & ~3);
+ // context point into the exception stack, at flags, followed by registers 0 .. 13
+ reg = context + 1;
+ dump_string(type);
+ dump_string(" at ");
+ // The stacked LR points one or two words afer the exception address
+ addr = (unsigned int *)((reg[13] & ~3) - offset);
+ dump_hex((unsigned int)addr);
+#ifdef HAS_MULTICORE
+ //dump_string(" on core ");
+ //dump_digit(_get_core());
+#endif
+ dump_string("\r\n");
+ dump_string("Registers:\r\n");
+ for (i = 0; i <= 13; i++) {
+ j = (i < 13) ? i : 14; // slot 13 actually holds the link register
+ dump_string(" r[");
+ RPI_AuxMiniUartWrite('0' + (j / 10));
+ RPI_AuxMiniUartWrite('0' + (j % 10));
+ dump_string("]=");
+ dump_hex(reg[i]);
+ dump_string("\r\n");
+ }
+ dump_string("Memory:\r\n");
+ for (i = -4; i <= 4; i++) {
+ dump_string(" ");
+ dump_hex((unsigned int) (addr + i));
+ RPI_AuxMiniUartWrite('=');
+ dump_hex(*(addr + i));
+ if (i == 0) {
+ dump_string(" <<<<<< \r\n");
+ } else {
+ dump_string("\r\n");
+ }
+ }
+ // The flags are pointed to by context, before the registers
+ flags = *context;
+ dump_string("Flags: \r\n NZCV--------------------IFTMMMMM\r\n ");
+ dump_binary(flags);
+ dump_string(" (");
+ // The last 5 bits of the flags are the mode
+ switch (flags & 0x1f) {
+ case 0x10:
+ dump_string("User");
+ break;
+ case 0x11:
+ dump_string("FIQ");
+ break;
+ case 0x12:
+ dump_string("IRQ");
+ break;
+ case 0x13:
+ dump_string("Supervisor");
+ break;
+ case 0x17:
+ dump_string("Abort");
+ break;
+ case 0x1B:
+ dump_string("Undefined");
+ break;
+ case 0x1F:
+ dump_string("System");
+ break;
+ default:
+ dump_string("Illegal");
+ break;
+ };
+ dump_string(" Mode)\r\n");
+
+ dump_string("Halted waiting for reset\r\n");
+ //rstlow = 0;
+ //led = 0;
+ // while (1) {
+ //for (i = 0; i < 1000000; i++) {
+
+ // // look for reset being low
+ // if (tube_is_rst_active()) {
+ // rstlow = 1;
+ // }
+ // // then reset on the next rising edge
+ // if (rstlow && (!tube_is_rst_active())) {
+ // reboot_now();
+ // }
+ //}
+ //if (led) {
+ // LED_OFF();
+ //} else {
+ // LED_ON();
+ //}
+ //led = ~led;
+ // }
+}
+
+void undefined_instruction_handler(unsigned int *context) {
+ dump_info(context, 4, "Undefined Instruction");
+}
+
+void prefetch_abort_handler(unsigned int *context) {
+ dump_info(context, 4, "Prefetch Abort");
+}
+
+void data_abort_handler(unsigned int *context) {
+ dump_info(context, 8, "Data Abort");
+}
+
+void swi_handler(unsigned int *context) {
+ dump_info(context, 4, "SWI");
+}
diff --git a/ff.cpp b/ff.cpp
new file mode 100644
index 0000000..5a27728
--- /dev/null
+++ b/ff.cpp
@@ -0,0 +1,6052 @@
+/*----------------------------------------------------------------------------/
+/ FatFs - Generic FAT file system module R0.12b /
+/-----------------------------------------------------------------------------/
+/
+/ Copyright (C) 2016, ChaN, all right reserved.
+/
+/ FatFs module is an open source software. Redistribution and use of FatFs in
+/ source and binary forms, with or without modification, are permitted provided
+/ that the following condition is met:
+
+/ 1. Redistributions of source code must retain the above copyright notice,
+/ this condition and the following disclaimer.
+/
+/ This software is provided by the copyright holder and contributors "AS IS"
+/ and any warranties related to this software are DISCLAIMED.
+/ The copyright owner or contributors be NOT LIABLE for any damages caused
+/ by use of this software.
+/----------------------------------------------------------------------------*/
+
+
+#include "ff.h" /* Declarations of FatFs API */
+#include "diskio.h" /* Declarations of device I/O functions */
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Definitions
+
+---------------------------------------------------------------------------*/
+
+#if _FATFS != 68020 /* Revision ID */
+#error Wrong include file (ff.h).
+#endif
+
+
+#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); }
+
+
+/* Reentrancy related */
+#if _FS_REENTRANT
+#if _USE_LFN == 1
+#error Static LFN work area cannot be used at thread-safe configuration
+#endif
+#define ENTER_FF(fs) { if (!lock_fs(fs)) return FR_TIMEOUT; }
+#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; }
+#else
+#define ENTER_FF(fs)
+#define LEAVE_FF(fs, res) return res
+#endif
+
+
+
+/* Definitions of sector size */
+#if (_MAX_SS < _MIN_SS) || (_MAX_SS != 512 && _MAX_SS != 1024 && _MAX_SS != 2048 && _MAX_SS != 4096) || (_MIN_SS != 512 && _MIN_SS != 1024 && _MIN_SS != 2048 && _MIN_SS != 4096)
+#error Wrong sector size configuration
+#endif
+#if _MAX_SS == _MIN_SS
+#define SS(fs) ((UINT)_MAX_SS) /* Fixed sector size */
+#else
+#define SS(fs) ((fs)->ssize) /* Variable sector size */
+#endif
+
+
+/* Timestamp */
+#if _FS_NORTC == 1
+#if _NORTC_YEAR < 1980 || _NORTC_YEAR > 2107 || _NORTC_MON < 1 || _NORTC_MON > 12 || _NORTC_MDAY < 1 || _NORTC_MDAY > 31
+#error Invalid _FS_NORTC settings
+#endif
+#define GET_FATTIME() ((DWORD)(_NORTC_YEAR - 1980) << 25 | (DWORD)_NORTC_MON << 21 | (DWORD)_NORTC_MDAY << 16)
+#else
+#define GET_FATTIME() get_fattime()
+#endif
+
+
+/* File lock controls */
+#if _FS_LOCK != 0
+#if _FS_READONLY
+#error _FS_LOCK must be 0 at read-only configuration
+#endif
+typedef struct {
+ FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */
+ DWORD clu; /* Object ID 2, directory (0:root) */
+ DWORD ofs; /* Object ID 3, directory offset */
+ WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */
+} FILESEM;
+#endif
+
+
+
+/* DBCS code ranges and SBCS upper conversion tables */
+
+#if _CODE_PAGE == 932 /* Japanese Shift-JIS */
+#define _DF1S 0x81 /* DBC 1st byte range 1 start */
+#define _DF1E 0x9F /* DBC 1st byte range 1 end */
+#define _DF2S 0xE0 /* DBC 1st byte range 2 start */
+#define _DF2E 0xFC /* DBC 1st byte range 2 end */
+#define _DS1S 0x40 /* DBC 2nd byte range 1 start */
+#define _DS1E 0x7E /* DBC 2nd byte range 1 end */
+#define _DS2S 0x80 /* DBC 2nd byte range 2 start */
+#define _DS2E 0xFC /* DBC 2nd byte range 2 end */
+
+#elif _CODE_PAGE == 936 /* Simplified Chinese GBK */
+#define _DF1S 0x81
+#define _DF1E 0xFE
+#define _DS1S 0x40
+#define _DS1E 0x7E
+#define _DS2S 0x80
+#define _DS2E 0xFE
+
+#elif _CODE_PAGE == 949 /* Korean */
+#define _DF1S 0x81
+#define _DF1E 0xFE
+#define _DS1S 0x41
+#define _DS1E 0x5A
+#define _DS2S 0x61
+#define _DS2E 0x7A
+#define _DS3S 0x81
+#define _DS3E 0xFE
+
+#elif _CODE_PAGE == 950 /* Traditional Chinese Big5 */
+#define _DF1S 0x81
+#define _DF1E 0xFE
+#define _DS1S 0x40
+#define _DS1E 0x7E
+#define _DS2S 0xA1
+#define _DS2E 0xFE
+
+#elif _CODE_PAGE == 437 /* U.S. */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 720 /* Arabic */
+#define _DF1S 0
+#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 737 /* Greek */
+#define _DF1S 0
+#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \
+ 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 771 /* KBL */
+#define _DF1S 0
+#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDC,0xDE,0xDE, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF}
+
+#elif _CODE_PAGE == 775 /* Baltic */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
+ 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 850 /* Latin 1 */
+#define _DF1S 0
+#define _EXCVT {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \
+ 0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \
+ 0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 852 /* Latin 2 */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \
+ 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF}
+
+#elif _CODE_PAGE == 855 /* Cyrillic */
+#define _DF1S 0
+#define _EXCVT {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \
+ 0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \
+ 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \
+ 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \
+ 0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 857 /* Turkish */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \
+ 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 860 /* Portuguese */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x86,0x8B,0x9F,0x96,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 861 /* Icelandic */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
+ 0xA4,0xA5,0xA6,0xA7,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 862 /* Hebrew */
+#define _DF1S 0
+#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 863 /* Canadian-French */
+#define _DF1S 0
+#define _EXCVT {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \
+ 0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \
+ 0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 864 /* Arabic */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 865 /* Nordic */
+#define _DF1S 0
+#define _EXCVT {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
+ 0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 866 /* Russian */
+#define _DF1S 0
+#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
+ 0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
+
+#elif _CODE_PAGE == 869 /* Greek 2 */
+#define _DF1S 0
+#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x86,0x9C,0x8D,0x8F,0x90, \
+ 0x91,0x90,0x92,0x95,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
+ 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
+ 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
+ 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \
+ 0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \
+ 0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF}
+
+#elif _CODE_PAGE == 1 /* ASCII (for only non-LFN cfg) */
+#if _USE_LFN != 0
+#error Cannot enable LFN without valid code page.
+#endif
+#define _DF1S 0
+
+#else
+#error Unknown code page
+
+#endif
+
+
+/* Character code support macros */
+#define IsUpper(c) (((c)>='A')&&((c)<='Z'))
+#define IsLower(c) (((c)>='a')&&((c)<='z'))
+#define IsDigit(c) (((c)>='0')&&((c)<='9'))
+
+#if _DF1S != 0 /* Code page is DBCS */
+
+#ifdef _DF2S /* Two 1st byte areas */
+#define IsDBCS1(c) (((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) || ((BYTE)(c) >= _DF2S && (BYTE)(c) <= _DF2E))
+#else /* One 1st byte area */
+#define IsDBCS1(c) ((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E)
+#endif
+
+#ifdef _DS3S /* Three 2nd byte areas */
+#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E) || ((BYTE)(c) >= _DS3S && (BYTE)(c) <= _DS3E))
+#else /* Two 2nd byte areas */
+#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E))
+#endif
+
+#else /* Code page is SBCS */
+
+#define IsDBCS1(c) 0
+#define IsDBCS2(c) 0
+
+#endif /* _DF1S */
+
+
+/* File attribute bits (internal use) */
+#define AM_VOL 0x08 /* Volume label */
+#define AM_LFN 0x0F /* LFN entry */
+#define AM_MASK 0x3F /* Mask of defined bits */
+
+
+/* File access control and file status flags (internal use) */
+#define FA_SEEKEND 0x20 /* Seek to end of the file on file open */
+#define FA_MODIFIED 0x40 /* File has been modified */
+#define FA_DIRTY 0x80 /* FIL.buf[] needs to be written-back */
+
+
+/* Name status flags */
+#define NSFLAG 11 /* Index of name status byte in fn[] */
+#define NS_LOSS 0x01 /* Out of 8.3 format */
+#define NS_LFN 0x02 /* Force to create LFN entry */
+#define NS_LAST 0x04 /* Last segment */
+#define NS_BODY 0x08 /* Lower case flag (body) */
+#define NS_EXT 0x10 /* Lower case flag (ext) */
+#define NS_DOT 0x20 /* Dot entry */
+#define NS_NOLFN 0x40 /* Do not find LFN */
+#define NS_NONAME 0x80 /* Not followed */
+
+
+/* Limits and boundaries (differ from specs but correct for real DOS/Windows) */
+#define MAX_FAT12 0xFF5 /* Maximum number of FAT12 clusters */
+#define MAX_FAT16 0xFFF5 /* Maximum number of FAT16 clusters */
+#define MAX_FAT32 0xFFFFFF5 /* Maximum number of FAT32 clusters */
+#define MAX_EXFAT 0x7FFFFFFD /* Maximum number of exFAT clusters (limited by implementation) */
+#define MAX_DIR 0x200000 /* Maximum size of FAT directory */
+#define MAX_DIR_EX 0x10000000 /* Maximum size of exFAT directory */
+
+
+/* FatFs refers the members in the FAT structures as byte array instead of
+/ structure members because the structure is not binary compatible between
+/ different platforms */
+
+#define BS_JmpBoot 0 /* x86 jump instruction (3-byte) */
+#define BS_OEMName 3 /* OEM name (8-byte) */
+#define BPB_BytsPerSec 11 /* Sector size [byte] (WORD) */
+#define BPB_SecPerClus 13 /* Cluster size [sector] (BYTE) */
+#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (WORD) */
+#define BPB_NumFATs 16 /* Number of FATs (BYTE) */
+#define BPB_RootEntCnt 17 /* Size of root directory area for FAT12/16 [entry] (WORD) */
+#define BPB_TotSec16 19 /* Volume size (16-bit) [sector] (WORD) */
+#define BPB_Media 21 /* Media descriptor byte (BYTE) */
+#define BPB_FATSz16 22 /* FAT size (16-bit) [sector] (WORD) */
+#define BPB_SecPerTrk 24 /* Track size for int13h [sector] (WORD) */
+#define BPB_NumHeads 26 /* Number of heads for int13h (WORD) */
+#define BPB_HiddSec 28 /* Volume offset from top of the drive (DWORD) */
+#define BPB_TotSec32 32 /* Volume size (32-bit) [sector] (DWORD) */
+#define BS_DrvNum 36 /* Physical drive number for int13h (BYTE) */
+#define BS_NTres 37 /* Error flag (BYTE) */
+#define BS_BootSig 38 /* Extended boot signature (BYTE) */
+#define BS_VolID 39 /* Volume serial number (DWORD) */
+#define BS_VolLab 43 /* Volume label string (8-byte) */
+#define BS_FilSysType 54 /* File system type string (8-byte) */
+#define BS_BootCode 62 /* Boot code (448-byte) */
+#define BS_55AA 510 /* Signature word (WORD) */
+
+#define BPB_FATSz32 36 /* FAT32: FAT size [sector] (DWORD) */
+#define BPB_ExtFlags32 40 /* FAT32: Extended flags (WORD) */
+#define BPB_FSVer32 42 /* FAT32: File system version (WORD) */
+#define BPB_RootClus32 44 /* FAT32: Root directory cluster (DWORD) */
+#define BPB_FSInfo32 48 /* FAT32: Offset of FSINFO sector (WORD) */
+#define BPB_BkBootSec32 50 /* FAT32: Offset of backup boot sector (WORD) */
+#define BS_DrvNum32 64 /* FAT32: Physical drive number for int13h (BYTE) */
+#define BS_NTres32 65 /* FAT32: Error flag (BYTE) */
+#define BS_BootSig32 66 /* FAT32: Extended boot signature (BYTE) */
+#define BS_VolID32 67 /* FAT32: Volume serial number (DWORD) */
+#define BS_VolLab32 71 /* FAT32: Volume label string (8-byte) */
+#define BS_FilSysType32 82 /* FAT32: File system type string (8-byte) */
+#define BS_BootCode32 90 /* FAT32: Boot code (420-byte) */
+
+#define BPB_ZeroedEx 11 /* exFAT: MBZ field (53-byte) */
+#define BPB_VolOfsEx 64 /* exFAT: Volume offset from top of the drive [sector] (QWORD) */
+#define BPB_TotSecEx 72 /* exFAT: Volume size [sector] (QWORD) */
+#define BPB_FatOfsEx 80 /* exFAT: FAT offset from top of the volume [sector] (DWORD) */
+#define BPB_FatSzEx 84 /* exFAT: FAT size [sector] (DWORD) */
+#define BPB_DataOfsEx 88 /* exFAT: Data offset from top of the volume [sector] (DWORD) */
+#define BPB_NumClusEx 92 /* exFAT: Number of clusters (DWORD) */
+#define BPB_RootClusEx 96 /* exFAT: Root directory cluster (DWORD) */
+#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */
+#define BPB_FSVerEx 104 /* exFAT: File system version (WORD) */
+#define BPB_VolFlagEx 106 /* exFAT: Volume flags (BYTE) */
+#define BPB_ActFatEx 107 /* exFAT: Active FAT flags (BYTE) */
+#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in byte (BYTE) */
+#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in sector (BYTE) */
+#define BPB_NumFATsEx 110 /* exFAT: Number of FATs (BYTE) */
+#define BPB_DrvNumEx 111 /* exFAT: Physical drive number for int13h (BYTE) */
+#define BPB_PercInUseEx 112 /* exFAT: Percent in use (BYTE) */
+#define BPB_RsvdEx 113 /* exFAT: Reserved (7-byte) */
+#define BS_BootCodeEx 120 /* exFAT: Boot code (390-byte) */
+
+#define FSI_LeadSig 0 /* FAT32 FSI: Leading signature (DWORD) */
+#define FSI_StrucSig 484 /* FAT32 FSI: Structure signature (DWORD) */
+#define FSI_Free_Count 488 /* FAT32 FSI: Number of free clusters (DWORD) */
+#define FSI_Nxt_Free 492 /* FAT32 FSI: Last allocated cluster (DWORD) */
+
+#define MBR_Table 446 /* MBR: Offset of partition table in the MBR */
+#define SZ_PTE 16 /* MBR: Size of a partition table entry */
+#define PTE_Boot 0 /* MBR PTE: Boot indicator */
+#define PTE_StHead 1 /* MBR PTE: Start head */
+#define PTE_StSec 2 /* MBR PTE: Start sector */
+#define PTE_StCyl 3 /* MBR PTE: Start cylinder */
+#define PTE_System 4 /* MBR PTE: System ID */
+#define PTE_EdHead 5 /* MBR PTE: End head */
+#define PTE_EdSec 6 /* MBR PTE: End sector */
+#define PTE_EdCyl 7 /* MBR PTE: End cylinder */
+#define PTE_StLba 8 /* MBR PTE: Start in LBA */
+#define PTE_SizLba 12 /* MBR PTE: Size in LBA */
+
+#define DIR_Name 0 /* Short file name (11-byte) */
+#define DIR_Attr 11 /* Attribute (BYTE) */
+#define DIR_NTres 12 /* Lower case flag (BYTE) */
+#define DIR_CrtTime10 13 /* Created time sub-second (BYTE) */
+#define DIR_CrtTime 14 /* Created time (DWORD) */
+#define DIR_LstAccDate 18 /* Last accessed date (WORD) */
+#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (WORD) */
+#define DIR_ModTime 22 /* Modified time (DWORD) */
+#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (WORD) */
+#define DIR_FileSize 28 /* File size (DWORD) */
+#define LDIR_Ord 0 /* LFN entry order and LLE flag (BYTE) */
+#define LDIR_Attr 11 /* LFN attribute (BYTE) */
+#define LDIR_Type 12 /* LFN type (BYTE) */
+#define LDIR_Chksum 13 /* Checksum of the SFN entry (BYTE) */
+#define LDIR_FstClusLO 26 /* Must be zero (WORD) */
+#define XDIR_Type 0 /* Type of exFAT directory entry (BYTE) */
+#define XDIR_NumLabel 1 /* Number of volume label characters (BYTE) */
+#define XDIR_Label 2 /* Volume label (11-WORD) */
+#define XDIR_CaseSum 4 /* Sum of case conversion table (DWORD) */
+#define XDIR_NumSec 1 /* Number of secondary entries (BYTE) */
+#define XDIR_SetSum 2 /* Sum of the set of directory entries (WORD) */
+#define XDIR_Attr 4 /* File attribute (WORD) */
+#define XDIR_CrtTime 8 /* Created time (DWORD) */
+#define XDIR_ModTime 12 /* Modified time (DWORD) */
+#define XDIR_AccTime 16 /* Last accessed time (DWORD) */
+#define XDIR_CrtTime10 20 /* Created time subsecond (BYTE) */
+#define XDIR_ModTime10 21 /* Modified time subsecond (BYTE) */
+#define XDIR_CrtTZ 22 /* Created timezone (BYTE) */
+#define XDIR_ModTZ 23 /* Modified timezone (BYTE) */
+#define XDIR_AccTZ 24 /* Last accessed timezone (BYTE) */
+#define XDIR_GenFlags 33 /* Gneral secondary flags (WORD) */
+#define XDIR_NumName 35 /* Number of file name characters (BYTE) */
+#define XDIR_NameHash 36 /* Hash of file name (WORD) */
+#define XDIR_ValidFileSize 40 /* Valid file size (QWORD) */
+#define XDIR_FstClus 52 /* First cluster of the file data (DWORD) */
+#define XDIR_FileSize 56 /* File/Directory size (QWORD) */
+
+#define SZDIRE 32 /* Size of a directory entry */
+#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */
+#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */
+#define RDDEM 0x05 /* Replacement of the character collides with DDEM */
+
+
+
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Work Area
+
+---------------------------------------------------------------------------*/
+
+/* Remark: Variables here without initial value shall be guaranteed zero/null
+/ at start-up. If not, either the linker or start-up routine being used is
+/ not compliance with C standard. */
+
+#if _VOLUMES < 1 || _VOLUMES > 9
+#error Wrong _VOLUMES setting
+#endif
+static FATFS *FatFs[_VOLUMES]; /* Pointer to the file system objects (logical drives) */
+static WORD Fsid; /* File system mount ID */
+
+#if _FS_RPATH != 0 && _VOLUMES >= 2
+static BYTE CurrVol; /* Current drive */
+#endif
+
+#if _FS_LOCK != 0
+static FILESEM Files[_FS_LOCK]; /* Open object lock semaphores */
+#endif
+
+#if _USE_LFN == 0 /* Non-LFN configuration */
+#define DEF_NAMBUF
+#define INIT_NAMBUF(fs)
+#define FREE_NAMBUF()
+#else
+#if _MAX_LFN < 12 || _MAX_LFN > 255
+#error Wrong _MAX_LFN setting
+#endif
+
+#if _USE_LFN == 1 /* LFN enabled with static working buffer */
+#if _FS_EXFAT
+static BYTE DirBuf[SZDIRE*19]; /* Directory entry block scratchpad buffer (19 entries in size) */
+#endif
+static WCHAR LfnBuf[_MAX_LFN+1]; /* LFN enabled with static working buffer */
+#define DEF_NAMBUF
+#define INIT_NAMBUF(fs)
+#define FREE_NAMBUF()
+
+#elif _USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */
+#if _FS_EXFAT
+#define DEF_NAMBUF WCHAR lbuf[_MAX_LFN+1]; BYTE dbuf[SZDIRE*19];
+#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; }
+#define FREE_NAMBUF()
+#else
+#define DEF_NAMBUF WCHAR lbuf[_MAX_LFN+1];
+#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; }
+#define FREE_NAMBUF()
+#endif
+
+#elif _USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */
+#if _FS_EXFAT
+#define DEF_NAMBUF WCHAR *lfn;
+#define INIT_NAMBUF(fs) { lfn = ff_memalloc((_MAX_LFN+1)*2 + SZDIRE*19); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+_MAX_LFN+1); }
+#define FREE_NAMBUF() ff_memfree(lfn)
+#else
+#define DEF_NAMBUF WCHAR *lfn;
+#define INIT_NAMBUF(fs) { lfn = ff_memalloc((_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; }
+#define FREE_NAMBUF() ff_memfree(lfn)
+#endif
+
+#else
+#error Wrong _USE_LFN setting
+#endif
+#endif
+
+#ifdef _EXCVT
+static const BYTE ExCvt[] = _EXCVT; /* Upper conversion table for SBCS extended characters */
+#endif
+
+
+
+
+
+
+/*--------------------------------------------------------------------------
+
+ Module Private Functions
+
+---------------------------------------------------------------------------*/
+
+
+/*-----------------------------------------------------------------------*/
+/* Load/Store multi-byte word in the FAT structure */
+/*-----------------------------------------------------------------------*/
+
+static
+WORD ld_word (const BYTE* ptr) /* Load a 2-byte little-endian word */
+{
+ WORD rv;
+
+ rv = ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+
+static
+DWORD ld_dword (const BYTE* ptr) /* Load a 4-byte little-endian word */
+{
+ DWORD rv;
+
+ rv = ptr[3];
+ rv = rv << 8 | ptr[2];
+ rv = rv << 8 | ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+
+#if _FS_EXFAT
+static
+QWORD ld_qword (const BYTE* ptr) /* Load an 8-byte little-endian word */
+{
+ QWORD rv;
+
+ rv = ptr[7];
+ rv = rv << 8 | ptr[6];
+ rv = rv << 8 | ptr[5];
+ rv = rv << 8 | ptr[4];
+ rv = rv << 8 | ptr[3];
+ rv = rv << 8 | ptr[2];
+ rv = rv << 8 | ptr[1];
+ rv = rv << 8 | ptr[0];
+ return rv;
+}
+#endif
+
+#if !_FS_READONLY
+static
+void st_word (BYTE* ptr, WORD val) /* Store a 2-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+
+static
+void st_dword (BYTE* ptr, DWORD val) /* Store a 4-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+
+#if _FS_EXFAT
+static
+void st_qword (BYTE* ptr, QWORD val) /* Store an 8-byte word in little-endian */
+{
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val; val >>= 8;
+ *ptr++ = (BYTE)val;
+}
+#endif
+#endif /* !_FS_READONLY */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* String functions */
+/*-----------------------------------------------------------------------*/
+
+/* Copy memory to memory */
+static
+void mem_cpy (void* dst, const void* src, UINT cnt) {
+ BYTE *d = (BYTE*)dst;
+ const BYTE *s = (const BYTE*)src;
+
+ if (cnt) {
+ do *d++ = *s++; while (--cnt);
+ }
+}
+
+/* Fill memory block */
+static
+void mem_set (void* dst, int val, UINT cnt) {
+ BYTE *d = (BYTE*)dst;
+
+ do *d++ = (BYTE)val; while (--cnt);
+}
+
+/* Compare memory block */
+static
+int mem_cmp (const void* dst, const void* src, UINT cnt) { /* ZR:same, NZ:different */
+ const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src;
+ int r = 0;
+
+ do {
+ r = *d++ - *s++;
+ } while (--cnt && r == 0);
+
+ return r;
+}
+
+/* Check if chr is contained in the string */
+static
+int chk_chr (const char* str, int chr) { /* NZ:contained, ZR:not contained */
+ while (*str && *str != chr) str++;
+ return *str;
+}
+
+
+
+
+#if _FS_REENTRANT
+/*-----------------------------------------------------------------------*/
+/* Request/Release grant to access the volume */
+/*-----------------------------------------------------------------------*/
+static
+int lock_fs (
+ FATFS* fs /* File system object */
+)
+{
+ return ff_req_grant(fs->sobj);
+}
+
+
+static
+void unlock_fs (
+ FATFS* fs, /* File system object */
+ FRESULT res /* Result code to be returned */
+)
+{
+ if (fs && res != FR_NOT_ENABLED && res != FR_INVALID_DRIVE && res != FR_TIMEOUT) {
+ ff_rel_grant(fs->sobj);
+ }
+}
+
+#endif
+
+
+
+#if _FS_LOCK != 0
+/*-----------------------------------------------------------------------*/
+/* File lock control functions */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT chk_lock ( /* Check if the file can be accessed */
+ DIR* dp, /* Directory object pointing the file to be checked */
+ int acc /* Desired access type (0:Read, 1:Write, 2:Delete/Rename) */
+)
+{
+ UINT i, be;
+
+ /* Search file semaphore table */
+ for (i = be = 0; i < _FS_LOCK; i++) {
+ if (Files[i].fs) { /* Existing entry */
+ if (Files[i].fs == dp->obj.fs && /* Check if the object matched with an open object */
+ Files[i].clu == dp->obj.sclust &&
+ Files[i].ofs == dp->dptr) break;
+ } else { /* Blank entry */
+ be = 1;
+ }
+ }
+ if (i == _FS_LOCK) { /* The object is not opened */
+ return (be || acc == 2) ? FR_OK : FR_TOO_MANY_OPEN_FILES; /* Is there a blank entry for new object? */
+ }
+
+ /* The object has been opened. Reject any open against writing file and all write mode open */
+ return (acc || Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK;
+}
+
+
+static
+int enq_lock (void) /* Check if an entry is available for a new object */
+{
+ UINT i;
+
+ for (i = 0; i < _FS_LOCK && Files[i].fs; i++) ;
+ return (i == _FS_LOCK) ? 0 : 1;
+}
+
+
+static
+UINT inc_lock ( /* Increment object open counter and returns its index (0:Internal error) */
+ DIR* dp, /* Directory object pointing the file to register or increment */
+ int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */
+)
+{
+ UINT i;
+
+
+ for (i = 0; i < _FS_LOCK; i++) { /* Find the object */
+ if (Files[i].fs == dp->obj.fs &&
+ Files[i].clu == dp->obj.sclust &&
+ Files[i].ofs == dp->dptr) break;
+ }
+
+ if (i == _FS_LOCK) { /* Not opened. Register it as new. */
+ for (i = 0; i < _FS_LOCK && Files[i].fs; i++) ;
+ if (i == _FS_LOCK) return 0; /* No free entry to register (int err) */
+ Files[i].fs = dp->obj.fs;
+ Files[i].clu = dp->obj.sclust;
+ Files[i].ofs = dp->dptr;
+ Files[i].ctr = 0;
+ }
+
+ if (acc && Files[i].ctr) return 0; /* Access violation (int err) */
+
+ Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */
+
+ return i + 1;
+}
+
+
+static
+FRESULT dec_lock ( /* Decrement object open counter */
+ UINT i /* Semaphore index (1..) */
+)
+{
+ WORD n;
+ FRESULT res;
+
+
+ if (--i < _FS_LOCK) { /* Shift index number origin from 0 */
+ n = Files[i].ctr;
+ if (n == 0x100) n = 0; /* If write mode open, delete the entry */
+ if (n > 0) n--; /* Decrement read mode open count */
+ Files[i].ctr = n;
+ if (n == 0) Files[i].fs = 0; /* Delete the entry if open count gets zero */
+ res = FR_OK;
+ } else {
+ res = FR_INT_ERR; /* Invalid index nunber */
+ }
+ return res;
+}
+
+
+static
+void clear_lock ( /* Clear lock entries of the volume */
+ FATFS *fs
+)
+{
+ UINT i;
+
+ for (i = 0; i < _FS_LOCK; i++) {
+ if (Files[i].fs == fs) Files[i].fs = 0;
+ }
+}
+
+#endif /* _FS_LOCK != 0 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Move/Flush disk access window in the file system object */
+/*-----------------------------------------------------------------------*/
+#if !_FS_READONLY
+static
+FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERROR */
+ FATFS* fs /* File system object */
+)
+{
+ DWORD wsect;
+ UINT nf;
+ FRESULT res = FR_OK;
+
+
+ if (fs->wflag) { /* Write back the sector if it is dirty */
+ wsect = fs->winsect; /* Current sector number */
+ if (disk_write(fs->drv, fs->win, wsect, 1) != RES_OK) {
+ res = FR_DISK_ERR;
+ } else {
+ fs->wflag = 0;
+ if (wsect - fs->fatbase < fs->fsize) { /* Is it in the FAT area? */
+ for (nf = fs->n_fats; nf >= 2; nf--) { /* Reflect the change to all FAT copies */
+ wsect += fs->fsize;
+ disk_write(fs->drv, fs->win, wsect, 1);
+ }
+ }
+ }
+ }
+ return res;
+}
+#endif
+
+
+static
+FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERROR */
+ FATFS* fs, /* File system object */
+ DWORD sector /* Sector number to make appearance in the fs->win[] */
+)
+{
+ FRESULT res = FR_OK;
+
+
+ if (sector != fs->winsect) { /* Window offset changed? */
+#if !_FS_READONLY
+ res = sync_window(fs); /* Write-back changes */
+#endif
+ if (res == FR_OK) { /* Fill sector window with new data */
+ if (disk_read(fs->drv, fs->win, sector, 1) != RES_OK) {
+ sector = 0xFFFFFFFF; /* Invalidate window if data is not reliable */
+ res = FR_DISK_ERR;
+ }
+ fs->winsect = sector;
+ }
+ }
+ return res;
+}
+
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Synchronize file system and strage device */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT sync_fs ( /* FR_OK:succeeded, !=0:error */
+ FATFS* fs /* File system object */
+)
+{
+ FRESULT res;
+
+
+ res = sync_window(fs);
+ if (res == FR_OK) {
+ /* Update FSInfo sector if needed */
+ if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) {
+ /* Create FSInfo structure */
+ mem_set(fs->win, 0, SS(fs));
+ st_word(fs->win + BS_55AA, 0xAA55);
+ st_dword(fs->win + FSI_LeadSig, 0x41615252);
+ st_dword(fs->win + FSI_StrucSig, 0x61417272);
+ st_dword(fs->win + FSI_Free_Count, fs->free_clst);
+ st_dword(fs->win + FSI_Nxt_Free, fs->last_clst);
+ /* Write it into the FSInfo sector */
+ fs->winsect = fs->volbase + 1;
+ disk_write(fs->drv, fs->win, fs->winsect, 1);
+ fs->fsi_flag = 0;
+ }
+ /* Make sure that no pending write process in the physical drive */
+ if (disk_ioctl(fs->drv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR;
+ }
+
+ return res;
+}
+
+#endif
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Get sector# from cluster# */
+/*-----------------------------------------------------------------------*/
+
+static
+DWORD clust2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */
+ FATFS* fs, /* File system object */
+ DWORD clst /* Cluster# to be converted */
+)
+{
+ clst -= 2;
+ if (clst >= fs->n_fatent - 2) return 0; /* Invalid cluster# */
+ return clst * fs->csize + fs->database;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT access - Read value of a FAT entry */
+/*-----------------------------------------------------------------------*/
+
+static
+DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */
+ _FDID* obj, /* Corresponding object */
+ DWORD clst /* Cluster number to get the value */
+)
+{
+ UINT wc, bc;
+ DWORD val;
+ FATFS *fs = obj->fs;
+
+
+ if (clst < 2 || clst >= fs->n_fatent) { /* Check if in valid range */
+ val = 1; /* Internal error */
+
+ } else {
+ val = 0xFFFFFFFF; /* Default value falls on disk error */
+
+ switch (fs->fs_type) {
+ case FS_FAT12 :
+ bc = (UINT)clst; bc += bc / 2;
+ if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break;
+ wc = fs->win[bc++ % SS(fs)];
+ if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break;
+ wc |= fs->win[bc % SS(fs)] << 8;
+ val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF);
+ break;
+
+ case FS_FAT16 :
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))) != FR_OK) break;
+ val = ld_word(fs->win + clst * 2 % SS(fs));
+ break;
+
+ case FS_FAT32 :
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break;
+ val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x0FFFFFFF;
+ break;
+#if _FS_EXFAT
+ case FS_EXFAT :
+ if (obj->objsize) {
+ DWORD cofs = clst - obj->sclust; /* Offset from start cluster */
+ DWORD clen = (DWORD)((obj->objsize - 1) / SS(fs)) / fs->csize; /* Number of clusters - 1 */
+
+ if (obj->stat == 2) { /* Is there no valid chain on the FAT? */
+ if (cofs <= clen) {
+ val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* Generate the value */
+ break;
+ }
+ }
+ if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the contiguous part? */
+ val = clst + 1; /* Generate the value */
+ break;
+ }
+ if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */
+ if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))) != FR_OK) break;
+ val = ld_dword(fs->win + clst * 4 % SS(fs)) & 0x7FFFFFFF;
+ break;
+ }
+ }
+ /* go next */
+#endif
+ default:
+ val = 1; /* Internal error */
+ }
+ }
+
+ return val;
+}
+
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT access - Change value of a FAT entry */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */
+ FATFS* fs, /* Corresponding file system object */
+ DWORD clst, /* FAT index number (cluster number) to be changed */
+ DWORD val /* New value to be set to the entry */
+)
+{
+ UINT bc;
+ BYTE *p;
+ FRESULT res = FR_INT_ERR;
+
+
+ if (clst >= 2 && clst < fs->n_fatent) { /* Check if in valid range */
+ switch (fs->fs_type) {
+ case FS_FAT12 : /* Bitfield items */
+ bc = (UINT)clst; bc += bc / 2;
+ res = move_window(fs, fs->fatbase + (bc / SS(fs)));
+ if (res != FR_OK) break;
+ p = fs->win + bc++ % SS(fs);
+ *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val;
+ fs->wflag = 1;
+ res = move_window(fs, fs->fatbase + (bc / SS(fs)));
+ if (res != FR_OK) break;
+ p = fs->win + bc % SS(fs);
+ *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F));
+ fs->wflag = 1;
+ break;
+
+ case FS_FAT16 : /* WORD aligned items */
+ res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2)));
+ if (res != FR_OK) break;
+ st_word(fs->win + clst * 2 % SS(fs), (WORD)val);
+ fs->wflag = 1;
+ break;
+
+ case FS_FAT32 : /* DWORD aligned items */
+#if _FS_EXFAT
+ case FS_EXFAT :
+#endif
+ res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4)));
+ if (res != FR_OK) break;
+ if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ val = (val & 0x0FFFFFFF) | (ld_dword(fs->win + clst * 4 % SS(fs)) & 0xF0000000);
+ }
+ st_dword(fs->win + clst * 4 % SS(fs), val);
+ fs->wflag = 1;
+ break;
+ }
+ }
+ return res;
+}
+
+#endif /* !_FS_READONLY */
+
+
+
+
+#if _FS_EXFAT && !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* exFAT: Accessing FAT and Allocation Bitmap */
+/*-----------------------------------------------------------------------*/
+
+/*---------------------------------------------*/
+/* exFAT: Find a contiguous free cluster block */
+/*---------------------------------------------*/
+
+static
+DWORD find_bitmap ( /* 0:No free cluster, 2..:Free cluster found, 0xFFFFFFFF:Disk error */
+ FATFS* fs, /* File system object */
+ DWORD clst, /* Cluster number to scan from */
+ DWORD ncl /* Number of contiguous clusters to find (1..) */
+)
+{
+ BYTE bm, bv;
+ UINT i;
+ DWORD val, scl, ctr;
+
+
+ clst -= 2; /* The first bit in the bitmap corresponds to cluster #2 */
+ if (clst >= fs->n_fatent - 2) clst = 0;
+ scl = val = clst; ctr = 0;
+ for (;;) {
+ if (move_window(fs, fs->database + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF; /* (assuming bitmap is located top of the cluster heap) */
+ i = val / 8 % SS(fs); bm = 1 << (val % 8);
+ do {
+ do {
+ bv = fs->win[i] & bm; bm <<= 1; /* Get bit value */
+ if (++val >= fs->n_fatent - 2) { /* Next cluster (with wrap-around) */
+ val = 0; bm = 0; i = 4096;
+ }
+ if (!bv) { /* Is it a free cluster? */
+ if (++ctr == ncl) return scl + 2; /* Check run length */
+ } else {
+ scl = val; ctr = 0; /* Encountered a live cluster, restart to scan */
+ }
+ if (val == clst) return 0; /* All cluster scanned? */
+ } while (bm);
+ bm = 1;
+ } while (++i < SS(fs));
+ }
+}
+
+
+/*------------------------------------*/
+/* exFAT: Set/Clear a block of bitmap */
+/*------------------------------------*/
+
+static
+FRESULT change_bitmap (
+ FATFS* fs, /* File system object */
+ DWORD clst, /* Cluster number to change from */
+ DWORD ncl, /* Number of clusters to be changed */
+ int bv /* bit value to be set (0 or 1) */
+)
+{
+ BYTE bm;
+ UINT i;
+ DWORD sect;
+
+
+ clst -= 2; /* The first bit corresponds to cluster #2 */
+ sect = fs->database + clst / 8 / SS(fs); /* Sector address (assuming bitmap is located top of the cluster heap) */
+ i = clst / 8 % SS(fs); /* Byte offset in the sector */
+ bm = 1 << (clst % 8); /* Bit mask in the byte */
+ for (;;) {
+ if (move_window(fs, sect++) != FR_OK) return FR_DISK_ERR;
+ do {
+ do {
+ if (bv == (int)((fs->win[i] & bm) != 0)) return FR_INT_ERR; /* Is the bit expected value? */
+ fs->win[i] ^= bm; /* Flip the bit */
+ fs->wflag = 1;
+ if (--ncl == 0) return FR_OK; /* All bits processed? */
+ } while (bm <<= 1); /* Next bit */
+ bm = 1;
+ } while (++i < SS(fs)); /* Next byte */
+ i = 0;
+ }
+}
+
+
+/*---------------------------------------------*/
+/* Complement contiguous part of the FAT chain */
+/*---------------------------------------------*/
+
+static
+FRESULT fill_fat_chain (
+ _FDID* obj /* Pointer to the corresponding object */
+)
+{
+ FRESULT res;
+ DWORD cl, n;
+
+ if (obj->stat == 3) { /* Has the object been changed 'fragmented'? */
+ for (cl = obj->sclust, n = obj->n_cont; n; cl++, n--) { /* Create cluster chain on the FAT */
+ res = put_fat(obj->fs, cl, cl + 1);
+ if (res != FR_OK) return res;
+ }
+ obj->stat = 0; /* Change status 'FAT chain is valid' */
+ }
+ return FR_OK;
+}
+
+#endif /* _FS_EXFAT && !_FS_READONLY */
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Remove a cluster chain */
+/*-----------------------------------------------------------------------*/
+static
+FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */
+ _FDID* obj, /* Corresponding object */
+ DWORD clst, /* Cluster to remove a chain from */
+ DWORD pclst /* Previous cluster of clst (0:an entire chain) */
+)
+{
+ FRESULT res = FR_OK;
+ DWORD nxt;
+ FATFS *fs = obj->fs;
+#if _FS_EXFAT || _USE_TRIM
+ DWORD scl = clst, ecl = clst;
+#endif
+#if _USE_TRIM
+ DWORD rt[2];
+#endif
+
+ if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Check if in valid range */
+
+ /* Mark the previous cluster 'EOC' on the FAT if it exists */
+ if (pclst && (!_FS_EXFAT || fs->fs_type != FS_EXFAT || obj->stat != 2)) {
+ res = put_fat(fs, pclst, 0xFFFFFFFF);
+ if (res != FR_OK) return res;
+ }
+
+ /* Remove the chain */
+ do {
+ nxt = get_fat(obj, clst); /* Get cluster status */
+ if (nxt == 0) break; /* Empty cluster? */
+ if (nxt == 1) return FR_INT_ERR; /* Internal error? */
+ if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error? */
+ if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ res = put_fat(fs, clst, 0); /* Mark the cluster 'free' on the FAT */
+ if (res != FR_OK) return res;
+ }
+ if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */
+ fs->free_clst++;
+ fs->fsi_flag |= 1;
+ }
+#if _FS_EXFAT || _USE_TRIM
+ if (ecl + 1 == nxt) { /* Is next cluster contiguous? */
+ ecl = nxt;
+ } else { /* End of contiguous cluster block */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */
+ if (res != FR_OK) return res;
+ }
+#endif
+#if _USE_TRIM
+ rt[0] = clust2sect(fs, scl); /* Start sector */
+ rt[1] = clust2sect(fs, ecl) + fs->csize - 1; /* End sector */
+ disk_ioctl(fs->drv, CTRL_TRIM, rt); /* Inform device the block can be erased */
+#endif
+ scl = ecl = nxt;
+ }
+#endif
+ clst = nxt; /* Next cluster */
+ } while (clst < fs->n_fatent); /* Repeat while not the last link */
+
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ if (pclst == 0) { /* Does object have no chain? */
+ obj->stat = 0; /* Change the object status 'initial' */
+ } else {
+ if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Did the chain got contiguous? */
+ obj->stat = 2; /* Change the object status 'contiguous' */
+ }
+ }
+ }
+#endif
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Stretch a chain or Create a new chain */
+/*-----------------------------------------------------------------------*/
+static
+DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */
+ _FDID* obj, /* Corresponding object */
+ DWORD clst /* Cluster# to stretch, 0:Create a new chain */
+)
+{
+ DWORD cs, ncl, scl;
+ FRESULT res;
+ FATFS *fs = obj->fs;
+
+
+ if (clst == 0) { /* Create a new chain */
+ scl = fs->last_clst; /* Get suggested cluster to start from */
+ if (scl == 0 || scl >= fs->n_fatent) scl = 1;
+ }
+ else { /* Stretch current chain */
+ cs = get_fat(obj, clst); /* Check the cluster status */
+ if (cs < 2) return 1; /* Invalid value */
+ if (cs == 0xFFFFFFFF) return cs; /* A disk error occurred */
+ if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */
+ scl = clst;
+ }
+
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ ncl = find_bitmap(fs, scl, 1); /* Find a free cluster */
+ if (ncl == 0 || ncl == 0xFFFFFFFF) return ncl; /* No free cluster or hard error? */
+ res = change_bitmap(fs, ncl, 1, 1); /* Mark the cluster 'in use' */
+ if (res == FR_INT_ERR) return 1;
+ if (res == FR_DISK_ERR) return 0xFFFFFFFF;
+ if (clst == 0) { /* Is it a new chain? */
+ obj->stat = 2; /* Set status 'contiguous chain' */
+ } else { /* This is a stretched chain */
+ if (obj->stat == 2 && ncl != scl + 1) { /* Is the chain got fragmented? */
+ obj->n_cont = scl - obj->sclust; /* Set size of the contiguous part */
+ obj->stat = 3; /* Change status 'just fragmented' */
+ }
+ }
+ } else
+#endif
+ { /* On the FAT12/16/32 volume */
+ ncl = scl; /* Start cluster */
+ for (;;) {
+ ncl++; /* Next cluster */
+ if (ncl >= fs->n_fatent) { /* Check wrap-around */
+ ncl = 2;
+ if (ncl > scl) return 0; /* No free cluster */
+ }
+ cs = get_fat(obj, ncl); /* Get the cluster status */
+ if (cs == 0) break; /* Found a free cluster */
+ if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* An error occurred */
+ if (ncl == scl) return 0; /* No free cluster */
+ }
+ }
+
+ if (_FS_EXFAT && fs->fs_type == FS_EXFAT && obj->stat == 2) { /* Is it a contiguous chain? */
+ res = FR_OK; /* FAT does not need to be written */
+ } else {
+ res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */
+ if (res == FR_OK && clst) {
+ res = put_fat(fs, clst, ncl); /* Link it from the previous one if needed */
+ }
+ }
+
+ if (res == FR_OK) { /* Update FSINFO if function succeeded. */
+ fs->last_clst = ncl;
+ if (fs->free_clst < fs->n_fatent - 2) fs->free_clst--;
+ fs->fsi_flag |= 1;
+ } else {
+ ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; /* Failed. Create error status */
+ }
+
+ return ncl; /* Return new cluster number or error status */
+}
+
+#endif /* !_FS_READONLY */
+
+
+
+
+#if _USE_FASTSEEK
+/*-----------------------------------------------------------------------*/
+/* FAT handling - Convert offset into cluster with link map table */
+/*-----------------------------------------------------------------------*/
+
+static
+DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t ofs /* File offset to be converted to cluster# */
+)
+{
+ DWORD cl, ncl, *tbl;
+ FATFS *fs = fp->obj.fs;
+
+
+ tbl = fp->cltbl + 1; /* Top of CLMT */
+ cl = (DWORD)(ofs / SS(fs) / fs->csize); /* Cluster order from top of the file */
+ for (;;) {
+ ncl = *tbl++; /* Number of cluters in the fragment */
+ if (ncl == 0) return 0; /* End of table? (error) */
+ if (cl < ncl) break; /* In this fragment? */
+ cl -= ncl; tbl++; /* Next fragment */
+ }
+ return cl + *tbl; /* Return the cluster number */
+}
+
+#endif /* _USE_FASTSEEK */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Set directory index */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_sdi ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp, /* Pointer to directory object */
+ DWORD ofs /* Offset of directory table */
+)
+{
+ DWORD csz, clst;
+ FATFS *fs = dp->obj.fs;
+
+
+ if (ofs >= (DWORD)((_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR) || ofs % SZDIRE) { /* Check range of offset and alignment */
+ return FR_INT_ERR;
+ }
+ dp->dptr = ofs; /* Set current offset */
+ clst = dp->obj.sclust; /* Table start cluster (0:root) */
+ if (clst == 0 && fs->fs_type >= FS_FAT32) { /* Replace cluster# 0 with root cluster# */
+ clst = fs->dirbase;
+ if (_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */
+ }
+
+ if (clst == 0) { /* Static table (root-directory in FAT12/16) */
+ if (ofs / SZDIRE >= fs->n_rootdir) return FR_INT_ERR; /* Is index out of range? */
+ dp->sect = fs->dirbase;
+
+ } else { /* Dynamic table (sub-directory or root-directory in FAT32+) */
+ csz = (DWORD)fs->csize * SS(fs); /* Bytes per cluster */
+ while (ofs >= csz) { /* Follow cluster chain */
+ clst = get_fat(&dp->obj, clst); /* Get next cluster */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (clst < 2 || clst >= fs->n_fatent) return FR_INT_ERR; /* Reached to end of table or internal error */
+ ofs -= csz;
+ }
+ dp->sect = clust2sect(fs, clst);
+ }
+ dp->clust = clst; /* Current cluster# */
+ if (!dp->sect) return FR_INT_ERR;
+ dp->sect += ofs / SS(fs); /* Sector# of the directory entry */
+ dp->dir = fs->win + (ofs % SS(fs)); /* Pointer to the entry in the win[] */
+
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Move directory table index next */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_next ( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */
+ DIR* dp, /* Pointer to the directory object */
+ int stretch /* 0: Do not stretch table, 1: Stretch table if needed */
+)
+{
+ DWORD ofs, clst;
+ FATFS *fs = dp->obj.fs;
+#if !_FS_READONLY
+ UINT n;
+#endif
+
+ ofs = dp->dptr + SZDIRE; /* Next entry */
+ if (!dp->sect || ofs >= (DWORD)((_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) return FR_NO_FILE; /* Report EOT when offset has reached max value */
+
+ if (ofs % SS(fs) == 0) { /* Sector changed? */
+ dp->sect++; /* Next sector */
+
+ if (!dp->clust) { /* Static table */
+ if (ofs / SZDIRE >= fs->n_rootdir) { /* Report EOT if it reached end of static table */
+ dp->sect = 0; return FR_NO_FILE;
+ }
+ }
+ else { /* Dynamic table */
+ if ((ofs / SS(fs) & (fs->csize - 1)) == 0) { /* Cluster changed? */
+ clst = get_fat(&dp->obj, dp->clust); /* Get next cluster */
+ if (clst <= 1) return FR_INT_ERR; /* Internal error */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ if (clst >= fs->n_fatent) { /* Reached end of dynamic table */
+#if !_FS_READONLY
+ if (!stretch) { /* If no stretch, report EOT */
+ dp->sect = 0; return FR_NO_FILE;
+ }
+ clst = create_chain(&dp->obj, dp->clust); /* Allocate a cluster */
+ if (clst == 0) return FR_DENIED; /* No free cluster */
+ if (clst == 1) return FR_INT_ERR; /* Internal error */
+ if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */
+ /* Clean-up the stretched table */
+ if (_FS_EXFAT) dp->obj.stat |= 4; /* The directory needs to be updated */
+ if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */
+ mem_set(fs->win, 0, SS(fs)); /* Clear window buffer */
+ for (n = 0, fs->winsect = clust2sect(fs, clst); n < fs->csize; n++, fs->winsect++) { /* Fill the new cluster with 0 */
+ fs->wflag = 1;
+ if (sync_window(fs) != FR_OK) return FR_DISK_ERR;
+ }
+ fs->winsect -= n; /* Restore window offset */
+#else
+ if (!stretch) dp->sect = 0; /* If no stretch, report EOT (this is to suppress warning) */
+ dp->sect = 0; return FR_NO_FILE; /* Report EOT */
+#endif
+ }
+ dp->clust = clst; /* Initialize data for new cluster */
+ dp->sect = clust2sect(fs, clst);
+ }
+ }
+ }
+ dp->dptr = ofs; /* Current entry */
+ dp->dir = fs->win + ofs % SS(fs); /* Pointer to the entry in the win[] */
+
+ return FR_OK;
+}
+
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Reserve a block of directory entries */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_alloc ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp, /* Pointer to the directory object */
+ UINT nent /* Number of contiguous entries to allocate */
+)
+{
+ FRESULT res;
+ UINT n;
+ FATFS *fs = dp->obj.fs;
+
+
+ res = dir_sdi(dp, 0);
+ if (res == FR_OK) {
+ n = 0;
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+#if _FS_EXFAT
+ if ((fs->fs_type == FS_EXFAT) ? (int)((dp->dir[XDIR_Type] & 0x80) == 0) : (int)(dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0)) {
+#else
+ if (dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0) {
+#endif
+ if (++n == nent) break; /* A block of contiguous free entries is found */
+ } else {
+ n = 0; /* Not a blank entry. Restart to search */
+ }
+ res = dir_next(dp, 1);
+ } while (res == FR_OK); /* Next entry with table stretch enabled */
+ }
+
+ if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */
+ return res;
+}
+
+#endif /* !_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* FAT: Directory handling - Load/Store start cluster number */
+/*-----------------------------------------------------------------------*/
+
+static
+DWORD ld_clust ( /* Returns the top cluster value of the SFN entry */
+ FATFS* fs, /* Pointer to the fs object */
+ const BYTE* dir /* Pointer to the key entry */
+)
+{
+ DWORD cl;
+
+ cl = ld_word(dir + DIR_FstClusLO);
+ if (fs->fs_type == FS_FAT32) {
+ cl |= (DWORD)ld_word(dir + DIR_FstClusHI) << 16;
+ }
+
+ return cl;
+}
+
+
+#if !_FS_READONLY
+static
+void st_clust (
+ FATFS* fs, /* Pointer to the fs object */
+ BYTE* dir, /* Pointer to the key entry */
+ DWORD cl /* Value to be set */
+)
+{
+ st_word(dir + DIR_FstClusLO, (WORD)cl);
+ if (fs->fs_type == FS_FAT32) {
+ st_word(dir + DIR_FstClusHI, (WORD)(cl >> 16));
+ }
+}
+#endif
+
+
+
+#if _USE_LFN != 0
+/*------------------------------------------------------------------------*/
+/* FAT-LFN: LFN handling */
+/*------------------------------------------------------------------------*/
+static
+const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* Offset of LFN characters in the directory entry */
+
+
+/*--------------------------------------------------------*/
+/* FAT-LFN: Compare a part of file name with an LFN entry */
+/*--------------------------------------------------------*/
+static
+int cmp_lfn ( /* 1:matched, 0:not matched */
+ const WCHAR* lfnbuf, /* Pointer to the LFN working buffer to be compared */
+ BYTE* dir /* Pointer to the directory entry containing the part of LFN */
+)
+{
+ UINT i, s;
+ WCHAR wc, uc;
+
+
+ if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */
+
+ i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */
+
+ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */
+ uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */
+ if (wc) {
+ if (i >= _MAX_LFN || ff_wtoupper(uc) != ff_wtoupper(lfnbuf[i++])) { /* Compare it */
+ return 0; /* Not matched */
+ }
+ wc = uc;
+ } else {
+ if (uc != 0xFFFF) return 0; /* Check filler */
+ }
+ }
+
+ if ((dir[LDIR_Ord] & LLEF) && wc && lfnbuf[i]) return 0; /* Last segment matched but different length */
+
+ return 1; /* The part of LFN matched */
+}
+
+
+#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 || _USE_LABEL || _FS_EXFAT
+/*-----------------------------------------------------*/
+/* FAT-LFN: Pick a part of file name from an LFN entry */
+/*-----------------------------------------------------*/
+static
+int pick_lfn ( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */
+ WCHAR* lfnbuf, /* Pointer to the LFN working buffer */
+ BYTE* dir /* Pointer to the LFN entry */
+)
+{
+ UINT i, s;
+ WCHAR wc, uc;
+
+
+ if (ld_word(dir + LDIR_FstClusLO) != 0) return 0; /* Check LDIR_FstClusLO */
+
+ i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */
+
+ for (wc = 1, s = 0; s < 13; s++) { /* Process all characters in the entry */
+ uc = ld_word(dir + LfnOfs[s]); /* Pick an LFN character */
+ if (wc) {
+ if (i >= _MAX_LFN) return 0; /* Buffer overflow? */
+ lfnbuf[i++] = wc = uc; /* Store it */
+ } else {
+ if (uc != 0xFFFF) return 0; /* Check filler */
+ }
+ }
+
+ if (dir[LDIR_Ord] & LLEF) { /* Put terminator if it is the last LFN part */
+ if (i >= _MAX_LFN) return 0; /* Buffer overflow? */
+ lfnbuf[i] = 0;
+ }
+
+ return 1; /* The part of LFN is valid */
+}
+#endif
+
+
+#if !_FS_READONLY
+/*-----------------------------------------*/
+/* FAT-LFN: Create an entry of LFN entries */
+/*-----------------------------------------*/
+static
+void put_lfn (
+ const WCHAR* lfn, /* Pointer to the LFN */
+ BYTE* dir, /* Pointer to the LFN entry to be created */
+ BYTE ord, /* LFN order (1-20) */
+ BYTE sum /* Checksum of the corresponding SFN */
+)
+{
+ UINT i, s;
+ WCHAR wc;
+
+
+ dir[LDIR_Chksum] = sum; /* Set checksum */
+ dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */
+ dir[LDIR_Type] = 0;
+ st_word(dir + LDIR_FstClusLO, 0);
+
+ i = (ord - 1) * 13; /* Get offset in the LFN working buffer */
+ s = wc = 0;
+ do {
+ if (wc != 0xFFFF) wc = lfn[i++]; /* Get an effective character */
+ st_word(dir + LfnOfs[s], wc); /* Put it */
+ if (wc == 0) wc = 0xFFFF; /* Padding characters for left locations */
+ } while (++s < 13);
+ if (wc == 0xFFFF || !lfn[i]) ord |= LLEF; /* Last LFN part is the start of LFN sequence */
+ dir[LDIR_Ord] = ord; /* Set the LFN order */
+}
+
+#endif /* !_FS_READONLY */
+#endif /* _USE_LFN != 0 */
+
+
+
+#if _USE_LFN != 0 && !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* FAT-LFN: Create a Numbered SFN */
+/*-----------------------------------------------------------------------*/
+
+static
+void gen_numname (
+ BYTE* dst, /* Pointer to the buffer to store numbered SFN */
+ const BYTE* src, /* Pointer to SFN */
+ const WCHAR* lfn, /* Pointer to LFN */
+ UINT seq /* Sequence number */
+)
+{
+ BYTE ns[8], c;
+ UINT i, j;
+ WCHAR wc;
+ DWORD sr;
+
+
+ mem_cpy(dst, src, 11);
+
+ if (seq > 5) { /* In case of many collisions, generate a hash number instead of sequential number */
+ sr = seq;
+ while (*lfn) { /* Create a CRC */
+ wc = *lfn++;
+ for (i = 0; i < 16; i++) {
+ sr = (sr << 1) + (wc & 1);
+ wc >>= 1;
+ if (sr & 0x10000) sr ^= 0x11021;
+ }
+ }
+ seq = (UINT)sr;
+ }
+
+ /* itoa (hexdecimal) */
+ i = 7;
+ do {
+ c = (BYTE)((seq % 16) + '0');
+ if (c > '9') c += 7;
+ ns[i--] = c;
+ seq /= 16;
+ } while (seq);
+ ns[i] = '~';
+
+ /* Append the number */
+ for (j = 0; j < i && dst[j] != ' '; j++) {
+ if (IsDBCS1(dst[j])) {
+ if (j == i - 1) break;
+ j++;
+ }
+ }
+ do {
+ dst[j++] = (i < 8) ? ns[i++] : ' ';
+ } while (j < 8);
+}
+#endif /* _USE_LFN != 0 && !_FS_READONLY */
+
+
+
+#if _USE_LFN != 0
+/*-----------------------------------------------------------------------*/
+/* FAT-LFN: Calculate checksum of an SFN entry */
+/*-----------------------------------------------------------------------*/
+
+static
+BYTE sum_sfn (
+ const BYTE* dir /* Pointer to the SFN entry */
+)
+{
+ BYTE sum = 0;
+ UINT n = 11;
+
+ do sum = (sum >> 1) + (sum << 7) + *dir++; while (--n);
+ return sum;
+}
+
+#endif /* _USE_LFN != 0 */
+
+
+
+#if _FS_EXFAT
+/*-----------------------------------------------------------------------*/
+/* exFAT: Checksum */
+/*-----------------------------------------------------------------------*/
+
+static
+WORD xdir_sum ( /* Get checksum of the directoly block */
+ const BYTE* dir /* Directory entry block to be calculated */
+)
+{
+ UINT i, szblk;
+ WORD sum;
+
+
+ szblk = (dir[XDIR_NumSec] + 1) * SZDIRE;
+ for (i = sum = 0; i < szblk; i++) {
+ if (i == XDIR_SetSum) { /* Skip sum field */
+ i++;
+ } else {
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i];
+ }
+ }
+ return sum;
+}
+
+
+
+static
+WORD xname_sum ( /* Get check sum (to be used as hash) of the name */
+ const WCHAR* name /* File name to be calculated */
+)
+{
+ WCHAR chr;
+ WORD sum = 0;
+
+
+ while ((chr = *name++) != 0) {
+ chr = ff_wtoupper(chr); /* File name needs to be ignored case */
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF);
+ sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8);
+ }
+ return sum;
+}
+
+
+#if !_FS_READONLY && _USE_MKFS
+static
+DWORD xsum32 (
+ BYTE dat, /* Data to be sumed */
+ DWORD sum /* Previous value */
+)
+{
+ sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat;
+ return sum;
+}
+#endif
+
+
+#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2
+/*------------------------------------------------------*/
+/* exFAT: Get object information from a directory block */
+/*------------------------------------------------------*/
+
+static
+void get_xdir_info (
+ BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */
+ FILINFO* fno /* Buffer to store the extracted file information */
+)
+{
+ UINT di, si;
+ WCHAR w;
+#if !_LFN_UNICODE
+ UINT nc;
+#endif
+
+ /* Get file name */
+#if _LFN_UNICODE
+ if (dirb[XDIR_NumName] <= _MAX_LFN) {
+ for (si = SZDIRE * 2, di = 0; di < dirb[XDIR_NumName]; si += 2, di++) {
+ if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */
+ w = ld_word(dirb + si); /* Get a character */
+ fno->fname[di] = w; /* Store it */
+ }
+ } else {
+ di = 0; /* Buffer overflow and inaccessible object */
+ }
+#else
+ for (si = SZDIRE * 2, di = nc = 0; nc < dirb[XDIR_NumName]; si += 2, nc++) {
+ if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */
+ w = ld_word(dirb + si); /* Get a character */
+ w = ff_convert(w, 0); /* Unicode -> OEM */
+ if (w == 0) { di = 0; break; } /* Could not be converted and inaccessible object */
+ if (_DF1S && w >= 0x100) { /* Put 1st byte if it is a DBC (always false at SBCS cfg) */
+ fno->fname[di++] = (char)(w >> 8);
+ }
+ if (di >= _MAX_LFN) { di = 0; break; } /* Buffer overflow and inaccessible object */
+ fno->fname[di++] = (char)w;
+ }
+#endif
+ if (di == 0) fno->fname[di++] = '?'; /* Inaccessible object? */
+ fno->fname[di] = 0; /* Terminate file name */
+
+ fno->altname[0] = 0; /* No SFN */
+ fno->fattrib = dirb[XDIR_Attr]; /* Attribute */
+ fno->fsize = (fno->fattrib & AM_DIR) ? 0 : ld_qword(dirb + XDIR_FileSize); /* Size */
+ fno->ftime = ld_word(dirb + XDIR_ModTime + 0); /* Time */
+ fno->fdate = ld_word(dirb + XDIR_ModTime + 2); /* Date */
+}
+
+#endif /* _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 */
+
+
+/*-----------------------------------*/
+/* exFAT: Get a directry entry block */
+/*-----------------------------------*/
+
+static
+FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */
+ DIR* dp /* Pointer to the reading direcotry object pointing the 85 entry */
+)
+{
+ FRESULT res;
+ UINT i, nent;
+ BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */
+
+
+ /* Load 85 entry */
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != 0x85) return FR_INT_ERR;
+ mem_cpy(dirb, dp->dir, SZDIRE);
+ nent = dirb[XDIR_NumSec] + 1;
+
+ /* Load C0 entry */
+ res = dir_next(dp, 0);
+ if (res != FR_OK) return res;
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != 0xC0) return FR_INT_ERR;
+ mem_cpy(dirb + SZDIRE, dp->dir, SZDIRE);
+
+ /* Load C1 entries */
+ if (nent < 3 || nent > 19) return FR_NO_FILE;
+ i = SZDIRE * 2; nent *= SZDIRE;
+ do {
+ res = dir_next(dp, 0);
+ if (res != FR_OK) return res;
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) return res;
+ if (dp->dir[XDIR_Type] != 0xC1) return FR_INT_ERR;
+ mem_cpy(dirb + i, dp->dir, SZDIRE);
+ i += SZDIRE;
+ } while (i < nent);
+
+ /* Sanity check */
+ if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR;
+
+ return FR_OK;
+}
+
+
+#if !_FS_READONLY || _FS_RPATH != 0
+/*------------------------------------------------*/
+/* exFAT: Load the object's directory entry block */
+/*------------------------------------------------*/
+static
+FRESULT load_obj_dir (
+ DIR* dp, /* Blank directory object to be used to access containing direcotry */
+ const _FDID* obj /* Object with containing directory information */
+)
+{
+ FRESULT res;
+
+
+ /* Open object containing directory */
+ dp->obj.fs = obj->fs;
+ dp->obj.sclust = obj->c_scl;
+ dp->obj.stat = (BYTE)obj->c_size;
+ dp->obj.objsize = obj->c_size & 0xFFFFFF00;
+ dp->blk_ofs = obj->c_ofs;
+
+ res = dir_sdi(dp, dp->blk_ofs); /* Goto the block location */
+ if (res == FR_OK) {
+ res = load_xdir(dp); /* Load the object's entry block */
+ }
+ return res;
+}
+#endif
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------*/
+/* exFAT: Store the directory block to the media */
+/*-----------------------------------------------*/
+static
+FRESULT store_xdir (
+ DIR* dp /* Pointer to the direcotry object */
+)
+{
+ FRESULT res;
+ UINT nent;
+ BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the direcotry entry block 85+C0+C1s */
+
+ /* Create set sum */
+ st_word(dirb + XDIR_SetSum, xdir_sum(dirb));
+ nent = dirb[XDIR_NumSec] + 1;
+
+ /* Store the set of directory to the volume */
+ res = dir_sdi(dp, dp->blk_ofs);
+ while (res == FR_OK) {
+ res = move_window(dp->obj.fs, dp->sect);
+ if (res != FR_OK) break;
+ mem_cpy(dp->dir, dirb, SZDIRE);
+ dp->obj.fs->wflag = 1;
+ if (--nent == 0) break;
+ dirb += SZDIRE;
+ res = dir_next(dp, 0);
+ }
+ return (res == FR_OK || res == FR_DISK_ERR) ? res : FR_INT_ERR;
+}
+
+
+
+/*-------------------------------------------*/
+/* exFAT: Create a new directory enrty block */
+/*-------------------------------------------*/
+
+static
+void create_xdir (
+ BYTE* dirb, /* Pointer to the direcotry entry block buffer */
+ const WCHAR* lfn /* Pointer to the nul terminated file name */
+)
+{
+ UINT i;
+ BYTE nb, nc;
+ WCHAR chr;
+
+
+ mem_set(dirb, 0, 2 * SZDIRE); /* Initialize 85+C0 entry */
+ dirb[XDIR_Type] = 0x85;
+ dirb[XDIR_Type + SZDIRE] = 0xC0;
+ st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */
+
+ i = SZDIRE * 2; /* C1 offset */
+ nc = 0; nb = 1; chr = 1;
+ do {
+ dirb[i++] = 0xC1; dirb[i++] = 0; /* Entry type C1 */
+ do { /* Fill name field */
+ if (chr && (chr = lfn[nc]) != 0) nc++; /* Get a character if exist */
+ st_word(dirb + i, chr); i += 2; /* Store it */
+ } while (i % SZDIRE);
+ nb++;
+ } while (lfn[nc]); /* Fill next entry if any char follows */
+
+ dirb[XDIR_NumName] = nc; /* Set name length */
+ dirb[XDIR_NumSec] = nb; /* Set number of C0+C1s */
+}
+
+#endif /* !_FS_READONLY */
+#endif /* _FS_EXFAT */
+
+
+
+#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 || _USE_LABEL || _FS_EXFAT
+/*-----------------------------------------------------------------------*/
+/* Read an object from the directory */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_read (
+ DIR* dp, /* Pointer to the directory object */
+ int vol /* Filtered by 0:file/directory or 1:volume label */
+)
+{
+ FRESULT res = FR_NO_FILE;
+ FATFS *fs = dp->obj.fs;
+ BYTE a, c;
+#if _USE_LFN != 0
+ BYTE ord = 0xFF, sum = 0xFF;
+#endif
+
+ while (dp->sect) {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ c = dp->dir[DIR_Name]; /* Test for the entry type */
+ if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of the directory */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ if (_USE_LABEL && vol) {
+ if (c == 0x83) break; /* Volume label entry? */
+ } else {
+ if (c == 0x85) { /* Start of the file entry block? */
+ dp->blk_ofs = dp->dptr; /* Get location of the block */
+ res = load_xdir(dp); /* Load the entry block */
+ if (res == FR_OK) {
+ dp->obj.attr = fs->dirbuf[XDIR_Attr] & AM_MASK; /* Get attribute */
+ }
+ break;
+ }
+ }
+ } else
+#endif
+ { /* On the FAT12/16/32 volume */
+ dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */
+#if _USE_LFN != 0 /* LFN configuration */
+ if (c == DDEM || c == '.' || (int)((a & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */
+ ord = 0xFF;
+ } else {
+ if (a == AM_LFN) { /* An LFN entry is found */
+ if (c & LLEF) { /* Is it start of an LFN sequence? */
+ sum = dp->dir[LDIR_Chksum];
+ c &= (BYTE)~LLEF; ord = c;
+ dp->blk_ofs = dp->dptr;
+ }
+ /* Check LFN validity and capture it */
+ ord = (c == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
+ } else { /* An SFN entry is found */
+ if (ord || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */
+ dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */
+ }
+ break;
+ }
+ }
+#else /* Non LFN configuration */
+ if (c != DDEM && c != '.' && a != AM_LFN && (int)((a & ~AM_ARC) == AM_VOL) == vol) { /* Is it a valid entry? */
+ break;
+ }
+#endif
+ }
+ res = dir_next(dp, 0); /* Next entry */
+ if (res != FR_OK) break;
+ }
+
+ if (res != FR_OK) dp->sect = 0; /* Terminate the read operation on error or EOT */
+ return res;
+}
+
+#endif /* _FS_MINIMIZE <= 1 || _USE_LABEL || _FS_RPATH >= 2 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Directory handling - Find an object in the directory */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
+ DIR* dp /* Pointer to the directory object with the file name */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+ BYTE c;
+#if _USE_LFN != 0
+ BYTE a, ord, sum;
+#endif
+
+ res = dir_sdi(dp, 0); /* Rewind directory object */
+ if (res != FR_OK) return res;
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ BYTE nc;
+ UINT di, ni;
+ WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */
+
+ while ((res = dir_read(dp, 0)) == FR_OK) { /* Read an item */
+ if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip the comparison if hash value mismatched */
+ for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */
+ if ((di % SZDIRE) == 0) di += 2;
+ if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break;
+ }
+ if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */
+ }
+ return res;
+ }
+#endif
+ /* On the FAT12/16/32 volume */
+#if _USE_LFN != 0
+ ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+#endif
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ c = dp->dir[DIR_Name];
+ if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */
+#if _USE_LFN != 0 /* LFN configuration */
+ dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK;
+ if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */
+ ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+ } else {
+ if (a == AM_LFN) { /* An LFN entry is found */
+ if (!(dp->fn[NSFLAG] & NS_NOLFN)) {
+ if (c & LLEF) { /* Is it start of LFN sequence? */
+ sum = dp->dir[LDIR_Chksum];
+ c &= (BYTE)~LLEF; ord = c; /* LFN start order */
+ dp->blk_ofs = dp->dptr; /* Start offset of LFN */
+ }
+ /* Check validity of the LFN entry and compare it with given name */
+ ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
+ }
+ } else { /* An SFN entry is found */
+ if (!ord && sum == sum_sfn(dp->dir)) break; /* LFN matched? */
+ if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */
+ ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
+ }
+ }
+#else /* Non LFN configuration */
+ dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK;
+ if (!(dp->dir[DIR_Attr] & AM_VOL) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */
+#endif
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK);
+
+ return res;
+}
+
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Register an object to the directory */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_register ( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */
+ DIR* dp /* Target directory with object name to be created */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+#if _USE_LFN != 0 /* LFN configuration */
+ UINT n, nlen, nent;
+ BYTE sn[12], sum;
+
+
+ if (dp->fn[NSFLAG] & (NS_DOT | NS_NONAME)) return FR_INVALID_NAME; /* Check name validity */
+ for (nlen = 0; fs->lfnbuf[nlen]; nlen++) ; /* Get lfn length */
+
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ DIR dj;
+
+ nent = (nlen + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */
+ res = dir_alloc(dp, nent); /* Allocate entries */
+ if (res != FR_OK) return res;
+ dp->blk_ofs = dp->dptr - SZDIRE * (nent - 1); /* Set block position */
+
+ if (dp->obj.sclust != 0 && (dp->obj.stat & 4)) { /* Has the sub-directory been stretched? */
+ dp->obj.stat &= 3;
+ dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase object size by cluster size */
+ res = fill_fat_chain(&dp->obj); /* Complement FAT chain if needed */
+ if (res != FR_OK) return res;
+ res = load_obj_dir(&dj, &dp->obj);
+ if (res != FR_OK) return res; /* Load the object status */
+ st_qword(fs->dirbuf + XDIR_FileSize, dp->obj.objsize); /* Update the allocation status */
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, dp->obj.objsize);
+ fs->dirbuf[XDIR_GenFlags] = dp->obj.stat | 1;
+ res = store_xdir(&dj); /* Store the object status */
+ if (res != FR_OK) return res;
+ }
+
+ create_xdir(fs->dirbuf, fs->lfnbuf); /* Create on-memory directory block to be written later */
+ return FR_OK;
+ }
+#endif
+ /* On the FAT12/16/32 volume */
+ mem_cpy(sn, dp->fn, 12);
+ if (sn[NSFLAG] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */
+ dp->fn[NSFLAG] = NS_NOLFN; /* Find only SFN */
+ for (n = 1; n < 100; n++) {
+ gen_numname(dp->fn, sn, fs->lfnbuf, n); /* Generate a numbered name */
+ res = dir_find(dp); /* Check if the name collides with existing SFN */
+ if (res != FR_OK) break;
+ }
+ if (n == 100) return FR_DENIED; /* Abort if too many collisions */
+ if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */
+ dp->fn[NSFLAG] = sn[NSFLAG];
+ }
+
+ /* Create an SFN with/without LFNs. */
+ nent = (sn[NSFLAG] & NS_LFN) ? (nlen + 12) / 13 + 1 : 1; /* Number of entries to allocate */
+ res = dir_alloc(dp, nent); /* Allocate entries */
+ if (res == FR_OK && --nent) { /* Set LFN entry if needed */
+ res = dir_sdi(dp, dp->dptr - nent * SZDIRE);
+ if (res == FR_OK) {
+ sum = sum_sfn(dp->fn); /* Checksum value of the SFN tied to the LFN */
+ do { /* Store LFN entries in bottom first */
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ put_lfn(fs->lfnbuf, dp->dir, (BYTE)nent, sum);
+ fs->wflag = 1;
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK && --nent);
+ }
+ }
+
+#else /* Non LFN configuration */
+ res = dir_alloc(dp, 1); /* Allocate an entry for SFN */
+
+#endif
+
+ /* Set SFN entry */
+ if (res == FR_OK) {
+ res = move_window(fs, dp->sect);
+ if (res == FR_OK) {
+ mem_set(dp->dir, 0, SZDIRE); /* Clean the entry */
+ mem_cpy(dp->dir + DIR_Name, dp->fn, 11); /* Put SFN */
+#if _USE_LFN != 0
+ dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */
+#endif
+ fs->wflag = 1;
+ }
+ }
+
+ return res;
+}
+
+#endif /* !_FS_READONLY */
+
+
+
+#if !_FS_READONLY && _FS_MINIMIZE == 0
+/*-----------------------------------------------------------------------*/
+/* Remove an object from the directory */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT dir_remove ( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */
+ DIR* dp /* Directory object pointing the entry to be removed */
+)
+{
+ FRESULT res;
+ FATFS *fs = dp->obj.fs;
+#if _USE_LFN != 0 /* LFN configuration */
+ DWORD last = dp->dptr;
+
+ res = (dp->blk_ofs == 0xFFFFFFFF) ? FR_OK : dir_sdi(dp, dp->blk_ofs); /* Goto top of the entry block if LFN is exist */
+ if (res == FR_OK) {
+ do {
+ res = move_window(fs, dp->sect);
+ if (res != FR_OK) break;
+ /* Mark an entry 'deleted' */
+ if (_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ dp->dir[XDIR_Type] &= 0x7F;
+ } else { /* On the FAT12/16/32 volume */
+ dp->dir[DIR_Name] = DDEM;
+ }
+ fs->wflag = 1;
+ if (dp->dptr >= last) break; /* If reached last entry then all entries of the object has been deleted. */
+ res = dir_next(dp, 0); /* Next entry */
+ } while (res == FR_OK);
+ if (res == FR_NO_FILE) res = FR_INT_ERR;
+ }
+#else /* Non LFN configuration */
+
+ res = move_window(fs, dp->sect);
+ if (res == FR_OK) {
+ dp->dir[DIR_Name] = DDEM;
+ fs->wflag = 1;
+ }
+#endif
+
+ return res;
+}
+
+#endif /* !_FS_READONLY && _FS_MINIMIZE == 0 */
+
+
+
+#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2
+/*-----------------------------------------------------------------------*/
+/* Get file information from directory entry */
+/*-----------------------------------------------------------------------*/
+
+static
+void get_fileinfo ( /* No return code */
+ DIR* dp, /* Pointer to the directory object */
+ FILINFO* fno /* Pointer to the file information to be filled */
+)
+{
+ UINT i, j;
+ TCHAR c;
+ DWORD tm;
+#if _USE_LFN != 0
+ WCHAR w, lfv;
+ FATFS *fs = dp->obj.fs;
+#endif
+
+
+ fno->fname[0] = 0; /* Invaidate file info */
+ if (!dp->sect) return; /* Exit if read pointer has reached end of directory */
+
+#if _USE_LFN != 0 /* LFN configuration */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ get_xdir_info(fs->dirbuf, fno);
+ return;
+ } else
+#endif
+ { /* On the FAT12/16/32 volume */
+ if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */
+ i = j = 0;
+ while ((w = fs->lfnbuf[j++]) != 0) { /* Get an LFN character */
+#if !_LFN_UNICODE
+ w = ff_convert(w, 0); /* Unicode -> OEM */
+ if (w == 0) { i = 0; break; } /* No LFN if it could not be converted */
+ if (_DF1S && w >= 0x100) { /* Put 1st byte if it is a DBC (always false at SBCS cfg) */
+ fno->fname[i++] = (char)(w >> 8);
+ }
+#endif
+ if (i >= _MAX_LFN) { i = 0; break; } /* No LFN if buffer overflow */
+ fno->fname[i++] = (TCHAR)w;
+ }
+ fno->fname[i] = 0; /* Terminate the LFN */
+ }
+ }
+
+ i = j = 0;
+ lfv = fno->fname[i]; /* LFN is exist if non-zero */
+ while (i < 11) { /* Copy name body and extension */
+ c = (TCHAR)dp->dir[i++];
+ if (c == ' ') continue; /* Skip padding spaces */
+ if (c == RDDEM) c = (TCHAR)DDEM; /* Restore replaced DDEM character */
+ if (i == 9) { /* Insert a . if extension is exist */
+ if (!lfv) fno->fname[j] = '.';
+ fno->altname[j++] = '.';
+ }
+#if _LFN_UNICODE
+ if (IsDBCS1(c) && i != 8 && i != 11 && IsDBCS2(dp->dir[i])) {
+ c = c << 8 | dp->dir[i++];
+ }
+ c = ff_convert(c, 1); /* OEM -> Unicode */
+ if (!c) c = '?';
+#endif
+ fno->altname[j] = c;
+ if (!lfv) {
+ if (IsUpper(c) && (dp->dir[DIR_NTres] & (i >= 9 ? NS_EXT : NS_BODY))) {
+ c += 0x20; /* To lower */
+ }
+ fno->fname[j] = c;
+ }
+ j++;
+ }
+ if (!lfv) {
+ fno->fname[j] = 0;
+ if (!dp->dir[DIR_NTres]) j = 0; /* Altname is no longer needed if neither LFN nor case info is exist. */
+ }
+ fno->altname[j] = 0; /* Terminate the SFN */
+
+#else /* Non-LFN configuration */
+ i = j = 0;
+ while (i < 11) { /* Copy name body and extension */
+ c = (TCHAR)dp->dir[i++];
+ if (c == ' ') continue; /* Skip padding spaces */
+ if (c == RDDEM) c = (TCHAR)DDEM; /* Restore replaced DDEM character */
+ if (i == 9) fno->fname[j++] = '.'; /* Insert a . if extension is exist */
+ fno->fname[j++] = c;
+ }
+ fno->fname[j] = 0;
+#endif
+
+ fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */
+ fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */
+ tm = ld_dword(dp->dir + DIR_ModTime); /* Timestamp */
+ fno->ftime = (WORD)tm; fno->fdate = (WORD)(tm >> 16);
+}
+
+#endif /* _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 */
+
+
+
+#if _USE_FIND && _FS_MINIMIZE <= 1
+/*-----------------------------------------------------------------------*/
+/* Pattern matching */
+/*-----------------------------------------------------------------------*/
+
+static
+WCHAR get_achar ( /* Get a character and advances ptr 1 or 2 */
+ const TCHAR** ptr /* Pointer to pointer to the SBCS/DBCS/Unicode string */
+)
+{
+#if !_LFN_UNICODE
+ WCHAR chr;
+
+ chr = (BYTE)*(*ptr)++; /* Get a byte */
+ if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */
+#ifdef _EXCVT
+ if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */
+#else
+ if (IsDBCS1(chr) && IsDBCS2(**ptr)) { /* Get DBC 2nd byte if needed */
+ chr = chr << 8 | (BYTE)*(*ptr)++;
+ }
+#endif
+ return chr;
+#else
+ return ff_wtoupper(*(*ptr)++); /* Get a word and to upper */
+#endif
+}
+
+
+static
+int pattern_matching ( /* 0:not matched, 1:matched */
+ const TCHAR* pat, /* Matching pattern */
+ const TCHAR* nam, /* String to be tested */
+ int skip, /* Number of pre-skip chars (number of ?s) */
+ int inf /* Infinite search (* specified) */
+)
+{
+ const TCHAR *pp, *np;
+ WCHAR pc, nc;
+ int nm, nx;
+
+
+ while (skip--) { /* Pre-skip name chars */
+ if (!get_achar(&nam)) return 0; /* Branch mismatched if less name chars */
+ }
+ if (!*pat && inf) return 1; /* (short circuit) */
+
+ do {
+ pp = pat; np = nam; /* Top of pattern and name to match */
+ for (;;) {
+ if (*pp == '?' || *pp == '*') { /* Wildcard? */
+ nm = nx = 0;
+ do { /* Analyze the wildcard chars */
+ if (*pp++ == '?') nm++; else nx = 1;
+ } while (*pp == '?' || *pp == '*');
+ if (pattern_matching(pp, np, nm, nx)) return 1; /* Test new branch (recurs upto number of wildcard blocks in the pattern) */
+ nc = *np; break; /* Branch mismatched */
+ }
+ pc = get_achar(&pp); /* Get a pattern char */
+ nc = get_achar(&np); /* Get a name char */
+ if (pc != nc) break; /* Branch mismatched? */
+ if (pc == 0) return 1; /* Branch matched? (matched at end of both strings) */
+ }
+ get_achar(&nam); /* nam++ */
+ } while (inf && nc); /* Retry until end of name if infinite search is specified */
+
+ return 0;
+}
+
+#endif /* _USE_FIND && _FS_MINIMIZE <= 1 */
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Pick a top segment and create the object name in directory form */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */
+ DIR* dp, /* Pointer to the directory object */
+ const TCHAR** path /* Pointer to pointer to the segment in the path string */
+)
+{
+#if _USE_LFN != 0 /* LFN configuration */
+ BYTE b, cf;
+ WCHAR w, *lfn;
+ UINT i, ni, si, di;
+ const TCHAR *p;
+
+ /* Create LFN in Unicode */
+ p = *path; lfn = dp->obj.fs->lfnbuf; si = di = 0;
+ for (;;) {
+ w = p[si++]; /* Get a character */
+ if (w < ' ') break; /* Break if end of the path name */
+ if (w == '/' || w == '\\') { /* Break if a separator is found */
+ while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */
+ break;
+ }
+ if (di >= _MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */
+#if !_LFN_UNICODE
+ w &= 0xFF;
+ if (IsDBCS1(w)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */
+ b = (BYTE)p[si++]; /* Get 2nd byte */
+ w = (w << 8) + b; /* Create a DBC */
+ if (!IsDBCS2(b)) return FR_INVALID_NAME; /* Reject invalid sequence */
+ }
+ w = ff_convert(w, 1); /* Convert ANSI/OEM to Unicode */
+ if (!w) return FR_INVALID_NAME; /* Reject invalid code */
+#endif
+ if (w < 0x80 && chk_chr("\"*:<>\?|\x7F", w)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */
+ lfn[di++] = w; /* Store the Unicode character */
+ }
+ *path = &p[si]; /* Return pointer to the next segment */
+ cf = (w < ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */
+#if _FS_RPATH != 0
+ if ((di == 1 && lfn[di - 1] == '.') ||
+ (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.')) { /* Is this segment a dot name? */
+ lfn[di] = 0;
+ for (i = 0; i < 11; i++) /* Create dot name for SFN entry */
+ dp->fn[i] = (i < di) ? '.' : ' ';
+ dp->fn[i] = cf | NS_DOT; /* This is a dot entry */
+ return FR_OK;
+ }
+#endif
+ while (di) { /* Snip off trailing spaces and dots if exist */
+ w = lfn[di - 1];
+ if (w != ' ' && w != '.') break;
+ di--;
+ }
+ lfn[di] = 0; /* LFN is created */
+ if (di == 0) return FR_INVALID_NAME; /* Reject nul name */
+
+ /* Create SFN in directory form */
+ mem_set(dp->fn, ' ', 11);
+ for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ; /* Strip leading spaces and dots */
+ if (si) cf |= NS_LOSS | NS_LFN;
+ while (di && lfn[di - 1] != '.') di--; /* Find extension (di<=si: no extension) */
+
+ i = b = 0; ni = 8;
+ for (;;) {
+ w = lfn[si++]; /* Get an LFN character */
+ if (!w) break; /* Break on end of the LFN */
+ if (w == ' ' || (w == '.' && si != di)) { /* Remove spaces and dots */
+ cf |= NS_LOSS | NS_LFN; continue;
+ }
+
+ if (i >= ni || si == di) { /* Extension or end of SFN */
+ if (ni == 11) { /* Long extension */
+ cf |= NS_LOSS | NS_LFN; break;
+ }
+ if (si != di) cf |= NS_LOSS | NS_LFN; /* Out of 8.3 format */
+ if (si > di) break; /* No extension */
+ si = di; i = 8; ni = 11; /* Enter extension section */
+ b <<= 2; continue;
+ }
+
+ if (w >= 0x80) { /* Non ASCII character */
+#ifdef _EXCVT
+ w = ff_convert(w, 0); /* Unicode -> OEM code */
+ if (w) w = ExCvt[w - 0x80]; /* Convert extended character to upper (SBCS) */
+#else
+ w = ff_convert(ff_wtoupper(w), 0); /* Upper converted Unicode -> OEM code */
+#endif
+ cf |= NS_LFN; /* Force create LFN entry */
+ }
+
+ if (_DF1S && w >= 0x100) { /* Is this DBC? (always false at SBCS cfg) */
+ if (i >= ni - 1) {
+ cf |= NS_LOSS | NS_LFN; i = ni; continue;
+ }
+ dp->fn[i++] = (BYTE)(w >> 8);
+ } else { /* SBC */
+ if (!w || chk_chr("+,;=[]", w)) { /* Replace illegal characters for SFN */
+ w = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */
+ } else {
+ if (IsUpper(w)) { /* ASCII large capital */
+ b |= 2;
+ } else {
+ if (IsLower(w)) { /* ASCII small capital */
+ b |= 1; w -= 0x20;
+ }
+ }
+ }
+ }
+ dp->fn[i++] = (BYTE)w;
+ }
+
+ if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */
+
+ if (ni == 8) b <<= 2;
+ if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* Create LFN entry when there are composite capitals */
+ if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */
+ if ((b & 0x03) == 0x01) cf |= NS_EXT; /* NT flag (Extension has only small capital) */
+ if ((b & 0x0C) == 0x04) cf |= NS_BODY; /* NT flag (Filename has only small capital) */
+ }
+
+ dp->fn[NSFLAG] = cf; /* SFN is created */
+
+ return FR_OK;
+
+
+#else /* _USE_LFN != 0 : Non-LFN configuration */
+ BYTE c, d, *sfn;
+ UINT ni, si, i;
+ const char *p;
+
+ /* Create file name in directory form */
+ p = *path; sfn = dp->fn;
+ mem_set(sfn, ' ', 11);
+ si = i = 0; ni = 8;
+#if _FS_RPATH != 0
+ if (p[si] == '.') { /* Is this a dot entry? */
+ for (;;) {
+ c = (BYTE)p[si++];
+ if (c != '.' || si >= 3) break;
+ sfn[i++] = c;
+ }
+ if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME;
+ *path = p + si; /* Return pointer to the next segment */
+ sfn[NSFLAG] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of the path */
+ return FR_OK;
+ }
+#endif
+ for (;;) {
+ c = (BYTE)p[si++];
+ if (c <= ' ') break; /* Break if end of the path name */
+ if (c == '/' || c == '\\') { /* Break if a separator is found */
+ while (p[si] == '/' || p[si] == '\\') si++; /* Skip duplicated separator if exist */
+ break;
+ }
+ if (c == '.' || i >= ni) { /* End of body or over size? */
+ if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Over size or invalid dot */
+ i = 8; ni = 11; /* Goto extension */
+ continue;
+ }
+ if (c >= 0x80) { /* Extended character? */
+#ifdef _EXCVT
+ c = ExCvt[c - 0x80]; /* To upper extended characters (SBCS cfg) */
+#else
+#if !_DF1S
+ return FR_INVALID_NAME; /* Reject extended characters (ASCII only cfg) */
+#endif
+#endif
+ }
+ if (IsDBCS1(c)) { /* Check if it is a DBC 1st byte (always false at SBCS cfg.) */
+ d = (BYTE)p[si++]; /* Get 2nd byte */
+ if (!IsDBCS2(d) || i >= ni - 1) return FR_INVALID_NAME; /* Reject invalid DBC */
+ sfn[i++] = c;
+ sfn[i++] = d;
+ } else { /* SBC */
+ if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) return FR_INVALID_NAME; /* Reject illegal chrs for SFN */
+ if (IsLower(c)) c -= 0x20; /* To upper */
+ sfn[i++] = c;
+ }
+ }
+ *path = p + si; /* Return pointer to the next segment */
+ if (i == 0) return FR_INVALID_NAME; /* Reject nul string */
+
+ if (sfn[0] == DDEM) sfn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */
+ sfn[NSFLAG] = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */
+
+ return FR_OK;
+#endif /* _USE_LFN != 0 */
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Follow a file path */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */
+ DIR* dp, /* Directory object to return last directory and found object */
+ const TCHAR* path /* Full-path string to find a file or directory */
+)
+{
+ FRESULT res;
+ BYTE ns;
+ _FDID *obj = &dp->obj;
+ FATFS *fs = obj->fs;
+
+
+#if _FS_RPATH != 0
+ if (*path != '/' && *path != '\\') { /* Without heading separator */
+ obj->sclust = fs->cdir; /* Start from the current directory */
+ } else
+#endif
+ { /* With heading separator */
+ while (*path == '/' || *path == '\\') path++; /* Strip heading separator */
+ obj->sclust = 0; /* Start from the root directory */
+ }
+#if _FS_EXFAT && _FS_RPATH != 0
+ if (fs->fs_type == FS_EXFAT && obj->sclust) { /* Retrieve the sub-directory status if needed */
+ DIR dj;
+
+ obj->c_scl = fs->cdc_scl;
+ obj->c_size = fs->cdc_size;
+ obj->c_ofs = fs->cdc_ofs;
+ res = load_obj_dir(&dj, obj);
+ if (res != FR_OK) return res;
+ obj->objsize = ld_dword(fs->dirbuf + XDIR_FileSize);
+ obj->stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ }
+#endif
+
+ if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */
+ dp->fn[NSFLAG] = NS_NONAME;
+ res = dir_sdi(dp, 0);
+
+ } else { /* Follow path */
+ for (;;) {
+ res = create_name(dp, &path); /* Get a segment name of the path */
+ if (res != FR_OK) break;
+ res = dir_find(dp); /* Find an object with the segment name */
+ ns = dp->fn[NSFLAG];
+ if (res != FR_OK) { /* Failed to find the object */
+ if (res == FR_NO_FILE) { /* Object is not found */
+ if (_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */
+ if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */
+ dp->fn[NSFLAG] = NS_NONAME;
+ res = FR_OK;
+ } else { /* Could not find the object */
+ if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */
+ }
+ }
+ break;
+ }
+ if (ns & NS_LAST) break; /* Last segment matched. Function completed. */
+ /* Get into the sub-directory */
+ if (!(obj->attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */
+ res = FR_NO_PATH; break;
+ }
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ obj->c_scl = obj->sclust; /* Save containing directory information for next dir */
+ obj->c_size = ((DWORD)obj->objsize & 0xFFFFFF00) | obj->stat;
+ obj->c_ofs = dp->blk_ofs;
+ obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Open next directory */
+ obj->stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize);
+ } else
+#endif
+ {
+ obj->sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */
+ }
+ }
+ }
+
+ return res;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Get logical drive number from path name */
+/*-----------------------------------------------------------------------*/
+
+static
+int get_ldnumber ( /* Returns logical drive number (-1:invalid drive) */
+ const TCHAR** path /* Pointer to pointer to the path name */
+)
+{
+ const TCHAR *tp, *tt;
+ UINT i;
+ int vol = -1;
+#if _STR_VOLUME_ID /* Find string drive id */
+ static const char* const str[] = {_VOLUME_STRS};
+ const char *sp;
+ char c;
+ TCHAR tc;
+#endif
+
+
+ if (*path) { /* If the pointer is not a null */
+ for (tt = *path; (UINT)*tt >= (_USE_LFN ? ' ' : '!') && *tt != ':'; tt++) ; /* Find ':' in the path */
+ if (*tt == ':') { /* If a ':' is exist in the path name */
+ tp = *path;
+ i = *tp++ - '0';
+ if (i < 10 && tp == tt) { /* Is there a numeric drive id? */
+ if (i < _VOLUMES) { /* If a drive id is found, get the value and strip it */
+ vol = (int)i;
+ *path = ++tt;
+ }
+ }
+#if _STR_VOLUME_ID
+ else { /* No numeric drive number, find string drive id */
+ i = 0; tt++;
+ do {
+ sp = str[i]; tp = *path;
+ do { /* Compare a string drive id with path name */
+ c = *sp++; tc = *tp++;
+ if (IsLower(tc)) tc -= 0x20;
+ } while (c && (TCHAR)c == tc);
+ } while ((c || tp != tt) && ++i < _VOLUMES); /* Repeat for each id until pattern match */
+ if (i < _VOLUMES) { /* If a drive id is found, get the value and strip it */
+ vol = (int)i;
+ *path = tt;
+ }
+ }
+#endif
+ return vol;
+ }
+#if _FS_RPATH != 0 && _VOLUMES >= 2
+ vol = CurrVol; /* Current drive */
+#else
+ vol = 0; /* Drive 0 */
+#endif
+ }
+ return vol;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Load a sector and check if it is an FAT boot sector */
+/*-----------------------------------------------------------------------*/
+
+static
+BYTE check_fs ( /* 0:FAT, 1:exFAT, 2:Valid BS but not FAT, 3:Not a BS, 4:Disk error */
+ FATFS* fs, /* File system object */
+ DWORD sect /* Sector# (lba) to check if it is an FAT-VBR or not */
+)
+{
+ fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */
+ if (move_window(fs, sect) != FR_OK) return 4; /* Load boot record */
+
+ if (ld_word(fs->win + BS_55AA) != 0xAA55) return 3; /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */
+
+ if (fs->win[BS_JmpBoot] == 0xE9 || (fs->win[BS_JmpBoot] == 0xEB && fs->win[BS_JmpBoot + 2] == 0x90)) {
+ if ((ld_dword(fs->win + BS_FilSysType) & 0xFFFFFF) == 0x544146) return 0; /* Check "FAT" string */
+ if (ld_dword(fs->win + BS_FilSysType32) == 0x33544146) return 0; /* Check "FAT3" string */
+ }
+#if _FS_EXFAT
+ if (!mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1;
+#endif
+ return 2;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Find logical drive and check if the volume is mounted */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT find_volume ( /* FR_OK(0): successful, !=0: any error occurred */
+ const TCHAR** path, /* Pointer to pointer to the path name (drive number) */
+ FATFS** rfs, /* Pointer to pointer to the found file system object */
+ BYTE mode /* !=0: Check write protection for write access */
+)
+{
+ BYTE fmt, *pt;
+ int vol;
+ DSTATUS stat;
+ DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4];
+ WORD nrsv;
+ FATFS *fs;
+ UINT i;
+
+
+ /* Get logical drive number */
+ *rfs = 0;
+ vol = get_ldnumber(path);
+ if (vol < 0) return FR_INVALID_DRIVE;
+
+ /* Check if the file system object is valid or not */
+ fs = FatFs[vol]; /* Get pointer to the file system object */
+ if (!fs) return FR_NOT_ENABLED; /* Is the file system object available? */
+
+ ENTER_FF(fs); /* Lock the volume */
+ *rfs = fs; /* Return pointer to the file system object */
+
+ mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */
+ if (fs->fs_type) { /* If the volume has been mounted */
+ stat = disk_status(fs->drv);
+ if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */
+ if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */
+ return FR_WRITE_PROTECTED;
+ }
+ return FR_OK; /* The file system object is valid */
+ }
+ }
+
+ /* The file system object is not valid. */
+ /* Following code attempts to mount the volume. (analyze BPB and initialize the fs object) */
+
+ fs->fs_type = 0; /* Clear the file system object */
+ fs->drv = LD2PD(vol); /* Bind the logical drive and a physical drive */
+ stat = disk_initialize(fs->drv); /* Initialize the physical drive */
+ if (stat & STA_NOINIT) { /* Check if the initialization succeeded */
+ return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */
+ }
+ if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */
+ return FR_WRITE_PROTECTED;
+ }
+#if _MAX_SS != _MIN_SS /* Get sector size (multiple sector size cfg only) */
+ if (disk_ioctl(fs->drv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR;
+ if (SS(fs) > _MAX_SS || SS(fs) < _MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR;
+#endif
+ /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */
+ bsect = 0;
+ fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT-VBR as SFD */
+ if (fmt == 2 || (fmt < 2 && LD2PT(vol) != 0)) { /* Not an FAT-VBR or forced partition number */
+ for (i = 0; i < 4; i++) { /* Get partition offset */
+ pt = fs->win + (MBR_Table + i * SZ_PTE);
+ br[i] = pt[PTE_System] ? ld_dword(pt + PTE_StLba) : 0;
+ }
+ i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */
+ if (i) i--;
+ do { /* Find an FAT volume */
+ bsect = br[i];
+ fmt = bsect ? check_fs(fs, bsect) : 3; /* Check the partition */
+ } while (!LD2PT(vol) && fmt >= 2 && ++i < 4);
+ }
+ if (fmt == 4) return FR_DISK_ERR; /* An error occured in the disk I/O layer */
+ if (fmt >= 2) return FR_NO_FILESYSTEM; /* No FAT volume is found */
+
+ /* An FAT volume is found. Following code initializes the file system object */
+
+#if _FS_EXFAT
+ if (fmt == 1) {
+ QWORD maxlba;
+
+ for (i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++) ; /* Check zero filler */
+ if (i < BPB_ZeroedEx + 53) return FR_NO_FILESYSTEM;
+
+ if (ld_word(fs->win + BPB_FSVerEx) != 0x100) return FR_NO_FILESYSTEM; /* Check exFAT revision (Must be 1.0) */
+
+ if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) /* (BPB_BytsPerSecEx must be equal to the physical sector size) */
+ return FR_NO_FILESYSTEM;
+
+ maxlba = ld_qword(fs->win + BPB_TotSecEx) + bsect; /* Last LBA + 1 of the volume */
+ if (maxlba >= 0x100000000) return FR_NO_FILESYSTEM; /* (It cannot be handled in 32-bit LBA) */
+
+ fs->fsize = ld_dword(fs->win + BPB_FatSzEx); /* Number of sectors per FAT */
+
+ fs->n_fats = fs->win[BPB_NumFATsEx]; /* Number of FATs */
+ if (fs->n_fats != 1) return FR_NO_FILESYSTEM; /* (Supports only 1 FAT) */
+
+ fs->csize = 1 << fs->win[BPB_SecPerClusEx]; /* Cluster size */
+ if (fs->csize == 0) return FR_NO_FILESYSTEM; /* (Must be 1..32768) */
+
+ nclst = ld_dword(fs->win + BPB_NumClusEx); /* Number of clusters */
+ if (nclst > MAX_EXFAT) return FR_NO_FILESYSTEM; /* (Too many clusters) */
+ fs->n_fatent = nclst + 2;
+
+ /* Boundaries and Limits */
+ fs->volbase = bsect;
+ fs->database = bsect + ld_dword(fs->win + BPB_DataOfsEx);
+ fs->fatbase = bsect + ld_dword(fs->win + BPB_FatOfsEx);
+ if (maxlba < (QWORD)fs->database + nclst * fs->csize) return FR_NO_FILESYSTEM; /* (Volume size must not be smaller than the size requiered) */
+ fs->dirbase = ld_dword(fs->win + BPB_RootClusEx);
+
+ /* Check if bitmap location is in assumption (at the first cluster) */
+ if (move_window(fs, clust2sect(fs, fs->dirbase)) != FR_OK) return FR_DISK_ERR;
+ for (i = 0; i < SS(fs); i += SZDIRE) {
+ if (fs->win[i] == 0x81 && ld_dword(fs->win + i + 20) == 2) break; /* 81 entry with cluster #2? */
+ }
+ if (i == SS(fs)) return FR_NO_FILESYSTEM;
+#if !_FS_READONLY
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */
+#endif
+ fmt = FS_EXFAT; /* FAT sub-type */
+ } else
+#endif /* _FS_EXFAT */
+ {
+ if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */
+
+ fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */
+ if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32);
+ fs->fsize = fasize;
+
+ fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */
+ if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */
+ fasize *= fs->n_fats; /* Number of sectors for FAT area */
+
+ fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */
+ if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */
+
+ fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */
+ if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */
+
+ tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */
+ if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32);
+
+ nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */
+ if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */
+
+ /* Determine the FAT sub type */
+ sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */
+ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
+ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */
+ if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
+ fmt = FS_FAT32;
+ if (nclst <= MAX_FAT16) fmt = FS_FAT16;
+ if (nclst <= MAX_FAT12) fmt = FS_FAT12;
+
+ /* Boundaries and Limits */
+ fs->n_fatent = nclst + 2; /* Number of FAT entries */
+ fs->volbase = bsect; /* Volume start sector */
+ fs->fatbase = bsect + nrsv; /* FAT start sector */
+ fs->database = bsect + sysect; /* Data start sector */
+ if (fmt == FS_FAT32) {
+ if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */
+ if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */
+ fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */
+ szbfat = fs->n_fatent * 4; /* (Needed FAT size) */
+ } else {
+ if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM;/* (BPB_RootEntCnt must not be 0) */
+ fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */
+ szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */
+ fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1);
+ }
+ if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */
+
+#if !_FS_READONLY
+ /* Get FSINFO if available */
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */
+ fs->fsi_flag = 0x80;
+#if (_FS_NOFSINFO & 3) != 3
+ if (fmt == FS_FAT32 /* Enable FSINFO only if FAT32 and BPB_FSInfo32 == 1 */
+ && ld_word(fs->win + BPB_FSInfo32) == 1
+ && move_window(fs, bsect + 1) == FR_OK)
+ {
+ fs->fsi_flag = 0;
+ if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSINFO data if available */
+ && ld_dword(fs->win + FSI_LeadSig) == 0x41615252
+ && ld_dword(fs->win + FSI_StrucSig) == 0x61417272)
+ {
+#if (_FS_NOFSINFO & 1) == 0
+ fs->free_clst = ld_dword(fs->win + FSI_Free_Count);
+#endif
+#if (_FS_NOFSINFO & 2) == 0
+ fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free);
+#endif
+ }
+ }
+#endif /* (_FS_NOFSINFO & 3) != 3 */
+#endif /* !_FS_READONLY */
+ }
+
+ fs->fs_type = fmt; /* FAT sub-type */
+ fs->id = ++Fsid; /* File system mount ID */
+#if _USE_LFN == 1
+ fs->lfnbuf = LfnBuf; /* Static LFN working buffer */
+#if _FS_EXFAT
+ fs->dirbuf = DirBuf; /* Static directory block working buuffer */
+#endif
+#endif
+#if _FS_RPATH != 0
+ fs->cdir = 0; /* Initialize current directory */
+#endif
+#if _FS_LOCK != 0 /* Clear file lock semaphores */
+ clear_lock(fs);
+#endif
+ return FR_OK;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Check if the file/directory object is valid or not */
+/*-----------------------------------------------------------------------*/
+
+static
+FRESULT validate ( /* Returns FR_OK or FR_INVALID_OBJECT */
+ _FDID* obj, /* Pointer to the _OBJ, the 1st member in the FIL/DIR object, to check validity */
+ FATFS** fs /* Pointer to pointer to the owner file system object to return */
+)
+{
+ FRESULT res;
+
+
+ if (!obj || !obj->fs || !obj->fs->fs_type || obj->fs->id != obj->id || (disk_status(obj->fs->drv) & STA_NOINIT)) {
+ *fs = 0; /* The object is invalid */
+ res = FR_INVALID_OBJECT;
+ } else {
+ *fs = obj->fs; /* Owner file sytem object */
+ ENTER_FF(obj->fs); /* Lock file system */
+ res = FR_OK;
+ }
+ return res;
+}
+
+
+
+
+/*---------------------------------------------------------------------------
+
+ Public Functions (FatFs API)
+
+----------------------------------------------------------------------------*/
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Mount/Unmount a Logical Drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mount (
+ FATFS* fs, /* Pointer to the file system object (NULL:unmount)*/
+ const TCHAR* path, /* Logical drive number to be mounted/unmounted */
+ BYTE opt /* Mode option 0:Do not mount (delayed mount), 1:Mount immediately */
+)
+{
+ FATFS *cfs;
+ int vol;
+ FRESULT res;
+ const TCHAR *rp = path;
+
+
+ /* Get logical drive number */
+ vol = get_ldnumber(&rp);
+ if (vol < 0) return FR_INVALID_DRIVE;
+ cfs = FatFs[vol]; /* Pointer to fs object */
+
+ if (cfs) {
+#if _FS_LOCK != 0
+ clear_lock(cfs);
+#endif
+#if _FS_REENTRANT /* Discard sync object of the current volume */
+ if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR;
+#endif
+ cfs->fs_type = 0; /* Clear old fs object */
+ }
+
+ if (fs) {
+ fs->fs_type = 0; /* Clear new fs object */
+#if _FS_REENTRANT /* Create sync object for the new volume */
+ if (!ff_cre_syncobj((BYTE)vol, &fs->sobj)) return FR_INT_ERR;
+#endif
+ }
+ FatFs[vol] = fs; /* Register new fs object */
+
+ if (!fs || opt != 1) return FR_OK; /* Do not mount now, it will be mounted later */
+
+ res = find_volume(&path, &fs, 0); /* Force mounted the volume */
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Open or Create a File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_open (
+ FIL* fp, /* Pointer to the blank file object */
+ const TCHAR* path, /* Pointer to the file name */
+ BYTE mode /* Access mode and file open mode flags */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+#if !_FS_READONLY
+ DWORD dw, cl, bcs, clst, sc;
+ FSIZE_t ofs;
+#endif
+ DEF_NAMBUF
+
+
+ if (!fp) return FR_INVALID_OBJECT;
+
+ /* Get logical drive */
+ mode &= _FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND | FA_SEEKEND;
+ res = find_volume(&path, &fs, mode);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+#if !_FS_READONLY /* R/W configuration */
+ if (res == FR_OK) {
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */
+ res = FR_INVALID_NAME;
+ }
+#if _FS_LOCK != 0
+ else {
+ res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0);
+ }
+#endif
+ }
+ /* Create or Open a file */
+ if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
+ if (res != FR_OK) { /* No file, create new */
+ if (res == FR_NO_FILE) /* There is no file to open, create a new entry */
+#if _FS_LOCK != 0
+ res = enq_lock() ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES;
+#else
+ res = dir_register(&dj);
+#endif
+ mode |= FA_CREATE_ALWAYS; /* File is created */
+ }
+ else { /* Any object is already existing */
+ if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */
+ res = FR_DENIED;
+ } else {
+ if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */
+ }
+ }
+ if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate it if overwrite mode */
+ dw = GET_FATTIME();
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ /* Get current allocation info */
+ fp->obj.fs = fs;
+ fp->obj.sclust = ld_dword(fs->dirbuf + XDIR_FstClus);
+ fp->obj.objsize = ld_qword(fs->dirbuf + XDIR_FileSize);
+ fp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ /* Initialize directory entry block */
+ st_dword(fs->dirbuf + XDIR_CrtTime, dw); /* Set created time */
+ fs->dirbuf[XDIR_CrtTime10] = 0;
+ st_dword(fs->dirbuf + XDIR_ModTime, dw); /* Set modified time */
+ fs->dirbuf[XDIR_ModTime10] = 0;
+ fs->dirbuf[XDIR_Attr] = AM_ARC; /* Reset attribute */
+ st_dword(fs->dirbuf + XDIR_FstClus, 0); /* Reset file allocation info */
+ st_qword(fs->dirbuf + XDIR_FileSize, 0);
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, 0);
+ fs->dirbuf[XDIR_GenFlags] = 1;
+ res = store_xdir(&dj);
+ if (res == FR_OK && fp->obj.sclust) { /* Remove the cluster chain if exist */
+ res = remove_chain(&fp->obj, fp->obj.sclust, 0);
+ fs->last_clst = fp->obj.sclust - 1; /* Reuse the cluster hole */
+ }
+ } else
+#endif
+ {
+ /* Clean directory info */
+ st_dword(dj.dir + DIR_CrtTime, dw); /* Set created time */
+ st_dword(dj.dir + DIR_ModTime, dw); /* Set modified time */
+ dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */
+ cl = ld_clust(fs, dj.dir); /* Get cluster chain */
+ st_clust(fs, dj.dir, 0); /* Reset file allocation info */
+ st_dword(dj.dir + DIR_FileSize, 0);
+ fs->wflag = 1;
+
+ if (cl) { /* Remove the cluster chain if exist */
+ dw = fs->winsect;
+ res = remove_chain(&dj.obj, cl, 0);
+ if (res == FR_OK) {
+ res = move_window(fs, dw);
+ fs->last_clst = cl - 1; /* Reuse the cluster hole */
+ }
+ }
+ }
+ }
+ }
+ else { /* Open an existing file */
+ if (res == FR_OK) { /* Following succeeded */
+ if (dj.obj.attr & AM_DIR) { /* It is a directory */
+ res = FR_NO_FILE;
+ } else {
+ if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* R/O violation */
+ res = FR_DENIED;
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ if (mode & FA_CREATE_ALWAYS) /* Set file change flag if created or overwritten */
+ mode |= FA_MODIFIED;
+ fp->dir_sect = fs->winsect; /* Pointer to the directory entry */
+ fp->dir_ptr = dj.dir;
+#if _FS_LOCK != 0
+ fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0);
+ if (!fp->obj.lockid) res = FR_INT_ERR;
+#endif
+ }
+#else /* R/O configuration */
+ if (res == FR_OK) {
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */
+ res = FR_INVALID_NAME;
+ } else {
+ if (dj.obj.attr & AM_DIR) { /* It is a directory */
+ res = FR_NO_FILE;
+ }
+ }
+ }
+#endif
+
+ if (res == FR_OK) {
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fp->obj.sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Get allocation info */
+ fp->obj.objsize = ld_qword(fs->dirbuf + XDIR_FileSize);
+ fp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ fp->obj.c_scl = dj.obj.sclust;
+ fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
+ fp->obj.c_ofs = dj.blk_ofs;
+ } else
+#endif
+ {
+ fp->obj.sclust = ld_clust(fs, dj.dir); /* Get allocation info */
+ fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize);
+ }
+#if _USE_FASTSEEK
+ fp->cltbl = 0; /* Disable fast seek mode */
+#endif
+ fp->obj.fs = fs; /* Validate the file object */
+ fp->obj.id = fs->id;
+ fp->flag = mode; /* Set file access mode */
+ fp->err = 0; /* Clear error flag */
+ fp->sect = 0; /* Invalidate current data sector */
+ fp->fptr = 0; /* Set file pointer top of the file */
+#if !_FS_READONLY
+#if !_FS_TINY
+ mem_set(fp->buf, 0, _MAX_SS); /* Clear sector buffer */
+#endif
+ if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */
+ fp->fptr = fp->obj.objsize; /* Offset to seek */
+ bcs = (DWORD)fs->csize * SS(fs); /* Cluster size in byte */
+ clst = fp->obj.sclust; /* Follow the cluster chain */
+ for (ofs = fp->obj.objsize; res == FR_OK && ofs > bcs; ofs -= bcs) {
+ clst = get_fat(&fp->obj, clst);
+ if (clst <= 1) res = FR_INT_ERR;
+ if (clst == 0xFFFFFFFF) res = FR_DISK_ERR;
+ }
+ fp->clust = clst;
+ if (res == FR_OK && ofs % SS(fs)) { /* Fill sector buffer if not on the sector boundary */
+ if ((sc = clust2sect(fs, clst)) == 0) {
+ res = FR_INT_ERR;
+ } else {
+ fp->sect = sc + (DWORD)(ofs / SS(fs));
+#if !_FS_TINY
+ if (disk_read(fs->drv, fp->buf, fp->sect, 1) != RES_OK) res = FR_DISK_ERR;
+#endif
+ }
+ }
+ }
+#endif
+ }
+
+ FREE_NAMBUF();
+ }
+
+ if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_read (
+ FIL* fp, /* Pointer to the file object */
+ void* buff, /* Pointer to data buffer */
+ UINT btr, /* Number of bytes to read */
+ UINT* br /* Pointer to number of bytes read */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, sect;
+ FSIZE_t remain;
+ UINT rcnt, cc, csect;
+ BYTE *rbuff = (BYTE*)buff;
+
+
+ *br = 0; /* Clear read byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+ remain = fp->obj.objsize - fp->fptr;
+ if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */
+
+ for ( ; btr; /* Repeat until all data read */
+ rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) {
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
+ if (csect == 0) { /* On the cluster boundary? */
+ if (fp->fptr == 0) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow cluster chain from the origin */
+ } else { /* Middle or end of the file */
+#if _USE_FASTSEEK
+ if (fp->cltbl) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ } else
+#endif
+ {
+ clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */
+ }
+ }
+ if (clst < 2) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ }
+ sect = clust2sect(fs, fp->clust); /* Get current sector */
+ if (!sect) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+ cc = btr / SS(fs); /* When remaining bytes >= sector size, */
+ if (cc) { /* Read maximum contiguous sectors directly */
+ if (csect + cc > fs->csize) { /* Clip at cluster boundary */
+ cc = fs->csize - csect;
+ }
+ if (disk_read(fs->drv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
+#if !_FS_READONLY && _FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */
+#if _FS_TINY
+ if (fs->wflag && fs->winsect - sect < cc) {
+ mem_cpy(rbuff + ((fs->winsect - sect) * SS(fs)), fs->win, SS(fs));
+ }
+#else
+ if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) {
+ mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs));
+ }
+#endif
+#endif
+ rcnt = SS(fs) * cc; /* Number of bytes transferred */
+ continue;
+ }
+#if !_FS_TINY
+ if (fp->sect != sect) { /* Load data sector if not in cache */
+#if !_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
+ }
+#endif
+ fp->sect = sect;
+ }
+ rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
+ if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */
+#if _FS_TINY
+ if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
+ mem_cpy(rbuff, fs->win + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
+#else
+ mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */
+#endif
+ }
+
+ LEAVE_FF(fs, FR_OK);
+}
+
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Write File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_write (
+ FIL* fp, /* Pointer to the file object */
+ const void* buff, /* Pointer to the data to be written */
+ UINT btw, /* Number of bytes to write */
+ UINT* bw /* Pointer to number of bytes written */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, sect;
+ UINT wcnt, cc, csect;
+ const BYTE *wbuff = (const BYTE*)buff;
+
+
+ *bw = 0; /* Clear write byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ /* Check fptr wrap-around (file size cannot reach 4GiB on FATxx) */
+ if ((!_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) {
+ btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr);
+ }
+
+ for ( ; btw; /* Repeat until all data written */
+ wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize, *bw += wcnt, btw -= wcnt) {
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */
+ if (csect == 0) { /* On the cluster boundary? */
+ if (fp->fptr == 0) { /* On the top of the file? */
+ clst = fp->obj.sclust; /* Follow from the origin */
+ if (clst == 0) { /* If no cluster is allocated, */
+ clst = create_chain(&fp->obj, 0); /* create a new cluster chain */
+ }
+ } else { /* On the middle or end of the file */
+#if _USE_FASTSEEK
+ if (fp->cltbl) {
+ clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */
+ } else
+#endif
+ {
+ clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */
+ }
+ }
+ if (clst == 0) break; /* Could not allocate a new cluster (disk full) */
+ if (clst == 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */
+ }
+#if _FS_TINY
+ if (fs->winsect == fp->sect && sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Write-back sector cache */
+#else
+ if (fp->flag & FA_DIRTY) { /* Write-back sector cache */
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ sect = clust2sect(fs, fp->clust); /* Get current sector */
+ if (!sect) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+ cc = btw / SS(fs); /* When remaining bytes >= sector size, */
+ if (cc) { /* Write maximum contiguous sectors directly */
+ if (csect + cc > fs->csize) { /* Clip at cluster boundary */
+ cc = fs->csize - csect;
+ }
+ if (disk_write(fs->drv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR);
+#if _FS_MINIMIZE <= 2
+#if _FS_TINY
+ if (fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
+ mem_cpy(fs->win, wbuff + ((fs->winsect - sect) * SS(fs)), SS(fs));
+ fs->wflag = 0;
+ }
+#else
+ if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */
+ mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs));
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+#endif
+ wcnt = SS(fs) * cc; /* Number of bytes transferred */
+ continue;
+ }
+#if _FS_TINY
+ if (fp->fptr >= fp->obj.objsize) { /* Avoid silly cache filling on the growing edge */
+ if (sync_window(fs) != FR_OK) ABORT(fs, FR_DISK_ERR);
+ fs->winsect = sect;
+ }
+#else
+ if (fp->sect != sect && /* Fill sector cache with file data */
+ fp->fptr < fp->obj.objsize &&
+ disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) {
+ ABORT(fs, FR_DISK_ERR);
+ }
+#endif
+ fp->sect = sect;
+ }
+ wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
+ if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */
+#if _FS_TINY
+ if (move_window(fs, fp->sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window */
+ mem_cpy(fs->win + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */
+ fs->wflag = 1;
+#else
+ mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */
+ fp->flag |= FA_DIRTY;
+#endif
+ }
+
+ fp->flag |= FA_MODIFIED; /* Set file change flag */
+
+ LEAVE_FF(fs, FR_OK);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Synchronize the File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_sync (
+ FIL* fp /* Pointer to the file object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD tm;
+ BYTE *dir;
+ DEF_NAMBUF
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) {
+ if (fp->flag & FA_MODIFIED) { /* Is there any change to the file? */
+#if !_FS_TINY
+ if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) LEAVE_FF(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ /* Update the directory entry */
+ tm = GET_FATTIME(); /* Modified time */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ res = fill_fat_chain(&fp->obj); /* Create FAT chain if needed */
+ if (res == FR_OK) {
+ DIR dj;
+
+ INIT_NAMBUF(fs);
+ res = load_obj_dir(&dj, &fp->obj); /* Load directory entry block */
+ if (res == FR_OK) {
+ fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive bit */
+ fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation info */
+ st_dword(fs->dirbuf + XDIR_FstClus, fp->obj.sclust);
+ st_qword(fs->dirbuf + XDIR_FileSize, fp->obj.objsize);
+ st_qword(fs->dirbuf + XDIR_ValidFileSize, fp->obj.objsize);
+ st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Update modified time */
+ fs->dirbuf[XDIR_ModTime10] = 0;
+ st_dword(fs->dirbuf + XDIR_AccTime, 0);
+ res = store_xdir(&dj); /* Restore it to the directory */
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ fp->flag &= (BYTE)~FA_MODIFIED;
+ }
+ }
+ FREE_NAMBUF();
+ }
+ } else
+#endif
+ {
+ res = move_window(fs, fp->dir_sect);
+ if (res == FR_OK) {
+ dir = fp->dir_ptr;
+ dir[DIR_Attr] |= AM_ARC; /* Set archive bit */
+ st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation info */
+ st_dword(dir + DIR_FileSize, (DWORD)fp->obj.objsize); /* Update file size */
+ st_dword(dir + DIR_ModTime, tm); /* Update modified time */
+ st_word(dir + DIR_LstAccDate, 0);
+ fs->wflag = 1;
+ res = sync_fs(fs); /* Restore it to the directory */
+ fp->flag &= (BYTE)~FA_MODIFIED;
+ }
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !_FS_READONLY */
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Close File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_close (
+ FIL* fp /* Pointer to the file object to be closed */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+
+#if !_FS_READONLY
+ res = f_sync(fp); /* Flush cached data */
+ if (res == FR_OK)
+#endif
+ {
+ res = validate(&fp->obj, &fs); /* Lock volume */
+ if (res == FR_OK) {
+#if _FS_LOCK != 0
+ res = dec_lock(fp->obj.lockid); /* Decrement file open counter */
+ if (res == FR_OK)
+#endif
+ {
+ fp->obj.fs = 0; /* Invalidate file object */
+ }
+#if _FS_REENTRANT
+ unlock_fs(fs, FR_OK); /* Unlock volume */
+#endif
+ }
+ }
+ return res;
+}
+
+
+
+
+#if _FS_RPATH >= 1
+/*-----------------------------------------------------------------------*/
+/* Change Current Directory or Current Drive, Get Current Directory */
+/*-----------------------------------------------------------------------*/
+
+#if _VOLUMES >= 2
+FRESULT f_chdrive (
+ const TCHAR* path /* Drive number */
+)
+{
+ int vol;
+
+
+ /* Get logical drive number */
+ vol = get_ldnumber(&path);
+ if (vol < 0) return FR_INVALID_DRIVE;
+
+ CurrVol = (BYTE)vol; /* Set it as current volume */
+
+ return FR_OK;
+}
+#endif
+
+
+FRESULT f_chdir (
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the path */
+ if (res == FR_OK) { /* Follow completed */
+ if (dj.fn[NSFLAG] & NS_NONAME) {
+ fs->cdir = dj.obj.sclust; /* It is the start directory itself */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->cdc_scl = dj.obj.c_scl;
+ fs->cdc_size = dj.obj.c_size;
+ fs->cdc_ofs = dj.obj.c_ofs;
+ }
+#endif
+ } else {
+ if (dj.obj.attr & AM_DIR) { /* It is a sub-directory */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->cdir = ld_dword(fs->dirbuf + XDIR_FstClus); /* Sub-directory cluster */
+ fs->cdc_scl = dj.obj.sclust; /* Save containing directory information */
+ fs->cdc_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat;
+ fs->cdc_ofs = dj.blk_ofs;
+ } else
+#endif
+ {
+ fs->cdir = ld_clust(fs, dj.dir); /* Sub-directory cluster */
+ }
+ } else {
+ res = FR_NO_PATH; /* Reached but a file */
+ }
+ }
+ }
+ FREE_NAMBUF();
+ if (res == FR_NO_FILE) res = FR_NO_PATH;
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+#if _FS_RPATH >= 2
+FRESULT f_getcwd (
+ TCHAR* buff, /* Pointer to the directory path */
+ UINT len /* Size of path */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ UINT i, n;
+ DWORD ccl;
+ TCHAR *tp;
+ FILINFO fno;
+ DEF_NAMBUF
+
+
+ *buff = 0;
+ /* Get logical drive */
+ res = find_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */
+ if (res == FR_OK) {
+ dj.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ i = len; /* Bottom of buffer (directory stack base) */
+ if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* (Cannot do getcwd on exFAT and returns root path) */
+ dj.obj.sclust = fs->cdir; /* Start to follow upper directory from current directory */
+ while ((ccl = dj.obj.sclust) != 0) { /* Repeat while current directory is a sub-directory */
+ res = dir_sdi(&dj, 1 * SZDIRE); /* Get parent directory */
+ if (res != FR_OK) break;
+ res = move_window(fs, dj.sect);
+ if (res != FR_OK) break;
+ dj.obj.sclust = ld_clust(fs, dj.dir); /* Goto parent directory */
+ res = dir_sdi(&dj, 0);
+ if (res != FR_OK) break;
+ do { /* Find the entry links to the child directory */
+ res = dir_read(&dj, 0);
+ if (res != FR_OK) break;
+ if (ccl == ld_clust(fs, dj.dir)) break; /* Found the entry */
+ res = dir_next(&dj, 0);
+ } while (res == FR_OK);
+ if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */
+ if (res != FR_OK) break;
+ get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */
+ for (n = 0; fno.fname[n]; n++) ;
+ if (i < n + 3) {
+ res = FR_NOT_ENOUGH_CORE; break;
+ }
+ while (n) buff[--i] = fno.fname[--n];
+ buff[--i] = '/';
+ }
+ }
+ tp = buff;
+ if (res == FR_OK) {
+#if _VOLUMES >= 2
+ *tp++ = '0' + CurrVol; /* Put drive number */
+ *tp++ = ':';
+#endif
+ if (i == len) { /* Root-directory */
+ *tp++ = '/';
+ } else { /* Sub-directroy */
+ do /* Add stacked path str */
+ *tp++ = buff[i++];
+ while (i < len);
+ }
+ }
+ *tp = 0;
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* _FS_RPATH >= 2 */
+#endif /* _FS_RPATH >= 1 */
+
+
+
+#if _FS_MINIMIZE <= 2
+/*-----------------------------------------------------------------------*/
+/* Seek File R/W Pointer */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_lseek (
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t ofs /* File pointer from top of file */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, bcs, nsect;
+ FSIZE_t ifptr;
+#if _USE_FASTSEEK
+ DWORD cl, pcl, ncl, tcl, dsc, tlen, ulen, *tbl;
+#endif
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res); /* Check validity */
+#if _USE_FASTSEEK
+ if (fp->cltbl) { /* Fast seek */
+ if (ofs == CREATE_LINKMAP) { /* Create CLMT */
+ tbl = fp->cltbl;
+ tlen = *tbl++; ulen = 2; /* Given table size and required table size */
+ cl = fp->obj.sclust; /* Origin of the chain */
+ if (cl) {
+ do {
+ /* Get a fragment */
+ tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */
+ do {
+ pcl = cl; ncl++;
+ cl = get_fat(&fp->obj, cl);
+ if (cl <= 1) ABORT(fs, FR_INT_ERR);
+ if (cl == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ } while (cl == pcl + 1);
+ if (ulen <= tlen) { /* Store the length and top of the fragment */
+ *tbl++ = ncl; *tbl++ = tcl;
+ }
+ } while (cl < fs->n_fatent); /* Repeat until end of chain */
+ }
+ *fp->cltbl = ulen; /* Number of items used */
+ if (ulen <= tlen) {
+ *tbl = 0; /* Terminate table */
+ } else {
+ res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */
+ }
+ } else { /* Fast seek */
+ if (ofs > fp->obj.objsize) ofs = fp->obj.objsize; /* Clip offset at the file size */
+ fp->fptr = ofs; /* Set file pointer */
+ if (ofs) {
+ fp->clust = clmt_clust(fp, ofs - 1);
+ dsc = clust2sect(fs, fp->clust);
+ if (!dsc) ABORT(fs, FR_INT_ERR);
+ dsc += (DWORD)((ofs - 1) / SS(fs)) & (fs->csize - 1);
+ if (fp->fptr % SS(fs) && dsc != fp->sect) { /* Refill sector cache if needed */
+#if !_FS_TINY
+#if !_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->drv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */
+#endif
+ fp->sect = dsc;
+ }
+ }
+ }
+ } else
+#endif
+
+ /* Normal Seek */
+ {
+#if _FS_EXFAT
+ if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4GiB-1 if at FATxx */
+#endif
+ if (ofs > fp->obj.objsize && (_FS_READONLY || !(fp->flag & FA_WRITE))) { /* In read-only mode, clip offset with the file size */
+ ofs = fp->obj.objsize;
+ }
+ ifptr = fp->fptr;
+ fp->fptr = nsect = 0;
+ if (ofs) {
+ bcs = (DWORD)fs->csize * SS(fs); /* Cluster size (byte) */
+ if (ifptr > 0 &&
+ (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */
+ fp->fptr = (ifptr - 1) & ~(FSIZE_t)(bcs - 1); /* start from the current cluster */
+ ofs -= fp->fptr;
+ clst = fp->clust;
+ } else { /* When seek to back cluster, */
+ clst = fp->obj.sclust; /* start from the first cluster */
+#if !_FS_READONLY
+ if (clst == 0) { /* If no cluster chain, create a new chain */
+ clst = create_chain(&fp->obj, 0);
+ if (clst == 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->obj.sclust = clst;
+ }
+#endif
+ fp->clust = clst;
+ }
+ if (clst != 0) {
+ while (ofs > bcs) { /* Cluster following loop */
+ ofs -= bcs; fp->fptr += bcs;
+#if !_FS_READONLY
+ if (fp->flag & FA_WRITE) { /* Check if in write mode or not */
+ if (_FS_EXFAT && fp->fptr > fp->obj.objsize) { /* No FAT chain object needs correct objsize to generate FAT value */
+ fp->obj.objsize = fp->fptr;
+ fp->flag |= FA_MODIFIED;
+ }
+ clst = create_chain(&fp->obj, clst); /* Follow chain with forceed stretch */
+ if (clst == 0) { /* Clip file size in case of disk full */
+ ofs = 0; break;
+ }
+ } else
+#endif
+ {
+ clst = get_fat(&fp->obj, clst); /* Follow cluster chain if not in write mode */
+ }
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ if (clst <= 1 || clst >= fs->n_fatent) ABORT(fs, FR_INT_ERR);
+ fp->clust = clst;
+ }
+ fp->fptr += ofs;
+ if (ofs % SS(fs)) {
+ nsect = clust2sect(fs, clst); /* Current sector */
+ if (!nsect) ABORT(fs, FR_INT_ERR);
+ nsect += (DWORD)(ofs / SS(fs));
+ }
+ }
+ }
+ if (!_FS_READONLY && fp->fptr > fp->obj.objsize) { /* Set file change flag if the file size is extended */
+ fp->obj.objsize = fp->fptr;
+ fp->flag |= FA_MODIFIED;
+ }
+ if (fp->fptr % SS(fs) && nsect != fp->sect) { /* Fill sector cache if needed */
+#if !_FS_TINY
+#if !_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->drv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */
+#endif
+ fp->sect = nsect;
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if _FS_MINIMIZE <= 1
+/*-----------------------------------------------------------------------*/
+/* Create a Directory Object */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_opendir (
+ DIR* dp, /* Pointer to directory object to create */
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ _FDID *obj;
+ DEF_NAMBUF
+
+
+ if (!dp) return FR_INVALID_OBJECT;
+
+ /* Get logical drive */
+ obj = &dp->obj;
+ res = find_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ obj->fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(dp, path); /* Follow the path to the directory */
+ if (res == FR_OK) { /* Follow completed */
+ if (!(dp->fn[NSFLAG] & NS_NONAME)) { /* It is not the origin directory itself */
+ if (obj->attr & AM_DIR) { /* This object is a sub-directory */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ obj->c_scl = obj->sclust; /* Save containing directory inforamation */
+ obj->c_size = ((DWORD)obj->objsize & 0xFFFFFF00) | obj->stat;
+ obj->c_ofs = dp->blk_ofs;
+ obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Get object location and status */
+ obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize);
+ obj->stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ } else
+#endif
+ {
+ obj->sclust = ld_clust(fs, dp->dir); /* Get object location */
+ }
+ } else { /* This object is a file */
+ res = FR_NO_PATH;
+ }
+ }
+ if (res == FR_OK) {
+ obj->id = fs->id;
+ res = dir_sdi(dp, 0); /* Rewind directory */
+#if _FS_LOCK != 0
+ if (res == FR_OK) {
+ if (obj->sclust) {
+ obj->lockid = inc_lock(dp, 0); /* Lock the sub directory */
+ if (!obj->lockid) res = FR_TOO_MANY_OPEN_FILES;
+ } else {
+ obj->lockid = 0; /* Root directory need not to be locked */
+ }
+ }
+#endif
+ }
+ }
+ FREE_NAMBUF();
+ if (res == FR_NO_FILE) res = FR_NO_PATH;
+ }
+ if (res != FR_OK) obj->fs = 0; /* Invalidate the directory object if function faild */
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Close Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_closedir (
+ DIR *dp /* Pointer to the directory object to be closed */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+
+
+ res = validate(&dp->obj, &fs); /* Check validity of the file object */
+ if (res == FR_OK) {
+#if _FS_LOCK != 0
+ if (dp->obj.lockid) { /* Decrement sub-directory open counter */
+ res = dec_lock(dp->obj.lockid);
+ }
+ if (res == FR_OK)
+#endif
+ {
+ dp->obj.fs = 0; /* Invalidate directory object */
+ }
+#if _FS_REENTRANT
+ unlock_fs(fs, FR_OK); /* Unlock volume */
+#endif
+ }
+ return res;
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read Directory Entries in Sequence */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_readdir (
+ DIR* dp, /* Pointer to the open directory object */
+ FILINFO* fno /* Pointer to file information to return */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = validate(&dp->obj, &fs); /* Check validity of the directory object */
+ if (res == FR_OK) {
+ if (!fno) {
+ res = dir_sdi(dp, 0); /* Rewind the directory object */
+ } else {
+ INIT_NAMBUF(fs);
+ res = dir_read(dp, 0); /* Read an item */
+ if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory */
+ if (res == FR_OK) { /* A valid entry is found */
+ get_fileinfo(dp, fno); /* Get the object information */
+ res = dir_next(dp, 0); /* Increment index for next */
+ if (res == FR_NO_FILE) res = FR_OK; /* Ignore end of directory now */
+ }
+ FREE_NAMBUF();
+ }
+ }
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if _USE_FIND
+/*-----------------------------------------------------------------------*/
+/* Find Next File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_findnext (
+ DIR* dp, /* Pointer to the open directory object */
+ FILINFO* fno /* Pointer to the file information structure */
+)
+{
+ FRESULT res;
+
+
+ for (;;) {
+ res = f_readdir(dp, fno); /* Get a directory item */
+ if (res != FR_OK || !fno || !fno->fname[0]) break; /* Terminate if any error or end of directory */
+ if (pattern_matching(dp->pat, fno->fname, 0, 0)) break; /* Test for the file name */
+#if _USE_LFN != 0 && _USE_FIND == 2
+ if (pattern_matching(dp->pat, fno->altname, 0, 0)) break; /* Test for alternative name if exist */
+#endif
+ }
+ return res;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Find First File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_findfirst (
+ DIR* dp, /* Pointer to the blank directory object */
+ FILINFO* fno, /* Pointer to the file information structure */
+ const TCHAR* path, /* Pointer to the directory to open */
+ const TCHAR* pattern /* Pointer to the matching pattern */
+)
+{
+ FRESULT res;
+
+
+ dp->pat = pattern; /* Save pointer to pattern string */
+ res = f_opendir(dp, path); /* Open the target directory */
+ if (res == FR_OK) {
+ res = f_findnext(dp, fno); /* Find the first item */
+ }
+ return res;
+}
+
+#endif /* _USE_FIND */
+
+
+
+#if _FS_MINIMIZE == 0
+/*-----------------------------------------------------------------------*/
+/* Get File Status */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_stat (
+ const TCHAR* path, /* Pointer to the file path */
+ FILINFO* fno /* Pointer to file information to return */
+)
+{
+ FRESULT res;
+ DIR dj;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &dj.obj.fs, 0);
+ if (res == FR_OK) {
+ INIT_NAMBUF(dj.obj.fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK) { /* Follow completed */
+ if (dj.fn[NSFLAG] & NS_NONAME) { /* It is origin directory */
+ res = FR_INVALID_NAME;
+ } else { /* Found an object */
+ if (fno) get_fileinfo(&dj, fno);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(dj.obj.fs, res);
+}
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Get Number of Free Clusters */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_getfree (
+ const TCHAR* path, /* Path name of the logical drive number */
+ DWORD* nclst, /* Pointer to a variable to return number of free clusters */
+ FATFS** fatfs /* Pointer to return pointer to corresponding file system object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD nfree, clst, sect, stat;
+ UINT i;
+ BYTE *p;
+ _FDID obj;
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+ if (res == FR_OK) {
+ *fatfs = fs; /* Return ptr to the fs object */
+ /* If free_clst is valid, return it without full cluster scan */
+ if (fs->free_clst <= fs->n_fatent - 2) {
+ *nclst = fs->free_clst;
+ } else {
+ /* Get number of free clusters */
+ nfree = 0;
+ if (fs->fs_type == FS_FAT12) { /* FAT12: Sector unalighed FAT entries */
+ clst = 2; obj.fs = fs;
+ do {
+ stat = get_fat(&obj, clst);
+ if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
+ if (stat == 1) { res = FR_INT_ERR; break; }
+ if (stat == 0) nfree++;
+ } while (++clst < fs->n_fatent);
+ } else {
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan bitmap table */
+ BYTE bm;
+ UINT b;
+
+ clst = fs->n_fatent - 2;
+ sect = fs->database;
+ i = 0;
+ do {
+ if (i == 0 && (res = move_window(fs, sect++)) != FR_OK) break;
+ for (b = 8, bm = fs->win[i]; b && clst; b--, clst--) {
+ if (!(bm & 1)) nfree++;
+ bm >>= 1;
+ }
+ i = (i + 1) % SS(fs);
+ } while (clst);
+ } else
+#endif
+ { /* FAT16/32: Sector alighed FAT entries */
+ clst = fs->n_fatent; sect = fs->fatbase;
+ i = 0; p = 0;
+ do {
+ if (i == 0) {
+ res = move_window(fs, sect++);
+ if (res != FR_OK) break;
+ p = fs->win;
+ i = SS(fs);
+ }
+ if (fs->fs_type == FS_FAT16) {
+ if (ld_word(p) == 0) nfree++;
+ p += 2; i -= 2;
+ } else {
+ if ((ld_dword(p) & 0x0FFFFFFF) == 0) nfree++;
+ p += 4; i -= 4;
+ }
+ } while (--clst);
+ }
+ }
+ *nclst = nfree; /* Return the free clusters */
+ fs->free_clst = nfree; /* Now free_clst is valid */
+ fs->fsi_flag |= 1; /* FSInfo is to be updated */
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Truncate File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_truncate (
+ FIL* fp /* Pointer to the file object */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD ncl;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (!(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ if (fp->obj.objsize > fp->fptr) {
+ if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */
+ res = remove_chain(&fp->obj, fp->obj.sclust, 0);
+ fp->obj.sclust = 0;
+ } else { /* When truncate a part of the file, remove remaining clusters */
+ ncl = get_fat(&fp->obj, fp->clust);
+ res = FR_OK;
+ if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (ncl == 1) res = FR_INT_ERR;
+ if (res == FR_OK && ncl < fs->n_fatent) {
+ res = remove_chain(&fp->obj, ncl, fp->clust);
+ }
+ }
+ fp->obj.objsize = fp->fptr; /* Set file size to current R/W point */
+ fp->flag |= FA_MODIFIED;
+#if !_FS_TINY
+ if (res == FR_OK && (fp->flag & FA_DIRTY)) {
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) {
+ res = FR_DISK_ERR;
+ } else {
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+ }
+#endif
+ if (res != FR_OK) ABORT(fs, res);
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Delete a File/Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_unlink (
+ const TCHAR* path /* Pointer to the file or directory path */
+)
+{
+ FRESULT res;
+ DIR dj, sdj;
+ DWORD dclst = 0;
+ FATFS *fs;
+#if _FS_EXFAT
+ _FDID obj;
+#endif
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, FA_WRITE);
+ dj.obj.fs = fs;
+ if (res == FR_OK) {
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) {
+ res = FR_INVALID_NAME; /* Cannot remove dot entry */
+ }
+#if _FS_LOCK != 0
+ if (res == FR_OK) res = chk_lock(&dj, 2); /* Check if it is an open object */
+#endif
+ if (res == FR_OK) { /* The object is accessible */
+ if (dj.fn[NSFLAG] & NS_NONAME) {
+ res = FR_INVALID_NAME; /* Cannot remove the origin directory */
+ } else {
+ if (dj.obj.attr & AM_RDO) {
+ res = FR_DENIED; /* Cannot remove R/O object */
+ }
+ }
+ if (res == FR_OK) {
+#if _FS_EXFAT
+ obj.fs = fs;
+ if (fs->fs_type == FS_EXFAT) {
+ obj.sclust = dclst = ld_dword(fs->dirbuf + XDIR_FstClus);
+ obj.objsize = ld_qword(fs->dirbuf + XDIR_FileSize);
+ obj.stat = fs->dirbuf[XDIR_GenFlags] & 2;
+ } else
+#endif
+ {
+ dclst = ld_clust(fs, dj.dir);
+ }
+ if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory ? */
+#if _FS_RPATH != 0
+ if (dclst == fs->cdir) { /* Is it the current directory? */
+ res = FR_DENIED;
+ } else
+#endif
+ {
+ sdj.obj.fs = fs; /* Open the sub-directory */
+ sdj.obj.sclust = dclst;
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ sdj.obj.objsize = obj.objsize;
+ sdj.obj.stat = obj.stat;
+ }
+#endif
+ res = dir_sdi(&sdj, 0);
+ if (res == FR_OK) {
+ res = dir_read(&sdj, 0); /* Read an item */
+ if (res == FR_OK) res = FR_DENIED; /* Not empty? */
+ if (res == FR_NO_FILE) res = FR_OK; /* Empty? */
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ res = dir_remove(&dj); /* Remove the directory entry */
+ if (res == FR_OK && dclst) { /* Remove the cluster chain if exist */
+#if _FS_EXFAT
+ res = remove_chain(&obj, dclst, 0);
+#else
+ res = remove_chain(&dj.obj, dclst, 0);
+#endif
+ }
+ if (res == FR_OK) res = sync_fs(fs);
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Create a Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mkdir (
+ const TCHAR* path /* Pointer to the directory path */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ BYTE *dir;
+ UINT n;
+ DWORD dsc, dcl, pcl, tm;
+ DEF_NAMBUF
+
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, FA_WRITE);
+ dj.obj.fs = fs;
+ if (res == FR_OK) {
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK) res = FR_EXIST; /* Any object with same name is already existing */
+ if (_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) {
+ res = FR_INVALID_NAME;
+ }
+ if (res == FR_NO_FILE) { /* Can create a new directory */
+ dcl = create_chain(&dj.obj, 0); /* Allocate a cluster for the new directory table */
+ dj.obj.objsize = (DWORD)fs->csize * SS(fs);
+ res = FR_OK;
+ if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster */
+ if (dcl == 1) res = FR_INT_ERR;
+ if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (res == FR_OK) res = sync_window(fs); /* Flush FAT */
+ tm = GET_FATTIME();
+ if (res == FR_OK) { /* Initialize the new directory table */
+ dsc = clust2sect(fs, dcl);
+ dir = fs->win;
+ mem_set(dir, 0, SS(fs));
+ if (!_FS_EXFAT || fs->fs_type != FS_EXFAT) {
+ mem_set(dir + DIR_Name, ' ', 11); /* Create "." entry */
+ dir[DIR_Name] = '.';
+ dir[DIR_Attr] = AM_DIR;
+ st_dword(dir + DIR_ModTime, tm);
+ st_clust(fs, dir, dcl);
+ mem_cpy(dir + SZDIRE, dir, SZDIRE); /* Create ".." entry */
+ dir[SZDIRE + 1] = '.'; pcl = dj.obj.sclust;
+ if (fs->fs_type == FS_FAT32 && pcl == fs->dirbase) pcl = 0;
+ st_clust(fs, dir + SZDIRE, pcl);
+ }
+ for (n = fs->csize; n; n--) { /* Write dot entries and clear following sectors */
+ fs->winsect = dsc++;
+ fs->wflag = 1;
+ res = sync_window(fs);
+ if (res != FR_OK) break;
+ mem_set(dir, 0, SS(fs));
+ }
+ }
+ if (res == FR_OK) res = dir_register(&dj); /* Register the object to the directoy */
+ if (res == FR_OK) {
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* Initialize directory entry block */
+ st_dword(fs->dirbuf + XDIR_ModTime, tm); /* Created time */
+ st_dword(fs->dirbuf + XDIR_FstClus, dcl); /* Table start cluster */
+ st_dword(fs->dirbuf + XDIR_FileSize, (DWORD)dj.obj.objsize); /* File size needs to be valid */
+ st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)dj.obj.objsize);
+ fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag (contiguous) */
+ fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ dir = dj.dir;
+ st_dword(dir + DIR_ModTime, tm); /* Created time */
+ st_clust(fs, dir, dcl); /* Table start cluster */
+ dir[DIR_Attr] = AM_DIR; /* Attribute */
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) res = sync_fs(fs);
+ } else {
+ remove_chain(&dj.obj, dcl, 0); /* Could not register, remove cluster chain */
+ }
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Rename a File/Directory */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_rename (
+ const TCHAR* path_old, /* Pointer to the object name to be renamed */
+ const TCHAR* path_new /* Pointer to the new name */
+)
+{
+ FRESULT res;
+ DIR djo, djn;
+ FATFS *fs;
+ BYTE buf[_FS_EXFAT ? SZDIRE * 2 : 24], *dir;
+ DWORD dw;
+ DEF_NAMBUF
+
+
+ get_ldnumber(&path_new); /* Ignore drive number of new name */
+ res = find_volume(&path_old, &fs, FA_WRITE); /* Get logical drive of the old object */
+ if (res == FR_OK) {
+ djo.obj.fs = fs;
+ INIT_NAMBUF(fs);
+ res = follow_path(&djo, path_old); /* Check old object */
+ if (res == FR_OK && (djo.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check validity of name */
+#if _FS_LOCK != 0
+ if (res == FR_OK) res = chk_lock(&djo, 2);
+#endif
+ if (res == FR_OK) { /* Object to be renamed is found */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* At exFAT */
+ BYTE nf, nn;
+ WORD nh;
+
+ mem_cpy(buf, fs->dirbuf, SZDIRE * 2); /* Save 85+C0 entry of old object */
+ mem_cpy(&djn, &djo, sizeof djo);
+ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
+ if (res == FR_OK) { /* Is new name already in use by any other object? */
+ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
+ }
+ if (res == FR_NO_FILE) { /* It is a valid path and no name collision */
+ res = dir_register(&djn); /* Register the new entry */
+ if (res == FR_OK) {
+ nf = fs->dirbuf[XDIR_NumSec]; nn = fs->dirbuf[XDIR_NumName];
+ nh = ld_word(fs->dirbuf + XDIR_NameHash);
+ mem_cpy(fs->dirbuf, buf, SZDIRE * 2);
+ fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn;
+ st_word(fs->dirbuf + XDIR_NameHash, nh);
+/* Start of critical section where any interruption can cause a cross-link */
+ res = store_xdir(&djn);
+ }
+ }
+ } else
+#endif
+ { /* At FAT12/FAT16/FAT32 */
+ mem_cpy(buf, djo.dir + DIR_Attr, 21); /* Save information about the object except name */
+ mem_cpy(&djn, &djo, sizeof (DIR)); /* Duplicate the directory object */
+ res = follow_path(&djn, path_new); /* Make sure if new object name is not in use */
+ if (res == FR_OK) { /* Is new name already in use by any other object? */
+ res = (djn.obj.sclust == djo.obj.sclust && djn.dptr == djo.dptr) ? FR_NO_FILE : FR_EXIST;
+ }
+ if (res == FR_NO_FILE) { /* It is a valid path and no name collision */
+ res = dir_register(&djn); /* Register the new entry */
+ if (res == FR_OK) {
+ dir = djn.dir; /* Copy information about object except name */
+ mem_cpy(dir + 13, buf + 2, 19);
+ dir[DIR_Attr] = buf[0] | AM_ARC;
+ fs->wflag = 1;
+ if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */
+ dw = clust2sect(fs, ld_clust(fs, dir));
+ if (!dw) {
+ res = FR_INT_ERR;
+ } else {
+/* Start of critical section where any interruption can cause a cross-link */
+ res = move_window(fs, dw);
+ dir = fs->win + SZDIRE * 1; /* Ptr to .. entry */
+ if (res == FR_OK && dir[1] == '.') {
+ st_clust(fs, dir, djn.obj.sclust);
+ fs->wflag = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (res == FR_OK) {
+ res = dir_remove(&djo); /* Remove old entry */
+ if (res == FR_OK) {
+ res = sync_fs(fs);
+ }
+ }
+/* End of critical section */
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !_FS_READONLY */
+#endif /* _FS_MINIMIZE == 0 */
+#endif /* _FS_MINIMIZE <= 1 */
+#endif /* _FS_MINIMIZE <= 2 */
+
+
+
+#if _USE_CHMOD && !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Change Attribute */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_chmod (
+ const TCHAR* path, /* Pointer to the file path */
+ BYTE attr, /* Attribute bits */
+ BYTE mask /* Attribute mask to change */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ dj.obj.fs = fs;
+ if (res == FR_OK) {
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */
+ if (res == FR_OK) {
+ mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ fs->dirbuf[XDIR_Attr] = (attr & mask) | (fs->dirbuf[XDIR_Attr] & (BYTE)~mask); /* Apply attribute change */
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ dj.dir[DIR_Attr] = (attr & mask) | (dj.dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) res = sync_fs(fs);
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Change Timestamp */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_utime (
+ const TCHAR* path, /* Pointer to the file/directory name */
+ const FILINFO* fno /* Pointer to the time stamp to be set */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ DEF_NAMBUF
+
+
+ res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */
+ dj.obj.fs = fs;
+ if (res == FR_OK) {
+ INIT_NAMBUF(fs);
+ res = follow_path(&dj, path); /* Follow the file path */
+ if (res == FR_OK && (dj.fn[NSFLAG] & (NS_DOT | NS_NONAME))) res = FR_INVALID_NAME; /* Check object validity */
+ if (res == FR_OK) {
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ st_dword(fs->dirbuf + XDIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime);
+ res = store_xdir(&dj);
+ } else
+#endif
+ {
+ st_dword(dj.dir + DIR_ModTime, (DWORD)fno->fdate << 16 | fno->ftime);
+ fs->wflag = 1;
+ }
+ if (res == FR_OK) res = sync_fs(fs);
+ }
+ FREE_NAMBUF();
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* _USE_CHMOD && !_FS_READONLY */
+
+
+
+#if _USE_LABEL
+/*-----------------------------------------------------------------------*/
+/* Get Volume Label */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_getlabel (
+ const TCHAR* path, /* Path name of the logical drive number */
+ TCHAR* label, /* Pointer to a buffer to return the volume label */
+ DWORD* vsn /* Pointer to a variable to return the volume serial number */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ UINT si, di;
+#if _LFN_UNICODE || _FS_EXFAT
+ WCHAR w;
+#endif
+
+ /* Get logical drive */
+ res = find_volume(&path, &fs, 0);
+
+ /* Get volume label */
+ if (res == FR_OK && label) {
+ dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */
+ res = dir_sdi(&dj, 0);
+ if (res == FR_OK) {
+ res = dir_read(&dj, 1); /* Find a volume label entry */
+ if (res == FR_OK) {
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ for (si = di = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */
+ w = ld_word(dj.dir + XDIR_Label + si * 2);
+#if _LFN_UNICODE
+ label[di++] = w;
+#else
+ w = ff_convert(w, 0); /* Unicode -> OEM */
+ if (w == 0) w = '?'; /* Replace wrong character */
+ if (_DF1S && w >= 0x100) label[di++] = (char)(w >> 8);
+ label[di++] = (char)w;
+#endif
+ }
+ label[di] = 0;
+ } else
+#endif
+ {
+ si = di = 0; /* Extract volume label from AM_VOL entry with code comversion */
+ do {
+#if _LFN_UNICODE
+ w = (si < 11) ? dj.dir[si++] : ' ';
+ if (IsDBCS1(w) && si < 11 && IsDBCS2(dj.dir[si])) {
+ w = w << 8 | dj.dir[si++];
+ }
+ label[di++] = ff_convert(w, 1); /* OEM -> Unicode */
+#else
+ label[di++] = dj.dir[si++];
+#endif
+ } while (di < 11);
+ do { /* Truncate trailing spaces */
+ label[di] = 0;
+ if (di == 0) break;
+ } while (label[--di] == ' ');
+ }
+ }
+ }
+ if (res == FR_NO_FILE) { /* No label entry and return nul string */
+ label[0] = 0;
+ res = FR_OK;
+ }
+ }
+
+ /* Get volume serial number */
+ if (res == FR_OK && vsn) {
+ res = move_window(fs, fs->volbase);
+ if (res == FR_OK) {
+ switch (fs->fs_type) {
+ case FS_EXFAT: di = BPB_VolIDEx; break;
+ case FS_FAT32: di = BS_VolID32; break;
+ default: di = BS_VolID;
+ }
+ *vsn = ld_dword(fs->win + di);
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+
+
+#if !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Set Volume Label */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_setlabel (
+ const TCHAR* label /* Pointer to the volume label to set */
+)
+{
+ FRESULT res;
+ DIR dj;
+ FATFS *fs;
+ BYTE dirvn[22];
+ UINT i, j, slen;
+ WCHAR w;
+ static const char badchr[] = "\"*+,.:;<=>\?[]|\x7F";
+
+
+ /* Get logical drive */
+ res = find_volume(&label, &fs, FA_WRITE);
+ if (res != FR_OK) LEAVE_FF(fs, res);
+ dj.obj.fs = fs;
+
+ /* Get length of given volume label */
+ for (slen = 0; (UINT)label[slen] >= ' '; slen++) ; /* Get name length */
+
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
+ for (i = j = 0; i < slen; ) { /* Create volume label in directory form */
+ w = label[i++];
+#if !_LFN_UNICODE
+ if (IsDBCS1(w)) {
+ w = (i < slen && IsDBCS2(label[i])) ? w << 8 | (BYTE)label[i++] : 0;
+ }
+ w = ff_convert(w, 1);
+#endif
+ if (w == 0 || chk_chr(badchr, w) || j == 22) { /* Check validity check validity of the volume label */
+ LEAVE_FF(fs, FR_INVALID_NAME);
+ }
+ st_word(dirvn + j, w); j += 2;
+ }
+ slen = j;
+ } else
+#endif
+ { /* On the FAT12/16/32 volume */
+ for ( ; slen && label[slen - 1] == ' '; slen--) ; /* Remove trailing spaces */
+ if (slen) { /* Is there a volume label to be set? */
+ dirvn[0] = 0; i = j = 0; /* Create volume label in directory form */
+ do {
+#if _LFN_UNICODE
+ w = ff_convert(ff_wtoupper(label[i++]), 0);
+#else
+ w = (BYTE)label[i++];
+ if (IsDBCS1(w)) {
+ w = (j < 10 && i < slen && IsDBCS2(label[i])) ? w << 8 | (BYTE)label[i++] : 0;
+ }
+#if _USE_LFN != 0
+ w = ff_convert(ff_wtoupper(ff_convert(w, 1)), 0);
+#else
+ if (IsLower(w)) w -= 0x20; /* To upper ASCII characters */
+#ifdef _EXCVT
+ if (w >= 0x80) w = ExCvt[w - 0x80]; /* To upper extended characters (SBCS cfg) */
+#else
+ if (!_DF1S && w >= 0x80) w = 0; /* Reject extended characters (ASCII cfg) */
+#endif
+#endif
+#endif
+ if (w == 0 || chk_chr(badchr, w) || j >= (UINT)((w >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */
+ LEAVE_FF(fs, FR_INVALID_NAME);
+ }
+ if (w >= 0x100) dirvn[j++] = (BYTE)(w >> 8);
+ dirvn[j++] = (BYTE)w;
+ } while (i < slen);
+ while (j < 11) dirvn[j++] = ' '; /* Fill remaining name field */
+ if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */
+ }
+ }
+
+ /* Set volume label */
+ dj.obj.sclust = 0; /* Open root directory */
+ res = dir_sdi(&dj, 0);
+ if (res == FR_OK) {
+ res = dir_read(&dj, 1); /* Get volume label entry */
+ if (res == FR_OK) {
+ if (_FS_EXFAT && fs->fs_type == FS_EXFAT) {
+ dj.dir[XDIR_NumLabel] = (BYTE)(slen / 2); /* Change the volume label */
+ mem_cpy(dj.dir + XDIR_Label, dirvn, slen);
+ } else {
+ if (slen) {
+ mem_cpy(dj.dir, dirvn, 11); /* Change the volume label */
+ } else {
+ dj.dir[DIR_Name] = DDEM; /* Remove the volume label */
+ }
+ }
+ fs->wflag = 1;
+ res = sync_fs(fs);
+ } else { /* No volume label entry is found or error */
+ if (res == FR_NO_FILE) {
+ res = FR_OK;
+ if (slen) { /* Create a volume label entry */
+ res = dir_alloc(&dj, 1); /* Allocate an entry */
+ if (res == FR_OK) {
+ mem_set(dj.dir, 0, SZDIRE); /* Clear the entry */
+ if (_FS_EXFAT && fs->fs_type == FS_EXFAT) {
+ dj.dir[XDIR_Type] = 0x83; /* Create 83 entry */
+ dj.dir[XDIR_NumLabel] = (BYTE)(slen / 2);
+ mem_cpy(dj.dir + XDIR_Label, dirvn, slen);
+ } else {
+ dj.dir[DIR_Attr] = AM_VOL; /* Create volume label entry */
+ mem_cpy(dj.dir, dirvn, 11);
+ }
+ fs->wflag = 1;
+ res = sync_fs(fs);
+ }
+ }
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* !_FS_READONLY */
+#endif /* _USE_LABEL */
+
+
+
+#if _USE_EXPAND && !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Allocate a Contiguous Blocks to the File */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_expand (
+ FIL* fp, /* Pointer to the file object */
+ FSIZE_t fsz, /* File size to be expanded to */
+ BYTE opt /* Operation mode 0:Find and prepare or 1:Find and allocate */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD n, clst, stcl, scl, ncl, tcl, lclst;
+
+
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (fsz == 0 || fp->obj.objsize != 0 || !(fp->flag & FA_WRITE)) LEAVE_FF(fs, FR_DENIED);
+#if _FS_EXFAT
+ if (fs->fs_type != FS_EXFAT && fsz >= 0x100000000) LEAVE_FF(fs, FR_DENIED); /* Check if in size limit */
+#endif
+ n = (DWORD)fs->csize * SS(fs); /* Cluster size */
+ tcl = (DWORD)(fsz / n) + ((fsz & (n - 1)) ? 1 : 0); /* Number of clusters required */
+ stcl = fs->last_clst; lclst = 0;
+ if (stcl < 2 || stcl >= fs->n_fatent) stcl = 2;
+
+#if _FS_EXFAT
+ if (fs->fs_type == FS_EXFAT) {
+ scl = find_bitmap(fs, stcl, tcl); /* Find a contiguous cluster block */
+ if (scl == 0) res = FR_DENIED; /* No contiguous cluster block was found */
+ if (scl == 0xFFFFFFFF) res = FR_DISK_ERR;
+ if (res == FR_OK) {
+ if (opt) {
+ res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */
+ lclst = scl + tcl - 1;
+ } else {
+ lclst = scl - 1;
+ }
+ }
+ } else
+#endif
+ {
+ scl = clst = stcl; ncl = 0;
+ for (;;) { /* Find a contiguous cluster block */
+ n = get_fat(&fp->obj, clst);
+ if (++clst >= fs->n_fatent) clst = 2;
+ if (n == 1) { res = FR_INT_ERR; break; }
+ if (n == 0xFFFFFFFF) { res = FR_DISK_ERR; break; }
+ if (n == 0) { /* Is it a free cluster? */
+ if (++ncl == tcl) break; /* Break if a contiguous cluster block is found */
+ } else {
+ scl = clst; ncl = 0; /* Not a free cluster */
+ }
+ if (clst == stcl) { res = FR_DENIED; break; } /* No contiguous cluster? */
+ }
+ if (res == FR_OK) {
+ if (opt) {
+ for (clst = scl, n = tcl; n; clst++, n--) { /* Create a cluster chain on the FAT */
+ res = put_fat(fs, clst, (n == 1) ? 0xFFFFFFFF : clst + 1);
+ if (res != FR_OK) break;
+ lclst = clst;
+ }
+ } else {
+ lclst = scl - 1;
+ }
+ }
+ }
+
+ if (res == FR_OK) {
+ fs->last_clst = lclst; /* Set suggested start cluster to start next */
+ if (opt) {
+ fp->obj.sclust = scl; /* Update object allocation information */
+ fp->obj.objsize = fsz;
+ if (_FS_EXFAT) fp->obj.stat = 2; /* Set status 'contiguous chain' */
+ fp->flag |= FA_MODIFIED;
+ if (fs->free_clst < fs->n_fatent - 2) { /* Update FSINFO */
+ fs->free_clst -= tcl;
+ fs->fsi_flag |= 1;
+ }
+ }
+ }
+
+ LEAVE_FF(fs, res);
+}
+
+#endif /* _USE_EXPAND && !_FS_READONLY */
+
+
+
+#if _USE_FORWARD
+/*-----------------------------------------------------------------------*/
+/* Forward data to the stream directly */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_forward (
+ FIL* fp, /* Pointer to the file object */
+ UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */
+ UINT btf, /* Number of bytes to forward */
+ UINT* bf /* Pointer to number of bytes forwarded */
+)
+{
+ FRESULT res;
+ FATFS *fs;
+ DWORD clst, sect;
+ FSIZE_t remain;
+ UINT rcnt, csect;
+ BYTE *dbuf;
+
+
+ *bf = 0; /* Clear transfer byte counter */
+ res = validate(&fp->obj, &fs); /* Check validity of the file object */
+ if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) LEAVE_FF(fs, res);
+ if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */
+
+ remain = fp->obj.objsize - fp->fptr;
+ if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */
+
+ for ( ; btf && (*func)(0, 0); /* Repeat until all data transferred or stream goes busy */
+ fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) {
+ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */
+ if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */
+ if (csect == 0) { /* On the cluster boundary? */
+ clst = (fp->fptr == 0) ? /* On the top of the file? */
+ fp->obj.sclust : get_fat(&fp->obj, fp->clust);
+ if (clst <= 1) ABORT(fs, FR_INT_ERR);
+ if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR);
+ fp->clust = clst; /* Update current cluster */
+ }
+ }
+ sect = clust2sect(fs, fp->clust); /* Get current data sector */
+ if (!sect) ABORT(fs, FR_INT_ERR);
+ sect += csect;
+#if _FS_TINY
+ if (move_window(fs, sect) != FR_OK) ABORT(fs, FR_DISK_ERR); /* Move sector window to the file data */
+ dbuf = fs->win;
+#else
+ if (fp->sect != sect) { /* Fill sector cache with file data */
+#if !_FS_READONLY
+ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */
+ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ fp->flag &= (BYTE)~FA_DIRTY;
+ }
+#endif
+ if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR);
+ }
+ dbuf = fp->buf;
+#endif
+ fp->sect = sect;
+ rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */
+ if (rcnt > btf) rcnt = btf; /* Clip it by btr if needed */
+ rcnt = (*func)(dbuf + ((UINT)fp->fptr % SS(fs)), rcnt); /* Forward the file data */
+ if (!rcnt) ABORT(fs, FR_INT_ERR);
+ }
+
+ LEAVE_FF(fs, FR_OK);
+}
+#endif /* _USE_FORWARD */
+
+
+
+#if _USE_MKFS && !_FS_READONLY
+/*-----------------------------------------------------------------------*/
+/* Create FAT file system on the logical drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_mkfs (
+ const TCHAR* path, /* Logical drive number */
+ BYTE opt, /* Format option */
+ DWORD au, /* Size of allocation unit [byte] */
+ void* work, /* Pointer to working buffer */
+ UINT len /* Size of working buffer */
+)
+{
+ const UINT n_fats = 1; /* Number of FATs for FAT12/16/32 volume (1 or 2) */
+ const UINT n_rootdir = 512; /* Number of root directory entries for FAT12/16 volume */
+ static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT12/16 volume (4Ks unit) */
+ static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */
+ BYTE fmt, sys, *buf, *pte, pdrv, part;
+ WORD ss;
+ DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n;
+ DWORD b_vol, b_fat, b_data; /* Base LBA for volume, fat, data */
+ DWORD sz_vol, sz_rsv, sz_fat, sz_dir; /* Size for volume, fat, dir, data */
+ UINT i;
+ int vol;
+ DSTATUS stat;
+#if _USE_TRIM || _FS_EXFAT
+ DWORD tbl[3];
+#endif
+
+
+ /* Check mounted drive and clear work area */
+ vol = get_ldnumber(&path); /* Get target logical drive */
+ if (vol < 0) return FR_INVALID_DRIVE;
+ if (FatFs[vol]) FatFs[vol]->fs_type = 0; /* Clear mounted volume */
+ pdrv = LD2PD(vol); /* Physical drive */
+ part = LD2PT(vol); /* Partition (0:create as new, 1-4:get from partition table) */
+
+ /* Check physical drive status */
+ stat = disk_initialize(pdrv);
+ if (stat & STA_NOINIT) return FR_NOT_READY;
+ if (stat & STA_PROTECT) return FR_WRITE_PROTECTED;
+ if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK || !sz_blk || sz_blk > 32768 || (sz_blk & (sz_blk - 1))) sz_blk = 1; /* Erase block to align data area */
+#if _MAX_SS != _MIN_SS /* Get sector size of the medium */
+ if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR;
+ if (ss > _MAX_SS || ss < _MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR;
+#else
+ ss = _MAX_SS;
+#endif
+ if ((au != 0 && au < ss) || au > 0x1000000 || (au & (au - 1))) return FR_INVALID_PARAMETER; /* Check if au is valid */
+ au /= ss; /* Cluster size in unit of sector */
+
+ /* Get working buffer */
+ buf = (BYTE*)work; /* Working buffer */
+ sz_buf = len / ss; /* Size of working buffer (sector) */
+ szb_buf = sz_buf * ss; /* Size of working buffer (byte) */
+ if (!szb_buf) return FR_MKFS_ABORTED;
+
+ /* Determine where the volume to be located (b_vol, sz_vol) */
+ if (_MULTI_PARTITION && part != 0) {
+ /* Get partition information from partition table in the MBR */
+ if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Load MBR */
+ if (ld_word(buf + BS_55AA) != 0xAA55) return FR_MKFS_ABORTED; /* Check if MBR is valid */
+ pte = buf + (MBR_Table + (part - 1) * SZ_PTE);
+ if (!pte[PTE_System]) return FR_MKFS_ABORTED; /* No partition? */
+ b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */
+ sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */
+ } else {
+ /* Create a single-partition in this function */
+ if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) return FR_DISK_ERR;
+ b_vol = (opt & FM_SFD) ? 0 : 63; /* Volume start sector */
+ if (sz_vol < b_vol) return FR_MKFS_ABORTED;
+ sz_vol -= b_vol; /* Volume size */
+ }
+ if (sz_vol < 128) return FR_MKFS_ABORTED; /* Check if volume size is >=128s */
+
+ /* Pre-determine the FAT type */
+ do {
+ if (_FS_EXFAT && (opt & FM_EXFAT)) { /* exFAT possible? */
+ if ((opt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || au > 128) { /* exFAT only, vol >= 64Ms or au > 128s ? */
+ fmt = FS_EXFAT; break;
+ }
+ }
+ if (au > 128) return FR_INVALID_PARAMETER; /* Too large au for FAT/FAT32 */
+ if (opt & FM_FAT32) { /* FAT32 possible? */
+ if ((opt & FM_ANY) == FM_FAT32 || !(opt & FM_FAT)) { /* FAT32 only or no-FAT? */
+ fmt = FS_FAT32; break;
+ }
+ }
+ if (!(opt & FM_FAT)) return FR_INVALID_PARAMETER; /* no-FAT? */
+ fmt = FS_FAT16;
+ } while (0);
+
+#if _FS_EXFAT
+ if (fmt == FS_EXFAT) { /* Create an exFAT volume */
+ DWORD szb_bit, szb_case, sum, nb, cl;
+ WCHAR ch, si;
+ UINT j, st;
+ BYTE b;
+
+ if (sz_vol < 0x1000) return FR_MKFS_ABORTED; /* Too small volume? */
+#if _USE_TRIM
+ tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area can be erased */
+ disk_ioctl(pdrv, CTRL_TRIM, tbl);
+#endif
+ /* Determine FAT location, data location and number of clusters */
+ if (!au) { /* au auto-selection */
+ au = 8;
+ if (sz_vol >= 0x80000) au = 64; /* >= 512Ks */
+ if (sz_vol >= 0x4000000) au = 256; /* >= 64Ms */
+ }
+ b_fat = b_vol + 32; /* FAT start at offset 32 */
+ sz_fat = ((sz_vol / au + 2) * 4 + ss - 1) / ss; /* Number of FAT sectors */
+ b_data = (b_fat + sz_fat + sz_blk - 1) & ~(sz_blk - 1); /* Align data area to the erase block boundary */
+ if (b_data >= sz_vol / 2) return FR_MKFS_ABORTED; /* Too small volume? */
+ n_clst = (sz_vol - (b_data - b_vol)) / au; /* Number of clusters */
+ if (n_clst <16) return FR_MKFS_ABORTED; /* Too few clusters? */
+ if (n_clst > MAX_EXFAT) return FR_MKFS_ABORTED; /* Too many clusters? */
+
+ szb_bit = (n_clst + 7) / 8; /* Size of allocation bitmap */
+ tbl[0] = (szb_bit + au * ss - 1) / (au * ss); /* Number of allocation bitmap clusters */
+
+ /* Create a compressed up-case table */
+ sect = b_data + au * tbl[0]; /* Table start sector */
+ sum = 0; /* Table checksum to be stored in the 82 entry */
+ st = si = i = j = szb_case = 0;
+ do {
+ switch (st) {
+ case 0:
+ ch = ff_wtoupper(si); /* Get an up-case char */
+ if (ch != si) {
+ si++; break; /* Store the up-case char if exist */
+ }
+ for (j = 1; (WCHAR)(si + j) && (WCHAR)(si + j) == ff_wtoupper((WCHAR)(si + j)); j++) ; /* Get run length of no-case block */
+ if (j >= 128) {
+ ch = 0xFFFF; st = 2; break; /* Compress the no-case block if run is >= 128 */
+ }
+ st = 1; /* Do not compress short run */
+ /* continue */
+ case 1:
+ ch = si++; /* Fill the short run */
+ if (--j == 0) st = 0;
+ break;
+ default:
+ ch = (WCHAR)j; si += j; /* Number of chars to skip */
+ st = 0;
+ }
+ sum = xsum32(buf[i + 0] = (BYTE)ch, sum); /* Put it into the write buffer */
+ sum = xsum32(buf[i + 1] = (BYTE)(ch >> 8), sum);
+ i += 2; szb_case += 2;
+ if (!si || i == szb_buf) { /* Write buffered data when buffer full or end of process */
+ n = (i + ss - 1) / ss;
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR;
+ sect += n; i = 0;
+ }
+ } while (si);
+ tbl[1] = (szb_case + au * ss - 1) / (au * ss); /* Number of up-case table clusters */
+ tbl[2] = 1; /* Number of root dir clusters */
+
+ /* Initialize the allocation bitmap */
+ sect = b_data; nsect = (szb_bit + ss - 1) / ss; /* Start of bitmap and number of sectors */
+ nb = tbl[0] + tbl[1] + tbl[2]; /* Number of clusters in-use by system */
+ do {
+ mem_set(buf, 0, szb_buf);
+ for (i = 0; nb >= 8 && i < szb_buf; buf[i++] = 0xFF, nb -= 8) ;
+ for (b = 1; nb && i < szb_buf; buf[i] |= b, b <<= 1, nb--) ;
+ n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR;
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Initialize the FAT */
+ sect = b_fat; nsect = sz_fat; /* Start of FAT and number of FAT sectors */
+ j = nb = cl = 0;
+ do {
+ mem_set(buf, 0, szb_buf); i = 0; /* Clear work area and reset write index */
+ if (cl == 0) { /* Set entry 0 and 1 */
+ st_dword(buf + i, 0xFFFFFFF8); i += 4; cl++;
+ st_dword(buf + i, 0xFFFFFFFF); i += 4; cl++;
+ }
+ do { /* Create chains of bitmap, up-case and root dir */
+ while (nb && i < szb_buf) { /* Create a chain */
+ st_dword(buf + i, (nb > 1) ? cl + 1 : 0xFFFFFFFF);
+ i += 4; cl++; nb--;
+ }
+ if (!nb && j < 3) nb = tbl[j++]; /* Next chain */
+ } while (nb && i < szb_buf);
+ n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR;
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Initialize the root directory */
+ mem_set(buf, 0, szb_buf);
+ buf[SZDIRE * 0 + 0] = 0x83; /* 83 entry (volume label) */
+ buf[SZDIRE * 1 + 0] = 0x81; /* 81 entry (allocation bitmap) */
+ st_dword(buf + SZDIRE * 1 + 20, 2);
+ st_dword(buf + SZDIRE * 1 + 24, szb_bit);
+ buf[SZDIRE * 2 + 0] = 0x82; /* 82 entry (up-case table) */
+ st_dword(buf + SZDIRE * 2 + 4, sum);
+ st_dword(buf + SZDIRE * 2 + 20, 2 + tbl[0]);
+ st_dword(buf + SZDIRE * 2 + 24, szb_case);
+ sect = b_data + au * (tbl[0] + tbl[1]); nsect = au; /* Start of the root directory and number of sectors */
+ do { /* Fill root directory sectors */
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, n) != RES_OK) return FR_DISK_ERR;
+ mem_set(buf, 0, ss);
+ sect += n; nsect -= n;
+ } while (nsect);
+
+ /* Create two set of the exFAT VBR blocks */
+ sect = b_vol;
+ for (n = 0; n < 2; n++) {
+ /* Main record (+0) */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11); /* Boot jump code (x86), OEM name */
+ st_dword(buf + BPB_VolOfsEx, b_vol); /* Volume offset in the physical drive [sector] */
+ st_dword(buf + BPB_TotSecEx, sz_vol); /* Volume size [sector] */
+ st_dword(buf + BPB_FatOfsEx, b_fat - b_vol); /* FAT offset [sector] */
+ st_dword(buf + BPB_FatSzEx, sz_fat); /* FAT size [sector] */
+ st_dword(buf + BPB_DataOfsEx, b_data - b_vol); /* Data offset [sector] */
+ st_dword(buf + BPB_NumClusEx, n_clst); /* Number of clusters */
+ st_dword(buf + BPB_RootClusEx, 2 + tbl[0] + tbl[1]); /* Root dir cluster # */
+ st_dword(buf + BPB_VolIDEx, GET_FATTIME()); /* VSN */
+ st_word(buf + BPB_FSVerEx, 0x100); /* File system version (1.00) */
+ for (buf[BPB_BytsPerSecEx] = 0, i = ss; i >>= 1; buf[BPB_BytsPerSecEx]++) ; /* Log2 of sector size [byte] */
+ for (buf[BPB_SecPerClusEx] = 0, i = au; i >>= 1; buf[BPB_SecPerClusEx]++) ; /* Log2 of cluster size [sector] */
+ buf[BPB_NumFATsEx] = 1; /* Number of FATs */
+ buf[BPB_DrvNumEx] = 0x80; /* Drive number (for int13) */
+ st_word(buf + BS_BootCodeEx, 0xFEEB); /* Boot code (x86) */
+ st_word(buf + BS_55AA, 0xAA55); /* Signature (placed here regardless of sector size) */
+ for (i = sum = 0; i < ss; i++) { /* VBR checksum */
+ if (i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx) sum = xsum32(buf[i], sum);
+ }
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR;
+ /* Extended bootstrap record (+1..+8) */
+ mem_set(buf, 0, ss);
+ st_word(buf + ss - 2, 0xAA55); /* Signature (placed at end of sector) */
+ for (j = 1; j < 9; j++) {
+ for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR;
+ }
+ /* OEM/Reserved record (+9..+10) */
+ mem_set(buf, 0, ss);
+ for ( ; j < 11; j++) {
+ for (i = 0; i < ss; sum = xsum32(buf[i++], sum)) ; /* VBR checksum */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR;
+ }
+ /* Sum record (+11) */
+ for (i = 0; i < ss; i += 4) st_dword(buf + i, sum); /* Fill with checksum value */
+ if (disk_write(pdrv, buf, sect++, 1) != RES_OK) return FR_DISK_ERR;
+ }
+
+ } else
+#endif /* _FS_EXFAT */
+ { /* Create an FAT12/16/32 volume */
+ do {
+ pau = au;
+ /* Pre-determine number of clusters and FAT sub-type */
+ if (fmt == FS_FAT32) { /* FAT32 volume */
+ if (!pau) { /* au auto-selection */
+ n = sz_vol / 0x20000; /* Volume size in unit of 128KS */
+ for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */
+ }
+ n_clst = sz_vol / pau; /* Number of clusters */
+ sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* FAT size [sector] */
+ sz_rsv = 32; /* Number of reserved sectors */
+ sz_dir = 0; /* No static directory */
+ if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) return FR_MKFS_ABORTED;
+ } else { /* FAT12/16 volume */
+ if (!pau) { /* au auto-selection */
+ n = sz_vol / 0x1000; /* Volume size in unit of 4KS */
+ for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */
+ }
+ n_clst = sz_vol / pau;
+ if (n_clst > MAX_FAT12) {
+ n = n_clst * 2 + 4; /* FAT size [byte] */
+ } else {
+ fmt = FS_FAT12;
+ n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */
+ }
+ sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */
+ sz_rsv = 1; /* Number of reserved sectors */
+ sz_dir = (DWORD)n_rootdir * SZDIRE / ss; /* Rootdir size [sector] */
+ }
+ b_fat = b_vol + sz_rsv; /* FAT base */
+ b_data = b_fat + sz_fat * n_fats + sz_dir; /* Data base */
+
+ /* Align data base to erase block boundary (for flash memory media) */
+ n = ((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data; /* Next nearest erase block from current data base */
+ if (fmt == FS_FAT32) { /* FAT32: Move FAT base */
+ sz_rsv += n; b_fat += n;
+ } else { /* FAT12/16: Expand FAT size */
+ sz_fat += n / n_fats;
+ }
+
+ /* Determine number of clusters and final check of validity of the FAT sub-type */
+ if (sz_vol < b_data + pau * 16 - b_vol) return FR_MKFS_ABORTED; /* Too small volume */
+ n_clst = (sz_vol - sz_rsv - sz_fat * n_fats - sz_dir) / pau;
+ if (fmt == FS_FAT32) {
+ if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32 */
+ if (!au && (au = pau / 2) != 0) continue; /* Adjust cluster size and retry */
+ return FR_MKFS_ABORTED;
+ }
+ }
+ if (fmt == FS_FAT16) {
+ if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */
+ if (!au && (pau * 2) <= 64) {
+ au = pau * 2; continue; /* Adjust cluster size and retry */
+ }
+ if ((opt & FM_FAT32)) {
+ fmt = FS_FAT32; continue; /* Switch type to FAT32 and retry */
+ }
+ if (!au && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */
+ return FR_MKFS_ABORTED;
+ }
+ if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */
+ if (!au && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */
+ return FR_MKFS_ABORTED;
+ }
+ }
+ if (fmt == FS_FAT12 && n_clst > MAX_FAT12) return FR_MKFS_ABORTED; /* Too many clusters for FAT12 */
+
+ /* Ok, it is the valid cluster configuration */
+ break;
+ } while (1);
+
+#if _USE_TRIM
+ tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area can be erased */
+ disk_ioctl(pdrv, CTRL_TRIM, tbl);
+#endif
+ /* Create FAT VBR */
+ mem_set(buf, 0, ss);
+ mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */
+ st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */
+ buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */
+ st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */
+ buf[BPB_NumFATs] = (BYTE)n_fats; /* Number of FATs */
+ st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */
+ if (sz_vol < 0x10000) {
+ st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */
+ } else {
+ st_dword(buf + BPB_TotSec32, sz_vol); /* Volume size in 32-bit LBA */
+ }
+ buf[BPB_Media] = 0xF8; /* Media descriptor byte */
+ st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */
+ st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */
+ st_dword(buf + BPB_HiddSec, b_vol); /* Volume offset in the physical drive [sector] */
+ if (fmt == FS_FAT32) {
+ st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */
+ st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */
+ st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */
+ st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */
+ st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */
+ buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */
+ buf[BS_BootSig32] = 0x29; /* Extended boot signature */
+ mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */
+ } else {
+ st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */
+ st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */
+ buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */
+ buf[BS_BootSig] = 0x29; /* Extended boot signature */
+ mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */
+ }
+ st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */
+ if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the VBR sector */
+
+ /* Create FSINFO record if needed */
+ if (fmt == FS_FAT32) {
+ disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */
+ mem_set(buf, 0, ss);
+ st_dword(buf + FSI_LeadSig, 0x41615252);
+ st_dword(buf + FSI_StrucSig, 0x61417272);
+ st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */
+ st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */
+ st_word(buf + BS_55AA, 0xAA55);
+ disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */
+ disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */
+ }
+
+ /* Initialize FAT area */
+ mem_set(buf, 0, (UINT)szb_buf);
+ sect = b_fat; /* FAT start sector */
+ for (i = 0; i < n_fats; i++) { /* Initialize FATs each */
+ if (fmt == FS_FAT32) {
+ st_dword(buf + 0, 0xFFFFFFF8); /* Entry 0 */
+ st_dword(buf + 4, 0xFFFFFFFF); /* Entry 1 */
+ st_dword(buf + 8, 0x0FFFFFFF); /* Entry 2 (root directory) */
+ } else {
+ st_dword(buf + 0, (fmt == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* Entry 0 and 1 */
+ }
+ nsect = sz_fat; /* Number of FAT sectors */
+ do { /* Fill FAT sectors */
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR;
+ mem_set(buf, 0, ss);
+ sect += n; nsect -= n;
+ } while (nsect);
+ }
+
+ /* Initialize root directory (fill with zero) */
+ nsect = (fmt == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */
+ do {
+ n = (nsect > sz_buf) ? sz_buf : nsect;
+ if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR;
+ sect += n; nsect -= n;
+ } while (nsect);
+ }
+
+ /* Determine system ID in the partition table */
+ if (_FS_EXFAT && fmt == FS_EXFAT) {
+ sys = 0x07; /* HPFS/NTFS/exFAT */
+ } else {
+ if (fmt == FS_FAT32) {
+ sys = 0x0C; /* FAT32X */
+ } else {
+ if (sz_vol >= 0x10000) {
+ sys = 0x06; /* FAT12/16 (>=64KS) */
+ } else {
+ sys = (fmt == FS_FAT16) ? 0x04 : 0x01; /* FAT16 (<64KS) : FAT12 (<64KS) */
+ }
+ }
+ }
+
+ if (_MULTI_PARTITION && part != 0) {
+ /* Update system ID in the partition table */
+ if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Read the MBR */
+ buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys; /* Set system type */
+ if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it back to the MBR */
+ } else {
+ if (!(opt & FM_SFD)) {
+ /* Create partition table in FDISK format */
+ mem_set(buf, 0, ss);
+ st_word(buf + BS_55AA, 0xAA55); /* MBR signature */
+ pte = buf + MBR_Table; /* Create partition table for single partition in the drive */
+ pte[PTE_Boot] = 0; /* Boot indicator */
+ pte[PTE_StHead] = 1; /* Start head */
+ pte[PTE_StSec] = 1; /* Start sector */
+ pte[PTE_StCyl] = 0; /* Start cylinder */
+ pte[PTE_System] = sys; /* System type */
+ n = (b_vol + sz_vol) / (63 * 255); /* (End CHS is incorrect) */
+ pte[PTE_EdHead] = 254; /* End head */
+ pte[PTE_EdSec] = (BYTE)(n >> 2 | 63); /* End sector */
+ pte[PTE_EdCyl] = (BYTE)n; /* End cylinder */
+ st_dword(pte + PTE_StLba, b_vol); /* Start offset in LBA */
+ st_dword(pte + PTE_SizLba, sz_vol); /* Size in sectors */
+ if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the MBR */
+ }
+ }
+
+ if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) return FR_DISK_ERR;
+
+ return FR_OK;
+}
+
+
+
+#if _MULTI_PARTITION
+/*-----------------------------------------------------------------------*/
+/* Create partition table on the physical drive */
+/*-----------------------------------------------------------------------*/
+
+FRESULT f_fdisk (
+ BYTE pdrv, /* Physical drive number */
+ const DWORD* szt, /* Pointer to the size table for each partitions */
+ void* work /* Pointer to the working buffer */
+)
+{
+ UINT i, n, sz_cyl, tot_cyl, b_cyl, e_cyl, p_cyl;
+ BYTE s_hd, e_hd, *p, *buf = (BYTE*)work;
+ DSTATUS stat;
+ DWORD sz_disk, sz_part, s_part;
+
+
+ stat = disk_initialize(pdrv);
+ if (stat & STA_NOINIT) return FR_NOT_READY;
+ if (stat & STA_PROTECT) return FR_WRITE_PROTECTED;
+ if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_disk)) return FR_DISK_ERR;
+
+ /* Determine the CHS without any care of the drive geometry */
+ for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2) ;
+ if (n == 256) n--;
+ e_hd = n - 1;
+ sz_cyl = 63 * n;
+ tot_cyl = sz_disk / sz_cyl;
+
+ /* Create partition table */
+ mem_set(buf, 0, _MAX_SS);
+ p = buf + MBR_Table; b_cyl = 0;
+ for (i = 0; i < 4; i++, p += SZ_PTE) {
+ p_cyl = (szt[i] <= 100U) ? (DWORD)tot_cyl * szt[i] / 100 : szt[i] / sz_cyl;
+ if (!p_cyl) continue;
+ s_part = (DWORD)sz_cyl * b_cyl;
+ sz_part = (DWORD)sz_cyl * p_cyl;
+ if (i == 0) { /* Exclude first track of cylinder 0 */
+ s_hd = 1;
+ s_part += 63; sz_part -= 63;
+ } else {
+ s_hd = 0;
+ }
+ e_cyl = b_cyl + p_cyl - 1;
+ if (e_cyl >= tot_cyl) return FR_INVALID_PARAMETER;
+
+ /* Set partition table */
+ p[1] = s_hd; /* Start head */
+ p[2] = (BYTE)((b_cyl >> 2) + 1); /* Start sector */
+ p[3] = (BYTE)b_cyl; /* Start cylinder */
+ p[4] = 0x06; /* System type (temporary setting) */
+ p[5] = e_hd; /* End head */
+ p[6] = (BYTE)((e_cyl >> 2) + 63); /* End sector */
+ p[7] = (BYTE)e_cyl; /* End cylinder */
+ st_dword(p + 8, s_part); /* Start sector in LBA */
+ st_dword(p + 12, sz_part); /* Partition size */
+
+ /* Next partition */
+ b_cyl += p_cyl;
+ }
+ st_word(p, 0xAA55);
+
+ /* Write it to the MBR */
+ return (disk_write(pdrv, buf, 0, 1) != RES_OK || disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) ? FR_DISK_ERR : FR_OK;
+}
+
+#endif /* _MULTI_PARTITION */
+#endif /* _USE_MKFS && !_FS_READONLY */
+
+
+
+
+#if _USE_STRFUNC
+/*-----------------------------------------------------------------------*/
+/* Get a string from the file */
+/*-----------------------------------------------------------------------*/
+
+TCHAR* f_gets (
+ TCHAR* buff, /* Pointer to the string buffer to read */
+ int len, /* Size of string buffer (characters) */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ int n = 0;
+ TCHAR c, *p = buff;
+ BYTE s[2];
+ UINT rc;
+
+
+ while (n < len - 1) { /* Read characters until buffer gets filled */
+#if _LFN_UNICODE
+#if _STRF_ENCODE == 3 /* Read a character in UTF-8 */
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ c = s[0];
+ if (c >= 0x80) {
+ if (c < 0xC0) continue; /* Skip stray trailer */
+ if (c < 0xE0) { /* Two-byte sequence */
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ c = (c & 0x1F) << 6 | (s[0] & 0x3F);
+ if (c < 0x80) c = '?';
+ } else {
+ if (c < 0xF0) { /* Three-byte sequence */
+ f_read(fp, s, 2, &rc);
+ if (rc != 2) break;
+ c = c << 12 | (s[0] & 0x3F) << 6 | (s[1] & 0x3F);
+ if (c < 0x800) c = '?';
+ } else { /* Reject four-byte sequence */
+ c = '?';
+ }
+ }
+ }
+#elif _STRF_ENCODE == 2 /* Read a character in UTF-16BE */
+ f_read(fp, s, 2, &rc);
+ if (rc != 2) break;
+ c = s[1] + (s[0] << 8);
+#elif _STRF_ENCODE == 1 /* Read a character in UTF-16LE */
+ f_read(fp, s, 2, &rc);
+ if (rc != 2) break;
+ c = s[0] + (s[1] << 8);
+#else /* Read a character in ANSI/OEM */
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ c = s[0];
+ if (IsDBCS1(c)) {
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ c = (c << 8) + s[0];
+ }
+ c = ff_convert(c, 1); /* OEM -> Unicode */
+ if (!c) c = '?';
+#endif
+#else /* Read a character without conversion */
+ f_read(fp, s, 1, &rc);
+ if (rc != 1) break;
+ c = s[0];
+#endif
+ if (_USE_STRFUNC == 2 && c == '\r') continue; /* Strip '\r' */
+ *p++ = c;
+ n++;
+ if (c == '\n') break; /* Break on EOL */
+ }
+ *p = 0;
+ return n ? buff : 0; /* When no data read (eof or error), return with error. */
+}
+
+
+
+
+#if !_FS_READONLY
+#include
+/*-----------------------------------------------------------------------*/
+/* Put a character to the file */
+/*-----------------------------------------------------------------------*/
+
+typedef struct {
+ FIL *fp; /* Ptr to the writing file */
+ int idx, nchr; /* Write index of buf[] (-1:error), number of chars written */
+ BYTE buf[64]; /* Write buffer */
+} putbuff;
+
+
+static
+void putc_bfd ( /* Buffered write with code conversion */
+ putbuff* pb,
+ TCHAR c
+)
+{
+ UINT bw;
+ int i;
+
+
+ if (_USE_STRFUNC == 2 && c == '\n') { /* LF -> CRLF conversion */
+ putc_bfd(pb, '\r');
+ }
+
+ i = pb->idx; /* Write index of pb->buf[] */
+ if (i < 0) return;
+
+#if _LFN_UNICODE
+#if _STRF_ENCODE == 3 /* Write a character in UTF-8 */
+ if (c < 0x80) { /* 7-bit */
+ pb->buf[i++] = (BYTE)c;
+ } else {
+ if (c < 0x800) { /* 11-bit */
+ pb->buf[i++] = (BYTE)(0xC0 | c >> 6);
+ } else { /* 16-bit */
+ pb->buf[i++] = (BYTE)(0xE0 | c >> 12);
+ pb->buf[i++] = (BYTE)(0x80 | (c >> 6 & 0x3F));
+ }
+ pb->buf[i++] = (BYTE)(0x80 | (c & 0x3F));
+ }
+#elif _STRF_ENCODE == 2 /* Write a character in UTF-16BE */
+ pb->buf[i++] = (BYTE)(c >> 8);
+ pb->buf[i++] = (BYTE)c;
+#elif _STRF_ENCODE == 1 /* Write a character in UTF-16LE */
+ pb->buf[i++] = (BYTE)c;
+ pb->buf[i++] = (BYTE)(c >> 8);
+#else /* Write a character in ANSI/OEM */
+ c = ff_convert(c, 0); /* Unicode -> OEM */
+ if (!c) c = '?';
+ if (c >= 0x100)
+ pb->buf[i++] = (BYTE)(c >> 8);
+ pb->buf[i++] = (BYTE)c;
+#endif
+#else /* Write a character without conversion */
+ pb->buf[i++] = (BYTE)c;
+#endif
+
+ if (i >= (int)(sizeof pb->buf) - 3) { /* Write buffered characters to the file */
+ f_write(pb->fp, pb->buf, (UINT)i, &bw);
+ i = (bw == (UINT)i) ? 0 : -1;
+ }
+ pb->idx = i;
+ pb->nchr++;
+}
+
+
+static
+int putc_flush ( /* Flush left characters in the buffer */
+ putbuff* pb
+)
+{
+ UINT nw;
+
+ if ( pb->idx >= 0 /* Flush buffered characters to the file */
+ && f_write(pb->fp, pb->buf, (UINT)pb->idx, &nw) == FR_OK
+ && (UINT)pb->idx == nw) return pb->nchr;
+ return EOF;
+}
+
+
+static
+void putc_init ( /* Initialize write buffer */
+ putbuff* pb,
+ FIL* fp
+)
+{
+ pb->fp = fp;
+ pb->nchr = pb->idx = 0;
+}
+
+
+
+int f_putc (
+ TCHAR c, /* A character to be output */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ putbuff pb;
+
+
+ putc_init(&pb, fp);
+ putc_bfd(&pb, c); /* Put the character */
+ return putc_flush(&pb);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Put a string to the file */
+/*-----------------------------------------------------------------------*/
+
+int f_puts (
+ const TCHAR* str, /* Pointer to the string to be output */
+ FIL* fp /* Pointer to the file object */
+)
+{
+ putbuff pb;
+
+
+ putc_init(&pb, fp);
+ while (*str) putc_bfd(&pb, *str++); /* Put the string */
+ return putc_flush(&pb);
+}
+
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Put a formatted string to the file */
+/*-----------------------------------------------------------------------*/
+
+int f_printf (
+ FIL* fp, /* Pointer to the file object */
+ const TCHAR* fmt, /* Pointer to the format string */
+ ... /* Optional arguments... */
+)
+{
+ va_list arp;
+ putbuff pb;
+ BYTE f, r;
+ UINT i, j, w;
+ DWORD v;
+ TCHAR c, d, str[32], *p;
+
+
+ putc_init(&pb, fp);
+
+ va_start(arp, fmt);
+
+ for (;;) {
+ c = *fmt++;
+ if (c == 0) break; /* End of string */
+ if (c != '%') { /* Non escape character */
+ putc_bfd(&pb, c);
+ continue;
+ }
+ w = f = 0;
+ c = *fmt++;
+ if (c == '0') { /* Flag: '0' padding */
+ f = 1; c = *fmt++;
+ } else {
+ if (c == '-') { /* Flag: left justified */
+ f = 2; c = *fmt++;
+ }
+ }
+ while (IsDigit(c)) { /* Precision */
+ w = w * 10 + c - '0';
+ c = *fmt++;
+ }
+ if (c == 'l' || c == 'L') { /* Prefix: Size is long int */
+ f |= 4; c = *fmt++;
+ }
+ if (!c) break;
+ d = c;
+ if (IsLower(d)) d -= 0x20;
+ switch (d) { /* Type is... */
+ case 'S' : /* String */
+ p = va_arg(arp, TCHAR*);
+ for (j = 0; p[j]; j++) ;
+ if (!(f & 2)) {
+ while (j++ < w) putc_bfd(&pb, ' ');
+ }
+ while (*p) putc_bfd(&pb, *p++);
+ while (j++ < w) putc_bfd(&pb, ' ');
+ continue;
+ case 'C' : /* Character */
+ putc_bfd(&pb, (TCHAR)va_arg(arp, int)); continue;
+ case 'B' : /* Binary */
+ r = 2; break;
+ case 'O' : /* Octal */
+ r = 8; break;
+ case 'D' : /* Signed decimal */
+ case 'U' : /* Unsigned decimal */
+ r = 10; break;
+ case 'X' : /* Hexdecimal */
+ r = 16; break;
+ default: /* Unknown type (pass-through) */
+ putc_bfd(&pb, c); continue;
+ }
+
+ /* Get an argument and put it in numeral */
+ v = (f & 4) ? (DWORD)va_arg(arp, long) : ((d == 'D') ? (DWORD)(long)va_arg(arp, int) : (DWORD)va_arg(arp, unsigned int));
+ if (d == 'D' && (v & 0x80000000)) {
+ v = 0 - v;
+ f |= 8;
+ }
+ i = 0;
+ do {
+ d = (TCHAR)(v % r); v /= r;
+ if (d > 9) d += (c == 'x') ? 0x27 : 0x07;
+ str[i++] = d + '0';
+ } while (v && i < sizeof str / sizeof str[0]);
+ if (f & 8) str[i++] = '-';
+ j = i; d = (f & 1) ? '0' : ' ';
+ while (!(f & 2) && j++ < w) putc_bfd(&pb, d);
+ do putc_bfd(&pb, str[--i]); while (i);
+ while (j++ < w) putc_bfd(&pb, d);
+ }
+
+ va_end(arp);
+
+ return putc_flush(&pb);
+}
+
+#endif /* !_FS_READONLY */
+#endif /* _USE_STRFUNC */
+
+#if !_LFN_UNICODE
+WCHAR ff_convert(WCHAR chr, UINT dir)
+{
+ return chr;
+}
+WCHAR ff_wtoupper(WCHAR chr)
+{
+ return chr;
+}
+#endif
diff --git a/ff.h b/ff.h
new file mode 100644
index 0000000..905dd66
--- /dev/null
+++ b/ff.h
@@ -0,0 +1,366 @@
+/*----------------------------------------------------------------------------/
+/ FatFs - Generic FAT file system module R0.12b /
+/-----------------------------------------------------------------------------/
+/
+/ Copyright (C) 2016, ChaN, all right reserved.
+/
+/ FatFs module is an open source software. Redistribution and use of FatFs in
+/ source and binary forms, with or without modification, are permitted provided
+/ that the following condition is met:
+
+/ 1. Redistributions of source code must retain the above copyright notice,
+/ this condition and the following disclaimer.
+/
+/ This software is provided by the copyright holder and contributors "AS IS"
+/ and any warranties related to this software are DISCLAIMED.
+/ The copyright owner or contributors be NOT LIABLE for any damages caused
+/ by use of this software.
+/----------------------------------------------------------------------------*/
+
+
+#ifndef _FATFS
+#define _FATFS 68020 /* Revision ID */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "integer.h" /* Basic integer types */
+#include "ffconf.h" /* FatFs configuration options */
+
+#if _FATFS != _FFCONF
+#error Wrong configuration file (ffconf.h).
+#endif
+
+
+
+/* Definitions of volume management */
+
+#if _MULTI_PARTITION /* Multiple partition configuration */
+typedef struct {
+ BYTE pd; /* Physical drive number */
+ BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
+} PARTITION;
+extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
+#define LD2PD(vol) (VolToPart[vol].pd) /* Get physical drive number */
+#define LD2PT(vol) (VolToPart[vol].pt) /* Get partition index */
+
+#else /* Single partition configuration */
+#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is bound to the same physical drive number */
+#define LD2PT(vol) 0 /* Find first valid partition or in SFD */
+
+#endif
+
+
+
+/* Type of path name strings on FatFs API */
+
+#if _LFN_UNICODE /* Unicode (UTF-16) string */
+#if _USE_LFN == 0
+#error _LFN_UNICODE must be 0 at non-LFN cfg.
+#endif
+#ifndef _INC_TCHAR
+typedef WCHAR TCHAR;
+#define _T(x) L ## x
+#define _TEXT(x) L ## x
+#endif
+#else /* ANSI/OEM string */
+#ifndef _INC_TCHAR
+typedef char TCHAR;
+#define _T(x) x
+#define _TEXT(x) x
+#endif
+#endif
+
+
+
+/* Type of file size variables */
+
+#if _FS_EXFAT
+#if _USE_LFN == 0
+#error LFN must be enabled when enable exFAT
+#endif
+typedef QWORD FSIZE_t;
+#else
+typedef DWORD FSIZE_t;
+#endif
+
+
+
+/* File system object structure (FATFS) */
+
+typedef struct {
+ BYTE fs_type; /* File system type (0:N/A) */
+ BYTE drv; /* Physical drive number */
+ BYTE n_fats; /* Number of FATs (1 or 2) */
+ BYTE wflag; /* win[] flag (b0:dirty) */
+ BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
+ WORD id; /* File system mount ID */
+ WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
+ WORD csize; /* Cluster size [sectors] */
+#if _MAX_SS != _MIN_SS
+ WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
+#endif
+#if _USE_LFN != 0
+ WCHAR* lfnbuf; /* LFN working buffer */
+#endif
+#if _FS_EXFAT
+ BYTE* dirbuf; /* Directory entry block scratchpad buffer */
+#endif
+#if _FS_REENTRANT
+ _SYNC_t sobj; /* Identifier of sync object */
+#endif
+#if !_FS_READONLY
+ DWORD last_clst; /* Last allocated cluster */
+ DWORD free_clst; /* Number of free clusters */
+#endif
+#if _FS_RPATH != 0
+ DWORD cdir; /* Current directory start cluster (0:root) */
+#if _FS_EXFAT
+ DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
+ DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
+ DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
+#endif
+#endif
+ DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
+ DWORD fsize; /* Size of an FAT [sectors] */
+ DWORD volbase; /* Volume base sector */
+ DWORD fatbase; /* FAT base sector */
+ DWORD dirbase; /* Root directory base sector/cluster */
+ DWORD database; /* Data base sector */
+ DWORD winsect; /* Current sector appearing in the win[] */
+ BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
+} FATFS;
+
+
+
+/* Object ID and allocation information (_FDID) */
+
+typedef struct {
+ FATFS* fs; /* Pointer to the owner file system object */
+ WORD id; /* Owner file system mount ID */
+ BYTE attr; /* Object attribute */
+ BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:got flagmented, b2:sub-directory stretched) */
+ DWORD sclust; /* Object start cluster (0:no cluster or root directory) */
+ FSIZE_t objsize; /* Object size (valid when sclust != 0) */
+#if _FS_EXFAT
+ DWORD n_cont; /* Size of coutiguous part, clusters - 1 (valid when stat == 3) */
+ DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
+ DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
+ DWORD c_ofs; /* Offset in the containing directory (valid when sclust != 0) */
+#endif
+#if _FS_LOCK != 0
+ UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
+#endif
+} _FDID;
+
+
+
+/* File object structure (FIL) */
+
+typedef struct {
+ _FDID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
+ BYTE flag; /* File status flags */
+ BYTE err; /* Abort flag (error code) */
+ FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
+ DWORD clust; /* Current cluster of fpter (invalid when fprt is 0) */
+ DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
+#if !_FS_READONLY
+ DWORD dir_sect; /* Sector number containing the directory entry */
+ BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */
+#endif
+#if _USE_FASTSEEK
+ DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
+#endif
+#if !_FS_TINY
+ BYTE buf[_MAX_SS]; /* File private data read/write window */
+#endif
+} FIL;
+
+
+
+/* Directory object structure (DIR) */
+
+typedef struct {
+ _FDID obj; /* Object identifier */
+ DWORD dptr; /* Current read/write offset */
+ DWORD clust; /* Current cluster */
+ DWORD sect; /* Current sector */
+ BYTE* dir; /* Pointer to the directory item in the win[] */
+ BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
+#if _USE_LFN != 0
+ DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
+#endif
+#if _USE_FIND
+ const TCHAR* pat; /* Pointer to the name matching pattern */
+#endif
+} DIR;
+
+
+
+/* File information structure (FILINFO) */
+
+typedef struct {
+ FSIZE_t fsize; /* File size */
+ WORD fdate; /* Modified date */
+ WORD ftime; /* Modified time */
+ BYTE fattrib; /* File attribute */
+#if _USE_LFN != 0
+ TCHAR altname[13]; /* Altenative file name */
+ TCHAR fname[_MAX_LFN + 1]; /* Primary file name */
+#else
+ TCHAR fname[13]; /* File name */
+#endif
+} FILINFO;
+
+
+
+/* File function return code (FRESULT) */
+
+typedef enum {
+ FR_OK = 0, /* (0) Succeeded */
+ FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
+ FR_INT_ERR, /* (2) Assertion failed */
+ FR_NOT_READY, /* (3) The physical drive cannot work */
+ FR_NO_FILE, /* (4) Could not find the file */
+ FR_NO_PATH, /* (5) Could not find the path */
+ FR_INVALID_NAME, /* (6) The path name format is invalid */
+ FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
+ FR_EXIST, /* (8) Access denied due to prohibited access */
+ FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
+ FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
+ FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
+ FR_NOT_ENABLED, /* (12) The volume has no work area */
+ FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
+ FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
+ FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
+ FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
+ FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
+ FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_LOCK */
+ FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
+} FRESULT;
+
+
+
+/*--------------------------------------------------------------*/
+/* FatFs module application interface */
+
+FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
+FRESULT f_close (FIL* fp); /* Close an open file object */
+FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
+FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
+FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
+FRESULT f_truncate (FIL* fp); /* Truncate the file */
+FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
+FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
+FRESULT f_closedir (DIR* dp); /* Close an open directory */
+FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
+FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
+FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
+FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
+FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
+FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
+FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
+FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
+FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
+FRESULT f_chdir (const TCHAR* path); /* Change current directory */
+FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
+FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
+FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
+FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
+FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
+FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
+FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */
+FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
+FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */
+FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */
+int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
+int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
+int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
+TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
+
+#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
+#define f_error(fp) ((fp)->err)
+#define f_tell(fp) ((fp)->fptr)
+#define f_size(fp) ((fp)->obj.objsize)
+#define f_rewind(fp) f_lseek((fp), 0)
+#define f_rewinddir(dp) f_readdir((dp), 0)
+
+#ifndef EOF
+#define EOF (-1)
+#endif
+
+
+
+
+/*--------------------------------------------------------------*/
+/* Additional user defined functions */
+
+/* RTC function */
+#if !_FS_READONLY && !_FS_NORTC
+DWORD get_fattime (void);
+#endif
+
+/* Unicode support functions */
+#if _USE_LFN != 0 /* Unicode - OEM code conversion */
+WCHAR ff_convert (WCHAR chr, UINT dir); /* OEM-Unicode bidirectional conversion */
+WCHAR ff_wtoupper (WCHAR chr); /* Unicode upper-case conversion */
+#if _USE_LFN == 3 /* Memory functions */
+void* ff_memalloc (UINT msize); /* Allocate memory block */
+void ff_memfree (void* mblock); /* Free memory block */
+#endif
+#endif
+
+/* Sync functions */
+#if _FS_REENTRANT
+int ff_cre_syncobj (BYTE vol, _SYNC_t* sobj); /* Create a sync object */
+int ff_req_grant (_SYNC_t sobj); /* Lock sync object */
+void ff_rel_grant (_SYNC_t sobj); /* Unlock sync object */
+int ff_del_syncobj (_SYNC_t sobj); /* Delete a sync object */
+#endif
+
+
+
+
+/*--------------------------------------------------------------*/
+/* Flags and offset address */
+
+
+/* File access mode and open method flags (3rd argument of f_open) */
+#define FA_READ 0x01
+#define FA_WRITE 0x02
+#define FA_OPEN_EXISTING 0x00
+#define FA_CREATE_NEW 0x04
+#define FA_CREATE_ALWAYS 0x08
+#define FA_OPEN_ALWAYS 0x10
+#define FA_OPEN_APPEND 0x30
+
+/* Fast seek controls (2nd argument of f_lseek) */
+#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
+
+/* Format options (2nd argument of f_mkfs) */
+#define FM_FAT 0x01
+#define FM_FAT32 0x02
+#define FM_EXFAT 0x04
+#define FM_ANY 0x07
+#define FM_SFD 0x08
+
+/* Filesystem type (FATFS.fs_type) */
+#define FS_FAT12 1
+#define FS_FAT16 2
+#define FS_FAT32 3
+#define FS_EXFAT 4
+
+/* File attribute bits for directory entry (FILINFO.fattrib) */
+#define AM_RDO 0x01 /* Read only */
+#define AM_HID 0x02 /* Hidden */
+#define AM_SYS 0x04 /* System */
+#define AM_DIR 0x10 /* Directory */
+#define AM_ARC 0x20 /* Archive */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FATFS */
diff --git a/ffconf.h b/ffconf.h
new file mode 100644
index 0000000..0ff0385
--- /dev/null
+++ b/ffconf.h
@@ -0,0 +1,267 @@
+/*---------------------------------------------------------------------------/
+/ FatFs - FAT file system module configuration file
+/---------------------------------------------------------------------------*/
+
+#define _FFCONF 68020 /* Revision ID */
+
+/*---------------------------------------------------------------------------/
+/ Function Configurations
+/---------------------------------------------------------------------------*/
+
+#define _FS_READONLY 0
+/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
+/ Read-only configuration removes writing API functions, f_write(), f_sync(),
+/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
+/ and optional writing functions as well. */
+
+
+#define _FS_MINIMIZE 0
+/* This option defines minimization level to remove some basic API functions.
+/
+/ 0: All basic functions are enabled.
+/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
+/ are removed.
+/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
+/ 3: f_lseek() function is removed in addition to 2. */
+
+
+#define _USE_STRFUNC 0
+/* This option switches string functions, f_gets(), f_putc(), f_puts() and
+/ f_printf().
+/
+/ 0: Disable string functions.
+/ 1: Enable without LF-CRLF conversion.
+/ 2: Enable with LF-CRLF conversion. */
+
+
+#define _USE_FIND 1
+/* This option switches filtered directory read functions, f_findfirst() and
+/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
+
+
+#define _USE_MKFS 0
+/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
+
+
+#define _USE_FASTSEEK 0
+/* This option switches fast seek function. (0:Disable or 1:Enable) */
+
+
+#define _USE_EXPAND 0
+/* This option switches f_expand function. (0:Disable or 1:Enable) */
+
+
+#define _USE_CHMOD 1
+/* This option switches attribute manipulation functions, f_chmod() and f_utime().
+/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
+
+
+#define _USE_LABEL 0
+/* This option switches volume label functions, f_getlabel() and f_setlabel().
+/ (0:Disable or 1:Enable) */
+
+
+#define _USE_FORWARD 0
+/* This option switches f_forward() function. (0:Disable or 1:Enable) */
+
+
+/*---------------------------------------------------------------------------/
+/ Locale and Namespace Configurations
+/---------------------------------------------------------------------------*/
+
+#define _CODE_PAGE 437
+/* This option specifies the OEM code page to be used on the target system.
+/ Incorrect setting of the code page can cause a file open failure.
+/
+/ 1 - ASCII (No extended character. Non-LFN cfg. only)
+/ 437 - U.S.
+/ 720 - Arabic
+/ 737 - Greek
+/ 771 - KBL
+/ 775 - Baltic
+/ 850 - Latin 1
+/ 852 - Latin 2
+/ 855 - Cyrillic
+/ 857 - Turkish
+/ 860 - Portuguese
+/ 861 - Icelandic
+/ 862 - Hebrew
+/ 863 - Canadian French
+/ 864 - Arabic
+/ 865 - Nordic
+/ 866 - Russian
+/ 869 - Greek 2
+/ 932 - Japanese (DBCS)
+/ 936 - Simplified Chinese (DBCS)
+/ 949 - Korean (DBCS)
+/ 950 - Traditional Chinese (DBCS)
+*/
+
+
+#define _USE_LFN 1
+#define _MAX_LFN 255
+/* The _USE_LFN switches the support of long file name (LFN).
+/
+/ 0: Disable support of LFN. _MAX_LFN has no effect.
+/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
+/ 2: Enable LFN with dynamic working buffer on the STACK.
+/ 3: Enable LFN with dynamic working buffer on the HEAP.
+/
+/ To enable the LFN, Unicode handling functions (option/unicode.c) must be added
+/ to the project. The working buffer occupies (_MAX_LFN + 1) * 2 bytes and
+/ additional 608 bytes at exFAT enabled. _MAX_LFN can be in range from 12 to 255.
+/ It should be set 255 to support full featured LFN operations.
+/ When use stack for the working buffer, take care on stack overflow. When use heap
+/ memory for the working buffer, memory management functions, ff_memalloc() and
+/ ff_memfree(), must be added to the project. */
+
+
+#define _LFN_UNICODE 0
+/* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16)
+/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
+/ This option also affects behavior of string I/O functions. */
+
+
+#define _STRF_ENCODE 3
+/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
+/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
+/
+/ 0: ANSI/OEM
+/ 1: UTF-16LE
+/ 2: UTF-16BE
+/ 3: UTF-8
+/
+/ This option has no effect when _LFN_UNICODE == 0. */
+
+
+#define _FS_RPATH 2
+/* This option configures support of relative path.
+/
+/ 0: Disable relative path and remove related functions.
+/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
+/ 2: f_getcwd() function is available in addition to 1.
+*/
+
+
+/*---------------------------------------------------------------------------/
+/ Drive/Volume Configurations
+/---------------------------------------------------------------------------*/
+
+#define _VOLUMES 1
+/* Number of volumes (logical drives) to be used. */
+
+
+#define _STR_VOLUME_ID 0
+#define _VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
+/* _STR_VOLUME_ID switches string support of volume ID.
+/ When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive
+/ number in the path name. _VOLUME_STRS defines the drive ID strings for each
+/ logical drives. Number of items must be equal to _VOLUMES. Valid characters for
+/ the drive ID strings are: A-Z and 0-9. */
+
+
+#define _MULTI_PARTITION 0
+/* This option switches support of multi-partition on a physical drive.
+/ By default (0), each logical drive number is bound to the same physical drive
+/ number and only an FAT volume found on the physical drive will be mounted.
+/ When multi-partition is enabled (1), each logical drive number can be bound to
+/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
+/ funciton will be available. */
+
+
+#define _MIN_SS 512
+#define _MAX_SS 512
+/* These options configure the range of sector size to be supported. (512, 1024,
+/ 2048 or 4096) Always set both 512 for most systems, all type of memory cards and
+/ harddisk. But a larger value may be required for on-board flash memory and some
+/ type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured
+/ to variable sector size and GET_SECTOR_SIZE command must be implemented to the
+/ disk_ioctl() function. */
+
+
+#define _USE_TRIM 0
+/* This option switches support of ATA-TRIM. (0:Disable or 1:Enable)
+/ To enable Trim function, also CTRL_TRIM command should be implemented to the
+/ disk_ioctl() function. */
+
+
+#define _FS_NOFSINFO 0
+/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
+/ option, and f_getfree() function at first time after volume mount will force
+/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
+/
+/ bit0=0: Use free cluster count in the FSINFO if available.
+/ bit0=1: Do not trust free cluster count in the FSINFO.
+/ bit1=0: Use last allocated cluster number in the FSINFO if available.
+/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
+*/
+
+
+
+/*---------------------------------------------------------------------------/
+/ System Configurations
+/---------------------------------------------------------------------------*/
+
+#define _FS_TINY 0
+/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
+/ At the tiny configuration, size of file object (FIL) is reduced _MAX_SS bytes.
+/ Instead of private sector buffer eliminated from the file object, common sector
+/ buffer in the file system object (FATFS) is used for the file data transfer. */
+
+
+#define _FS_EXFAT 1
+/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
+/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
+/ Note that enabling exFAT discards C89 compatibility. */
+
+
+#define _FS_NORTC 0
+#define _NORTC_MON 1
+#define _NORTC_MDAY 1
+#define _NORTC_YEAR 2016
+/* The option _FS_NORTC switches timestamp functiton. If the system does not have
+/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
+/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
+/ defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR in local time.
+/ To enable timestamp function (_FS_NORTC = 0), get_fattime() function need to be
+/ added to the project to get current time form real-time clock. _NORTC_MON,
+/ _NORTC_MDAY and _NORTC_YEAR have no effect.
+/ These options have no effect at read-only configuration (_FS_READONLY = 1). */
+
+
+#define _FS_LOCK 0
+/* The option _FS_LOCK switches file lock function to control duplicated file open
+/ and illegal operation to open objects. This option must be 0 when _FS_READONLY
+/ is 1.
+/
+/ 0: Disable file lock function. To avoid volume corruption, application program
+/ should avoid illegal open, remove and rename to the open objects.
+/ >0: Enable file lock function. The value defines how many files/sub-directories
+/ can be opened simultaneously under file lock control. Note that the file
+/ lock control is independent of re-entrancy. */
+
+
+#define _FS_REENTRANT 0
+#define _FS_TIMEOUT 1000
+#define _SYNC_t HANDLE
+/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
+/ module itself. Note that regardless of this option, file access to different
+/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
+/ and f_fdisk() function, are always not re-entrant. Only file/directory access
+/ to the same volume is under control of this function.
+/
+/ 0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect.
+/ 1: Enable re-entrancy. Also user provided synchronization handlers,
+/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
+/ function, must be added to the project. Samples are available in
+/ option/syscall.c.
+/
+/ The _FS_TIMEOUT defines timeout period in unit of time tick.
+/ The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
+/ SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be
+/ included somewhere in the scope of ff.h. */
+
+/* #include // O/S definitions */
+
+
+/*--- End of configuration options ---*/
diff --git a/font b/font
new file mode 100644
index 0000000..191ac46
Binary files /dev/null and b/font differ
diff --git a/gcr.cpp b/gcr.cpp
new file mode 100644
index 0000000..67f4547
--- /dev/null
+++ b/gcr.cpp
@@ -0,0 +1,1386 @@
+/*
+ gcr.c - Group Code Recording helper functions
+
+ (C) 2001-2005 Markus Brenner
+ and Pete Rittwage
+ based on code by Andreas Boose
+
+ V 0.30 created file based on n2d
+ V 0.31 improved error handling of convert_GCR_sector()
+ V 0.32 removed some functions, added sector-2-GCR conversion
+ V 0.33 improved sector extraction, added find_track_cycle() function
+ V 0.34 added MAX_SYNC_OFFSET constant, for better error conversion
+ V 0.35 improved find_track_cycle() function
+ V 0.36 added bad GCR code detection
+ V 0.36b improved find_sync(), added find_sector_gap(), find_sector0()
+ V 0.36c convert_GCR_sector: search good header before using a damaged one
+ improved find_track_cycle(), return max len if no cycle found
+ V 0.36d most additions/fixes referenced in mnib.c (pjr)
+*/
+
+#include
+#include
+#include
+#include
+#include "gcr.h"
+#include "prot.h"
+#include "debug.h"
+
+char sector_map_1541[MAX_TRACKS_1541 + 1] = {
+ 0,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, /* 1 - 10 */
+ 21, 21, 21, 21, 21, 21, 21, 19, 19, 19, /* 11 - 20 */
+ 19, 19, 19, 19, 18, 18, 18, 18, 18, 18, /* 21 - 30 */
+ 17, 17, 17, 17, 17, /* 31 - 35 */
+ 17, 17, 17, 17, 17, 17, 17 /* 36 - 42 (non-standard) */
+};
+
+
+BYTE speed_map_1541[MAX_TRACKS_1541] = {
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 1 - 10 */
+ 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, /* 11 - 20 */
+ 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, /* 21 - 30 */
+ 0, 0, 0, 0, 0, /* 31 - 35 */
+ 0, 0, 0, 0, 0, 0, 0 /* 36 - 42 (non-standard) */
+};
+
+
+/* Burst Nibbler defaults
+int capacity_min[] = { 6183, 6598, 7073, 7616 };
+int capacity[] = { 6231, 6646, 7121, 7664 };
+int capacity_max[] = { 6311, 6726, 7201, 7824 };
+*/
+
+/* New calculated defaults: 296rpm, 300rpm, 304rpm */
+int capacity_min[] = { (int) (DENSITY0 / 303), (int) (DENSITY1 / 303), (int) (DENSITY2 / 303), (int) (DENSITY3 / 303) };
+int capacity[] = { (int) (DENSITY0 / 300), (int) (DENSITY1 / 300), (int) (DENSITY2 / 300), (int) (DENSITY3 / 300) };
+int capacity_max[] = { (int) (DENSITY0 / 296), (int) (DENSITY1 / 296), (int) (DENSITY2 / 296), (int) (DENSITY3 / 296) };
+
+/* Nibble-to-GCR conversion table */
+static BYTE GCR_conv_data[16] = {
+ 0x0a, 0x0b, 0x12, 0x13,
+ 0x0e, 0x0f, 0x16, 0x17,
+ 0x09, 0x19, 0x1a, 0x1b,
+ 0x0d, 0x1d, 0x1e, 0x15
+};
+
+/* GCR-to-Nibble conversion tables */
+static BYTE GCR_decode_high[32] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x80, 0x00, 0x10, 0xff, 0xc0, 0x40, 0x50,
+ 0xff, 0xff, 0x20, 0x30, 0xff, 0xf0, 0x60, 0x70,
+ 0xff, 0x90, 0xa0, 0xb0, 0xff, 0xd0, 0xe0, 0xff
+};
+
+static BYTE GCR_decode_low[32] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x08, 0x00, 0x01, 0xff, 0x0c, 0x04, 0x05,
+ 0xff, 0xff, 0x02, 0x03, 0xff, 0x0f, 0x06, 0x07,
+ 0xff, 0x09, 0x0a, 0x0b, 0xff, 0x0d, 0x0e, 0xff
+};
+
+
+int
+find_sync(BYTE ** gcr_pptr, BYTE * gcr_end)
+{
+ while (1)
+ {
+ if ((*gcr_pptr) + 1 >= gcr_end)
+ {
+ *gcr_pptr = gcr_end;
+ return 0; /* not found */
+ }
+
+ // sync flag goes up after the 10th bit
+ if ( ((*gcr_pptr)[0] & 0x03) == 0x03 && (*gcr_pptr)[1] == 0xff)
+ break;
+
+ (*gcr_pptr)++;
+ }
+
+ (*gcr_pptr)++;
+ while (*gcr_pptr < gcr_end && **gcr_pptr == 0xff)
+ (*gcr_pptr)++;
+ return (*gcr_pptr < gcr_end);
+}
+
+void
+convert_4bytes_to_GCR(BYTE * buffer, BYTE * ptr)
+{
+ *ptr = GCR_conv_data[(*buffer) >> 4] << 3;
+ *ptr |= GCR_conv_data[(*buffer) & 0x0f] >> 2;
+ ptr++;
+
+ *ptr = GCR_conv_data[(*buffer) & 0x0f] << 6;
+ buffer++;
+ *ptr |= GCR_conv_data[(*buffer) >> 4] << 1;
+ *ptr |= GCR_conv_data[(*buffer) & 0x0f] >> 4;
+ ptr++;
+
+ *ptr = GCR_conv_data[(*buffer) & 0x0f] << 4;
+ buffer++;
+ *ptr |= GCR_conv_data[(*buffer) >> 4] >> 1;
+ ptr++;
+
+ *ptr = GCR_conv_data[(*buffer) >> 4] << 7;
+ *ptr |= GCR_conv_data[(*buffer) & 0x0f] << 2;
+ buffer++;
+ *ptr |= GCR_conv_data[(*buffer) >> 4] >> 3;
+ ptr++;
+
+ *ptr = GCR_conv_data[(*buffer) >> 4] << 5;
+ *ptr |= GCR_conv_data[(*buffer) & 0x0f];
+}
+
+int
+convert_4bytes_from_GCR(BYTE * gcr, BYTE * plain)
+{
+ BYTE hnibble, lnibble;
+ int badGCR, nConverted;
+
+ badGCR = 0;
+
+ hnibble = GCR_decode_high[gcr[0] >> 3];
+ lnibble = GCR_decode_low[((gcr[0] << 2) | (gcr[1] >> 6)) & 0x1f];
+ if ((hnibble == 0xff || lnibble == 0xff) && !badGCR)
+ badGCR = 1;
+ *plain++ = hnibble | lnibble;
+
+ hnibble = GCR_decode_high[(gcr[1] >> 1) & 0x1f];
+ lnibble = GCR_decode_low[((gcr[1] << 4) | (gcr[2] >> 4)) & 0x1f];
+ if ((hnibble == 0xff || lnibble == 0xff) && !badGCR)
+ badGCR = 2;
+ *plain++ = hnibble | lnibble;
+
+ hnibble = GCR_decode_high[((gcr[2] << 1) | (gcr[3] >> 7)) & 0x1f];
+ lnibble = GCR_decode_low[(gcr[3] >> 2) & 0x1f];
+ if ((hnibble == 0xff || lnibble == 0xff) && !badGCR)
+ badGCR = 3;
+ *plain++ = hnibble | lnibble;
+
+ hnibble = GCR_decode_high[((gcr[3] << 3) | (gcr[4] >> 5)) & 0x1f];
+ lnibble = GCR_decode_low[gcr[4] & 0x1f];
+ if ((hnibble == 0xff || lnibble == 0xff) && !badGCR)
+ badGCR = 4;
+ *plain++ = hnibble | lnibble;
+
+ nConverted = (badGCR == 0) ? 4 : (badGCR - 1);
+
+ return (nConverted);
+}
+
+int
+extract_id(BYTE * gcr_track, BYTE * id)
+{
+ BYTE header[10];
+ BYTE *gcr_ptr, *gcr_end;
+ int track, sector;
+
+ track = 18;
+ sector = 0;
+ gcr_ptr = gcr_track;
+ gcr_end = gcr_track + NIB_TRACK_LENGTH;
+
+ do
+ {
+ if (!find_sync(&gcr_ptr, gcr_end))
+ return (0);
+
+ convert_4bytes_from_GCR(gcr_ptr, header);
+ convert_4bytes_from_GCR(gcr_ptr + 5, header + 4);
+ } while (header[0] != 0x08 || header[2] != sector ||
+ header[3] != track);
+
+ id[0] = header[5];
+ id[1] = header[4];
+ return (1);
+}
+
+int
+extract_cosmetic_id(BYTE * gcr_track, BYTE * id)
+{
+ BYTE secbuf[260];
+ int error;
+
+ /* get sector into buffer- we don't care about id mismatch here */
+ error = convert_GCR_sector(gcr_track, gcr_track + NIB_TRACK_LENGTH, secbuf, 18, 0, id);
+
+ /* no valid 18,0 sector */
+ if(error != SECTOR_OK && error != ID_MISMATCH)
+ return (0);
+
+ id[0] = secbuf[0xa3];
+ id[1] = secbuf[0xa4];
+ return (1);
+}
+
+BYTE
+convert_GCR_sector(BYTE * gcr_start, BYTE * gcr_cycle, BYTE * d64_sector,
+ int track, int sector, BYTE * id)
+{
+ BYTE header[10]; /* block header */
+ BYTE hdr_chksum; /* header checksum */
+ BYTE blk_chksum; /* block checksum */
+ BYTE gcr_buffer[2 * NIB_TRACK_LENGTH];
+ BYTE *gcr_ptr, *gcr_end, *gcr_last;
+ BYTE *sectordata;
+ BYTE error_code;
+ int sync_found, i, j, nConverted;
+ size_t track_len;
+
+ error_code = SECTOR_OK;
+
+ if (track > MAX_TRACK_D64)
+ return (0);
+ if (gcr_cycle == NULL || gcr_cycle <= gcr_start)
+ return (0);
+
+ /* initialize sector data with Original Format Pattern */
+ memset(d64_sector, 0x01, 260);
+ d64_sector[0] = 0x07; /* Block header mark */
+ d64_sector[1] = 0x4b; /* Use Original Format Pattern */
+
+ for (blk_chksum = 0, i = 1; i < 257; i++)
+ blk_chksum ^= d64_sector[i + 1];
+ d64_sector[257] = blk_chksum;
+
+ /* copy to temp. buffer with twice the track data */
+ track_len = gcr_cycle - gcr_start;
+ memcpy(gcr_buffer, gcr_start, track_len);
+ memcpy(gcr_buffer + track_len, gcr_start, track_len);
+ track_len *= 2;
+
+ /* Check for at least one Sync */
+ gcr_end = gcr_buffer + track_len;
+ sync_found = 0;
+ for (gcr_ptr = gcr_buffer; gcr_ptr < gcr_end; gcr_ptr++)
+ {
+ if (*gcr_ptr == 0xff)
+ {
+ if (sync_found < 2)
+ sync_found++;
+ }
+ else /* (*gcr_ptr != 0xff) */
+ {
+ if (sync_found < 2)
+ sync_found = 0;
+ else
+ sync_found = 3;
+ }
+ }
+ if (sync_found != 3)
+ return (SYNC_NOT_FOUND);
+
+ /* Check for missing SYNCs */
+ gcr_last = gcr_ptr = gcr_buffer;
+ while (gcr_ptr < gcr_end)
+ {
+ find_sync(&gcr_ptr, gcr_end);
+ if ((gcr_ptr - gcr_last) > MAX_SYNC_OFFSET)
+ {
+ //printf("no sync for %d\n", gcr_ptr-gcr_last);
+ return (SYNC_NOT_FOUND);
+ }
+ else
+ gcr_last = gcr_ptr;
+ }
+
+ /* Try to find a good block header for Track/Sector */
+ gcr_ptr = gcr_buffer;
+ gcr_end = gcr_buffer + track_len;
+
+ do
+ {
+ if (!find_sync(&gcr_ptr, gcr_end) || gcr_ptr >= gcr_end - 10)
+ {
+ error_code = HEADER_NOT_FOUND;
+ break;
+ }
+ convert_4bytes_from_GCR(gcr_ptr, header);
+ convert_4bytes_from_GCR(gcr_ptr + 5, header + 4);
+ gcr_ptr++;
+
+ //printf("%0.2d: t%0.2d s%0.2d id1:%0.1x id2:%0.1x\n",
+ //header[0],header[3],header[2],header[5],header[4]);
+ } while (header[0] != 0x08 || header[2] != sector ||
+ header[3] != track || header[5] != id[0] || header[4] != id[1]);
+
+ if (error_code == HEADER_NOT_FOUND)
+ {
+ error_code = SECTOR_OK;
+ /* Try to find next best match for header */
+ gcr_ptr = gcr_buffer;
+ gcr_end = gcr_buffer + track_len;
+ do
+ {
+ if (!find_sync(&gcr_ptr, gcr_end))
+ return (HEADER_NOT_FOUND);
+
+ if (gcr_ptr >= gcr_end - 10)
+ return (HEADER_NOT_FOUND);
+
+ convert_4bytes_from_GCR(gcr_ptr, header);
+ convert_4bytes_from_GCR(gcr_ptr + 5, header + 4);
+ gcr_ptr++;
+
+ } while (header[0] == 0x07 || header[2] != sector || header[3] != track);
+ }
+
+ if (header[0] != 0x08)
+ error_code = (error_code == SECTOR_OK) ? HEADER_NOT_FOUND : error_code;
+
+ /* Header checksum */
+ hdr_chksum = 0;
+ for (i = 1; i <= 4; i++)
+ hdr_chksum = hdr_chksum ^ header[i];
+
+ if (hdr_chksum != header[5])
+ {
+ //printf("(S%d HEAD_CHKSUM $%0.2x != $%0,2x) ",
+ // sector, hdr_chksum, header[5]);
+ error_code = (error_code == SECTOR_OK) ?
+ BAD_HEADER_CHECKSUM : error_code;
+ }
+
+ // verify that our header contains no bad GCR, since it can be false positive checksum match
+ for(j = 0; j < 10; j++)
+ {
+ if (is_bad_gcr(gcr_ptr - 1, 10, j)) error_code = (error_code == SECTOR_OK) ? BAD_GCR_CODE : error_code;
+ }
+
+ // next look for data portion
+ if (!find_sync(&gcr_ptr, gcr_end))
+ return (DATA_NOT_FOUND);
+
+ for (i = 0, sectordata = d64_sector; i < 65; i++)
+ {
+ if (gcr_ptr >= gcr_end - 5)
+ return (DATA_NOT_FOUND);
+
+ if (4 != (nConverted = convert_4bytes_from_GCR(gcr_ptr, sectordata)))
+ {
+#if 0
+ // XXX Disabled for unknown reason.
+ if ((i < 64) || (nConverted == 0))
+ error_code = BAD_GCR_CODE;
+#endif
+ }
+
+ gcr_ptr += 5;
+ sectordata += 4;
+ }
+
+ /* check for correct disk ID */
+ if (header[5] != id[0] || header[4] != id[1])
+ error_code = (error_code == SECTOR_OK) ? ID_MISMATCH : error_code;
+
+ /* check for Block header mark */
+ if (d64_sector[0] != 0x07)
+ error_code = (error_code == SECTOR_OK) ? DATA_NOT_FOUND : error_code;
+
+ /* Block checksum */
+ for (i = 1, blk_chksum = 0; i <= 256; i++)
+ blk_chksum ^= d64_sector[i];
+
+ if (blk_chksum != d64_sector[257])
+ {
+ //printf("(S%d DATA_CHKSUM $%0.2x != $%0.2x) ",
+ // sector, blk_chksum, d64_sector[257]);
+ error_code = (error_code == SECTOR_OK) ? BAD_DATA_CHECKSUM : error_code;
+ }
+
+ // verify that our data contains no bad GCR, since it can be false positive checksum match
+ for(j = 0; j < 320; j++)
+ if (is_bad_gcr(gcr_ptr - 325, 320, j)) error_code = (error_code == SECTOR_OK) ? BAD_GCR_CODE : error_code;
+
+ return (error_code);
+}
+
+void
+convert_sector_to_GCR(BYTE * buffer, BYTE * ptr,
+ int track, int sector, BYTE * diskID, int error)
+{
+ int i;
+ BYTE buf[4], databuf[0x104], chksum;
+ BYTE tempID[3];
+
+ memcpy(tempID, diskID, 3);
+ memset(ptr, 0x55, 361); /* 'unformat' GCR sector */
+
+ if (error == SYNC_NOT_FOUND)
+ return;
+
+ if (error == HEADER_NOT_FOUND)
+ {
+ ptr += 24;
+ }
+ else
+ {
+ memset(ptr, 0xff, 5); /* Sync */
+ ptr += 5;
+
+ if (error == ID_MISMATCH)
+ {
+ tempID[0] ^= 0xff;
+ tempID[1] ^= 0xff;
+ }
+
+ buf[0] = 0x08; /* Header identifier */
+ buf[1] = sector ^ track ^ tempID[1] ^ tempID[0];
+ buf[2] = (BYTE) sector;
+ buf[3] = (BYTE) track;
+
+ if (error == BAD_HEADER_CHECKSUM)
+ buf[1] ^= 0xff;
+
+ convert_4bytes_to_GCR(buf, ptr);
+ ptr += 5;
+
+ buf[0] = tempID[1];
+ buf[1] = tempID[0];
+ buf[2] = buf[3] = 0x0f;
+ convert_4bytes_to_GCR(buf, ptr);
+ ptr += 5;
+
+ memset(ptr, 0x55, 9); /* Header Gap */
+ ptr += 9;
+ }
+
+ if (error == DATA_NOT_FOUND)
+ return;
+
+ memset(ptr, 0xff, 5); /* Sync */
+ ptr += 5;
+
+ chksum = 0;
+ databuf[0] = 0x07;
+ for (i = 0; i < 0x100; i++)
+ {
+ databuf[i + 1] = buffer[i];
+ chksum ^= buffer[i];
+ }
+
+ if (error == BAD_DATA_CHECKSUM)
+ chksum ^= 0xff;
+
+ databuf[0x101] = chksum;
+ databuf[0x102] = 0; /* 2 bytes filler */
+ databuf[0x103] = 0;
+
+ for (i = 0; i < 65; i++)
+ {
+ convert_4bytes_to_GCR(databuf + (4 * i), ptr);
+ ptr += 5;
+ }
+
+ /* 7 0x55 gap bytes in my reference disk */
+ memset(ptr, 0x55, 7); /* Gap before next sector */
+ ptr += 7;
+}
+
+size_t
+find_track_cycle(BYTE ** cycle_start, BYTE ** cycle_stop, int cap_min, int cap_max)
+{
+ BYTE *nib_track; /* start of nibbled track data */
+ BYTE *start_pos; /* start of periodic area */
+ BYTE *cycle_pos; /* start of cycle repetition */
+ BYTE *stop_pos; /* maximum position allowed for cycle */
+ BYTE *data_pos; /* cycle search variable */
+ BYTE *p1, *p2; /* local pointers for comparisons */
+
+ nib_track = *cycle_start;
+ stop_pos = nib_track + NIB_TRACK_LENGTH - gap_match_length;
+ cycle_pos = NULL;
+
+ /* try to find a normal track cycle */
+ for (start_pos = nib_track;; find_sync(&start_pos, stop_pos))
+ {
+ if ((data_pos = start_pos + cap_min) >= stop_pos)
+ break; /* no cycle found */
+
+ while (find_sync(&data_pos, stop_pos))
+ {
+ p1 = start_pos;
+ cycle_pos = data_pos;
+
+ for (p2 = cycle_pos; p2 < stop_pos;)
+ {
+ /* try to match all remaining syncs, too */
+ if (memcmp(p1, p2, gap_match_length) != 0)
+ {
+ cycle_pos = NULL;
+ break;
+ }
+ if (!find_sync(&p1, stop_pos))
+ break;
+ if (!find_sync(&p2, stop_pos))
+ break;
+ }
+
+ if (cycle_pos != NULL && check_valid_data(data_pos, gap_match_length))
+ {
+ *cycle_start = start_pos;
+ *cycle_stop = cycle_pos;
+ return (cycle_pos - start_pos);
+ }
+ }
+ }
+
+ /* we got nothing useful, return it all */
+ *cycle_start = nib_track;
+ *cycle_stop = nib_track + NIB_TRACK_LENGTH;
+ return NIB_TRACK_LENGTH;
+}
+
+size_t
+find_nondos_track_cycle(BYTE ** cycle_start, BYTE ** cycle_stop, int cap_min,
+ int cap_max)
+{
+ BYTE *nib_track; /* start of nibbled track data */
+ BYTE *start_pos; /* start of periodic area */
+ BYTE *cycle_pos; /* start of cycle repetition */
+ BYTE *stop_pos; /* maximum position allowed for cycle */
+ BYTE *p1, *p2; /* local pointers for comparisons */
+
+ nib_track = *cycle_start;
+ start_pos = nib_track;
+ stop_pos = nib_track + NIB_TRACK_LENGTH - gap_match_length;
+ cycle_pos = NULL;
+
+ /* try to find a track cycle ignoring sync */
+ for (p1 = start_pos; p1 < stop_pos; p1++)
+ {
+ /* now try to match it */
+ for (p2 = p1 + cap_min; p2 < stop_pos; p2++)
+ {
+ /* try to match data */
+ if (memcmp(p1, p2, gap_match_length) != 0)
+ cycle_pos = NULL;
+ else
+ cycle_pos = p2;
+
+ /* we found one! */
+ if (cycle_pos != NULL && check_valid_data(cycle_pos, gap_match_length))
+ {
+ *cycle_start = p1;
+ *cycle_stop = cycle_pos;
+ return (cycle_pos - p1);
+ }
+ }
+ }
+
+ /* we got nothing useful */
+ *cycle_start = nib_track;
+ *cycle_stop = nib_track + NIB_TRACK_LENGTH;
+ return NIB_TRACK_LENGTH;
+}
+
+int
+check_valid_data(BYTE * data, int matchlen)
+{
+ //makes assumptions on whether this is good data to match cycles
+ int i, redund = 0;
+
+ for (i = 0; i < matchlen; i++)
+ {
+ if (data[i] == data[i + 1] || data[i] == data[i + 2] ||
+ data[i] == data[i + 3] || data[i] == data[i + 4])
+ redund++;
+ }
+
+ if (redund > 1)
+ return 0;
+ else
+ return 1;
+}
+
+BYTE *
+find_sector0(BYTE * work_buffer, int tracklen, size_t * p_sectorlen)
+{
+ BYTE *pos, *buffer_end, *sync_last;
+
+ pos = work_buffer;
+ buffer_end = work_buffer + 2 * tracklen - 10;
+ *p_sectorlen = 0;
+
+ if (!find_sync(&pos, buffer_end))
+ return NULL;
+ sync_last = pos;
+
+ /* try to find sector 0 */
+ while (pos < buffer_end)
+ {
+ if (!find_sync(&pos, buffer_end))
+ return NULL;
+ if (pos[0] == 0x52 && (pos[1] & 0xc0) == 0x40 &&
+ (pos[2] & 0x0f) == 0x05 && (pos[3] & 0xfc) == 0x28)
+ {
+ *p_sectorlen = pos - sync_last;
+ break;
+ }
+ sync_last = pos;
+ }
+
+ /* find last GCR byte before sync */
+ do
+ {
+ pos -= 1;
+ if (pos == work_buffer)
+ pos += tracklen;
+ } while (*pos == 0xff);
+
+ /* move to first sync byte */
+ pos += 1;
+ while (pos >= work_buffer + tracklen)
+ pos -= tracklen;
+
+ /* make sure sync is long enough or else shift back */
+ //if(*(pos+1) != 0xff) pos -= 1;
+
+ return pos;
+}
+
+BYTE *
+find_sector_gap(BYTE * work_buffer, int tracklen, size_t * p_sectorlen)
+{
+ size_t gap, maxgap;
+ BYTE *pos;
+ BYTE *buffer_end;
+ BYTE *sync_last;
+ BYTE *sync_max;
+
+ pos = work_buffer;
+ buffer_end = work_buffer + 2 * tracklen - 10;
+ *p_sectorlen = 0;
+
+ if (!find_sync(&pos, buffer_end))
+ return NULL;
+ sync_last = pos;
+ maxgap = 0;
+
+ /* try to find biggest (sector) gap */
+ while (pos < buffer_end)
+ {
+ if (!find_sync(&pos, buffer_end))
+ break;
+ gap = pos - sync_last;
+ if (gap > maxgap)
+ {
+ maxgap = gap;
+ sync_max = pos;
+ }
+ sync_last = pos;
+ }
+ *p_sectorlen = maxgap;
+
+ if (maxgap == 0)
+ return NULL; /* no gap found */
+
+ /* find last GCR byte before sync */
+ pos = sync_max;
+ do
+ {
+ pos -= 1;
+ if (pos == work_buffer)
+ pos += tracklen;
+ } while (*pos == 0xff);
+
+ /* move to first sync GCR byte */
+ pos += 1;
+ while (pos >= work_buffer + tracklen)
+ pos -= tracklen;
+
+ /* make sure sync is long enough or else shift back */
+ //if(*(pos+1) != 0xff) pos -= 1;
+
+ return pos;
+}
+
+// checks if there is any reasonable section of formatted (GCR) data
+int
+check_formatted(BYTE * gcrdata)
+{
+ int i, run = 0;
+
+ /* try to find longest good gcr run */
+ for (i = 0; i < NIB_TRACK_LENGTH; i++)
+ {
+ if (is_bad_gcr(gcrdata, NIB_TRACK_LENGTH, i))
+ run = 0;
+ else
+ run++;
+
+ if (run >= GCR_MIN_FORMATTED)
+ return 1;
+
+ }
+ return 0;
+}
+
+/*
+ Try to extract one complete cycle of GCR data from an 8kB buffer.
+ Align track to sector gap if possible, else align to sector 0,
+ else copy cyclic loop from begin of source.
+ If buffer has no good GCR, return tracklen = 0;
+ [Input] destination buffer, source buffer
+ [Return] length of copied track fragment
+ */
+int
+extract_GCR_track(BYTE * destination, BYTE * source, int * align,
+ int force_align, size_t cap_min, size_t cap_max)
+{
+ BYTE work_buffer[NIB_TRACK_LENGTH*2]; /* working buffer */
+ BYTE *cycle_start; /* start position of cycle */
+ BYTE *cycle_stop; /* stop position of cycle */
+ size_t track_len;
+ BYTE *sector0_pos; /* position of sector 0 */
+ BYTE *sectorgap_pos;/* position of sector gap */
+ BYTE *longsync_pos; /* position of longest sync run */
+ BYTE *weakgap_pos; /* position of weak bit run */
+ BYTE *marker_pos; /* generic marker used by protection handlers */
+ size_t sector0_len; /* length of gap before sector 0 */
+ size_t sectorgap_len; /* length of longest gap */
+
+ sector0_pos = NULL;
+ sectorgap_pos = NULL;
+ longsync_pos = NULL;
+ weakgap_pos = NULL;
+ marker_pos = NULL;
+
+ cap_min -= CAP_MIN_ALLOWANCE;
+
+ /* if this track doesn't have enough formatted data, return blank */
+ if (!check_formatted(source)) return 0;
+
+ cycle_start = source;
+ memset(work_buffer, 0, sizeof(work_buffer));
+ memcpy(work_buffer, cycle_start, NIB_TRACK_LENGTH);
+
+ /* find cycle */
+ find_track_cycle(&cycle_start, &cycle_stop, cap_min, cap_max);
+ track_len = cycle_stop - cycle_start;
+
+ /* second pass to find a cycle in track w/o syncs */
+ if (track_len > cap_max || track_len < cap_min)
+ {
+ //printf("(N)");
+ find_nondos_track_cycle(&cycle_start, &cycle_stop, cap_min, cap_max);
+ track_len = cycle_stop - cycle_start;
+ }
+
+ /* copy twice the data to work buffer */
+ memcpy(work_buffer, cycle_start, track_len);
+ memcpy(work_buffer + track_len, cycle_start, track_len);
+
+ // forced track alignments
+ if (force_align != ALIGN_NONE)
+ {
+ if (force_align == ALIGN_VMAX)
+ {
+ *align = ALIGN_VMAX;
+ marker_pos = align_vmax(work_buffer, track_len);
+ }
+
+ if (force_align == ALIGN_AUTOGAP)
+ {
+ *align = ALIGN_AUTOGAP;
+ marker_pos = auto_gap(work_buffer, track_len);
+ }
+
+ if (force_align == ALIGN_LONGSYNC)
+ {
+ *align = ALIGN_LONGSYNC;
+ marker_pos = find_long_sync(work_buffer, track_len);
+ }
+
+ if (force_align == ALIGN_WEAK)
+ {
+ *align = ALIGN_WEAK;
+ marker_pos = find_weak_gap(work_buffer, track_len);
+ }
+
+ if (force_align == ALIGN_GAP)
+ {
+ *align = ALIGN_GAP;
+ marker_pos = find_sector_gap(work_buffer, track_len, §orgap_len);
+ }
+
+ if (force_align == ALIGN_SEC0)
+ {
+ *align = ALIGN_SEC0;;
+ marker_pos = find_sector0(work_buffer, track_len, §or0_len);
+ }
+
+ // we found a protection track
+ if (marker_pos)
+ {
+ memcpy(destination, marker_pos, track_len);
+ return (track_len);
+ }
+ }
+
+ /* autogap tracks with no detected cycle */
+ if (track_len == NIB_TRACK_LENGTH)
+ {
+ marker_pos = auto_gap(work_buffer, track_len);
+ if (marker_pos)
+ {
+ memcpy(destination, marker_pos, track_len);
+ *align = ALIGN_AUTOGAP;
+ return (track_len);
+ }
+ }
+
+ /* try to guess original alignment on "normal" sized tracks */
+ sector0_pos = find_sector0(work_buffer, track_len, §or0_len);
+ sectorgap_pos = find_sector_gap(work_buffer, track_len, §orgap_len);
+
+ //if (sectorgap_len >= sector0_len + 0x40) /* Burstnibbler's calc */
+ if (sectorgap_len > GCR_BLOCK_DATA_LEN + SIGNIFICANT_GAPLEN_DIFF)
+ {
+ *align = ALIGN_GAP;
+ memcpy(destination, sectorgap_pos, track_len);
+ return (track_len);
+ }
+
+ // no good gap found, try sector 0
+ if (sector0_len != 0)
+ {
+ *align = ALIGN_SEC0;
+ memcpy(destination, sector0_pos, track_len);
+ return (track_len);
+ }
+
+ // no sector 0 found, use gap anyway
+ if (sectorgap_len)
+ {
+ memcpy(destination, sectorgap_pos, track_len);
+ *align = ALIGN_GAP;
+ return (track_len);
+ }
+
+ // we aren't dealing with a normal track here, so autogap it
+ marker_pos = auto_gap(work_buffer, track_len);
+ if (marker_pos)
+ {
+ memcpy(destination, marker_pos, track_len);
+ *align = ALIGN_AUTOGAP;
+ return (track_len);
+ }
+
+ // we give up, just return everything
+ memcpy(destination, work_buffer, track_len);
+ *align = ALIGN_NONE;
+ return (track_len);
+}
+
+/*
+ * This strips exactly one byte at minrun from each
+ * eligible run when called. It can be called repeatedly
+ * for a proportional reduction.
+ */
+int
+strip_runs(BYTE * buffer, int length, int minrun, BYTE target)
+{
+ int run, skipped;
+ BYTE *source, *end;
+
+ run = 0;
+ skipped = 0;
+ end = buffer + length;
+
+ for (source = buffer; source < end - 2; source++)
+ {
+ if (*source == target)
+ {
+ // fixed to only remove bytes before minimum amount of sync
+ if ( run == minrun && target == 0xff )
+ skipped++;
+ else if ( run == minrun && *(source+2) == 0xff )
+ skipped++;
+ else
+ *buffer++ = target;
+
+ run++;
+ }
+ else
+ {
+ run = 0;
+ *buffer++ = *source;
+ }
+ }
+ return skipped;
+}
+
+/* try to shorten inert data until length <= length_max */
+int
+reduce_runs(BYTE * buffer, int length, int length_max, int minrun,
+ BYTE target)
+{
+ int skipped;
+
+ do
+ {
+ if (length <= length_max)
+ return (length);
+
+ skipped = strip_runs(buffer, length, minrun, target);
+ length -= skipped;
+ }
+ while (skipped > 0 && length > length_max);
+
+ return (length);
+}
+
+// this routine checks the track data and makes simple decisions
+// about the special cases of being all sync or having no sync
+int
+check_sync_flags(BYTE * gcrdata, int density, int length)
+{
+ int i, syncs = 0;
+
+ // check manually for SYNCKILL
+ for (i = 0; i < length; i++)
+ {
+ if (gcrdata[i] == 0xff)
+ syncs++;
+ }
+
+ //printf("syncs: %d\n",syncs);
+
+ if(!syncs)
+ return (density |= BM_NO_SYNC);
+ else if (syncs == length)
+ return (density |= BM_FF_TRACK);
+ else
+ return(density);
+}
+
+int
+compare_tracks(BYTE * track1, BYTE * track2, int length1, int length2,
+ int same_disk, char *outputstring)
+{
+ int match, j, k, sync_diff, presync_diff;
+ int gap_diff, weak_diff, size_diff;
+ char tmpstr[256];
+
+ match = 0;
+ j = 0;
+ k = 0;
+ sync_diff = 0;
+ presync_diff = 0;
+ gap_diff = 0;
+ weak_diff = 0;
+ size_diff= 0;
+ outputstring[0] = '\0';
+
+ if (length1 == length2 && 0 == memcmp(track1, track2, length1))
+ match = 1;
+
+ else if (length1 > 0 && length2 > 0)
+ {
+ for (j = k = 0; j < length2 && k < length1; j++, k++)
+ {
+ if (track1[j] == track2[k])
+ continue;
+
+ // we ignore sync length differences
+ if (track1[j] == 0xff)
+ {
+ sync_diff++;
+ k--;
+ continue;
+ }
+
+ if (track2[k] == 0xff)
+ {
+ sync_diff++;
+ j--;
+ continue;
+ }
+
+ // we ignore start of sync differences
+ if (j < length1 - 1 && k < length2 - 1)
+ {
+ if ((track1[j] & 0x01) == 0x01 && track1[j + 1] == 0xff)
+ {
+ presync_diff++;
+ k--;
+ continue;
+ }
+
+ if ((track2[k] & 0x01) == 0x01 && track2[k + 1] == 0xff)
+ {
+ presync_diff++;
+ j--;
+ continue;
+ }
+ }
+
+ // we ignore bad gcr bytes
+ if (is_bad_gcr(track1, length1, j) ||
+ is_bad_gcr(track2, length2, k))
+ {
+ weak_diff++;
+ j++;
+ k++;
+ continue;
+ }
+
+ // it just didn't work out. :)
+ break;
+ }
+
+ if (j < length1 - 1 || k < length2 - 1)
+ size_diff++;
+
+ // we got to the end of one of them OK and not all sync/weak
+ if ((j >= length1 - 1 || k >= length2 - 1) &&
+ (sync_diff < 0x100 && weak_diff < 0x100))
+ match = 1;
+ }
+
+ if (sync_diff)
+ {
+ sprintf(tmpstr, "(sync:%d)", sync_diff);
+ strcat(outputstring, tmpstr);
+ }
+
+ if (presync_diff)
+ {
+ sprintf(tmpstr, "(presync:%d)", presync_diff);
+ strcat(outputstring, tmpstr);
+ }
+
+ if (gap_diff)
+ {
+ sprintf(tmpstr, "(gap:%d)", gap_diff);
+ strcat(outputstring, tmpstr);
+ }
+
+ if (weak_diff)
+ {
+ sprintf(tmpstr, "(weak:%d)", weak_diff);
+ strcat(outputstring, tmpstr);
+ }
+
+ if (size_diff)
+ {
+ sprintf(tmpstr, "(size:%d)", size_diff);
+ strcat(outputstring, tmpstr);
+ }
+
+ return match;
+}
+
+int
+compare_sectors(BYTE * track1, BYTE * track2, int length1, int length2,
+ BYTE * id1, BYTE * id2, int track, char * outputstring)
+{
+ int sec_match, numsecs;
+ int sector, error1, error2, i, empty;
+ BYTE checksum1, checksum2;
+ BYTE secbuf1[260], secbuf2[260];
+ char tmpstr[256];
+
+ sec_match = 0;
+ numsecs = 0;
+ checksum1 = 0;
+ checksum2 = 0;
+ outputstring[0] = '\0';
+
+ // ignore dead tracks
+ if (!length1 || !length2 || length1 + length2 == 0x4000)
+ return 0;
+
+ // check for sector matches
+ for (sector = 0; sector < sector_map_1541[track / 2]; sector++)
+ {
+ numsecs++;
+
+ memset(secbuf1, 0, sizeof(secbuf1));
+ memset(secbuf2, 0, sizeof(secbuf2));
+ tmpstr[0] = '\0';
+
+ error1 = convert_GCR_sector(track1, track1 + length1,
+ secbuf1, track / 2, sector, id1);
+
+ error2 = convert_GCR_sector(track2, track2 + length2,
+ secbuf2, track / 2, sector, id2);
+
+ // compare data returned
+ checksum1 = checksum2 = empty = 0;
+ for (i = 2; i <= 256; i++)
+ {
+ checksum1 ^= secbuf1[i];
+ checksum2 ^= secbuf2[i];
+
+ if (secbuf1[i] == 0x01)
+ empty++;
+ }
+
+ // continue checking
+ if (checksum1 == checksum2 && error1 == error2 && empty < 254)
+ {
+ if(error1 == SECTOR_OK)
+ {
+ //sprintf(tmpstr,"S%d: std sector data match\n",sector);
+ }
+ else
+ {
+ sprintf(tmpstr,"S%d: non-std sector data match (%.2x)\n",sector, checksum1);
+ }
+ sec_match++;
+ }
+ else if (checksum1 == checksum2 && error1 == error2)
+ {
+ if(error1 == SECTOR_OK)
+ {
+ //sprintf(tmpstr,"S%d: empty sector match\n",sector);
+ sec_match++;
+ }
+ else
+ {
+ sprintf(tmpstr,"S%d: unrecognized sector (%.2x/%.2x)(%.2x/%.2x)\n",sector,checksum1,error1,checksum2,error2);
+ }
+ }
+ else
+ {
+ if (checksum1 != checksum2)
+ sprintf(tmpstr,
+ "S%d: data/error mismatch (%.2x/E%d)(%.2x/E%d)\n", sector,
+ checksum1, error1, checksum2, error2);
+ else
+ sprintf(tmpstr,
+ "S%d: error mismatch (E%d/E%d)\n", sector,
+ error1, error2);
+
+ }
+ strcat(outputstring, tmpstr);
+ }
+
+ if (sec_match == sector_map_1541[track / 2])
+ return sec_match;
+ else
+ return 0;
+
+}
+
+// check for CBM DOS errors
+int
+check_errors(BYTE * gcrdata, int length, int track, BYTE * id, char * errorstring)
+{
+ int errors, sector, errorcode;
+ char tmpstr[16];
+ BYTE secbuf[260];
+
+ errors = 0;
+ errorstring[0] = '\0';
+
+ for (sector = 0; sector < sector_map_1541[track/2]; sector++)
+ {
+ errorcode = convert_GCR_sector(gcrdata, gcrdata + length, secbuf, (track/2), sector, id);
+
+ if (errorcode != SECTOR_OK)
+ {
+ errors++;
+ sprintf(tmpstr, "[E%dS%d]", errorcode, sector);
+ strcat(errorstring, tmpstr);
+ }
+ }
+ return errors;
+}
+
+// check for CBM DOS empty sectors
+int
+check_empty(BYTE * gcrdata, int length, int track, BYTE * id, char * errorstring)
+{
+ int i, empty, sector, errorcode;
+ char tmpstr[16], temp_errorstring[256];
+ BYTE secbuf[260];
+
+ empty = 0;
+ errorstring[0] = '\0';
+ temp_errorstring[0] = '\0';
+
+ for (sector = 0; sector < sector_map_1541[track / 2]; sector++)
+ {
+ errorcode = convert_GCR_sector(gcrdata, gcrdata + length, secbuf,
+ (track / 2), sector, id);
+
+ if (errorcode == SECTOR_OK)
+ {
+ /* checks for empty (unused) sector */
+ for (i = 2; i <= 256; i++)
+ {
+ if (secbuf[i] != 0x01)
+ {
+ //printf("%d:%0.2x ",i,secbuf[i]);
+ break;
+ }
+ }
+
+ if (i == 257)
+ {
+ sprintf(tmpstr, "%d-", sector);
+ strcat(temp_errorstring, tmpstr);
+ empty++;
+ }
+ }
+ }
+
+ if (empty)
+ sprintf(errorstring, "EMPTY:%d (%s)", empty, temp_errorstring);
+
+ return (empty);
+}
+
+/*
+ * Replace 'srcbyte' by 'dstbyte'
+ * Returns total number of bytes replaced
+ */
+int
+replace_bytes(BYTE * buffer, int length, BYTE srcbyte, BYTE dstbyte)
+{
+ int i, replaced;
+
+ replaced = 0;
+
+ for (i = 0; i < length; i++)
+ {
+ if (buffer[i] == srcbyte)
+ {
+ buffer[i] = dstbyte;
+ replaced++;
+ }
+ }
+ return replaced;
+}
+
+// Check if byte at pos contains a 000 bit combination
+int
+is_bad_gcr(BYTE * gcrdata, size_t length, size_t pos)
+{
+ unsigned int lastbyte, mask, data;
+
+ lastbyte = (pos == 0) ? gcrdata[length - 1] : gcrdata[pos - 1];
+ data = ((lastbyte & 0x03) << 8) | gcrdata[pos];
+
+ for (mask = (7 << 7); mask >= 7; mask >>= 1)
+ {
+ if ((data & mask) == 0)
+ break;
+ }
+ return (mask >= 7);
+}
+
+/*
+ * Check and "correct" bad GCR bits:
+ * substitute bad GCR bytes by 0x00 until next good GCR byte
+ * when two in a row or row+1 occur (bad bad -or- bad good bad)
+ *
+ * all known disks that use this for protection break out
+ * of it with $55, $AA, or $FF byte.
+ *
+ * fix_first, fix_last not used because while "correct", the real hardware
+ * is not this precise and it fails the protection checks sometimes.
+ */
+int
+check_bad_gcr(BYTE * gcrdata, int length, int fix)
+{
+ /* state machine definitions */
+ enum ebadgcr { S_BADGCR_OK, S_BADGCR_ONCE_BAD, S_BADGCR_LOST };
+
+ int i, lastpos;
+ enum ebadgcr sbadgcr;
+ int total, b_badgcr, n_badgcr;
+
+ i = 0;
+ total = 0;
+ lastpos = length - 1;
+
+ if (is_bad_gcr(gcrdata, length, length - 1))
+ sbadgcr = S_BADGCR_ONCE_BAD;
+ else
+ sbadgcr = S_BADGCR_OK;
+
+ for (i = 0; i < length - 1; i++)
+ {
+ b_badgcr = is_bad_gcr(gcrdata, length, i);
+ n_badgcr = is_bad_gcr(gcrdata, length, i + 1);
+
+ switch (sbadgcr)
+ {
+ case S_BADGCR_OK:
+ if (b_badgcr)
+ {
+ total++;
+ //sbadgcr = S_BADGCR_ONCE_BAD;
+ sbadgcr = S_BADGCR_LOST;
+ }
+ break;
+
+ case S_BADGCR_ONCE_BAD:
+ if (b_badgcr || n_badgcr)
+ {
+ sbadgcr = S_BADGCR_LOST;
+ //if(fix) fix_first_gcr(gcrdata, length, lastpos);
+ if (fix)
+ gcrdata[lastpos] = 0x00;
+ total++;
+ }
+ else
+ sbadgcr = S_BADGCR_OK;
+ break;
+
+ case S_BADGCR_LOST:
+ if (b_badgcr || n_badgcr)
+ {
+ if (fix)
+ gcrdata[lastpos] = 0x00;
+ }
+ else
+ {
+ sbadgcr = S_BADGCR_OK;
+ //if(fix) fix_last_gcr(gcrdata, length, lastpos);
+ if (fix)
+ gcrdata[lastpos] = 0x00;
+ }
+ total++;
+ break;
+ }
+ lastpos = i;
+ }
+
+ // clean up after last byte; lastpos = length - 1
+ b_badgcr = is_bad_gcr(gcrdata, length, 0);
+ n_badgcr = is_bad_gcr(gcrdata, length, 1);
+ switch (sbadgcr)
+ {
+ case S_BADGCR_OK:
+ break;
+
+ case S_BADGCR_ONCE_BAD:
+ if (b_badgcr || n_badgcr)
+ {
+ //if(fix) fix_first_gcr(gcrdata, length, lastpos);
+ if (fix)
+ gcrdata[lastpos] = 0x00;
+ total++;
+ }
+ break;
+
+ case S_BADGCR_LOST:
+ if (b_badgcr || n_badgcr)
+ gcrdata[lastpos] = 0x00;
+ else
+ {
+ //if(fix) fix_last_gcr(gcrdata, length, lastpos);
+ if (fix)
+ gcrdata[lastpos] = 0x00;
+ }
+ total++;
+ break;
+ }
+ return total;
+}
diff --git a/gcr.h b/gcr.h
new file mode 100644
index 0000000..31541de
--- /dev/null
+++ b/gcr.h
@@ -0,0 +1,162 @@
+/*
+ gcr.h - Group Code Recording helper functions
+
+ (C) 2001-05 Markus Brenner
+ and Pete Rittwage
+ based on code by Andreas Boose
+
+ V 0.33 improved sector extraction, added find_track_cycle() function
+ V 0.34 added MAX_SYNC_OFFSET constant, approximated to 800 GCR bytes
+ V 0.35 modified find_track_cycle() interface
+ V 0.36 added bad GCR code detection
+ V 0.36a added find_sector_gap(), find_sector0(), extract_GCR_track()
+ V 0.36d Untold number of additions and consequent bugfixes. (pjr)
+
+*/
+
+#ifndef _GCR_
+#define _GCR_
+
+#include "integer.h" /* Basic integer types */
+
+//#define BYTE unsigned char
+//#define DWORD unsigned int
+#define MAX_TRACKS_1541 42
+#define MAX_TRACKS_1571 (MAX_TRACKS_1541 * 2)
+#define MAX_HALFTRACKS_1541 (MAX_TRACKS_1541 * 2)
+#define MAX_HALFTRACKS_1571 (MAX_TRACKS_1571 * 2)
+
+/* D64 constants */
+#define BLOCKSONDISK 683
+#define BLOCKSEXTRA 85
+#define MAXBLOCKSONDISK (BLOCKSONDISK+BLOCKSEXTRA)
+#define MAX_TRACK_D64 40
+
+/* G64 constants (only needed for current VICE support */
+#define G64_TRACK_MAXLEN 7928
+#define G64_TRACK_LENGTH (G64_TRACK_MAXLEN+2)
+
+/* NIB format constants */
+#define NIB_TRACK_LENGTH 0x2000
+#define NIB_HEADER_SIZE 0xFF
+
+/*
+ number of GCR bytes until NO SYNC error
+ timer counts down from $d000 to $8000 (20480 cycles)
+ until timeout when waiting for a SYNC signal
+ This is approx. 20.48 ms, which is approx 1/10th disk revolution
+ 8000 GCR bytes / 10 = 800 bytes
+*/
+//#define MAX_SYNC_OFFSET 800
+/* this was too small for Lode Runner original (805), so increase to 820 */
+//#define MAX_SYNC_OFFSET 820
+#define MAX_SYNC_OFFSET 0x1500
+
+#define SIGNIFICANT_GAPLEN_DIFF 0x20
+
+#define GCR_BLOCK_HEADER_LEN 24
+#define GCR_BLOCK_DATA_LEN 337
+#define GCR_BLOCK_LEN (GCR_BLOCK_HEADER_LEN + GCR_BLOCK_DATA_LEN)
+
+/* To calculate the bytes per rotation:
+
+ 4,000,000 * 60
+ b/minute = ------------------------------------------------ = x bytes/minute
+ speed_zone_divisor * 8bits
+
+4,000,000 is the base clock frequency divided by 4.
+8 is the number of bits per byte.
+60 gets us to a minute of data, which we can then divide by RPM to
+get our numbers.
+
+speed zone divisors are 13, 14, 15, 16 for densities 3, 2, 1, 0 respectively
+*/
+
+#define DENSITY3 2307692.308 // bytes per minute
+#define DENSITY2 2142857.143
+#define DENSITY1 2000000.000
+#define DENSITY0 1875000.000
+
+/* Some disks have much less data than we normally expect to be able to write at a given density.
+ It's like short tracks, but it's a mastering issue not a protection.
+ This keeps us from getting errors in the track cycle detection */
+#define CAP_MIN_ALLOWANCE 150
+
+/* minimum amount of good sequential GCR for formatted track */
+#define GCR_MIN_FORMATTED 64 // chessmaster track 29 is shortest so far
+
+/* Disk Controller error codes */
+#define SECTOR_OK 0x01
+#define HEADER_NOT_FOUND 0x02
+#define SYNC_NOT_FOUND 0x03
+#define DATA_NOT_FOUND 0x04
+#define BAD_DATA_CHECKSUM 0x05
+#define BAD_GCR_CODE 0x06
+#define VERIFY_ERROR 0x07
+#define WRITE_PROTECTED 0x08
+#define BAD_HEADER_CHECKSUM 0x09
+#define ID_MISMATCH 0x0b
+#define DISK_NOT_INSERTED 0x0f
+
+#define BM_MATCH 0x10
+#define BM_NO_CYCLE 0x20
+#define BM_NO_SYNC 0x40
+#define BM_FF_TRACK 0x80
+
+#define ALIGN_NONE 0
+#define ALIGN_GAP 1
+#define ALIGN_SEC0 2
+#define ALIGN_LONGSYNC 3
+#define ALIGN_WEAK 4
+#define ALIGN_VMAX 5
+#define ALIGN_AUTOGAP 6
+
+#define GCR_MASK_BAD_FIRST 0
+#define GCR_MASK_BAD_LAST 1
+
+/* global variables */
+extern char sector_map_1541[];
+extern BYTE speed_map_1541[];
+extern int capacity[];
+extern int capacity_min[];
+extern int capacity_max[];\
+extern int gap_match_length;
+
+/* prototypes */
+int find_sync(BYTE ** gcr_pptr, BYTE * gcr_end);
+void convert_4bytes_to_GCR(BYTE * buffer, BYTE * ptr);
+int convert_4bytes_from_GCR(BYTE * gcr, BYTE * plain);
+int extract_id(BYTE * gcr_track, BYTE * id);
+int extract_cosmetic_id(BYTE * gcr_track, BYTE * id);
+size_t find_track_cycle(BYTE ** cycle_start, BYTE ** cycle_stop, int cap_min,
+ int cap_max);
+size_t find_nondos_track_cycle(BYTE ** cycle_start, BYTE ** cycle_stop,
+ int cap_min, int cap_max);
+BYTE convert_GCR_sector(BYTE * gcr_start, BYTE * gcr_end,
+ BYTE * d64_sector, int track, int sector, BYTE * id);
+void convert_sector_to_GCR(BYTE * buffer, BYTE * ptr,
+ int track, int sector, BYTE * diskID, int error);
+BYTE * find_sector_gap(BYTE * work_buffer, int tracklen, size_t * p_sectorlen);
+BYTE * find_sector0(BYTE * work_buffer, int tracklen, size_t * p_sectorlen);
+int extract_GCR_track(BYTE * destination, BYTE * source, int * align,
+ int force_align, size_t cap_min, size_t cap_max);
+int replace_bytes(BYTE * buffer, int length, BYTE srcbyte, BYTE dstbyte);
+int check_bad_gcr(BYTE * gcrdata, int length, int fix);
+int check_sync_flags(BYTE * gcrdata, int density, int length);
+void bitshift(BYTE * gcrdata, int length, int bits);
+int check_errors(BYTE * gcrdata, int length, int track, BYTE * id,
+ char * errorstring);
+int check_empty(BYTE * gcrdata, int length, int track, BYTE * id,
+ char * errorstring);
+int compare_tracks(BYTE * track1, BYTE * track2, int length1, int length2,
+ int same_disk, char * outputstring);
+int compare_sectors(BYTE * track1, BYTE * track2, int length1, int length2,
+ BYTE * id1, BYTE * id2, int track, char * outputstring);
+int strip_runs(BYTE * buffer, int length, int minrun, BYTE target);
+int reduce_runs(BYTE * buffer, int length, int length_max, int minrun,
+ BYTE target);
+int is_bad_gcr(BYTE * gcrdata, size_t length, size_t pos);
+int check_formatted(BYTE * gcrdata);
+int check_valid_data(BYTE * data, int length);
+
+#endif
diff --git a/iec_bus.cpp b/iec_bus.cpp
new file mode 100644
index 0000000..05a0910
--- /dev/null
+++ b/iec_bus.cpp
@@ -0,0 +1,126 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#include "iec_bus.h"
+
+u32 IEC_Bus::PIGPIO_MASK_IN_ATN = 1 << PIGPIO_ATN;
+u32 IEC_Bus::PIGPIO_MASK_IN_DATA = 1 << PIGPIO_DATA;
+u32 IEC_Bus::PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_CLOCK;
+u32 IEC_Bus::PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_SRQ;
+u32 IEC_Bus::PIGPIO_MASK_IN_RESET = 1 << PIGPIO_RESET;
+
+bool IEC_Bus::PI_Atn = false;
+bool IEC_Bus::PI_Data = false;
+bool IEC_Bus::PI_Clock = false;
+bool IEC_Bus::PI_Reset = false;
+
+bool IEC_Bus::VIA_Atna = false;
+bool IEC_Bus::VIA_Data = false;
+bool IEC_Bus::VIA_Clock = false;
+
+bool IEC_Bus::DataSetToOut = false;
+bool IEC_Bus::AtnaDataSetToOut = false;
+bool IEC_Bus::ClockSetToOut = false;
+
+bool IEC_Bus::OutputLED = false;
+bool IEC_Bus::OutputSound = false;
+
+bool IEC_Bus::Resetting = false;
+
+bool IEC_Bus::splitIECLines = false;
+bool IEC_Bus::invertIECInputs = false;
+
+u32 IEC_Bus::myOutsGPFSEL1 = 0;
+u32 IEC_Bus::myOutsGPFSEL0 = 0;
+bool IEC_Bus::InputButton[5];
+bool IEC_Bus::InputButtonPrev[5];
+u32 IEC_Bus::validInputCount[5];
+u32 IEC_Bus::inputRepeatThreshold[5];
+u32 IEC_Bus::inputRepeat[5];
+u32 IEC_Bus::inputRepeatPrev[5];
+
+m6522* IEC_Bus::VIA = 0;
+
+void IEC_Bus::Read(void)
+{
+ IOPort* portB = 0;
+ unsigned gplev0 = read32(ARM_GPIO_GPLEV0);
+
+ int index;
+ int buttonCount = sizeof(ButtonPinFlags) / sizeof(unsigned);
+ for (index = 0; index < buttonCount; ++index)
+ {
+ UpdateButton(index, gplev0);
+ }
+
+ if (VIA) portB = VIA->GetPortB();
+
+ bool ATNIn = (gplev0 & PIGPIO_MASK_IN_ATN) == (invertIECInputs ? PIGPIO_MASK_IN_ATN : 0);
+ if (PI_Atn != ATNIn)
+ {
+ PI_Atn = ATNIn;
+
+ if (VIA)
+ {
+ if ((portB->GetDirection() & 0x10) != 0)
+ {
+ // Emulate the XOR gate UD3
+ // We only need to do this when fully emulating, iec commands do this internally
+ AtnaDataSetToOut = (VIA_Atna != PI_Atn);
+ }
+
+ portB->SetInput(VIAPORTPINS_ATNIN, ATNIn); //is inverted and then connected to pb7 and ca1
+ VIA->InputCA1(ATNIn);
+ }
+ }
+
+ if (portB && (portB->GetDirection() & 0x10) == 0)
+ AtnaDataSetToOut = false; // If the ATNA PB4 gets set to an input then we can't be pulling data low. (Maniac Mansion does this)
+
+ if (!AtnaDataSetToOut && !DataSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software)
+ {
+ bool DATAIn = (gplev0 & PIGPIO_MASK_IN_DATA) == (invertIECInputs ? PIGPIO_MASK_IN_DATA : 0);
+ if (PI_Data != DATAIn)
+ {
+ PI_Data = DATAIn;
+ if (VIA) portB->SetInput(VIAPORTPINS_DATAIN, DATAIn); // VIA DATAin pb0 output from inverted DIN 5 DATA
+ }
+ }
+ else
+ {
+ PI_Data = true;
+ if (VIA) portB->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software
+ }
+
+ if (!ClockSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software)
+ {
+ bool CLOCKIn = (gplev0 & PIGPIO_MASK_IN_CLOCK) == (invertIECInputs ? PIGPIO_MASK_IN_CLOCK : 0);
+ if (PI_Clock != CLOCKIn)
+ {
+ PI_Clock = CLOCKIn;
+ if (VIA) portB->SetInput(VIAPORTPINS_CLOCKIN, CLOCKIn); // VIA CLKin pb2 output from inverted DIN 4 CLK
+ }
+ }
+ else
+ {
+ PI_Clock = true;
+ if (VIA) portB->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software
+ }
+
+ Resetting = (gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0);
+}
diff --git a/iec_bus.h b/iec_bus.h
new file mode 100644
index 0000000..b8d2f31
--- /dev/null
+++ b/iec_bus.h
@@ -0,0 +1,602 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef IEC_BUS_H
+#define IEC_BUS_H
+
+#include "debug.h"
+#include "m6522.h"
+
+#include "rpi-gpio.h"
+#include "rpiHardware.h"
+
+#define INPUT_BUTTON_DEBOUNCE_THRESHOLD 20000
+#define INPUT_BUTTON_REPEAT_THRESHOLD 460000
+
+// DIN ATN is inverted and then connected to pb7 and ca1
+// - also input to xor with ATNAout pb4
+// - output of xor is inverted and connected to DIN pin 5 DATAout (OC so can only pull low)
+// VIA ATNin pb7 input to xor
+// VIA ATNin ca1 output from inverted DIN 3 ATN
+// DIN DATA is inverted and then connected to pb1
+// VIA DATAin pb0 output from inverted DIN 5 DATA
+// VIA DATAout pb1 inverted and then connected to DIN DATA
+// DIN CLK is inverted and then connected to pb2
+// VIA CLKin pb2 output from inverted DIN 4 CLK
+// VIA CLKout pb3 inverted and then connected to DIN CLK
+
+// $1800
+// PB 0 data in
+// PB 1 data out
+// PB 2 clock in
+// PB 3 clock out
+// PB 4 ATNA
+// PB 5,6 device address
+// PB 7,CA1 ATN IN
+
+// If ATN and ATNA are out of sync
+// - VIA's DATA IN will automatically set high and hence the DATA line will be pulled low (activated)
+// If ATN and ATNA are in sync
+// - the output from the XOR gate will be low and the output of its inverter will go high
+// - when this occurs the DATA line must be still able to be pulled low via the PC or VIA's inverted PB1 (DATA OUT)
+//
+// Therefore in the same vein if PB7 is set to output it could cause the input of the XOR to be pulled low
+//
+
+enum PIGPIO
+{
+ // Original Non-split lines
+ PIGPIO_ATN = 2, // 3
+ PIGPIO_DATA = 18, // 12
+ PIGPIO_CLOCK = 17, // 11
+ PIGPIO_SRQ = 19, // 35
+ PIGPIO_RESET = 3, // 5
+
+
+ // Pinout for those that want to split the lines (and the common ones like buttons, sound and LED)
+ // 0 IDSC //28
+ // 1 IDSD //27
+ // 2 I2C_SDA //3
+ // 3 I2C_CLK //5
+ PIGPIO_IN_BUTTON4 = 4, // 07 Common
+ PIGPIO_IN_BUTTON5 = 5, // 29 Common
+ PIGPIO_OUT_RESET = 6, // 31
+ // 7 SPI0_CS1 //26
+ // 8 SPI0_CS0 //24
+ // 9 SPI0_MISO //21
+ // 10 SPI0_MOSI //19
+ // 11 SPI0_CLK //23
+ PIGPIO_OUT_ATN = 12, // 32
+ PIGPIO_OUT_SOUND = 13, // 33 Common
+ // 14 TX //8
+ // 15 RX //10
+ PIGPIO_OUT_LED = 16, // 36 Common
+ PIGPIO_OUT_CLOCK = 17, // 11
+ PIGPIO_OUT_DATA = 18, // 12
+ PIGPIO_OUT_SRQ = 19, // 35
+ PIGPIO_IN_RESET = 20, // 38
+ PIGPIO_IN_SRQ = 21, // 40
+ PIGPIO_IN_BUTTON2 = 22, // 15 Common
+ PIGPIO_IN_BUTTON3 = 23, // 16 Common
+ PIGPIO_IN_ATN = 24, // 18
+ PIGPIO_IN_DATA = 25, // 22
+ PIGPIO_IN_CLOCK = 26, // 37
+ PIGPIO_IN_BUTTON1 = 27 // 13 Common
+};
+
+enum PIGPIOMasks
+{
+ // Non-split lines
+ //PIGPIO_MASK_IN_ATN = 1 << PIGPIO_ATN,
+ //PIGPIO_MASK_IN_DATA = 1 << PIGPIO_DATA,
+ //PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_CLOCK,
+ //PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_SRQ,
+ //PIGPIO_MASK_IN_RESET = 1 << PIGPIO_RESET,
+
+ // Split lines
+ //PIGPIO_MASK_IN_ATN = 1 << PIGPIO_IN_ATN,
+ //PIGPIO_MASK_IN_DATA = 1 << PIGPIO_IN_DATA,
+ //PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_IN_CLOCK,
+ //PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_IN_SRQ,
+ //PIGPIO_MASK_IN_RESET = 1 << PIGPIO_IN_RESET,
+
+ PIGPIO_MASK_IN_BUTTON1 = 1 << PIGPIO_IN_BUTTON1,
+ PIGPIO_MASK_IN_BUTTON2 = 1 << PIGPIO_IN_BUTTON2,
+ PIGPIO_MASK_IN_BUTTON3 = 1 << PIGPIO_IN_BUTTON3,
+ PIGPIO_MASK_IN_BUTTON4 = 1 << PIGPIO_IN_BUTTON4,
+ PIGPIO_MASK_IN_BUTTON5 = 1 << PIGPIO_IN_BUTTON5,
+};
+
+static const unsigned ButtonPinFlags[5] = { PIGPIO_MASK_IN_BUTTON1, PIGPIO_MASK_IN_BUTTON2, PIGPIO_MASK_IN_BUTTON3, PIGPIO_MASK_IN_BUTTON4, PIGPIO_MASK_IN_BUTTON5 };
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Original Non-split lines
+///////////////////////////////////////////////////////////////////////////////////////////////
+// I kind of stuffed up selecting what pins should be what.
+// Originally I wanted the hardware interface to be as simple as possible and was focused on all the connections down one end of the Pi header.
+// Also, originally I was only worried about ATN, DATA, CLOCK and RESET. Buttons were optional. With DATA and CLOCK only needing to go output.
+// With hindsight (now wanting to support NIBTools ATN should have been put in the same pin group as DATA and CLOCK);
+// This now requires 2 GPIO writes as PIN2 is in SEL0 and pins 17 and 18 are in SEL1.
+//GPFSEL0
+// 9 8 7 6 5 4 3 2 1 0
+// 000 000 000 000 000 000 000 001 000 000
+// To support NIBTools ATN can be made an output as well
+static const u32 PI_OUTPUT_MASK_GPFSEL0 = ~((1 << (PIGPIO_ATN * 3)));
+//GPFSEL1 RX TX
+// 19 18 17 16 15 14 13 12 11 10
+// 000 001 001 000 000 000 000 000 000 000 (bits 21 and 24 for GPIO 17 and 18 as outputs)
+//static const u32 PI_OUTPUT_MASK_GPFSEL1 = ~((1 << 21) | (1 << 24));
+// 000 001 001 001 000 000 001 000 000 000 (bits 21 and 24 for GPIO 17 and 18 as outputs)
+//static const u32 PI_OUTPUT_MASK_GPFSEL1 = ~((1 << ((PIGPIO_OUT_SOUND - 10) * 3)) | (1 << ((PIGPIO_OUT_LED - 10) * 3)) | (1 << ((PIGPIO_CLOCK - 10) * 3)) | (1 << ((PIGPIO_DATA - 10) * 3)));
+static const u32 PI_OUTPUT_MASK_GPFSEL1 = ~((1 << ((PIGPIO_CLOCK - 10) * 3)) | (1 << ((PIGPIO_DATA - 10) * 3)));
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Pinout for those that want to split the lines (and the common ones like buttons, sound and LED)
+///////////////////////////////////////////////////////////////////////////////////////////////
+// OUT (need 7)
+// CLK
+// DATA
+// SRQ
+// ATN
+// RST
+// LED
+// SND
+
+// IN (need 10)
+// CLK
+// DATA
+// ATN
+// RST
+// SRQ
+// BTN1
+// BTN2
+// BTN3
+// BTN4
+// BTN5
+
+//GPFSEL0
+// S S S
+// P P P I I
+// I I I 2 2
+// 0 0 0 C C
+// M C C S S I I
+// I E E C D D D
+// S 0 1 W R R L A S S
+// O RST B5 B4 1 1 D C
+// 9 8 7 6 5 4 3 2 1 0
+//GPFSEL1
+// RX TX S S
+// RX TX P P
+// RX TX I I
+// RX TX 0 0
+// RX TX M
+// RX TX C O
+// W W W W RX TX W W L S
+// SRQ DTA CLK LED RX TX SND ATN K I
+// 19 18 17 16 15 14 13 12 11 10
+//GPFSEL2
+// X X
+// X X
+// X X
+// X X
+// X X
+// X X
+// X X R R R R R R R R
+// X X B1 CLK DTA ATN B3 B2 SRQ RST
+// 29 28 27 26 25 24 23 22 21 20
+// 000 000 000 000 000 000 000 000 000 000
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+enum VIAPortPins
+{
+ VIAPORTPINS_DATAIN = 0x01, //pb0
+ VIAPORTPINS_DATAOUT = 0x02, //pb1
+ VIAPORTPINS_CLOCKIN = 0x04, //pb2
+ VIAPORTPINS_CLOCKOUT = 0x08,//pb3
+ VIAPORTPINS_ATNAOUT = 0x10, //pb4
+ VIAPORTPINS_DEVSEL0 = 0x20, //pb5
+ VIAPORTPINS_DEVSEL1 = 0x40, //pb5
+ VIAPORTPINS_ATNIN = 0x80 //bp7
+};
+
+typedef bool(*CheckStatus)();
+
+class IEC_Bus
+{
+public:
+ static inline void Initialise(void)
+ {
+ volatile int index; // Force a real delay in the loop below.
+
+ // Clear all outputs to 0
+ write32(ARM_GPIO_GPCLR0, 0xFFFFFFFF);
+
+ if (!splitIECLines)
+ {
+ // This means that when any pin is turn to output it will output a 0 and pull lines low (ie an activation state on the IEC bus)
+ // Note: on the IEC bus you never output a 1 you simply tri state and it will be pulled up to a 1 (ie inactive state on the IEC bus) if no one else is pulling it low.
+
+ myOutsGPFSEL0 = read32(ARM_GPIO_GPFSEL0);
+ myOutsGPFSEL1 = read32(ARM_GPIO_GPFSEL1);
+
+ myOutsGPFSEL1 |= (1 << ((PIGPIO_OUT_LED - 10) * 3));
+ myOutsGPFSEL1 |= (1 << ((PIGPIO_OUT_SOUND - 10) * 3));
+ }
+ else
+ {
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON4, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON5, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_RESET, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_SRQ, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON2, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON3, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_ATN, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_DATA, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_CLOCK, FS_INPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON1, FS_INPUT);
+
+
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_RESET, FS_OUTPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_ATN, FS_OUTPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SOUND, FS_OUTPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_LED, FS_OUTPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_CLOCK, FS_OUTPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_DATA, FS_OUTPUT);
+ RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SRQ, FS_OUTPUT);
+ }
+
+
+ // Set up audio.
+ write32(CM_PWMDIV, CM_PASSWORD + 0x2000);
+ write32(CM_PWMCTL, CM_PASSWORD + CM_ENAB + CM_SRC_OSCILLATOR); // Use Default 100MHz Clock
+ // Set PWM
+ write32(PWM_RNG1, 0x1B4); // 8bit 44100Hz Mono
+ write32(PWM_RNG2, 0x1B4);
+ write32(PWM_CTL, PWM_USEF2 + PWM_PWEN2 + PWM_USEF1 + PWM_PWEN1 + PWM_CLRF1);
+
+
+ int buttonCount = sizeof(ButtonPinFlags) / sizeof(unsigned);
+ for (index = 0; index < buttonCount; ++index)
+ {
+ InputButton[index] = false;
+ InputButtonPrev[index] = false;
+ validInputCount[index] = 0;
+ inputRepeatThreshold[index] = INPUT_BUTTON_REPEAT_THRESHOLD;
+ inputRepeat[index] = 0;
+ inputRepeatPrev[index] = 0;
+ }
+
+ // Enable the internal pullups for the input button pins using the method described in BCM2835-ARM-Peripherals manual.
+ RPI_GpioBase->GPPUD = 2;
+ for (index = 0; index < 150; ++index)
+ {
+ }
+ RPI_GpioBase->GPPUDCLK0 = PIGPIO_MASK_IN_BUTTON1 | PIGPIO_MASK_IN_BUTTON2 | PIGPIO_MASK_IN_BUTTON3 | PIGPIO_MASK_IN_BUTTON4 | PIGPIO_MASK_IN_BUTTON5;
+ for (index = 0; index < 150; ++index)
+ {
+ }
+ RPI_GpioBase->GPPUD = 0;
+ RPI_GpioBase->GPPUDCLK0 = 0;
+ }
+
+ static inline void UpdateButton(int index, unsigned gplev0)
+ {
+ bool inputcurrent = (gplev0 & ButtonPinFlags[index]) == 0;
+
+ InputButtonPrev[index] = InputButton[index];
+ inputRepeatPrev[index] = inputRepeat[index];
+
+ if (inputcurrent)
+ {
+ validInputCount[index]++;
+ if (validInputCount[index] == INPUT_BUTTON_DEBOUNCE_THRESHOLD)
+ {
+ InputButton[index] = true;
+ inputRepeatThreshold[index] = INPUT_BUTTON_DEBOUNCE_THRESHOLD + INPUT_BUTTON_REPEAT_THRESHOLD;
+ inputRepeat[index]++;
+ }
+
+ if (validInputCount[index] == inputRepeatThreshold[index])
+ {
+ inputRepeat[index]++;
+ inputRepeatThreshold[index] += INPUT_BUTTON_REPEAT_THRESHOLD / inputRepeat[index];
+ }
+ }
+ else
+ {
+ InputButton[index] = false;
+ validInputCount[index] = 0;
+ inputRepeatThreshold[index] = INPUT_BUTTON_REPEAT_THRESHOLD;
+ inputRepeat[index] = 0;
+ inputRepeatPrev[index] = 0;
+ }
+ }
+
+ static void Read(void);
+
+ static void WaitUntilReset(void)
+ {
+ unsigned gplev0;
+ do
+ {
+ gplev0 = read32(ARM_GPIO_GPLEV0);
+ Resetting = (gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0);
+
+ if (Resetting)
+ IEC_Bus::WaitMicroSeconds(100);
+ }
+ while (Resetting);
+ }
+
+ // Out going
+ static void PortB_OnPortOut(void* pUserData, unsigned char status)
+ {
+ bool oldDataSetToOut = DataSetToOut;
+ bool oldClockSetToOut = ClockSetToOut;
+
+ // These are the values the VIA is trying to set the outputs to
+ VIA_Atna = (status & (unsigned char)VIAPORTPINS_ATNAOUT) != 0;
+ VIA_Data = (status & (unsigned char)VIAPORTPINS_DATAOUT) != 0; // VIA DATAout PB1 inverted and then connected to DIN DATA
+ VIA_Clock = (status & (unsigned char)VIAPORTPINS_CLOCKOUT) != 0; // VIA CLKout PB3 inverted and then connected to DIN CLK
+
+ // Emulate the XOR gate UD3
+ AtnaDataSetToOut = (VIA_Atna != PI_Atn);
+
+ if (AtnaDataSetToOut)
+ {
+ // if the output of the XOR gate is high (ie VIA_Atna != PI_Atn) then this is inverted and pulls DATA low (activating it)
+ PI_Data = true;
+ if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software
+ }
+
+ if (VIA)
+ {
+ // If the VIA's data and clock outputs ever get set to inputs the real hardware reads these lines as asserted.
+ bool PB1SetToInput = (VIA->GetPortB()->GetDirection() & 2) == 0;
+ bool PB3SetToInput = (VIA->GetPortB()->GetDirection() & 8) == 0;
+ if (PB1SetToInput) VIA_Data = true;
+ if (PB3SetToInput) VIA_Clock = true;
+ }
+
+ ClockSetToOut = VIA_Clock;
+ DataSetToOut = VIA_Data;
+
+ if (!oldDataSetToOut && DataSetToOut)
+ {
+ PI_Data = true;
+ if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAOUT, true); // simulate the read in software
+ }
+
+ if (!oldClockSetToOut && ClockSetToOut)
+ {
+ PI_Clock = true;
+ if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software
+ }
+
+ }
+
+ static inline void RefreshOuts(void)
+ {
+ unsigned set = 0;
+ unsigned clear = 0;
+
+ if (!splitIECLines)
+ {
+ unsigned outputs = 0;
+
+ if (AtnaDataSetToOut || DataSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_DATA - 10) * 3));
+ if (ClockSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_CLOCK - 10) * 3));
+
+ unsigned nValue = (myOutsGPFSEL1 & PI_OUTPUT_MASK_GPFSEL1) | outputs;
+ write32(ARM_GPIO_GPFSEL1, nValue);
+ }
+ else
+ {
+ if (AtnaDataSetToOut || DataSetToOut) set |= 1 << PIGPIO_OUT_DATA;
+ else clear |= 1 << PIGPIO_OUT_DATA;
+
+ if (ClockSetToOut) set |= 1 << PIGPIO_OUT_CLOCK;
+ else clear |= 1 << PIGPIO_OUT_CLOCK;
+ }
+
+ if (OutputLED) set |= 1 << PIGPIO_OUT_LED;
+ else clear |= 1 << PIGPIO_OUT_LED;
+
+ if (OutputSound) set |= 1 << PIGPIO_OUT_SOUND;
+ else clear |= 1 << PIGPIO_OUT_SOUND;
+
+ write32(ARM_GPIO_GPSET0, set);
+ write32(ARM_GPIO_GPCLR0, clear);
+ }
+
+ static void WaitMicroSeconds(u32 amount)
+ {
+ u32 count;
+
+ for (count = 0; count < amount; ++count)
+ {
+ unsigned before;
+ unsigned after;
+ // We try to update every micro second and use as a rough timer to count micro seconds
+ before = read32(ARM_SYSTIMER_CLO);
+ do
+ {
+ after = read32(ARM_SYSTIMER_CLO);
+ } while (after == before);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Manual methods used by IEC_Commands
+ static inline void AssertData()
+ {
+ if (!DataSetToOut)
+ {
+ DataSetToOut = true;
+ RefreshOuts();
+ }
+ }
+ static inline void ReleaseData()
+ {
+ if (DataSetToOut)
+ {
+ DataSetToOut = false;
+ RefreshOuts();
+ }
+ }
+
+ static inline void AssertClock()
+ {
+ if (!ClockSetToOut)
+ {
+ ClockSetToOut = true;
+ RefreshOuts();
+ }
+ }
+ static inline void ReleaseClock()
+ {
+ if (ClockSetToOut)
+ {
+ ClockSetToOut = false;
+ RefreshOuts();
+ }
+ }
+ static inline bool GetPI_Atn() { return PI_Atn; }
+ static inline bool IsAtnAsserted() { return PI_Atn; }
+ static inline bool IsAtnReleased() { return !PI_Atn; }
+ static inline bool GetPI_Data() { return PI_Data; }
+ static inline bool IsDataAsserted() { return PI_Data; }
+ static inline bool IsDataReleased() { return !PI_Data; }
+ static inline bool GetPI_Clock() { return PI_Clock; }
+ static inline bool IsClockAsserted() { return PI_Clock; }
+ static inline bool IsClockReleased() { return !PI_Clock; }
+ static inline bool GetPI_Reset() { return PI_Reset; }
+ static inline bool IsDataSetToOut() { return DataSetToOut; }
+ static inline bool IsAtnaDataSetToOut() { return AtnaDataSetToOut; }
+ static inline bool IsClockSetToOut() { return ClockSetToOut; }
+ static inline bool IsReset() { return Resetting; }
+
+ static inline void WaitWhileAtnAsserted()
+ {
+ while (IsAtnAsserted())
+ {
+ Read();
+ }
+ }
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ static inline void SetSplitIECLines(bool value)
+ {
+ splitIECLines = value;
+ if (splitIECLines)
+ {
+ PIGPIO_MASK_IN_ATN = 1 << PIGPIO_IN_ATN;
+ PIGPIO_MASK_IN_DATA = 1 << PIGPIO_IN_DATA;
+ PIGPIO_MASK_IN_CLOCK = 1 << PIGPIO_IN_CLOCK;
+ PIGPIO_MASK_IN_SRQ = 1 << PIGPIO_IN_SRQ;
+ PIGPIO_MASK_IN_RESET = 1 << PIGPIO_IN_RESET;
+ }
+ }
+
+ static inline void SetInvertIECInputs(bool value)
+ {
+ invertIECInputs = value;
+ if (value)
+ {
+ PI_Atn = ~PI_Atn;
+ PI_Data = ~PI_Data;
+ PI_Clock = ~PI_Clock;
+ PI_Reset = ~PI_Reset;
+ }
+ }
+
+ // CA1 input ATN
+ // If CA1 is ever set to output
+ // - CA1 will start to drive pb7
+ // CA2, CB1 and CB2 are not connected
+ // - check if pulled high or low
+ static m6522* VIA;
+
+ static inline void Reset(void)
+ {
+ WaitUntilReset();
+
+ // VIA $1800
+ // CA2, CB1 and CB2 are not connected (reads as high)
+ // VIA $1C00
+ // CB1 not connected (reads as high)
+
+ VIA_Atna = false;
+ VIA_Data = false;
+ VIA_Clock = false;
+
+ DataSetToOut = false;
+ ClockSetToOut = false;
+
+ PI_Atn = false;
+ PI_Data = false;
+ PI_Clock = false;
+
+ AtnaDataSetToOut = (VIA_Atna != PI_Atn);
+ if (AtnaDataSetToOut) PI_Data = true;
+
+ RefreshOuts();
+ }
+
+ static bool GetInputButtonPressed(int buttonIndex) { return InputButton[buttonIndex] && !InputButtonPrev[buttonIndex]; }
+ static bool GetInputButtonReleased(int buttonIndex) { return !InputButton[buttonIndex] && InputButtonPrev[buttonIndex]; }
+ static bool GetInputButton(int buttonIndex) { return InputButton[buttonIndex]; }
+ static bool GetInputButtonRepeating(int buttonIndex) { return inputRepeat[buttonIndex] != inputRepeatPrev[buttonIndex]; }
+
+ static bool OutputLED;
+ static bool OutputSound;
+
+private:
+ static bool splitIECLines;
+ static bool invertIECInputs;
+ static u32 PIGPIO_MASK_IN_ATN;
+ static u32 PIGPIO_MASK_IN_DATA;
+ static u32 PIGPIO_MASK_IN_CLOCK;
+ static u32 PIGPIO_MASK_IN_SRQ;
+ static u32 PIGPIO_MASK_IN_RESET;
+
+ static bool PI_Atn;
+ static bool PI_Data;
+ static bool PI_Clock;
+ static bool PI_Reset;
+
+ static bool VIA_Atna;
+ static bool VIA_Data;
+ static bool VIA_Clock;
+
+ static bool DataSetToOut;
+ static bool AtnaDataSetToOut;
+ static bool ClockSetToOut;
+ static bool Resetting;
+
+ static u32 myOutsGPFSEL0;
+ static u32 myOutsGPFSEL1;
+ static bool InputButton[5];
+ static bool InputButtonPrev[5];
+ static u32 validInputCount[5];
+ static u32 inputRepeatThreshold[5];
+ static u32 inputRepeat[5];
+ static u32 inputRepeatPrev[5];
+};
+#endif
\ No newline at end of file
diff --git a/iec_commands.cpp b/iec_commands.cpp
new file mode 100644
index 0000000..7ed074a
--- /dev/null
+++ b/iec_commands.cpp
@@ -0,0 +1,1875 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+
+//TODO when we have different file types we then need to detect these type of wildcards;-
+//*=S selects only sequential files
+//*=P selects program files
+//*=R selects relative files
+//*=U selects user-files
+
+#include "iec_commands.h"
+#include "iec_bus.h"
+#include "ff.h"
+#include "DiskImage.h"
+#include "Petscii.h"
+#include
+#include
+#include
+#include
+#include
+
+#define CBM_NAME_LENGTH 16
+#define CBM_NAME_LENGTH_MINUS_D64 CBM_NAME_LENGTH-4
+
+#define DIRECTORY_ENTRY_SIZE 32
+
+#define EOI_RECVD (1<<0)
+#define COMMAND_RECVD (1<<1)
+
+extern unsigned versionMajor;
+extern unsigned versionMinor;
+
+#define WaitWhile(checkStatus) \
+ do\
+ {\
+ IEC_Bus::Read();\
+ if (CheckATN()) return true;\
+ } while (checkStatus)
+
+#define VERSION_OFFSET_IN_DIR_HEADER 17
+static u8 DirectoryHeader[] =
+{
+ 1, 4, // BASIC start address
+ 1, 1, // next line pointer
+ 0, 0, // line number 0
+ 0x12, // Reverse on
+ 0x22, // Quote
+ 'P', 'I', '1', '5', '4', '1', ' ', 'V', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', // Name
+ 0x22, // Quote
+ 0x20, // Space
+ 'P', 'I', ' ', '2', 'A', // ID, Dos Disk 2A
+ 00
+};
+
+static const u8 DirectoryBlocksFree[] = {
+ 1, 1, // Next line pointer
+ 0, 0, // 16bit free blocks value
+ 'B', 'L', 'O', 'C', 'K', 'S', ' ', 'F', 'R', 'E', 'E', '.',
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00
+};
+
+static const u8 filetypes[] = {
+ 'D', 'E', 'L', // 0
+ 'S', 'E', 'Q', // 1
+ 'P', 'R', 'G', // 2
+ 'U', 'S', 'R', // 3
+ 'R', 'E', 'L', // 4
+ 'C', 'B', 'M', // 5
+ 'D', 'I', 'R', // 6
+};
+
+#define DISKNAME_OFFSET_IN_DIR_BLOCK 144
+#define DISKID_OFFSET_IN_DIR_BLOCK 162
+static u8 blankD64DIRBAM[] =
+{
+ 0x12, 0x01, 0x41, 0x00, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f,
+ 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f,
+ 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f,
+ 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f,
+ 0x15, 0xff, 0xff, 0x1f, 0x15, 0xff, 0xff, 0x1f, 0x11, 0xfc, 0xff, 0x07, 0x13, 0xff, 0xff, 0x07,
+ 0x13, 0xff, 0xff, 0x07, 0x13, 0xff, 0xff, 0x07, 0x13, 0xff, 0xff, 0x07, 0x13, 0xff, 0xff, 0x07,
+ 0x13, 0xff, 0xff, 0x07, 0x12, 0xff, 0xff, 0x03, 0x12, 0xff, 0xff, 0x03, 0x12, 0xff, 0xff, 0x03,
+ 0x12, 0xff, 0xff, 0x03, 0x12, 0xff, 0xff, 0x03, 0x12, 0xff, 0xff, 0x03, 0x11, 0xff, 0xff, 0x01,
+ 0x11, 0xff, 0xff, 0x01, 0x11, 0xff, 0xff, 0x01, 0x11, 0xff, 0xff, 0x01, 0x11, 0xff, 0xff, 0x01,
+ 0x42, 0x4c, 0x41, 0x4e, 0x4b, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0x31, 0x41, 0xa0, 0x32, 0x41, 0xa0, 0xa0, 0xa0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ // 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static char ErrorMessage[64];
+
+static u8* InsertNumber(u8* msg, u8 value)
+{
+ if (value >= 100)
+ {
+ *msg++ = '0' + value / 100;
+ value %= 100;
+ }
+ *msg++ = '0' + value / 10;
+ *msg++ = '0' + value % 10;
+ return msg;
+}
+
+static void SetHeaderVersion()
+{
+ u8* ptr = DirectoryHeader + VERSION_OFFSET_IN_DIR_HEADER;
+ ptr = InsertNumber(ptr, versionMajor);
+ *ptr++ = '.';
+ ptr = InsertNumber(ptr, versionMinor);
+}
+
+#define ERROR_00_OK 0
+//01,FILES SCRATCHED,XX,00
+//20,READ ERROR,TT,SS header not found
+//21,READ ERROR,TT,SS sync not found
+//22,READ ERROR,TT,SS header checksum fail
+//23,READ ERROR,TT,SS data block checksum fail
+//24,READ ERROR,TT,SS
+#define ERROR_25_WRITE_ERROR 25 //25,WRITE ERROR,TT,SS verify error
+//26,WRITE PROTECT ON,TT,SS
+//27,READ ERROR,TT,SS header checksum fail
+//28,WRITE ERROR,TT,SS sync not found after write
+//29,DISK ID MISMATCH,TT,SS
+#define ERROR_30_SYNTAX_ERROR 30 //30,SYNTAX ERROR,00,00 could not parse the command
+#define ERROR_31_SYNTAX_ERROR 31 //31,SYNTAX ERROR,00,00 unknown command
+#define ERROR_32_SYNTAX_ERROR 32 //32,SYNTAX ERROR,00,00 command too long (ie > 40 characters)
+#define ERROR_33_SYNTAX_ERROR 33 //33,SYNTAX ERROR,00,00 Wildcard * and ? was used in an open or save command
+#define ERROR_34_SYNTAX_ERROR 34 //34,SYNTAX ERROR,00,00 File name could not be found in the command
+#define ERROR_39_FILE_NOT_FOUND 39 //39,FILE NOT FOUND,00,00 User program of type 'USR' was not found
+//50,RECORD NOT PRESENT,00,00
+//51,OVERFLOW IN RECORD,00,00
+//52,FILE TOO LARGE,00,00
+//60,WRITE FILE OPEN,00,00 An at tempt was made to OPEN a file that had not previously been CLOSEd after writing.
+//61,FILE NOT OPEN,00,00 A file was accessed that had not been OPENed.
+#define ERROR_62_FILE_NOT_FOUND 62 //62,FILE NOT FOUND,00,00 An attempt was made to load a program or open does not exist
+#define ERROR_63_FILE_EXISTS 63 //63,FILE EXISTS,00,00 Tried to rename a file to the same name of an existing file
+//65,NO BLOCK,TT,SS
+//66,ILLEGAL TRACK OR SECTOR,TT,SS
+//67,ILLEGAL TRACK OR SECTOR,TT,SS
+//70,NO CHANNEL,00,00 An attempt was made to open more files than channels available
+//71,DIR ERROR,TT,SS
+//72,DISK FULL,00,00
+#define ERROR_73_DOSVERSION 73 // 73,VERSION,00,00
+#define ERROR_74_DRlVE_NOT_READY 74 //74,DRlVE NOT READY,00,00
+//75,FORMAT SPEED ERROR,00,00
+
+void Error(u8 errorCode, u8 track = 0, u8 sector = 0)
+{
+ char* msg = "UNKNOWN";
+ switch (errorCode)
+ {
+ case ERROR_00_OK:
+ msg = "OK";
+ break;
+ case ERROR_25_WRITE_ERROR:
+ msg = "WRITE ERROR";
+ break;
+ case ERROR_73_DOSVERSION:
+ msg = "PI1541";
+ break;
+ case ERROR_30_SYNTAX_ERROR:
+ case ERROR_31_SYNTAX_ERROR:
+ case ERROR_32_SYNTAX_ERROR:
+ case ERROR_33_SYNTAX_ERROR:
+ case ERROR_34_SYNTAX_ERROR:
+ msg = "SYNTAX ERROR";
+ break;
+ case ERROR_39_FILE_NOT_FOUND:
+ msg = "FILE NOT FOUND";
+ break;
+ case ERROR_62_FILE_NOT_FOUND:
+ msg = "FILE NOT FOUND";
+ break;
+ case ERROR_63_FILE_EXISTS:
+ msg = "FILE EXISTS";
+ break;
+ default:
+ DEBUG_LOG("EC=%d?\r\n", errorCode);
+ break;
+ }
+ sprintf(ErrorMessage, "%02d, %s, %02d, %02d", errorCode, msg, track, sector);
+}
+
+static inline bool IsDirectory(FILINFO& filInfo)
+{
+ return (filInfo.fattrib & AM_DIR) == AM_DIR;
+}
+
+void IEC_Commands::Channel::Close()
+{
+ if (open)
+ {
+ if (writing)
+ {
+ u32 bytesWritten;
+ if (f_write(&file, buffer, cursor, &bytesWritten) != FR_OK)
+ {
+ }
+ }
+ f_close(&file);
+ open = false;
+ }
+ cursor = 0;
+ bytesSent = 0;
+}
+
+IEC_Commands::IEC_Commands()
+{
+ deviceID = 8;
+ usingVIC20 = false;
+ Reset();
+}
+
+void IEC_Commands::Reset(void)
+{
+ receivedCommand = false;
+ receivedEOI = false;
+ secondaryAddress = 0;
+ selectedImageName[0] = 0;
+ atnSequence = ATN_SEQUENCE_IDLE;
+ deviceRole = DEVICE_ROLE_PASSIVE;
+ commandCode = 0;
+ Error(ERROR_00_OK);
+ CloseAllChannels();
+}
+
+void IEC_Commands::CloseAllChannels()
+{
+ for (int i = 0; i < 15; ++i)
+ {
+ channels[i].Close();
+ }
+}
+
+bool IEC_Commands::CheckATN(void)
+{
+ bool atnAsserted = IEC_Bus::IsAtnAsserted();
+ if (atnSequence == ATN_SEQUENCE_RECEIVE_COMMAND_CODE)
+ {
+ // TO CHECK is this case needed? Just let it complete
+ if (!atnAsserted) atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ return !atnAsserted;
+ }
+ else
+ {
+ if (atnAsserted) atnSequence = ATN_SEQUENCE_ATN;
+ return atnAsserted;
+ }
+}
+
+// Paraphrasing Jim Butterfield;- https://www.atarimagazines.com/compute/issue38/073_1_HOW_THE_VIC_64_SERIAL_BUS_WORKS.php
+// The talker is asserting the Clock line.
+// The listener is asserting the Data line.
+// There could be more than one listener, in which case all of the listeners are asserting the Data line.
+// When the talker is ready it releases the Clock line.
+// The listener must detect this and respond, but it doesn't have to do so immediately.
+// When the listener is ready to listen, it releases the Data line.
+// The Data line will go "unasserted" only when all listeners have released it - in other words, when all listeners are ready to accept data.
+
+// What happens next is variable. Either the talker will assert the Clock line again in less than 200 microseconds - usually within 60 microseconds - or
+// it will do nothing. The listener should be watching, and if 200 microseconds pass without the Clock line going to true, it has a special task to perform : note EOI (End Or Identify).
+// If the Ready for Data signal isn't acknowledged by the talker within 200 microseconds, the listener knows that the talker is trying to signal EOI ie, "this character will be the last one."
+
+// So if the listener sees the 200 microsecond time - out, it must signal "OK, I noticed the EOI" back to the talker, I does this by asserting Data line for at least 60 microseconds, and then releasing it.
+// The talker will then revert to transmitting the character in the usual way; within 60 microseconds it will assert Clock line, and transmission will continue.
+// At this point, the Clock line is asserted whether or not we have gone through the EOI sequence; we're back to a common transmission sequence.
+
+// The talker has eight bits to send. They will go out without handshake; in other words, the listener had better be there to catch them, since the talker won't wait to hear from the listener.
+// At this point, the talker controls both lines, Clock and Data. At the beginning of the sequence, it is asserting the Clock, while the Data line is released.
+// The Data line will change soon, since we'll send the data over it.
+
+// For each bit, we set the Data line true or false according to whether the bit is one or zero.
+// As soon as that's set, the Clock line is released, signalling "data ready."
+// The talker will typically have a bit in place and be signalling ready in 70 microseconds or less. Once the talker has signalled "data ready, "it will hold the two lines steady for at least 20 microseconds timing needs to be increased to
+// 60 microseconds if the Commodore 64 is listening, since the 64's video chip may interrupt the processor for 42 microseconds at a time, and without the extra wait the 64 might completely miss a bit.
+// The listener plays a passive role here; it sends nothing, and just watches.
+// As soon as it sees the Clock line false, it grabs the bit from the Data line and puts it away.
+// It then waits for the clock line to go true, in order to prepare for the next bit.
+// When the talker figures the data has been held for a sufficient length of time, it pulls the Clock line true and releases the Data line to false.
+// Then it starts to prepare the next bit.
+
+// After the eighth bit has been sent, it's the listener's turn to acknowledge. At this moment, the Clock line is asserted and the Data line is released.
+// The listener must acknowledge receiving the byte OK by asserting the Data line. The talker is now watching the Data line.
+// If the listener doesn't pull the Data line true within one millisecond it will know that something's wrong and may alarm appropriately.
+
+// We're finished, and back where we started. The talker is holding the Clock line true, and the listener is holding the Data line true. We're ready for step 1; we may send another character - unless EOI has happened.
+// If EOI was sent or received in this last transmission, both talker and listener "let go."
+// After a suitable pause, the Clock and Data lines are released to false and transmission stops.
+
+bool IEC_Commands::WriteIECSerialPort(u8 data, bool eoi)
+{
+ IEC_Bus::WaitMicroSeconds(50); //sidplay64-sd2iec needs this?
+
+ // When the talker is ready it releases the Clock line.
+ IEC_Bus::ReleaseClock();
+
+ // Wait for all listeners to be ready. They singal this by releasing the Data line.
+ WaitWhile(IEC_Bus::IsDataAsserted());
+
+ if (eoi) // End Or Identify
+ {
+ WaitWhile(IEC_Bus::IsDataReleased());
+ WaitWhile(IEC_Bus::IsDataAsserted());
+ }
+
+ IEC_Bus::AssertClock();
+ IEC_Bus::WaitMicroSeconds(40);
+ WaitWhile(IEC_Bus::IsDataAsserted());
+ IEC_Bus::WaitMicroSeconds(21);
+
+ // At this point, the talker controls both lines, Clock and Data. At the beginning of the sequence, it is asserting the Clock, while the Data line is released.
+ for (u8 i = 0; i < 8; ++i)
+ {
+ IEC_Bus::WaitMicroSeconds(45);
+ if (data & 1 << i) IEC_Bus::ReleaseData();
+ else IEC_Bus::AssertData();
+ IEC_Bus::WaitMicroSeconds(22);
+ IEC_Bus::ReleaseClock();
+ if (usingVIC20) IEC_Bus::WaitMicroSeconds(34);
+ else IEC_Bus::WaitMicroSeconds(75);
+ IEC_Bus::AssertClock();
+ IEC_Bus::WaitMicroSeconds(22);
+ IEC_Bus::ReleaseData();
+ IEC_Bus::WaitMicroSeconds(14);
+ }
+
+ // After the eighth bit has been sent, it's the listener's turn to acknowledge. At this moment, the Clock line is asserted and the Data line is released.
+ WaitWhile(IEC_Bus::IsDataReleased());
+ return false;
+}
+
+bool IEC_Commands::ReadIECSerialPort(u8& byte)
+{
+ byte = 0;
+
+ // When the talker is ready it releases the Clock line.
+ WaitWhile(IEC_Bus::IsClockAsserted());
+
+ // We release data first
+ IEC_Bus::ReleaseData();
+ WaitWhile(IEC_Bus::IsDataAsserted());
+
+ timer.Start(200);
+ do
+ {
+ IEC_Bus::Read();
+ if (CheckATN()) return true;
+ }
+ while (IEC_Bus::IsClockReleased() && !timer.Tick());
+
+ if (timer.TimedOut())
+ {
+ IEC_Bus::AssertData();
+ IEC_Bus::WaitMicroSeconds(73);
+ IEC_Bus::ReleaseData();
+ WaitWhile(IEC_Bus::IsClockReleased());
+ receivedEOI = true;
+ }
+
+ for (u8 i = 0; i < 8; ++i)
+ {
+ WaitWhile(IEC_Bus::IsClockAsserted());
+ byte = (byte >> 1) | (!!IEC_Bus::IsDataReleased() << 7);
+ WaitWhile(IEC_Bus::IsClockReleased());
+ }
+
+ IEC_Bus::AssertData();
+ return false;
+}
+
+void IEC_Commands::SimulateIECBegin(void)
+{
+ SetHeaderVersion();
+ Reset();
+ IEC_Bus::Read();
+}
+
+// Paraphrasing Jim Butterfield
+// The computer is the only device that will assert ATN.
+// When it does so, all other devices drop what they are doing and become listeners.
+// Bytes sent by the computer during an ATN period are commands "Talk," "Listen," "Untalk," and "Unlisten" telling a specific device that it will become (or cease to be) a talker or listener.
+// The commands go to all devices, and all devices acknowledge them, but only the ones with the suitable device numbers will switch into talk and listen mode.
+// These commands are sometimes followed by a secondary address, and after ATN is released, perhaps by a file name.
+
+// ATN SEQUENCES
+// When ATN is asserted, everybody stops what they are doing.
+// The computer will quickly assert the Clock line (it's going to send soon).
+// At the same time, the processor releases the Data line to false, but all other devices are getting ready to listen and will each assert the Data line.
+// They had better do this within one millisecond, since the processor is watching and may sound an alarm("device not present") if it doesn't see this take place.
+// Under normal circumstances, transmission now takes place.
+// The computer is sending commands rather than data, but the characters are exchanged with exactly the same timing and handshakes.
+// All devices receive the commands, but only the specified device acts upon it.
+
+// TURNAROUND
+// An unusual sequence takes place following ATN if the computer wishes the remote device to become a talker.
+// This will usually take place only after a Talk command has been sent.
+// Immediately after ATN is released, the selected device will be behaving like a listener.
+// After all, it's been listening during the ATN cycle, and the computer has been a talker.
+// At this instant, we have "wrong way" logic; the device is asserting the Data line, and the computer is asserting the Clock line.
+// We must turn this around. The computer quickly realizes what's going on, and asserts the Data line, as well as releasing the Clock line.
+// The device waits for this: when it sees the Clock line released, it releases the Data line (which stays asserted anyway since the computer is asserting it)
+// and then asserts the Clock line. We're now in our starting position, with the talker (that's the device) asserting the Clock, and the listener (the computer) asserting the Data line true.
+IEC_Commands::UpdateAction IEC_Commands::SimulateIECUpdate(void)
+{
+ if (IEC_Bus::IsReset())
+ {
+ // If the computer is resetting then just wait until it has come out of reset.
+ do
+ {
+ //DEBUG_LOG("Reset during SimulateIECUpdate\r\n");
+ IEC_Bus::Read();
+ IEC_Bus::WaitMicroSeconds(100);
+ }
+ while (IEC_Bus::IsReset());
+ IEC_Bus::WaitMicroSeconds(20);
+ return RESET;
+ }
+
+ updateAction = NONE;
+ switch (atnSequence)
+ {
+ case ATN_SEQUENCE_IDLE:
+ IEC_Bus::Read();
+ if (IEC_Bus::IsAtnAsserted()) atnSequence = ATN_SEQUENCE_ATN;
+ else if (selectedImageName[0] != 0) updateAction = IMAGE_SELECTED;
+ break;
+ case ATN_SEQUENCE_ATN:
+ // All devices must release the Clock line as the computer will be the one assering it.
+ IEC_Bus::ReleaseClock();
+ // Tell computer we are ready to listen by asserting the Data line.
+ IEC_Bus::AssertData();
+
+ deviceRole = DEVICE_ROLE_PASSIVE;
+ atnSequence = ATN_SEQUENCE_RECEIVE_COMMAND_CODE;
+ receivedEOI = false;
+
+ // Wait until the computer is ready to talk
+ // TODO: should set a timer here and if it times out (before the clock is released) go back to IDLE?
+ while (IEC_Bus::IsClockReleased())
+ {
+ IEC_Bus::Read();
+ }
+ break;
+ case ATN_SEQUENCE_RECEIVE_COMMAND_CODE:
+ ReadIECSerialPort(commandCode);
+ // Command Code
+ // 20 Listen + device address (0-1e)
+ // 3f Unlisten
+ // 40 Talk + device address (0-1e)
+ // 5f Untalk
+ // 60 Open Channel or Data + secondary address (0-f)
+ // 70 Undefined
+ // 80 Undefined
+ // 90 Undefined
+ // a0 Undefined
+ // b0 Undefined
+ // c0 Undefined
+ // d0 Undefined
+ // e0 Close + secondary address or channel (0-f)
+ // f0 Open + secondary address or channel (0-f)
+
+ // Notes: from various CBM-DOS books highlighting various DOS requirements.
+
+ // Secondary addresses 0 and 1 are reserved by the DOS for saving and loading programs.
+
+ // Secondary address 15 is designated as the command and error channel.
+ // The command/error channel 15 may be opened while a file is open, but when channel 15 is closed, all other channels are closed as well.
+
+ // OPEN lfn, 8, sa, "filename,filetype,mode"
+ // lfn - logical file number
+ // When the logical file number is between 1 and 127, a PRINT# statement sends a RETURN character to the file after each variable.
+ // If the logical file number is greater than 127 (128 - 255), the PRINT# statement sends an additional linefeed after each RETURN.
+ // The lfn simply a way of assiging a deviceID and a secondary address/channel to a single ID the computer can reference.
+
+ // Should several files be open at once, they must all use different secondary addresses/channels, as only one file can use a channel.
+
+ // If a file is opened with the secondary address of a previously opened file, the previous file is closed.
+
+ // A maximum of 3 channels can be opened with the 1541 at a time.
+
+ // When specifying the filename to be written to (in the OPEN command), you must be sure that the file name does not already exist.
+ // If a file that already exists is to be to opened for writing, the file must first be deleted.
+
+ //DEBUG_LOG("%0x\r\n", commandCode);
+
+ if (commandCode == 0x20 + deviceID) // Listen
+ {
+ secondaryAddress = commandCode & 0x0f;
+ deviceRole = DEVICE_ROLE_LISTEN;
+ if (IEC_Bus::IsAtnAsserted()) atnSequence = ATN_SEQUENCE_RECEIVE_COMMAND_CODE;
+ else atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ }
+ else if (commandCode == 0x3f) // Unlisten
+ {
+ if (deviceRole == DEVICE_ROLE_LISTEN) deviceRole = DEVICE_ROLE_PASSIVE;
+ atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ }
+ else if (commandCode == 0x40 + deviceID) // Talk
+ {
+ secondaryAddress = commandCode & 0x0f;
+ deviceRole = DEVICE_ROLE_TALK;
+ if (IEC_Bus::IsAtnAsserted()) atnSequence = ATN_SEQUENCE_RECEIVE_COMMAND_CODE;
+ else atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ }
+ else if (commandCode == 0x5f) // Untalk
+ {
+ if (deviceRole == DEVICE_ROLE_TALK) deviceRole = DEVICE_ROLE_PASSIVE;
+ atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ }
+ else if ((commandCode & 0x60) == 0x60) // Set secondary addresses for 6*, e* and f* commands
+ {
+ secondaryAddress = commandCode & 0x0f;
+ if ((commandCode & 0xf0) == 0xe0) // Close
+ {
+ CloseFile(secondaryAddress);
+
+ if (IEC_Bus::IsAtnAsserted()) atnSequence = ATN_SEQUENCE_RECEIVE_COMMAND_CODE;
+ else atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ }
+ else // Open
+ {
+ atnSequence = ATN_SEQUENCE_HANDLE_COMMAND_CODE;
+ }
+ }
+ else
+ {
+ IEC_Bus::ReleaseClock();
+ IEC_Bus::ReleaseData();
+ IEC_Bus::WaitWhileAtnAsserted();
+ atnSequence = ATN_SEQUENCE_COMPLETE;
+ }
+ break;
+ case ATN_SEQUENCE_HANDLE_COMMAND_CODE:
+ IEC_Bus::WaitWhileAtnAsserted();
+ if (deviceRole == DEVICE_ROLE_LISTEN)
+ {
+ Listen();
+ }
+ else if (deviceRole == DEVICE_ROLE_TALK)
+ {
+ // Do the turn around and become the talker
+ IEC_Bus::ReleaseData();
+ IEC_Bus::AssertClock();
+ Talk();
+ }
+ atnSequence = ATN_SEQUENCE_COMPLETE;
+ break;
+ case ATN_SEQUENCE_COMPLETE:
+ IEC_Bus::ReleaseClock();
+ IEC_Bus::ReleaseData();
+
+ if (receivedCommand)
+ {
+ Channel& channelCommand = channels[15];
+
+ //DEBUG_LOG("%s sa = %d\r\n", channelCommand.buffer, secondaryAddress);
+
+ if (secondaryAddress == 0xf) //channel 0xf (15) is reserved as the command channel.
+ {
+ ProcessCommand();
+ }
+ else
+ {
+ // We have no time to do anything now. I don't know why!
+ // The ATN sequence is complete and we go back to idle. Maybe anther ATN sequence starts immediately?
+ // We should be able to open files now but doing so takes time and breaks communication.
+ // So instead, we just cache what we need to know and open the file later (at the start of talking or listening).
+ Channel& channel = channels[secondaryAddress];
+ memcpy(channel.command, channelCommand.buffer, channelCommand.cursor);
+ }
+
+ // Command has been processed so reset it now.
+ receivedCommand = false;
+ }
+ atnSequence = ATN_SEQUENCE_IDLE;
+ break;
+ }
+ return updateAction;
+}
+
+static bool CopyFile(char* filenameNew, char* filenameOld, bool concatenate)
+{
+ FRESULT res;
+ FIL fpIn;
+ bool success = false;
+ res = f_open(&fpIn, filenameOld, FA_READ);
+ if (res == FR_OK)
+ {
+ FIL fpOut;
+ u8 mode = FA_WRITE;
+ if (!concatenate) mode |= FA_CREATE_ALWAYS;
+ else mode |= FA_OPEN_APPEND;
+
+ res = f_open(&fpOut, filenameNew, mode);
+ if (res == FR_OK)
+ {
+ char buffer[1024];
+ u32 bytes;
+
+ success = true;
+ do
+ {
+ f_read(&fpIn, buffer, sizeof(buffer), &bytes);
+ if (bytes > 0)
+ {
+ // TODO: Should check for disk full.
+ if (f_write(&fpOut, buffer, bytes, &bytes) != FR_OK)
+ {
+ success = false;
+ break;
+ }
+ }
+ } while (bytes != 0);
+
+ f_close(&fpOut);
+ }
+ f_close(&fpIn);
+ }
+ return success;
+}
+
+static const char* ParseName(const char* text, char* name, bool convert, bool includeSpace = false)
+{
+ char* ptrOut = name;
+ const char* ptr = text;
+ *name = 0;
+
+ if (isspace(*ptr) || *ptr == ',' || *ptr == '=' || *ptr == ':')
+ {
+ ptr++;
+ }
+
+ // TODO: Should not do this - should use command length to test for the end of a command (use indicies instead of pointers?)
+ while (*ptr != '\0')
+ {
+ if (!isspace(*ptr))
+ break;
+ ptr++;
+ }
+ if (*ptr != 0)
+ {
+ while (*ptr != '\0')
+ {
+ if ((!includeSpace && isspace(*ptr)) || *ptr == ',' || *ptr == '=' || *ptr == ':')
+ break;
+ if (convert) *ptrOut++ = petscii2ascii(*ptr++);
+ else *ptrOut++ = *ptr++;
+ }
+ }
+ *ptrOut = 0;
+ return ptr;
+}
+
+static const char* ParseNextName(const char* text, char* name, bool convert)
+{
+ char* ptrOut = name;
+ const char* ptr;
+ *name = 0;
+
+ // TODO: looking for these is bad for binary parameters (binary parameter commands should not come through here)
+ ptr = strchr(text, ':');
+ if (ptr == 0) ptr = strchr(text, '=');
+ if (ptr == 0) ptr = strchr(text, ',');
+
+ if (ptr)
+ return ParseName(ptr, name, convert);
+ *ptrOut = 0;
+ return ptr;
+}
+
+static bool ParseFilenames(const char* text, char* filenameA, char* filenameB)
+{
+ bool success = false;
+ text = ParseNextName(text, filenameA, true);
+ if (text)
+ {
+ ParseNextName(text, filenameB, true);
+ if (filenameB[0] != 0) success = true;
+ else Error(ERROR_34_SYNTAX_ERROR); // File name could not be found in the command
+ }
+ else
+ {
+ Error(ERROR_31_SYNTAX_ERROR); // could not parse the command
+ }
+ return success;
+}
+
+// If it is a disk image then we select it,
+// else if it is a folder then we enter it,
+// else it is some other random file we do nothing.
+bool IEC_Commands::Enter(DIR& dir, FILINFO& filInfo)
+{
+ filInfoSelectedImage = filInfo;
+
+ if (DiskImage::IsDiskImageExtention(filInfo.fname))
+ {
+ strcpy((char*)selectedImageName, filInfo.fname);
+ return true;
+ }
+ else if (IsDirectory(filInfo))
+ {
+ if (f_chdir(filInfo.fname) == FR_OK) updateAction = DIR_PUSHED;
+ else Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ return false;
+}
+
+static int ParsePartition(char** buf)
+{
+ int part = 0;
+
+ // Capital letters coming from sidplay64-sd2iec have the high bit set. Was confusing isdigit!
+ while ((isdigit(**buf) && !(**buf & 0x80)) || **buf == ' ' || **buf == '@')
+ {
+ if (isdigit(**buf)) part = part * 10 + (**buf - '0');
+ (*buf)++;
+ }
+ return 0;
+}
+
+void IEC_Commands::CD(int partition, char* filename)
+{
+ char filenameEdited[256];
+
+ if (filename[0] == '/' && filename[1] == '/')
+ sprintf(filenameEdited, "\\\\1541\\%s", filename + 2);
+ else
+ strcpy(filenameEdited, filename);
+
+ int len = strlen(filenameEdited);
+
+ for (int i = 0; i < len; i++)
+ {
+ if (filenameEdited[i] == '/')
+ filenameEdited[i] = '\\';
+ filenameEdited[i] = petscii2ascii(filenameEdited[i]);
+ }
+
+ DEBUG_LOG("CD %s\r\n", filenameEdited);
+ if (filenameEdited[0] == '_' && len == 1)
+ {
+ updateAction = POP_DIR;
+ }
+ else
+ {
+ DIR dir;
+ FILINFO filInfo;
+
+ char path[256] = { 0 };
+ char* pattern = strrchr(filenameEdited, '\\');
+
+ if (pattern)
+ {
+ // Now we look for a folder
+ int len = pattern - filenameEdited;
+ strncpy(path, filenameEdited, len);
+
+ pattern++;
+
+ if ((f_stat(path, &filInfo) != FR_OK) || !IsDirectory(filInfo))
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ else
+ {
+ char cwd[1024];
+ if (f_getcwd(cwd, 1024) == FR_OK)
+ {
+ f_chdir(path);
+
+ char cwd2[1024];
+ f_getcwd(cwd2, 1024);
+
+ bool found = f_findfirst(&dir, &filInfo, ".", pattern) == FR_OK && filInfo.fname[0] != 0;
+
+ //DEBUG_LOG("%s pattern = %s\r\n", filInfo.fname, pattern);
+
+ if (found)
+ {
+ if (DiskImage::IsDiskImageExtention(filInfo.fname))
+ {
+ if (f_stat(filInfo.fname, &filInfoSelectedImage) == FR_OK)
+ {
+ strcpy((char*)selectedImageName, filInfo.fname);
+ }
+ else
+ {
+ f_chdir(cwd);
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ }
+ else
+ {
+ //DEBUG_LOG("attemting changing dir %s\r\n", filInfo.fname);
+ if (f_chdir(filInfo.fname) != FR_OK)
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ f_chdir(cwd);
+ }
+ else
+ {
+ updateAction = DIR_PUSHED;
+ }
+ }
+ }
+ else
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ f_chdir(cwd);
+ }
+
+ }
+ //if (f_getcwd(cwd, 1024) == FR_OK)
+ // DEBUG_LOG("CWD on exit = %s\r\n", cwd);
+ }
+ }
+ else
+ {
+ bool found = FindFirst(dir, filenameEdited, filInfo);
+
+ if (found)
+ {
+ if (DiskImage::IsDiskImageExtention(filInfo.fname))
+ {
+ if (f_stat(filInfo.fname, &filInfoSelectedImage) == FR_OK)
+ strcpy((char*)selectedImageName, filInfo.fname);
+ else
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ else
+ {
+ //DEBUG_LOG("attemting changing dir %s\r\n", filInfo.fname);
+ if (f_chdir(filInfo.fname) != FR_OK)
+ Error(ERROR_62_FILE_NOT_FOUND);
+ else
+ updateAction = DIR_PUSHED;
+ }
+ }
+ else
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ }
+ }
+}
+
+void IEC_Commands::MKDir(int partition, char* filename)
+{
+ char filenameEdited[256];
+
+ if (filename[0] == '/' && filename[1] == '/')
+ sprintf(filenameEdited, "\\\\1541\\%s", filename + 2);
+ else
+ strcpy(filenameEdited, filename);
+ int len = strlen(filenameEdited);
+
+ for (int i = 0; i < len; i++)
+ {
+ if (filenameEdited[i] == '/')
+ filenameEdited[i] = '\\';
+
+ filenameEdited[i] = petscii2ascii(filenameEdited[i]);
+ }
+
+ f_mkdir(filenameEdited);
+
+ // Force the FileBrowser to refresh incase it just heppeded to be in the folder that they are looking at
+ updateAction = REFRESH;
+}
+
+void IEC_Commands::RMDir(void)
+{
+ DIR dir;
+ FILINFO filInfo;
+ FRESULT res;
+ char filename[256];
+ Channel& channel = channels[15];
+
+ const char* text = (char*)channel.buffer;
+
+ text = ParseNextName(text, filename, true);
+ if (filename[0])
+ {
+ res = f_findfirst(&dir, &filInfo, ".", (const TCHAR*)filename);
+ if (res == FR_OK)
+ {
+ if (filInfo.fname[0] != 0 && IsDirectory(filInfo))
+ {
+ DEBUG_LOG("rmdir %s\r\n", filInfo.fname);
+ f_unlink(filInfo.fname);
+ updateAction = REFRESH;
+ }
+ }
+ else
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ }
+ else
+ {
+ Error(ERROR_34_SYNTAX_ERROR);
+ }
+}
+
+void IEC_Commands::FolderCommand(void)
+{
+ Channel& channel = channels[15];
+
+ switch (toupper(channel.buffer[0]))
+ {
+ case 'M':
+ {
+ char* in = (char*)channel.buffer;
+ int part;
+
+ part = ParsePartition(&in);
+ if (part > 0)
+ {
+ // Only have one drive partition
+ //Error(ERROR_74_DRlVE_NOT_READY);
+ return;
+ }
+ in += 2; // Skip command
+ if (*in == ':')
+ in++;
+ MKDir(part, in);
+ }
+ break;
+ case 'C':
+ {
+ char* in = (char*)channel.buffer;
+ int part;
+
+ part = ParsePartition(&in);
+ if (part > 0)
+ {
+ // Only have one drive partition
+ //Error(ERROR_74_DRlVE_NOT_READY);
+ return;
+ }
+ in += 2; // Skip command
+ if (*in == ':')
+ in++;
+ CD(part, in);
+ }
+ break;
+ case 'R':
+ RMDir();
+ break;
+ default:
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+ }
+}
+
+void IEC_Commands::Copy(void)
+{
+ //COPY:newfile = oldfile1, oldfile2,...
+
+ // Only named data records can be combined.
+
+ // TODO: checkfor wildcards and set the error if found.
+ char filenameNew[256];
+ char filenameToCopy[256];
+ Channel& channel = channels[15];
+
+ FILINFO filInfo;
+ FRESULT res;
+ const char* text = (char*)channel.buffer;
+
+ text = ParseNextName(text, filenameNew, true);
+
+ //DEBUG_LOG("Copy %s\r\n", filenameNew);
+ if (filenameNew[0] != 0)
+ {
+ res = f_stat(filenameNew, &filInfo);
+ if (res == FR_NO_FILE)
+ {
+ int fileCount = 0;
+ do
+ {
+ text = ParseNextName(text, filenameToCopy, true);
+ if (filenameToCopy[0] != 0)
+ {
+ //DEBUG_LOG("Copy source %s\r\n", filenameToCopy);
+ res = f_stat(filenameToCopy, &filInfo);
+ if (res == FR_OK)
+ {
+ if (!IsDirectory(filInfo))
+ {
+ //DEBUG_LOG("copying %s to %s\r\n", filenameToCopy, filenameNew);
+ if (CopyFile(filenameNew, filenameToCopy, fileCount != 0)) updateAction = REFRESH;
+ else Error(ERROR_25_WRITE_ERROR);
+ }
+ }
+ else
+ {
+ // If you want to copy the entire folder then implement that here.
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ }
+ fileCount++;
+ } while (filenameToCopy[0] != 0);
+ }
+ else
+ {
+ DEBUG_LOG("Copy file exists\r\n");
+ Error(ERROR_63_FILE_EXISTS);
+ }
+ }
+ else
+ {
+ Error(ERROR_34_SYNTAX_ERROR);
+ }
+}
+void IEC_Commands::Memory(void)
+{
+ Channel& channel = channels[15];
+ char* text = (char*)channel.buffer;
+ u16 address;
+ int length;
+ u8 bytes = 1;
+ u8* ptr;
+
+ if (channel.cursor > 2)
+ {
+ char code = toupper(channel.buffer[2]);
+ if (code == 'R' || code == 'W' || code == 'E')
+ {
+ ptr = (u8*)strchr(text, ':');
+ if (ptr == 0) ptr = (u8*)&channel.buffer[3];
+ else ptr++;
+
+ length = channel.cursor - 3;
+
+ address = (u16)((u8)(ptr[1]) << 8) | (u16)ptr[0];
+ if (length > 2)
+ {
+ bytes = ptr[2];
+ if (bytes == 0)
+ bytes = 1;
+ }
+
+ switch (code)
+ {
+ case 'R':
+ DEBUG_LOG("M-R %04x %d\r\n", address, bytes);
+ break;
+ case 'W':
+ DEBUG_LOG("M-W %04x %d\r\n", address, bytes);
+ break;
+ case 'E':
+ // Memory execute impossible at this level of emulation!
+ DEBUG_LOG("M-E %04x\r\n", address);
+ break;
+ }
+ }
+ else
+ {
+ Error(ERROR_31_SYNTAX_ERROR);
+ }
+ }
+}
+
+void IEC_Commands::New(void)
+{
+ Channel& channel = channels[15];
+ char filenameNew[256];
+ char ID[256];
+
+ if (ParseFilenames((char*)channel.buffer, filenameNew, ID))
+ {
+ FILINFO filInfo;
+ FRESULT res;
+ char* ptr;
+ int i;
+ //bool g64 = false;
+
+ //if (strstr(filenameNew, ".g64") || strstr(filenameNew, ".G64"))
+ // g64 = true;
+ //else
+ if(!(strstr(filenameNew, ".d64") || strstr(filenameNew, ".D64")))
+ strcat(filenameNew, ".d64");
+
+ res = f_stat(filenameNew, &filInfo);
+ if (res == FR_NO_FILE)
+ {
+ FIL fpOut;
+ res = f_open(&fpOut, filenameNew, FA_CREATE_ALWAYS | FA_WRITE);
+ if (res == FR_OK)
+ {
+ char buffer[256];
+ u32 bytes;
+ u32 blocks;
+
+ memset(buffer, 0, sizeof(buffer));
+ // TODO: Should check for disk full.
+ for (blocks = 0; blocks < 357; ++blocks)
+ {
+ if (f_write(&fpOut, buffer, 256, &bytes) != FR_OK)
+ break;
+ }
+ ptr = (char*)&blankD64DIRBAM[DISKNAME_OFFSET_IN_DIR_BLOCK];
+ int len = strlen(filenameNew);
+ for (i = 0; i < len; ++i)
+ {
+ *ptr++ = ascii2petscii(filenameNew[i]);
+ }
+ for (; i < 18; ++i)
+ {
+ *ptr++ = 0xa0;
+ }
+ for (i = 0; i < 2; ++i)
+ {
+ *ptr++ = ascii2petscii(ID[i]);
+ }
+ f_write(&fpOut, blankD64DIRBAM, 256, &bytes);
+ buffer[1] = 0xff;
+ f_write(&fpOut, buffer, 256, &bytes);
+ buffer[1] = 0;
+ for (blocks = 0; blocks < 324; ++blocks)
+ {
+ if (f_write(&fpOut, buffer, 256, &bytes) != FR_OK)
+ break;
+ }
+ f_close(&fpOut);
+ // Mount the new disk? Shoud we do this or let them do it manually?
+ if (f_stat(filenameNew, &filInfo) == FR_OK)
+ {
+ DIR dir;
+ Enter(dir, filInfo);
+ }
+ }
+ }
+ else
+ {
+ Error(ERROR_63_FILE_EXISTS);
+ }
+ }
+}
+
+void IEC_Commands::Rename(void)
+{
+ // RENAME:newname=oldname
+
+ // Note: 1541 ROM will not allow you to rename a file until it is closed.
+
+ Channel& channel = channels[15];
+ char filenameNew[256];
+ char filenameOld[256];
+
+ if (ParseFilenames((char*)channel.buffer, filenameNew, filenameOld))
+ {
+ FRESULT res;
+ FILINFO filInfo;
+
+ res = f_stat(filenameNew, &filInfo);
+ if (res == FR_NO_FILE)
+ {
+ res = f_stat(filenameOld, &filInfo);
+ if (res == FR_OK)
+ {
+ // Rename folders too.
+ //DEBUG_LOG("Renaming %s to %s\r\n", filenameOld, filenameNew);
+ f_rename(filenameOld, filenameNew);
+ }
+ else
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ }
+ else
+ {
+ Error(ERROR_63_FILE_EXISTS);
+ }
+ }
+}
+
+void IEC_Commands::Scratch(void)
+{
+ // SCRATCH: filename1, filename2...
+
+ // More than one file can be deleted by using a single command.
+ // But remember that only 40 characters at a time can be sent
+ // over the transmission channel to the disk drive.
+
+ // wildcard characters can be used
+
+ Channel& channel = channels[15];
+ DIR dir;
+ FILINFO filInfo;
+ FRESULT res;
+ char filename[256];
+ const char* text = (const char*)channel.buffer;
+
+ text = ParseNextName(text, filename, true);
+ while (filename[0])
+ {
+ res = f_findfirst(&dir, &filInfo, ".", (const TCHAR*)filename);
+ while (res == FR_OK && filInfo.fname[0])
+ {
+ if (filInfo.fname[0] != 0 && !IsDirectory(filInfo))
+ {
+ //DEBUG_LOG("Scratching %s\r\n", filInfo.fname);
+ f_unlink(filInfo.fname);
+ }
+ res = f_findnext(&dir, &filInfo);
+ }
+ text = ParseNextName(text, filename, true);
+ }
+}
+
+void IEC_Commands::User(void)
+{
+ Channel& channel = channels[15];
+
+ //DEBUG_LOG("User channel.buffer[1] = %c\r\n", channel.buffer[1]);
+
+ switch (toupper(channel.buffer[1]))
+ {
+ case 'A':
+ case 'B':
+ case '1': // Direct access block read (Jumps via FFEA to B-R function)
+ case '2': // Direct access block write (Jumps via FFEC to B-W function)
+ // Direct acces is unsupported. Without a mounted disk image tracks and sectors have no meaning.
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+
+ // U3/C - U8/H meaningless at this level of emulation!
+
+ // U9 (UI)
+ case 'I':
+ case '9':
+ //DEBUG_LOG("ui c=%d\r\n", channel.cursor);
+ if (channel.cursor == 2)
+ {
+ // Soft reset
+ Error(ERROR_73_DOSVERSION);
+ return;
+ }
+ switch (channel.buffer[2])
+ {
+ case '+':
+ usingVIC20 = true;
+ break;
+ case '-':
+ usingVIC20 = false;
+ break;
+ default:
+ Error(ERROR_73_DOSVERSION);
+ break;
+ }
+ break;
+
+ case 'J':
+ case ':':
+ case 202:
+ // Hard reset
+ Error(ERROR_73_DOSVERSION);
+ break;
+ case '0':
+ //OPEN1,8,15,"U0>"+CHR$(9):CLOSE1
+ if ((channel.buffer[2] & 0x1f) == 0x1e && channel.buffer[3] >= 4 && channel.buffer[3] <= 30)
+ {
+ SetDeviceId(channel.buffer[3]);
+ updateAction = DEVICEID_CHANGED;
+ DEBUG_LOG("Changed deviceID to %d\r\n", channel.buffer[3]);
+ }
+ else
+ {
+ Error(ERROR_31_SYNTAX_ERROR);
+ }
+ break;
+ default:
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+ }
+}
+
+void IEC_Commands::ProcessCommand(void)
+{
+ Error(ERROR_00_OK);
+
+ Channel& channel = channels[15];
+
+ //DEBUG_LOG("CMD %s %d\r\n", channel.buffer, channel.cursor);
+
+ if (channel.cursor > 0 && channel.buffer[channel.cursor - 1] == 0x0d)
+ channel.cursor--;
+
+ if (channel.cursor == 0)
+ {
+ Error(ERROR_30_SYNTAX_ERROR);
+ }
+ else
+ {
+ //DEBUG_LOG("ProcessCommand %s", channel.buffer);
+
+ if (toupper(channel.buffer[0]) != 'X' && toupper(channel.buffer[1]) == 'D')
+ {
+ FolderCommand();
+ return;
+ }
+
+ switch (toupper(channel.buffer[0]))
+ {
+ case 'B':
+ // B-P not implemented
+ // B-A allocate bit in BAM not implemented
+ // B-F free bit in BAM not implemented
+ // B-E block execute impossible at this level of emulation!
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+ case 'C':
+ if (channel.buffer[1] == 'P')
+ Error(ERROR_31_SYNTAX_ERROR); // Change Partition not implemented yet
+ else
+ Copy();
+ break;
+ case 'D':
+ Error(ERROR_31_SYNTAX_ERROR); // DI, DR, DW not implemented yet
+ break;
+ case 'G':
+ Error(ERROR_31_SYNTAX_ERROR); // G-P not implemented yet
+ break;
+ case 'I':
+ // Initialise
+ break;
+ case 'M':
+ Memory();
+ break;
+ case 'N':
+ New();
+ break;
+ case 'P':
+ Error(ERROR_31_SYNTAX_ERROR); // P not implemented yet
+ break;
+ case 'R':
+ Rename();
+ break;
+ case 'S':
+ if (channel.buffer[1] == '-')
+ {
+ // Swap drive number
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+ }
+ Scratch();
+ break;
+ case 'T':
+ // RTC support
+ Error(ERROR_31_SYNTAX_ERROR); // T-R and T-W not implemented yet
+ break;
+ case 'U':
+ User();
+ break;
+ case 'V':
+ break;
+ case 'W':
+ // Save out current options?
+ //OPEN1, 9, 15, "XW":CLOSE1
+ break;
+ case 'X':
+ // Extended commands not implemented yet
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+ default:
+ Error(ERROR_31_SYNTAX_ERROR);
+ break;
+ }
+ }
+}
+
+void IEC_Commands::Listen()
+{
+ u8 byte;
+
+ if ((commandCode & 0x0f) == 0x0f || (commandCode & 0xf0) == 0xf0)
+ {
+ Channel& channel = channels[15];
+ channel.Close();
+
+ while (!ReadIECSerialPort(byte))
+ {
+ if (!channel.WriteFull())
+ {
+ channel.buffer[channel.cursor++] = byte;
+ }
+ if (receivedEOI)
+ receivedCommand = true;
+ }
+
+ if (channel.cursor > 1)
+ {
+ // Strip any CRs from the command
+ if (channel.buffer[channel.cursor - 1] == 0x0d) channel.cursor -= 1;
+ else if (channel.buffer[channel.cursor - 2] == 0x0d) channel.cursor -= 2;
+ }
+
+ // TODO: Should not do this - should use command length to test for the end of a command
+ if (!channel.WriteFull())
+ channel.buffer[channel.cursor++] = 0;
+ }
+ else
+ {
+ OpenFile();
+ SaveFile();
+ }
+}
+
+void IEC_Commands::Talk()
+{
+ if (commandCode == 0x6f)
+ {
+ SendError();
+ }
+ else
+ {
+ Channel& channelCommand = channels[15];
+ //DEBUG_LOG("cmd = %s\r\n", channelCommand.buffer);
+
+ if (channelCommand.buffer[0] == '$')
+ {
+ LoadDirectory();
+ }
+ else
+ {
+ OpenFile();
+ LoadFile();
+ }
+ }
+}
+
+bool IEC_Commands::FindFirst(DIR& dir, const char* matchstr, FILINFO& filInfo)
+{
+ char pattern[256];
+ FRESULT res;
+
+ // CBM-FileBrowser can only determine if it is a disk image if the extention is in the name.
+ // So for files that are too long we stomp the last 4 characters with the image extention and pattern match it.
+ // This basically changes a file name from something like
+ // SOMELONGDISKIMAGENAME.D64 to SOMELONGDISKIMAGENAME*.D64
+ // so the actual SOMELONGDISKIMAGENAMETHATISWAYTOOLONGFORCBMFILEBROWSERTODISPLAY.D64 will be found.
+ strcpy(pattern, matchstr);
+ char* ext = strrchr(matchstr, '.');
+ if (ext)
+ {
+ char* ptr = strrchr(pattern, '.');
+ *ptr++ = '*';
+ for (int i = 0; i < 4; i++)
+ {
+ *ptr++ = *ext++;
+ }
+ *ptr = 0;
+ }
+ else
+ {
+ // For folders we do the same except we need to change the last character to a *
+ int len = strlen(matchstr);
+ if (len >= CBM_NAME_LENGTH)
+ pattern[CBM_NAME_LENGTH - 1] = '*';
+ }
+ //DEBUG_LOG("Pattern %s -> %s\r\n", matchstr, pattern);
+ res = f_findfirst(&dir, &filInfo, ".", (const TCHAR*)pattern);
+ //DEBUG_LOG("found file %s\r\n", filInfo.fname);
+ if (res != FR_OK || filInfo.fname[0] == 0)
+ {
+ //Error(ERROR_62_FILE_NOT_FOUND);
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+bool IEC_Commands::SendBuffer(Channel& channel, bool eoi)
+{
+ for (u32 i = 0; i < channel.cursor; ++i)
+ {
+ u8 finalbyte = eoi && (channel.bytesSent == (channel.filInfo.fsize - 1));
+ if (WriteIECSerialPort(channel.buffer[i], finalbyte))
+ {
+ return true;
+ }
+ channel.bytesSent++;
+ }
+ channel.cursor = 0;
+ return false;
+}
+
+void IEC_Commands::LoadFile()
+{
+ Channel& channel = channels[secondaryAddress];
+
+ //DEBUG_LOG("LoadFile %s %s\r\n", channel.buffer, channel.filInfo.fname);
+
+ if (channel.filInfo.fname[0] != 0)
+ {
+ u32 bytesRead;
+ do
+ {
+ f_read(&channel.file, channel.buffer, sizeof(channel.buffer), &bytesRead);
+ if (bytesRead > 0)
+ {
+ channel.cursor = bytesRead;
+ if (SendBuffer(channel, true))
+ return;
+ }
+ }
+ while (bytesRead > 0);
+ }
+ else
+ {
+ //DEBUG_LOG("Can't find %s", channel.buffer);
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+}
+
+void IEC_Commands::SaveFile()
+{
+ u32 bytesWritten;
+ u8 byte;
+
+ Channel& channel = channels[secondaryAddress];
+ if (channel.open && channel.writing)
+ {
+ while (!ReadIECSerialPort(byte))
+ {
+ channel.buffer[channel.cursor++] = byte;
+ if (channel.WriteFull())
+ {
+ if (f_write(&channel.file, channel.buffer, sizeof(channel.buffer), &bytesWritten) != FR_OK)
+ {
+ }
+ channel.cursor = 0;
+ }
+ }
+ }
+}
+
+void IEC_Commands::SendError()
+{
+ int len = strlen(ErrorMessage);
+ int index = 0;
+ bool finalByte;
+ do
+ {
+ finalByte = index == len;
+ if (WriteIECSerialPort(ErrorMessage[index++], finalByte))
+ break;
+ }
+ while (!finalByte);
+}
+
+void IEC_Commands::AddDirectoryEntry(Channel& channel, const char* name, u16 blocks, int fileType)
+{
+ u8* data = channel.buffer + channel.cursor;
+ const u32 dirEntryLength = DIRECTORY_ENTRY_SIZE;
+ int i = 0;
+ int index = 0;
+ bool diskImage = DiskImage::IsDiskImageExtention(name);
+
+ //DEBUG_LOG("name = %s blocks = %d", name, blocks);
+
+ memset(data, ' ', dirEntryLength);
+ data[dirEntryLength - 1] = 0;
+
+ data[index++] = 0x01;
+ data[index++] = 0x01;
+ data[index++] = blocks & 0xff;
+ data[index++] = blocks >> 8;
+
+ if (blocks < 1000)
+ index++;
+ if (blocks < 100)
+ index++;
+ if (blocks < 10)
+ index++;
+
+ data[index++] = '"';
+
+ // CBM-FileBrowser can only determine if it is a disk image if the extention is in the name.
+ // So for files that are too long we stomp the last 4 characters with the image extention and pattern match it.
+ // This basically changes a file name from something like
+ // SOMELONGDISKIMAGENAME.D64 to SOMELONGDISKIMAGENAME*.D64
+ // so the actual SOMELONGDISKIMAGENAMETHATISWAYTOOLONGFORCBMFILEBROWSERTODISPLAY.D64 will be found.
+ if (strlen(name) > CBM_NAME_LENGTH && diskImage)
+ {
+ const char* extName = strrchr(name, '.');
+
+ do
+ {
+ data[index + i++] = ascii2petscii(*name++);
+ }
+ while (!(*name == 0x22 || *name == 0 || i == CBM_NAME_LENGTH_MINUS_D64));
+
+ for (int extIndex = 0; extIndex < 4; ++extIndex)
+ {
+ data[index + i++] = ascii2petscii(*extName++);
+ }
+ }
+ else
+ {
+ do
+ {
+ data[index + i++] = ascii2petscii(*name++);
+ }
+ while (!(*name == 0x22 || *name == 0 || i == CBM_NAME_LENGTH));
+ }
+ data[index + i] = '"';
+ index++;
+ index += CBM_NAME_LENGTH;
+ index++;
+
+ for (i = 0; i < 3; ++i)
+ {
+ data[index++] = filetypes[fileType * 3 + i];
+ }
+ channel.cursor += dirEntryLength;
+}
+
+void IEC_Commands::LoadDirectory()
+{
+ DIR dir;
+ FILINFO filInfo;
+ char* ext;
+
+ Channel& channel = channels[0];
+
+ memcpy(channel.buffer, DirectoryHeader, sizeof(DirectoryHeader));
+ channel.cursor = sizeof(DirectoryHeader);
+
+ FRESULT res;
+ res = f_opendir(&dir, ".");
+ if (res == FR_OK)
+ {
+ do
+ {
+ res = f_readdir(&dir, &filInfo);
+ ext = strrchr(filInfo.fname, '.');
+
+ if (res == FR_OK && filInfo.fname[0] != 0 && !(ext && strcasecmp(ext, ".png") == 0))
+ {
+ const char* fileName = filInfo.fname;
+ //DEBUG_LOG("%s", fileName);
+
+ if (!channel.CanFit(DIRECTORY_ENTRY_SIZE))
+ SendBuffer(channel, false);
+
+ if (filInfo.fattrib & AM_DIR) AddDirectoryEntry(channel, fileName, 0, 6);
+ else AddDirectoryEntry(channel, fileName, filInfo.fsize / 256 + 1, 2);
+ }
+ }
+ while (res == FR_OK && filInfo.fname[0] != 0);
+ f_closedir(&dir);
+ }
+ SendBuffer(channel, false);
+
+ memcpy(channel.buffer, DirectoryBlocksFree, sizeof(DirectoryBlocksFree));
+
+ FATFS* fs;
+ DWORD fre_clust, fre_sect, free_blocks;
+ res = f_getfree("", &fre_clust, &fs);
+ if (res == FR_OK)
+ {
+ fre_sect = fre_clust * fs->csize;
+ free_blocks = fre_sect << 1;
+
+ if (free_blocks > 0x10000)
+ {
+ channel.buffer[2] = 0xff;
+ channel.buffer[3] = 0xff;
+ }
+ else
+ {
+ channel.buffer[2] = free_blocks & 0xff;
+ channel.buffer[3] = (free_blocks >> 8) & 0xff;;
+ }
+ }
+ else
+ {
+ channel.buffer[2] = 0;
+ channel.buffer[3] = 0;
+ }
+ channel.cursor = sizeof(DirectoryBlocksFree);
+
+ channel.filInfo.fsize = channel.bytesSent + channel.cursor;
+ SendBuffer(channel, true);
+}
+
+void IEC_Commands::OpenFile()
+{
+ // OPEN lfn,id,sa,"filename,filetype,mode"
+ // lfn
+ // When the logical file number(lfn) is between 1 and 127, a PRINT# statement sends a RETURN character to the file after each variable.
+ // If the logical file number is greater than 127 (128 - 255), the PRINT# statement sends an additional linefeed after each RETURN.
+ // sa
+ // Should several files be open at once, they must all use different secondary addresses, as only one file can use a channel.
+ // mode
+ // The last parameter(mode) establishes how the channel will used. There are four possibilities:
+ // W Write a file
+ // R Read a file
+ // A Add to a sequential file
+ // M read a file that has not been closed
+
+ // If a file that already exists is to be to opened for writing, the file must first be deleted.
+
+ // The file type must be given when the file is opened. The file type may be shortened to one of following:
+ // S - sequential file
+ // U - user file
+ // P - program
+ // R - relative file
+ u8 secondary = secondaryAddress;
+ Channel& channel = channels[secondary];
+ if (channel.command[0] == '#')
+ {
+ // Direct acces is unsupported. Without a mounted disk image tracks and sectors have no meaning.
+ //DEBUG_LOG("Driect access\r\n");
+ }
+ else if (channel.command[0] == '$')
+ {
+ }
+ else
+ {
+ if (!channel.open)
+ {
+ bool found = false;
+ DIR dir;
+ FRESULT res;
+ const char* text;
+ char filename[256];
+ char filetype[8];
+ char filemode[8];
+ bool needFileToExist = true;
+ bool writing = false;
+ u8 mode = FA_READ;
+
+ filetype[0] = 0;
+ filemode[0] = 0;
+
+ if (secondary == 1)
+ strcat(filemode, "W");
+
+ char* in = (char*)channel.command;
+ int part = ParsePartition(&in);
+ if (part > 0)
+ {
+ // Only have one drive partition
+ //Error(ERROR_74_DRlVE_NOT_READY);
+ return;
+ }
+ if (*in == ':')
+ in++;
+
+ text = ParseName((char*)in, filename, true, true);
+ if (text)
+ {
+ text = ParseNextName(text, filetype, true);
+ if (text)
+ text = ParseNextName(text, filemode, true);
+ }
+
+ if (toupper(filetype[0]) == 'L')
+ {
+ //DEBUG_LOG("Rel file\r\n");
+ return;
+ }
+ else
+ {
+ switch (toupper(filemode[0]))
+ {
+ case 'W':
+ needFileToExist = false;
+ writing = true;
+ mode = FA_CREATE_ALWAYS | FA_WRITE;
+ break;
+ case 'A':
+ needFileToExist = true;
+ writing = true;
+ mode = FA_OPEN_APPEND | FA_WRITE;
+ break;
+ case 'R':
+ needFileToExist = true;
+ writing = false;
+ mode = FA_READ;
+ break;
+ }
+ }
+
+ channel.writing = writing;
+
+ //DEBUG_LOG("OpenFile %s %d NE=%d T=%c M=%c W=%d %0x\r\n", filename, secondary, needFileToExist, filetype[0], filemode[0], writing, mode);
+
+ if (needFileToExist)
+ {
+ if (FindFirst(dir, filename, channel.filInfo))
+ {
+ //DEBUG_LOG("found\r\n");
+ res = FR_OK;
+ while ((channel.filInfo.fattrib & AM_DIR) == AM_DIR)
+ {
+ res = f_findnext(&dir, &channel.filInfo);
+ }
+
+ if (res == FR_OK && channel.filInfo.fname[0] != 0)
+ {
+ found = true;
+ res = f_open(&channel.file, channel.filInfo.fname, mode);
+ if (res == FR_OK)
+ channel.open = true;
+ //DEBUG_LOG("Opened existing size = %d\r\n", (int)channel.filInfo.fsize);
+ }
+ }
+ else
+ {
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+
+ if (!found)
+ {
+ DEBUG_LOG("Can't find %s", filename);
+ Error(ERROR_62_FILE_NOT_FOUND);
+ }
+ }
+ else
+ {
+ res = f_open(&channel.file, filename, mode);
+ if (res == FR_OK)
+ {
+ channel.open = true;
+ channel.cursor = 0;
+ //DEBUG_LOG("Opened new sa=%d m=%0x\r\n", secondary, mode);
+ res = f_stat(filename, &channel.filInfo);
+ }
+ else
+ {
+ //DEBUG_LOG("Open failed %d\r\n", res);
+ }
+ }
+ }
+ else
+ {
+ //DEBUG_LOG("Channel aready opened %d\r\n", channel.cursor);
+ }
+ }
+}
+
+void IEC_Commands::CloseFile(u8 secondary)
+{
+ Channel& channel = channels[secondary];
+
+ channel.Close();
+}
diff --git a/iec_commands.h b/iec_commands.h
new file mode 100644
index 0000000..e441a6b
--- /dev/null
+++ b/iec_commands.h
@@ -0,0 +1,168 @@
+// Pi1541 - A Commodore 1541 disk drive emulator
+// Copyright(C) 2018 Stephen White
+//
+// This file is part of Pi1541.
+//
+// Pi1541 is free software : you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Pi1541 is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Pi1541. If not, see .
+
+#ifndef IEC_COMMANDS_H
+#define IEC_COMMANDS_H
+
+#include "iec_bus.h"
+#include "ff.h"
+#include "debug.h"
+
+struct TimerMicroSeconds
+{
+ TimerMicroSeconds()
+ {
+ count = 0;
+ timeout = 0;
+ }
+
+ void Start(u32 amount)
+ {
+ count = 0;
+ timeout = amount;
+ }
+
+ inline bool TimedOut() { return count >= timeout; }
+
+ bool Tick()
+ {
+ delay_us(1);
+ count++;
+ return TimedOut();
+ }
+
+ u32 count;
+ u32 timeout;
+};
+
+class IEC_Commands
+{
+public:
+ enum UpdateAction
+ {
+ NONE,
+ IMAGE_SELECTED,
+ DIR_PUSHED,
+ POP_DIR,
+ POP_TO_ROOT,
+ REFRESH,
+ DEVICEID_CHANGED,
+ RESET
+ };
+
+ IEC_Commands();
+ void Initialise();
+
+ void SetDeviceId(u8 id) { deviceID = id; }
+ u8 GetDeviceId() { return deviceID; }
+
+ void Reset(void);
+ void SimulateIECBegin(void);
+ UpdateAction SimulateIECUpdate(void);
+
+ const char* GetNameOfImageSelected() const { return selectedImageName; }
+ const FILINFO* GetImageSelected() const { return &filInfoSelectedImage; }
+protected:
+ enum ATNSequence
+ {
+ ATN_SEQUENCE_IDLE,
+ ATN_SEQUENCE_ATN,
+ ATN_SEQUENCE_RECEIVE_COMMAND_CODE,
+ ATN_SEQUENCE_HANDLE_COMMAND_CODE,
+ ATN_SEQUENCE_COMPLETE
+ };
+
+ enum DeviceRole
+ {
+ DEVICE_ROLE_PASSIVE,
+ DEVICE_ROLE_LISTEN,
+ DEVICE_ROLE_TALK
+ };
+
+ struct Channel
+ {
+ FILINFO filInfo;
+ FIL file;
+ bool writing;
+ u32 cursor;
+ u32 bytesSent;
+ bool open;
+
+ u8 buffer[0x1000];
+ u8 command[0x100];
+
+ void Close();
+ bool WriteFull() const { return cursor >= sizeof(buffer); }
+ bool CanFit(u32 bytes) const { return bytes <= sizeof(buffer) - cursor; }
+ };
+
+ bool CheckATN(void);
+ bool WriteIECSerialPort(u8 data, bool eoi);
+ bool ReadIECSerialPort(u8& byte);
+
+ void Listen();
+ void Talk();
+ void LoadFile();
+ void SaveFile();
+
+ void AddDirectoryEntry(Channel& channel, const char* name, u16 blocks, int fileType);
+ void LoadDirectory();
+ void OpenFile();
+ void CloseFile(u8 secondary);
+ void CloseAllChannels();
+ void SendError();
+
+ bool Enter(DIR& dir, FILINFO& filInfo);
+ bool FindFirst(DIR& dir, const char* matchstr, FILINFO& filInfo);
+
+ void FolderCommand(void);
+ void CD(int partition, char* filename);
+ void MKDir(int partition, char* filename);
+ void RMDir(void);
+
+ void Copy(void);
+ void New(void);
+ void Rename(void);
+ void Scratch(void);
+
+ void Memory(void);
+ void User(void);
+
+ void ProcessCommand(void);
+
+ bool SendBuffer(Channel& channel, bool eoi);
+
+ UpdateAction updateAction;
+ u8 commandCode;
+ bool receivedCommand : 1;
+ bool receivedEOI : 1; // End Or Identify
+ bool usingVIC20 : 1; // When sending data we need to wait longer for the 64 as its VICII may be stealing its cycles. VIC20 does not have this problem and can accept data faster.
+
+ u8 deviceID;
+ u8 secondaryAddress;
+ ATNSequence atnSequence;
+ DeviceRole deviceRole;
+
+ TimerMicroSeconds timer;
+
+ Channel channels[16];
+
+ char selectedImageName[256];
+ FILINFO filInfoSelectedImage;
+};
+#endif
\ No newline at end of file
diff --git a/integer.h b/integer.h
new file mode 100644
index 0000000..bd89f37
--- /dev/null
+++ b/integer.h
@@ -0,0 +1,40 @@
+/*-------------------------------------------*/
+/* Integer type definitions for FatFs module */
+/*-------------------------------------------*/
+
+#ifndef _FF_INTEGER
+#define _FF_INTEGER
+
+#ifdef _WIN32 /* FatFs development platform */
+
+#include
+#include
+typedef unsigned __int64 QWORD;
+
+#else /* Embedded platform */
+
+/* These types MUST be 16-bit or 32-bit */
+typedef int INT;
+typedef unsigned int UINT;
+
+/* This type MUST be 8-bit */
+typedef unsigned char BYTE;
+
+/* These types MUST be 16 bit */
+typedef short SHORT;
+typedef unsigned short WORD;
+typedef unsigned short WCHAR;
+
+/* These types MUST be 32-bit */
+typedef long LONG;
+typedef unsigned long DWORD;
+
+/* This type MUST be 64-bit (Remove this for C89 compatibility) */
+typedef unsigned long long QWORD;
+
+#endif
+
+//#include
+#include
+
+#endif
diff --git a/interrupt.c b/interrupt.c
new file mode 100644
index 0000000..0b33802
--- /dev/null
+++ b/interrupt.c
@@ -0,0 +1,159 @@
+#include "interrupt.h"
+#include "rpiHardware.h"
+#include "bcm2835int.h"
+
+#define ARM_IC_IRQ_PENDING(irq) ( (irq) < ARM_IRQ2_BASE \
+ ? ARM_IC_IRQ_PENDING_1 \
+ : ((irq) < ARM_IRQBASIC_BASE \
+ ? ARM_IC_IRQ_PENDING_2 \
+ : ARM_IC_IRQ_BASIC_PENDING))
+#define ARM_IC_IRQS_ENABLE(irq) ( (irq) < ARM_IRQ2_BASE \
+ ? ARM_IC_ENABLE_IRQS_1 \
+ : ((irq) < ARM_IRQBASIC_BASE \
+ ? ARM_IC_ENABLE_IRQS_2 \
+ : ARM_IC_ENABLE_BASIC_IRQS))
+#define ARM_IC_IRQS_DISABLE(irq) ( (irq) < ARM_IRQ2_BASE \
+ ? ARM_IC_DISABLE_IRQS_1 \
+ : ((irq) < ARM_IRQBASIC_BASE \
+ ? ARM_IC_DISABLE_IRQS_2 \
+ : ARM_IC_DISABLE_BASIC_IRQS))
+#define ARM_IRQ_MASK(irq) (1 << ((irq) & (ARM_IRQS_PER_REG-1)))
+
+static IRQHandler* IRQHandlers[IRQ_LINES] = { 0 };
+static void* Params[IRQ_LINES] = { 0 };
+
+void InterruptSystemInitialize()
+{
+ InstructionSyncBarrier();
+
+ DataMemBarrier();
+
+ write32(ARM_IC_FIQ_CONTROL, 0);
+
+ write32(ARM_IC_DISABLE_IRQS_1, (u32)-1);
+ write32(ARM_IC_DISABLE_IRQS_2, (u32)-1);
+ write32(ARM_IC_DISABLE_BASIC_IRQS, (u32)-1);
+
+ // Ack pending IRQs
+ write32(ARM_IC_IRQ_BASIC_PENDING, read32(ARM_IC_IRQ_BASIC_PENDING));
+ write32(ARM_IC_IRQ_PENDING_1, read32(ARM_IC_IRQ_PENDING_1));
+ write32(ARM_IC_IRQ_PENDING_2, read32(ARM_IC_IRQ_PENDING_2));
+
+ DataMemBarrier();
+
+ EnableInterrupts();
+}
+
+void InterruptSystemConnectIRQ(unsigned IRQIndex, IRQHandler* handler, void* param)
+{
+ IRQHandlers[IRQIndex] = handler;
+ Params[IRQIndex] = param;
+
+ InterruptSystemEnableIRQ(IRQIndex);
+}
+
+void InterruptSystemDisconnectIRQ(unsigned IRQIndex)
+{
+ InterruptSystemDisableIRQ(IRQIndex);
+
+ IRQHandlers[IRQIndex] = 0;
+ Params[IRQIndex] = 0;
+}
+
+void InterruptSystemEnableIRQ(unsigned IRQIndex)
+{
+ //DEBUG_LOG("InterruptSystemEnableIRQ %d\r\n", IRQIndex);
+ DataMemBarrier();
+ write32(ARM_IC_IRQS_ENABLE(IRQIndex), ARM_IRQ_MASK(IRQIndex));
+ DataMemBarrier();
+}
+
+void InterruptSystemDisableIRQ(unsigned IRQIndex)
+{
+ DataMemBarrier();
+ write32 (ARM_IC_IRQS_DISABLE(IRQIndex), ARM_IRQ_MASK(IRQIndex));
+ DataMemBarrier();
+}
+
+void InterruptHandler(void)
+{
+// DEBUG_LOG("InterruptHandler\r\n");
+
+ DataMemBarrier();
+
+ //(irq) < ARM_IRQ2_BASE ? ARM_IC_IRQ_PENDING_1 : ((irq) < ARM_IRQBASIC_BASE ? ARM_IC_IRQ_PENDING_2 : ARM_IC_IRQ_BASIC_PENDING
+
+ //for (unsigned IRQIndex = 0; IRQIndex < IRQ_LINES; ++IRQIndex)
+ //{
+ // u32 nPendReg = ARM_IC_IRQ_PENDING(IRQIndex);
+ // u32 IRQIndexMask = ARM_IRQ_MASK(IRQIndex);
+ //
+ // if (read32(nPendReg) & IRQIndexMask)
+ // {
+ // IRQHandler* pHandler = IRQHandlers[IRQIndex];
+
+ // if (pHandler != 0)
+ // (*pHandler)(Params[IRQIndex]);
+ // else
+ // InterruptSystemDisableIRQ(IRQIndex);
+ // }
+ //}
+
+ unsigned IRQIndex;
+ u32 nPendReg;
+ u32 pendValue;
+
+ nPendReg = ARM_IC_IRQ_PENDING_1;
+ pendValue = read32(nPendReg);
+ for (IRQIndex = 0; IRQIndex < ARM_IRQ2_BASE; ++IRQIndex)
+ {
+ u32 IRQIndexMask = ARM_IRQ_MASK(IRQIndex);
+
+ if (pendValue & IRQIndexMask)
+ {
+ IRQHandler* pHandler = IRQHandlers[IRQIndex];
+
+ if (pHandler != 0)
+ (*pHandler)(Params[IRQIndex]);
+ else
+ InterruptSystemDisableIRQ(IRQIndex);
+ }
+ }
+
+ nPendReg = ARM_IC_IRQ_PENDING_2;
+ pendValue = read32(nPendReg);
+ for (;IRQIndex < ARM_IRQBASIC_BASE; ++IRQIndex)
+ {
+ u32 IRQIndexMask = ARM_IRQ_MASK(IRQIndex);
+
+ if (pendValue & IRQIndexMask)
+ {
+ IRQHandler* pHandler = IRQHandlers[IRQIndex];
+
+ if (pHandler != 0)
+ (*pHandler)(Params[IRQIndex]);
+ else
+ InterruptSystemDisableIRQ(IRQIndex);
+ }
+ }
+
+ nPendReg = ARM_IC_IRQ_BASIC_PENDING;
+ pendValue = read32(nPendReg);
+ for (;IRQIndex < IRQ_LINES; ++IRQIndex)
+ {
+ u32 IRQIndexMask = ARM_IRQ_MASK(IRQIndex);
+
+ if (pendValue & IRQIndexMask)
+ {
+ IRQHandler* pHandler = IRQHandlers[IRQIndex];
+
+ if (pHandler != 0)
+ (*pHandler)(Params[IRQIndex]);
+ else
+ InterruptSystemDisableIRQ(IRQIndex);
+ }
+ }
+
+ DataMemBarrier();
+}
+
diff --git a/interrupt.h b/interrupt.h
new file mode 100644
index 0000000..8703e40
--- /dev/null
+++ b/interrupt.h
@@ -0,0 +1,29 @@
+#ifndef interrupt_h
+#define interrupt_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "rpi-interrupts.h"
+
+#define EnableInterrupts() __asm volatile ("cpsie i")
+#define DisableInterrupts() __asm volatile ("cpsid i")
+
+typedef void IRQHandler(void* param);
+
+void InterruptSystemInitialize();
+
+void InterruptSystemConnectIRQ(unsigned IRQIndex, IRQHandler* handler, void* param);
+void InterruptSystemDisconnectIRQ(unsigned IRQIndex);
+
+void InterruptSystemEnableIRQ(unsigned IRQIndex);
+void InterruptSystemDisableIRQ(unsigned IRQIndex);
+
+void InterruptHandler(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/kernel.h b/kernel.h
new file mode 100644
index 0000000..ed26e28
--- /dev/null
+++ b/kernel.h
@@ -0,0 +1,78 @@
+//
+// kernel.h
+//
+// Circle - A C++ bare metal environment for Raspberry Pi
+// Copyright (C) 2014 R. Stange
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#ifndef _kernel_h
+#define _kernel_h
+
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+#include "emmc.h"
+//#include
+//#include
+//#include
+
+//#include
+
+#include "iec_bus.h"
+#include "iec_commands.h"
+
+
+enum TShutdownMode
+{
+ ShutdownNone,
+ ShutdownHalt,
+ ShutdownReboot
+};
+
+
+class CKernel
+{
+public:
+ CKernel (void);
+ ~CKernel (void);
+
+ bool Initialize (void);
+
+ TShutdownMode Run (void);
+
+
+//private:
+// static void KeyPressedHandler(const char *pString);
+// static void ShutdownHandler(void);
+//
+// static void KeyStatusHandlerRaw(unsigned char ucModifiers, const unsigned char RawKeys[6]);
+
+private:
+
+ // do not change this order
+ CEMMCDevice m_EMMC;
+
+ IEC_Commands m_IEC_Commands;
+};
+
+#endif
diff --git a/kernel.lst b/kernel.lst
new file mode 100644
index 0000000..26298ea
--- /dev/null
+++ b/kernel.lst
@@ -0,0 +1,92402 @@
+
+kernel.elf: file format elf32-littlearm
+
+
+Disassembly of section .text:
+
+01f00000 <_start>:
+ 1f00000: e59ff018 ldr pc, [pc, #24] ; 1f00020 <_reset_h>
+ 1f00004: e59ff018 ldr pc, [pc, #24] ; 1f00024 <_undefined_instruction_vector_h>
+ 1f00008: e59ff018 ldr pc, [pc, #24] ; 1f00028 <_software_interrupt_vector_h>
+ 1f0000c: e59ff018 ldr pc, [pc, #24] ; 1f0002c <_prefetch_abort_vector_h>
+ 1f00010: e59ff018 ldr pc, [pc, #24] ; 1f00030 <_data_abort_vector_h>
+ 1f00014: e59ff018 ldr pc, [pc, #24] ; 1f00034 <_unused_handler_h>
+ 1f00018: e59ff018 ldr pc, [pc, #24] ; 1f00038 <_interrupt_vector_h>
+ 1f0001c: e59ff018 ldr pc, [pc, #24] ; 1f0003c <_fast_interrupt_vector_h>
+
+01f00020 <_reset_h>:
+ 1f00020: 01f000e0 .word 0x01f000e0
+
+01f00024 <_undefined_instruction_vector_h>:
+ 1f00024: 01f001d0 .word 0x01f001d0
+
+01f00028 <_software_interrupt_vector_h>:
+ 1f00028: 01f0020c .word 0x01f0020c
+
+01f0002c <_prefetch_abort_vector_h>:
+ 1f0002c: 01f001e4 .word 0x01f001e4
+
+01f00030 <_data_abort_vector_h>:
+ 1f00030: 01f001f8 .word 0x01f001f8
+
+01f00034 <_unused_handler_h>:
+ 1f00034: 01f000e0 .word 0x01f000e0
+
+01f00038 <_interrupt_vector_h>:
+ 1f00038: 01f0018c .word 0x01f0018c
+
+01f0003c <_fast_interrupt_vector_h>:
+ 1f0003c: 01f0018c .word 0x01f0018c
+
+01f00040 <_GLOBAL__sub_I_versionMajor>:
+ 1f00040: e30d31dc movw r3, #53724 ; 0xd1dc
+ 1f00044: e34031f9 movt r3, #505 ; 0x1f9
+ 1f00048: e92d4070 push {r4, r5, r6, lr}
+ 1f0004c: e3086008 movw r6, #32776 ; 0x8008
+ 1f00050: e34061f6 movt r6, #502 ; 0x1f6
+ 1f00054: e3a04000 mov r4, #0
+ 1f00058: e1a02006 mov r2, r6
+ 1f0005c: e5834000 str r4, [r3]
+ 1f00060: e1a00003 mov r0, r3
+ 1f00064: e5834004 str r4, [r3, #4]
+ 1f00068: e3001dd0 movw r1, #3536 ; 0xdd0
+ 1f0006c: e34011f0 movt r1, #496 ; 0x1f0
+ 1f00070: e5834008 str r4, [r3, #8]
+ 1f00074: e30d51f8 movw r5, #53752 ; 0xd1f8
+ 1f00078: e34051f9 movt r5, #505 ; 0x1f9
+ 1f0007c: e583400c str r4, [r3, #12]
+ 1f00080: e5834014 str r4, [r3, #20]
+ 1f00084: eb00f8fa bl 1f3e474 <__aeabi_atexit>
+ 1f00088: e309027c movw r0, #37500 ; 0x927c
+ 1f0008c: e34001fa movt r0, #506 ; 0x1fa
+ 1f00090: eb0048f9 bl 1f1247c
+ 1f00094: e1a00005 mov r0, r5
+ 1f00098: eb009229 bl 1f24944
+ 1f0009c: e1a02006 mov r2, r6
+ 1f000a0: e1a00005 mov r0, r5
+ 1f000a4: e3041958 movw r1, #18776 ; 0x4958
+ 1f000a8: e34011f2 movt r1, #498 ; 0x1f2
+ 1f000ac: eb00f8f0 bl 1f3e474 <__aeabi_atexit>
+ 1f000b0: e30937a4 movw r3, #38820 ; 0x97a4
+ 1f000b4: e34031fa movt r3, #506 ; 0x1fa
+ 1f000b8: e3080828 movw r0, #34856 ; 0x8828
+ 1f000bc: e34001f8 movt r0, #504 ; 0x1f8
+ 1f000c0: e5c34008 strb r4, [r3, #8]
+ 1f000c4: e583400c str r4, [r3, #12]
+ 1f000c8: e5834010 str r4, [r3, #16]
+ 1f000cc: e5834014 str r4, [r3, #20]
+ 1f000d0: e5834018 str r4, [r3, #24]
+ 1f000d4: e583401c str r4, [r3, #28]
+ 1f000d8: e8bd4070 pop {r4, r5, r6, lr}
+ 1f000dc: ea005764 b 1f15e74
+
+01f000e0 <_reset_>:
+ 1f000e0: eb000053 bl 1f00234 <_enable_l1_cache>
+ 1f000e4: e10f0000 mrs r0, CPSR
+ 1f000e8: e220001a eor r0, r0, #26
+ 1f000ec: e310001f tst r0, #31
+ 1f000f0: e3c0001f bic r0, r0, #31
+ 1f000f4: e38000d3 orr r0, r0, #211 ; 0xd3
+ 1f000f8: 1a000004 bne 1f00110 <_not_in_hyp_mode>
+ 1f000fc: e3800c01 orr r0, r0, #256 ; 0x100
+ 1f00100: e28fe00c add lr, pc, #12
+ 1f00104: e16ff000 msr SPSR_fsxc, r0
+ 1f00108: e12ef30e .word 0xe12ef30e
+ 1f0010c: e160006e .word 0xe160006e
+
+01f00110 <_not_in_hyp_mode>:
+ 1f00110: e121f000 msr CPSR_c, r0
+
+01f00114 <_reset_continue>:
+ 1f00114: e59f0080 ldr r0, [pc, #128] ; 1f0019c
+ 1f00118: e3a01000 mov r1, #0
+ 1f0011c: e8b003fc ldm r0!, {r2, r3, r4, r5, r6, r7, r8, r9}
+ 1f00120: e8a103fc stmia r1!, {r2, r3, r4, r5, r6, r7, r8, r9}
+ 1f00124: e8b003fc ldm r0!, {r2, r3, r4, r5, r6, r7, r8, r9}
+ 1f00128: e8a103fc stmia r1!, {r2, r3, r4, r5, r6, r7, r8, r9}
+ 1f0012c: e59f4068 ldr r4, [pc, #104] ; 1f0019c
+ 1f00130: e3a000d2 mov r0, #210 ; 0xd2
+ 1f00134: e121f000 msr CPSR_c, r0
+ 1f00138: e244d601 sub sp, r4, #1048576 ; 0x100000
+ 1f0013c: e3a000d1 mov r0, #209 ; 0xd1
+ 1f00140: e121f000 msr CPSR_c, r0
+ 1f00144: e244d602 sub sp, r4, #2097152 ; 0x200000
+ 1f00148: e3a000db mov r0, #219 ; 0xdb
+ 1f0014c: e121f000 msr CPSR_c, r0
+ 1f00150: e244d606 sub sp, r4, #6291456 ; 0x600000
+ 1f00154: e3a000d7 mov r0, #215 ; 0xd7
+ 1f00158: e121f000 msr CPSR_c, r0
+ 1f0015c: e244d605 sub sp, r4, #5242880 ; 0x500000
+ 1f00160: e3a000df mov r0, #223 ; 0xdf
+ 1f00164: e121f000 msr CPSR_c, r0
+ 1f00168: e244d501 sub sp, r4, #4194304 ; 0x400000
+ 1f0016c: e3a000d3 mov r0, #211 ; 0xd3
+ 1f00170: e121f000 msr CPSR_c, r0
+ 1f00174: e244d000 sub sp, r4, #0
+ 1f00178: e3a0060f mov r0, #15728640 ; 0xf00000
+ 1f0017c: ee010f50 mcr 15, 0, r0, cr1, cr0, {2}
+ 1f00180: e3a00101 mov r0, #1073741824 ; 0x40000000
+ 1f00184: eee80a10 vmsr fpexc, r0
+ 1f00188: ea000087 b 1f003ac <_cstartup>
+
+01f0018c :
+ 1f0018c: e24ee004 sub lr, lr, #4
+ 1f00190: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
+ 1f00194: eb004709 bl 1f11dc0
+ 1f00198: e8fd9fff ldm sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, pc}^
+ 1f0019c: 01f00000 .word 0x01f00000
+
+01f001a0 <_get_stack_pointer>:
+ 1f001a0: e1a0000d mov r0, sp
+ 1f001a4: e1a0f00e mov pc, lr
+
+01f001a8 <_get_cpsr>:
+ 1f001a8: e10f0000 mrs r0, CPSR
+ 1f001ac: e1a0f00e mov pc, lr
+
+01f001b0 <_enable_interrupts>:
+ 1f001b0: e10f0000 mrs r0, CPSR
+ 1f001b4: e3c000c0 bic r0, r0, #192 ; 0xc0
+ 1f001b8: e121f000 msr CPSR_c, r0
+ 1f001bc: e1a0f00e mov pc, lr
+
+01f001c0 <_disable_interrupts>:
+ 1f001c0: e10f0000 mrs r0, CPSR
+ 1f001c4: e38010c0 orr r1, r0, #192 ; 0xc0
+ 1f001c8: e121f001 msr CPSR_c, r1
+ 1f001cc: e1a0f00e mov pc, lr
+
+01f001d0 <_undefined_instruction_handler_>:
+ 1f001d0: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
+ 1f001d4: e14f0000 mrs r0, SPSR
+ 1f001d8: e92d0001 stmfd sp!, {r0}
+ 1f001dc: e1a0000d mov r0, sp
+ 1f001e0: eb000273 bl 1f00bb4
+
+01f001e4 <_prefetch_abort_handler_>:
+ 1f001e4: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
+ 1f001e8: e14f0000 mrs r0, SPSR
+ 1f001ec: e92d0001 stmfd sp!, {r0}
+ 1f001f0: e1a0000d mov r0, sp
+ 1f001f4: eb000272 bl 1f00bc4
+
+01f001f8 <_data_abort_handler_>:
+ 1f001f8: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
+ 1f001fc: e14f0000 mrs r0, SPSR
+ 1f00200: e92d0001 stmfd sp!, {r0}
+ 1f00204: e1a0000d mov r0, sp
+ 1f00208: eb000271 bl 1f00bd4
+
+01f0020c <_swi_handler_>:
+ 1f0020c: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}
+ 1f00210: e14f0000 mrs r0, SPSR
+ 1f00214: e92d0001 stmfd sp!, {r0}
+ 1f00218: e1a0000d mov r0, sp
+ 1f0021c: eb000270 bl 1f00be4
+
+01f00220 <_enable_unaligned_access>:
+ 1f00220: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
+ 1f00224: e3c00002 bic r0, r0, #2
+ 1f00228: e3800501 orr r0, r0, #4194304 ; 0x400000
+ 1f0022c: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
+ 1f00230: e1a0f00e mov pc, lr
+
+01f00234 <_enable_l1_cache>:
+ 1f00234: ee110f10 mrc 15, 0, r0, cr1, cr0, {0}
+ 1f00238: e3800b02 orr r0, r0, #2048 ; 0x800
+ 1f0023c: e3800004 orr r0, r0, #4
+ 1f00240: e3800a01 orr r0, r0, #4096 ; 0x1000
+ 1f00244: ee010f10 mcr 15, 0, r0, cr1, cr0, {0}
+ 1f00248: e1a0f00e mov pc, lr
+
+01f0024c <_invalidate_icache>:
+ 1f0024c: e3a00000 mov r0, #0
+ 1f00250: ee070f15 mcr 15, 0, r0, cr7, cr5, {0}
+ 1f00254: e1a0f00e mov pc, lr
+
+01f00258 <_invalidate_dcache>:
+ 1f00258: e3a00000 mov r0, #0
+ 1f0025c: ee070f16 mcr 15, 0, r0, cr7, cr6, {0}
+ 1f00260: e1a0f00e mov pc, lr
+
+01f00264 <_clean_invalidate_dcache>:
+ 1f00264: e3a00000 mov r0, #0
+ 1f00268: ee070f1e mcr 15, 0, r0, cr7, cr14, {0}
+ 1f0026c: e1a0f00e mov pc, lr
+
+01f00270 <_invalidate_dcache_mva>:
+ 1f00270: ee070f36 mcr 15, 0, r0, cr7, cr6, {1}
+ 1f00274: e1a0f00e mov pc, lr
+
+01f00278 <_clean_invalidate_dcache_mva>:
+ 1f00278: ee070f3e mcr 15, 0, r0, cr7, cr14, {1}
+ 1f0027c: e1a0f00e mov pc, lr
+
+01f00280 <_invalidate_dtlb>:
+ 1f00280: e3a00000 mov r0, #0
+ 1f00284: ee080f16 mcr 15, 0, r0, cr8, cr6, {0}
+ 1f00288: e1a0f00e mov pc, lr
+
+01f0028c <_invalidate_dtlb_mva>:
+ 1f0028c: ee080f36 mcr 15, 0, r0, cr8, cr6, {1}
+ 1f00290: e1a0f00e mov pc, lr
+
+01f00294 <_data_memory_barrier>:
+ 1f00294: f57ff05f dmb sy
+ 1f00298: e1a0f00e mov pc, lr
+
+01f0029c <_init_core>:
+ 1f0029c: e10f0000 mrs r0, CPSR
+ 1f002a0: e220001a eor r0, r0, #26
+ 1f002a4: e310001f tst r0, #31
+ 1f002a8: e3c0001f bic r0, r0, #31
+ 1f002ac: e38000d3 orr r0, r0, #211 ; 0xd3
+ 1f002b0: 1a000004 bne 1f002c8 <_init_not_in_hyp_mode>
+ 1f002b4: e3800c01 orr r0, r0, #256 ; 0x100
+ 1f002b8: e28fe00c add lr, pc, #12
+ 1f002bc: e16ff000 msr SPSR_fsxc, r0
+ 1f002c0: e12ef30e .word 0xe12ef30e
+ 1f002c4: e160006e .word 0xe160006e
+
+01f002c8 <_init_not_in_hyp_mode>:
+ 1f002c8: e121f000 msr CPSR_c, r0
+
+01f002cc <_init_continue>:
+ 1f002cc: e59f40a8 ldr r4, [pc, #168] ; 1f0037c <_get_core+0xc>
+ 1f002d0: e3a000d2 mov r0, #210 ; 0xd2
+ 1f002d4: e121f000 msr CPSR_c, r0
+ 1f002d8: e244d502 sub sp, r4, #8388608 ; 0x800000
+ 1f002dc: e3a000d1 mov r0, #209 ; 0xd1
+ 1f002e0: e121f000 msr CPSR_c, r0
+ 1f002e4: e244d609 sub sp, r4, #9437184 ; 0x900000
+ 1f002e8: e3a000db mov r0, #219 ; 0xdb
+ 1f002ec: e121f000 msr CPSR_c, r0
+ 1f002f0: e244d503 sub sp, r4, #12582912 ; 0xc00000
+ 1f002f4: e3a000d7 mov r0, #215 ; 0xd7
+ 1f002f8: e121f000 msr CPSR_c, r0
+ 1f002fc: e244d60b sub sp, r4, #11534336 ; 0xb00000
+ 1f00300: e3a000df mov r0, #223 ; 0xdf
+ 1f00304: e121f000 msr CPSR_c, r0
+ 1f00308: e244d60a sub sp, r4, #10485760 ; 0xa00000
+ 1f0030c: e3a000d3 mov r0, #211 ; 0xd3
+ 1f00310: e121f000 msr CPSR_c, r0
+ 1f00314: e244d607 sub sp, r4, #7340032 ; 0x700000
+ 1f00318: e3a0060f mov r0, #15728640 ; 0xf00000
+ 1f0031c: ee010f50 mcr 15, 0, r0, cr1, cr0, {2}
+ 1f00320: e3a00101 mov r0, #1073741824 ; 0x40000000
+ 1f00324: eee80a10 vmsr fpexc, r0
+ 1f00328: eb000d07 bl 1f0374c
+
+01f0032c <_spin_core>:
+ 1f0032c: e3a00053 mov r0, #83 ; 0x53
+ 1f00330: eb000d70 bl 1f038f8
+ 1f00334: e3a00050 mov r0, #80 ; 0x50
+ 1f00338: eb000d6e bl 1f038f8
+ 1f0033c: e3a00049 mov r0, #73 ; 0x49
+ 1f00340: eb000d6c bl 1f038f8
+ 1f00344: e3a0004e mov r0, #78 ; 0x4e
+ 1f00348: eb000d6a bl 1f038f8
+ 1f0034c: eb000007 bl 1f00370 <_get_core>
+ 1f00350: e2800030 add r0, r0, #48 ; 0x30
+ 1f00354: eb000d67 bl 1f038f8
+ 1f00358: e3a0000d mov r0, #13
+ 1f0035c: eb000d65 bl 1f038f8
+ 1f00360: e3a0000a mov r0, #10
+ 1f00364: eb000d63 bl 1f038f8
+
+01f00368 <_spin_core1>:
+ 1f00368: e320f003 wfi
+ 1f0036c: eafffffd b 1f00368 <_spin_core1>
+
+01f00370 <_get_core>:
+ 1f00370: ee100fb0 mrc 15, 0, r0, cr0, cr0, {5}
+ 1f00374: e2000003 and r0, r0, #3
+ 1f00378: e1a0f00e mov pc, lr
+ 1f0037c: 01f00000 .word 0x01f00000
+
+01f00380 <_toggle_test_pin>:
+ 1f00380: e3a01602 mov r1, #2097152 ; 0x200000
+
+01f00384 <_toggle_test_pin_loop>:
+ 1f00384: e59f2014 ldr r2, [pc, #20] ; 1f003a0 <_toggle_test_pin_loop+0x1c>
+ 1f00388: e5821000 str r1, [r2]
+ 1f0038c: e59f2010 ldr r2, [pc, #16] ; 1f003a4 <_toggle_test_pin_loop+0x20>
+ 1f00390: e5821000 str r1, [r2]
+ 1f00394: e2500001 subs r0, r0, #1
+ 1f00398: 1afffff9 bne 1f00384 <_toggle_test_pin_loop>
+ 1f0039c: e1a0f00e mov pc, lr
+ 1f003a0: 3f20001c .word 0x3f20001c
+ 1f003a4: 3f200028 .word 0x3f200028
+
+01f003a8 <_init>:
+ 1f003a8: e12fff1e bx lr
+
+01f003ac <_cstartup>:
+ 1f003ac: e92d41f0 push {r4, r5, r6, r7, r8, lr}
+ 1f003b0: e305ecdc movw lr, #23772 ; 0x5cdc
+ 1f003b4: e340e1f6 movt lr, #502 ; 0x1f6
+ 1f003b8: e3085004 movw r5, #32772 ; 0x8004
+ 1f003bc: e340520e movt r5, #526 ; 0x20e
+ 1f003c0: e15e0005 cmp lr, r5
+ 1f003c4: e1a06000 mov r6, r0
+ 1f003c8: e1a07001 mov r7, r1
+ 1f003cc: e1a08002 mov r8, r2
+ 1f003d0: 2a00003e bcs 1f004d0 <_cstartup+0x124>
+ 1f003d4: e1e0400e mvn r4, lr
+ 1f003d8: e7a0c15e sbfx ip, lr, #2, #1
+ 1f003dc: e0844005 add r4, r4, r5
+ 1f003e0: e20cc003 and ip, ip, #3
+ 1f003e4: e1a04124 lsr r4, r4, #2
+ 1f003e8: e2842001 add r2, r4, #1
+ 1f003ec: e15c0002 cmp ip, r2
+ 1f003f0: 21a0c002 movcs ip, r2
+ 1f003f4: e3520006 cmp r2, #6
+ 1f003f8: 91a0c002 movls ip, r2
+ 1f003fc: 8a000043 bhi 1f00510 <_cstartup+0x164>
+ 1f00400: e35c0001 cmp ip, #1
+ 1f00404: e3053cdc movw r3, #23772 ; 0x5cdc
+ 1f00408: e34031f6 movt r3, #502 ; 0x1f6
+ 1f0040c: e3a01000 mov r1, #0
+ 1f00410: e58e1000 str r1, [lr]
+ 1f00414: 0a00003b beq 1f00508 <_cstartup+0x15c>
+ 1f00418: e35c0002 cmp ip, #2
+ 1f0041c: e5831004 str r1, [r3, #4]
+ 1f00420: 0a000036 beq 1f00500 <_cstartup+0x154>
+ 1f00424: e35c0003 cmp ip, #3
+ 1f00428: e5831008 str r1, [r3, #8]
+ 1f0042c: 0a000031 beq 1f004f8 <_cstartup+0x14c>
+ 1f00430: e35c0004 cmp ip, #4
+ 1f00434: e583100c str r1, [r3, #12]
+ 1f00438: 0a00002c beq 1f004f0 <_cstartup+0x144>
+ 1f0043c: e35c0006 cmp ip, #6
+ 1f00440: e5831010 str r1, [r3, #16]
+ 1f00444: 1a000027 bne 1f004e8 <_cstartup+0x13c>
+ 1f00448: e5831014 str r1, [r3, #20]
+ 1f0044c: e2833018 add r3, r3, #24
+ 1f00450: e152000c cmp r2, ip
+ 1f00454: 0a00001d beq 1f004d0 <_cstartup+0x124>
+ 1f00458: e06c1002 rsb r1, ip, r2
+ 1f0045c: e06c4004 rsb r4, ip, r4
+ 1f00460: e2412004 sub r2, r1, #4
+ 1f00464: e3540002 cmp r4, #2
+ 1f00468: e1a02122 lsr r2, r2, #2
+ 1f0046c: e2822001 add r2, r2, #1
+ 1f00470: e1a04102 lsl r4, r2, #2
+ 1f00474: 9a00000a bls 1f004a4 <_cstartup+0xf8>
+ 1f00478: f2c00050 vmov.i32 q8, #0 ; 0x00000000
+ 1f0047c: e08ec10c add ip, lr, ip, lsl #2
+ 1f00480: e3a00000 mov r0, #0
+ 1f00484: e2800001 add r0, r0, #1
+ 1f00488: f44c0adf vst1.64 {d16-d17}, [ip :64]
+ 1f0048c: e1520000 cmp r2, r0
+ 1f00490: e28cc010 add ip, ip, #16
+ 1f00494: 8afffffa bhi 1f00484 <_cstartup+0xd8>
+ 1f00498: e1510004 cmp r1, r4
+ 1f0049c: e0833104 add r3, r3, r4, lsl #2
+ 1f004a0: 0a00000a beq 1f004d0 <_cstartup+0x124>
+ 1f004a4: e1a00003 mov r0, r3
+ 1f004a8: e3a01000 mov r1, #0
+ 1f004ac: e3082004 movw r2, #32772 ; 0x8004
+ 1f004b0: e340220e movt r2, #526 ; 0x20e
+ 1f004b4: e4801004 str r1, [r0], #4
+ 1f004b8: e1500005 cmp r0, r5
+ 1f004bc: 2a000003 bcs 1f004d0 <_cstartup+0x124>
+ 1f004c0: e2830008 add r0, r3, #8
+ 1f004c4: e5831004 str r1, [r3, #4]
+ 1f004c8: e1500002 cmp r0, r2
+ 1f004cc: 35831008 strcc r1, [r3, #8]
+ 1f004d0: eb00f7f6 bl 1f3e4b0 <__libc_init_array>
+ 1f004d4: e1a02008 mov r2, r8
+ 1f004d8: e1a01007 mov r1, r7
+ 1f004dc: e1a00006 mov r0, r6
+ 1f004e0: eb000587 bl 1f01b04
+ 1f004e4: eafffffe b 1f004e4 <_cstartup+0x138>
+ 1f004e8: e2833014 add r3, r3, #20
+ 1f004ec: eaffffd7 b 1f00450 <_cstartup+0xa4>
+ 1f004f0: e2833010 add r3, r3, #16
+ 1f004f4: eaffffd5 b 1f00450 <_cstartup+0xa4>
+ 1f004f8: e283300c add r3, r3, #12
+ 1f004fc: eaffffd3 b 1f00450 <_cstartup+0xa4>
+ 1f00500: e2833008 add r3, r3, #8
+ 1f00504: eaffffd1 b 1f00450 <_cstartup+0xa4>
+ 1f00508: e2833004 add r3, r3, #4
+ 1f0050c: eaffffcf b 1f00450 <_cstartup+0xa4>
+ 1f00510: e35c0000 cmp ip, #0
+ 1f00514: 01a0300e moveq r3, lr
+ 1f00518: 0affffce beq 1f00458 <_cstartup+0xac>
+ 1f0051c: eaffffb7 b 1f00400 <_cstartup+0x54>
+
+01f00520 <_exit>:
+ 1f00520: eafffffe b 1f00520 <_exit>
+
+01f00524 <_close>:
+ 1f00524: e3e00000 mvn r0, #0
+ 1f00528: e12fff1e bx lr
+
+01f0052c :
+ 1f0052c: e3083000 movw r3, #32768 ; 0x8000
+ 1f00530: e340320e movt r3, #526 ; 0x20e
+ 1f00534: e3a0200c mov r2, #12
+ 1f00538: e3e00000 mvn r0, #0
+ 1f0053c: e5832000 str r2, [r3]
+ 1f00540: e12fff1e bx lr
+
+01f00544 :
+ 1f00544: e3083000 movw r3, #32768 ; 0x8000
+ 1f00548: e340320e movt r3, #526 ; 0x20e
+ 1f0054c: e3a0200b mov r2, #11
+ 1f00550: e3e00000 mvn r0, #0
+ 1f00554: e5832000 str r2, [r3]
+ 1f00558: e12fff1e bx lr
+
+01f0055c <_fstat>:
+ 1f0055c: e3a03a02 mov r3, #8192 ; 0x2000
+ 1f00560: e3a00000 mov r0, #0
+ 1f00564: e5813004 str r3, [r1, #4]
+ 1f00568: e12fff1e bx lr
+
+01f0056c :
+ 1f0056c: e3a00001 mov r0, #1
+ 1f00570: e12fff1e bx lr
+
+01f00574 <_getpid>:
+ 1f00574: e3a00001 mov r0, #1
+ 1f00578: e12fff1e bx lr
+
+01f0057c <_isatty>:
+ 1f0057c: e3a00001 mov r0, #1
+ 1f00580: e12fff1e bx lr
+
+01f00584 :
+ 1f00584: e3083000 movw r3, #32768 ; 0x8000
+ 1f00588: e340320e movt r3, #526 ; 0x20e
+ 1f0058c: e3a02016 mov r2, #22
+ 1f00590: e3e00000 mvn r0, #0
+ 1f00594: e5832000 str r2, [r3]
+ 1f00598: e12fff1e bx lr
+
+01f0059c <_kill>:
+ 1f0059c: e3083000 movw r3, #32768 ; 0x8000
+ 1f005a0: e340320e movt r3, #526 ; 0x20e
+ 1f005a4: e3a02016 mov r2, #22
+ 1f005a8: e3e00000 mvn r0, #0
+ 1f005ac: e5832000 str r2, [r3]
+ 1f005b0: e12fff1e bx lr
+
+01f005b4 :
+ 1f005b4: e3083000 movw r3, #32768 ; 0x8000
+ 1f005b8: e340320e movt r3, #526 ; 0x20e
+ 1f005bc: e3a0201f mov r2, #31
+ 1f005c0: e3e00000 mvn r0, #0
+ 1f005c4: e5832000 str r2, [r3]
+ 1f005c8: e12fff1e bx lr
+
+01f005cc <_lseek>:
+ 1f005cc: e3a00000 mov r0, #0
+ 1f005d0: e12fff1e bx lr
+
+01f005d4 :
+ 1f005d4: e3e00000 mvn r0, #0
+ 1f005d8: e12fff1e bx lr
+
+01f005dc <_read>:
+ 1f005dc: e3a00000 mov r0, #0
+ 1f005e0: e12fff1e bx lr
+
+01f005e4 <_sbrk>:
+ 1f005e4: e3082004 movw r2, #32772 ; 0x8004
+ 1f005e8: e34021f6 movt r2, #502 ; 0x1f6
+ 1f005ec: e3081004 movw r1, #32772 ; 0x8004
+ 1f005f0: e340120e movt r1, #526 ; 0x20e
+ 1f005f4: e5923000 ldr r3, [r2]
+ 1f005f8: e3530000 cmp r3, #0
+ 1f005fc: 01a03001 moveq r3, r1
+ 1f00600: e0831000 add r1, r3, r0
+ 1f00604: e1a00003 mov r0, r3
+ 1f00608: e5821000 str r1, [r2]
+ 1f0060c: e12fff1e bx lr
+
+01f00610 :
+ 1f00610: e3a03a02 mov r3, #8192 ; 0x2000
+ 1f00614: e3a00000 mov r0, #0
+ 1f00618: e5813004 str r3, [r1, #4]
+ 1f0061c: e12fff1e bx lr
+
+01f00620 :
+ 1f00620: e3e00000 mvn r0, #0
+ 1f00624: e12fff1e bx lr
+
+01f00628 :
+ 1f00628: e3083000 movw r3, #32768 ; 0x8000
+ 1f0062c: e340320e movt r3, #526 ; 0x20e
+ 1f00630: e3a02002 mov r2, #2
+ 1f00634: e3e00000 mvn r0, #0
+ 1f00638: e5832000 str r2, [r3]
+ 1f0063c: e12fff1e bx lr
+
+01f00640 :
+ 1f00640: e3083000 movw r3, #32768 ; 0x8000
+ 1f00644: e340320e movt r3, #526 ; 0x20e
+ 1f00648: e3a0200a mov r2, #10
+ 1f0064c: e3e00000 mvn r0, #0
+ 1f00650: e5832000 str r2, [r3]
+ 1f00654: e12fff1e bx lr
+
+01f00658 :
+ 1f00658: ea000ca6 b 1f038f8
+
+01f0065c <_write>:
+ 1f0065c: e92d4070 push {r4, r5, r6, lr}
+ 1f00660: e2526000 subs r6, r2, #0
+ 1f00664: da000005 ble 1f00680 <_write+0x24>
+ 1f00668: e1a04001 mov r4, r1
+ 1f0066c: e0815006 add r5, r1, r6
+ 1f00670: e0d400d1 ldrsb r0, [r4], #1
+ 1f00674: eb000c9f bl 1f038f8
+ 1f00678: e1550004 cmp r5, r4
+ 1f0067c: 1afffffb bne 1f00670 <_write+0x14>
+ 1f00680: e1a00006 mov r0, r6
+ 1f00684: e8bd8070 pop {r4, r5, r6, pc}
+
+01f00688 :
+ 1f00688: ea00f7b6 b 1f3e568
+
+01f0068c :
+ 1f0068c: ea00f7bd b 1f3e588
+
+01f00690 :
+ 1f00690: e12fff1e bx lr
+
+01f00694 :
+ 1f00694: e12fff1e bx lr
+
+01f00698 :
+ 1f00698: e3a03000 mov r3, #0
+ 1f0069c: e3433f10 movt r3, #16144 ; 0x3f10
+ 1f006a0: e3a01001 mov r1, #1
+ 1f006a4: e3451a00 movt r1, #23040 ; 0x5a00
+ 1f006a8: e3a02020 mov r2, #32
+ 1f006ac: e3452a00 movt r2, #23040 ; 0x5a00
+ 1f006b0: e5831024 str r1, [r3, #36] ; 0x24
+ 1f006b4: e583201c str r2, [r3, #28]
+ 1f006b8: eafffffe b 1f006b8
+
+01f006bc :
+ 1f006bc: e200000f and r0, r0, #15
+ 1f006c0: e3500009 cmp r0, #9
+ 1f006c4: 92800030 addls r0, r0, #48 ; 0x30
+ 1f006c8: 82800037 addhi r0, r0, #55 ; 0x37
+ 1f006cc: ea000c89 b 1f038f8
+
+01f006d0 :
+ 1f006d0: e92d4070 push {r4, r5, r6, lr}
+ 1f006d4: e1a05000 mov r5, r0
+ 1f006d8: e3a04008 mov r4, #8
+ 1f006dc: e1a03e25 lsr r3, r5, #28
+ 1f006e0: e1a05205 lsl r5, r5, #4
+ 1f006e4: e3530009 cmp r3, #9
+ 1f006e8: e2830037 add r0, r3, #55 ; 0x37
+ 1f006ec: 92830030 addls r0, r3, #48 ; 0x30
+ 1f006f0: eb000c80 bl 1f038f8
+ 1f006f4: e2544001 subs r4, r4, #1
+ 1f006f8: 1afffff7 bne 1f006dc
+ 1f006fc: e8bd8070 pop {r4, r5, r6, pc}
+
+01f00700 :
+ 1f00700: e92d4070 push {r4, r5, r6, lr}
+ 1f00704: e1a05000 mov r5, r0
+ 1f00708: e3a04020 mov r4, #32
+ 1f0070c: e1a00fa5 lsr r0, r5, #31
+ 1f00710: e1a05085 lsl r5, r5, #1
+ 1f00714: e2800030 add r0, r0, #48 ; 0x30
+ 1f00718: eb000c76 bl 1f038f8
+ 1f0071c: e2544001 subs r4, r4, #1
+ 1f00720: 1afffff9 bne 1f0070c
+ 1f00724: e8bd8070 pop {r4, r5, r6, pc}
+
+01f00728 :
+ 1f00728: e92d4010 push {r4, lr}
+ 1f0072c: e2804001 add r4, r0, #1
+ 1f00730: e1d000d0 ldrsb r0, [r0]
+ 1f00734: e3500000 cmp r0, #0
+ 1f00738: 08bd8010 popeq {r4, pc}
+ 1f0073c: eb000c6d bl 1f038f8
+ 1f00740: e0d400d1 ldrsb r0, [r4], #1
+ 1f00744: e3500000 cmp r0, #0
+ 1f00748: 1afffffb bne 1f0073c
+ 1f0074c: e8bd8010 pop {r4, pc}
+
+01f00750 :
+ 1f00750: e92d4ff8 push {r3, r4, r5, r6, r7, r8, r9, sl, fp, lr}
+ 1f00754: e3c08003 bic r8, r0, #3
+ 1f00758: e1a05001 mov r5, r1
+ 1f0075c: e1d200d0 ldrsb r0, [r2]
+ 1f00760: e3500000 cmp r0, #0
+ 1f00764: 0a000004 beq 1f0077c
+ 1f00768: e2824001 add r4, r2, #1
+ 1f0076c: eb000c61 bl 1f038f8
+ 1f00770: e0d400d1 ldrsb r0, [r4], #1
+ 1f00774: e3500000 cmp r0, #0
+ 1f00778: 1afffffb bne 1f0076c
+ 1f0077c: e30840c4 movw r4, #32964 ; 0x80c4
+ 1f00780: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00784: e3a00020 mov r0, #32
+ 1f00788: eb000c5a bl 1f038f8
+ 1f0078c: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00790: e3500000 cmp r0, #0
+ 1f00794: 1afffffb bne 1f00788
+ 1f00798: e5983038 ldr r3, [r8, #56] ; 0x38
+ 1f0079c: e3a06008 mov r6, #8
+ 1f007a0: e3c33003 bic r3, r3, #3
+ 1f007a4: e0655003 rsb r5, r5, r3
+ 1f007a8: e1a04005 mov r4, r5
+ 1f007ac: e1a03e24 lsr r3, r4, #28
+ 1f007b0: e1a04204 lsl r4, r4, #4
+ 1f007b4: e3530009 cmp r3, #9
+ 1f007b8: e2830037 add r0, r3, #55 ; 0x37
+ 1f007bc: 92830030 addls r0, r3, #48 ; 0x30
+ 1f007c0: eb000c4c bl 1f038f8
+ 1f007c4: e2566001 subs r6, r6, #1
+ 1f007c8: 1afffff7 bne 1f007ac
+ 1f007cc: e308402c movw r4, #32812 ; 0x802c
+ 1f007d0: e34041f5 movt r4, #501 ; 0x1f5
+ 1f007d4: e3a0000d mov r0, #13
+ 1f007d8: eb000c46 bl 1f038f8
+ 1f007dc: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f007e0: e3500000 cmp r0, #0
+ 1f007e4: 1afffffb bne 1f007d8
+ 1f007e8: e3084008 movw r4, #32776 ; 0x8008
+ 1f007ec: e34041f5 movt r4, #501 ; 0x1f5
+ 1f007f0: e3a00052 mov r0, #82 ; 0x52
+ 1f007f4: eb000c3f bl 1f038f8
+ 1f007f8: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f007fc: e3500000 cmp r0, #0
+ 1f00800: 1afffffb bne 1f007f4
+ 1f00804: e3067667 movw r7, #26215 ; 0x6667
+ 1f00808: e3467666 movt r7, #26214 ; 0x6666
+ 1f0080c: e1a04000 mov r4, r0
+ 1f00810: e1a0a000 mov sl, r0
+ 1f00814: e1a06008 mov r6, r8
+ 1f00818: e30890cc movw r9, #32972 ; 0x80cc
+ 1f0081c: e34091f5 movt r9, #501 ; 0x1f5
+ 1f00820: e3a00020 mov r0, #32
+ 1f00824: eb000c33 bl 1f038f8
+ 1f00828: e1f900d1 ldrsb r0, [r9, #1]!
+ 1f0082c: e3500000 cmp r0, #0
+ 1f00830: 1afffffb bne 1f00824
+ 1f00834: e0c3279a smull r2, r3, sl, r7
+ 1f00838: e1a00fca asr r0, sl, #31
+ 1f0083c: e30890d4 movw r9, #32980 ; 0x80d4
+ 1f00840: e34091f5 movt r9, #501 ; 0x1f5
+ 1f00844: e060b143 rsb fp, r0, r3, asr #2
+ 1f00848: e28b0030 add r0, fp, #48 ; 0x30
+ 1f0084c: e6af0070 sxtb r0, r0
+ 1f00850: eb000c28 bl 1f038f8
+ 1f00854: e1a0018b lsl r0, fp, #3
+ 1f00858: e080008b add r0, r0, fp, lsl #1
+ 1f0085c: e060000a rsb r0, r0, sl
+ 1f00860: e2800030 add r0, r0, #48 ; 0x30
+ 1f00864: e6af0070 sxtb r0, r0
+ 1f00868: eb000c22 bl 1f038f8
+ 1f0086c: e3a0005d mov r0, #93 ; 0x5d
+ 1f00870: eb000c20 bl 1f038f8
+ 1f00874: e1f900d1 ldrsb r0, [r9, #1]!
+ 1f00878: e3500000 cmp r0, #0
+ 1f0087c: 1afffffb bne 1f00870
+ 1f00880: e5b6a004 ldr sl, [r6, #4]!
+ 1f00884: e3a09008 mov r9, #8
+ 1f00888: e1a03e2a lsr r3, sl, #28
+ 1f0088c: e1a0a20a lsl sl, sl, #4
+ 1f00890: e3530009 cmp r3, #9
+ 1f00894: e2830037 add r0, r3, #55 ; 0x37
+ 1f00898: 92830030 addls r0, r3, #48 ; 0x30
+ 1f0089c: eb000c15 bl 1f038f8
+ 1f008a0: e2599001 subs r9, r9, #1
+ 1f008a4: 1afffff7 bne 1f00888
+ 1f008a8: e308902c movw r9, #32812 ; 0x802c
+ 1f008ac: e34091f5 movt r9, #501 ; 0x1f5
+ 1f008b0: e3a0000d mov r0, #13
+ 1f008b4: eb000c0f bl 1f038f8
+ 1f008b8: e1f900d1 ldrsb r0, [r9, #1]!
+ 1f008bc: e3500000 cmp r0, #0
+ 1f008c0: 1afffffb bne 1f008b4
+ 1f008c4: e2844001 add r4, r4, #1
+ 1f008c8: e354000e cmp r4, #14
+ 1f008cc: 0a000003 beq 1f008e0
+ 1f008d0: e354000d cmp r4, #13
+ 1f008d4: 11a0a004 movne sl, r4
+ 1f008d8: 03a0a00e moveq sl, #14
+ 1f008dc: eaffffcd b 1f00818
+ 1f008e0: e3084018 movw r4, #32792 ; 0x8018
+ 1f008e4: e34041f5 movt r4, #501 ; 0x1f5
+ 1f008e8: e3a0004d mov r0, #77 ; 0x4d
+ 1f008ec: eb000c01 bl 1f038f8
+ 1f008f0: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f008f4: e3500000 cmp r0, #0
+ 1f008f8: 1afffffb bne 1f008ec
+ 1f008fc: e2455010 sub r5, r5, #16
+ 1f00900: e3e07002 mvn r7, #2
+ 1f00904: e3084030 movw r4, #32816 ; 0x8030
+ 1f00908: e34041f5 movt r4, #501 ; 0x1f5
+ 1f0090c: e3a00020 mov r0, #32
+ 1f00910: eb000bf8 bl 1f038f8
+ 1f00914: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00918: e3500000 cmp r0, #0
+ 1f0091c: 1afffffb bne 1f00910
+ 1f00920: e1a06005 mov r6, r5
+ 1f00924: e3a04008 mov r4, #8
+ 1f00928: e1a03e26 lsr r3, r6, #28
+ 1f0092c: e1a06206 lsl r6, r6, #4
+ 1f00930: e3530009 cmp r3, #9
+ 1f00934: e2830037 add r0, r3, #55 ; 0x37
+ 1f00938: 92830030 addls r0, r3, #48 ; 0x30
+ 1f0093c: eb000bed bl 1f038f8
+ 1f00940: e2544001 subs r4, r4, #1
+ 1f00944: 1afffff7 bne 1f00928
+ 1f00948: e3a0003d mov r0, #61 ; 0x3d
+ 1f0094c: e3a06008 mov r6, #8
+ 1f00950: eb000be8 bl 1f038f8
+ 1f00954: e4954004 ldr r4, [r5], #4
+ 1f00958: e1a03e24 lsr r3, r4, #28
+ 1f0095c: e1a04204 lsl r4, r4, #4
+ 1f00960: e3530009 cmp r3, #9
+ 1f00964: e2830037 add r0, r3, #55 ; 0x37
+ 1f00968: 92830030 addls r0, r3, #48 ; 0x30
+ 1f0096c: eb000be1 bl 1f038f8
+ 1f00970: e2566001 subs r6, r6, #1
+ 1f00974: 1afffff7 bne 1f00958
+ 1f00978: e3570001 cmp r7, #1
+ 1f0097c: 1308402c movwne r4, #32812 ; 0x802c
+ 1f00980: 13a0000d movne r0, #13
+ 1f00984: 134041f5 movtne r4, #501 ; 0x1f5
+ 1f00988: 0a000081 beq 1f00b94
+ 1f0098c: eb000bd9 bl 1f038f8
+ 1f00990: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00994: e3500000 cmp r0, #0
+ 1f00998: 1afffffb bne 1f0098c
+ 1f0099c: e3570005 cmp r7, #5
+ 1f009a0: 0a000001 beq 1f009ac
+ 1f009a4: e2877001 add r7, r7, #1
+ 1f009a8: eaffffd5 b 1f00904
+ 1f009ac: e5986000 ldr r6, [r8]
+ 1f009b0: e3084034 movw r4, #32820 ; 0x8034
+ 1f009b4: e34041f5 movt r4, #501 ; 0x1f5
+ 1f009b8: e3a00046 mov r0, #70 ; 0x46
+ 1f009bc: eb000bcd bl 1f038f8
+ 1f009c0: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f009c4: e3500000 cmp r0, #0
+ 1f009c8: 1afffffb bne 1f009bc
+ 1f009cc: e1a05006 mov r5, r6
+ 1f009d0: e3a04020 mov r4, #32
+ 1f009d4: e1a00fa5 lsr r0, r5, #31
+ 1f009d8: e1a05085 lsl r5, r5, #1
+ 1f009dc: e2800030 add r0, r0, #48 ; 0x30
+ 1f009e0: eb000bc4 bl 1f038f8
+ 1f009e4: e2544001 subs r4, r4, #1
+ 1f009e8: 1afffff9 bne 1f009d4
+ 1f009ec: e3084064 movw r4, #32868 ; 0x8064
+ 1f009f0: e34041f5 movt r4, #501 ; 0x1f5
+ 1f009f4: e3a00020 mov r0, #32
+ 1f009f8: eb000bbe bl 1f038f8
+ 1f009fc: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00a00: e3500000 cmp r0, #0
+ 1f00a04: 1afffffb bne 1f009f8
+ 1f00a08: e206301f and r3, r6, #31
+ 1f00a0c: e2433010 sub r3, r3, #16
+ 1f00a10: e353000f cmp r3, #15
+ 1f00a14: 979ff103 ldrls pc, [pc, r3, lsl #2]
+ 1f00a18: ea000055 b 1f00b74
+ 1f00a1c: 01f00ab4 .word 0x01f00ab4
+ 1f00a20: 01f00b54 .word 0x01f00b54
+ 1f00a24: 01f00ad4 .word 0x01f00ad4
+ 1f00a28: 01f00af4 .word 0x01f00af4
+ 1f00a2c: 01f00b74 .word 0x01f00b74
+ 1f00a30: 01f00b74 .word 0x01f00b74
+ 1f00a34: 01f00b74 .word 0x01f00b74
+ 1f00a38: 01f00b14 .word 0x01f00b14
+ 1f00a3c: 01f00b74 .word 0x01f00b74
+ 1f00a40: 01f00b74 .word 0x01f00b74
+ 1f00a44: 01f00b74 .word 0x01f00b74
+ 1f00a48: 01f00b34 .word 0x01f00b34
+ 1f00a4c: 01f00b74 .word 0x01f00b74
+ 1f00a50: 01f00b74 .word 0x01f00b74
+ 1f00a54: 01f00b74 .word 0x01f00b74
+ 1f00a58: 01f00a5c .word 0x01f00a5c
+ 1f00a5c: e3084098 movw r4, #32920 ; 0x8098
+ 1f00a60: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00a64: e3a00053 mov r0, #83 ; 0x53
+ 1f00a68: eb000ba2 bl 1f038f8
+ 1f00a6c: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00a70: e3500000 cmp r0, #0
+ 1f00a74: 1afffffb bne 1f00a68
+ 1f00a78: e30840d8 movw r4, #32984 ; 0x80d8
+ 1f00a7c: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00a80: e3a00020 mov r0, #32
+ 1f00a84: eb000b9b bl 1f038f8
+ 1f00a88: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00a8c: e3500000 cmp r0, #0
+ 1f00a90: 1afffffb bne 1f00a84
+ 1f00a94: e30840a8 movw r4, #32936 ; 0x80a8
+ 1f00a98: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00a9c: e3a00048 mov r0, #72 ; 0x48
+ 1f00aa0: eb000b94 bl 1f038f8
+ 1f00aa4: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00aa8: e3500000 cmp r0, #0
+ 1f00aac: 1afffffb bne 1f00aa0
+ 1f00ab0: e8bd8ff8 pop {r3, r4, r5, r6, r7, r8, r9, sl, fp, pc}
+ 1f00ab4: e3084068 movw r4, #32872 ; 0x8068
+ 1f00ab8: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00abc: e3a00055 mov r0, #85 ; 0x55
+ 1f00ac0: eb000b8c bl 1f038f8
+ 1f00ac4: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00ac8: e3500000 cmp r0, #0
+ 1f00acc: 1afffffb bne 1f00ac0
+ 1f00ad0: eaffffe8 b 1f00a78
+ 1f00ad4: e3084074 movw r4, #32884 ; 0x8074
+ 1f00ad8: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00adc: e3a00049 mov r0, #73 ; 0x49
+ 1f00ae0: eb000b84 bl 1f038f8
+ 1f00ae4: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00ae8: e3500000 cmp r0, #0
+ 1f00aec: 1afffffb bne 1f00ae0
+ 1f00af0: eaffffe0 b 1f00a78
+ 1f00af4: e3084078 movw r4, #32888 ; 0x8078
+ 1f00af8: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00afc: e3a00053 mov r0, #83 ; 0x53
+ 1f00b00: eb000b7c bl 1f038f8
+ 1f00b04: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00b08: e3500000 cmp r0, #0
+ 1f00b0c: 1afffffb bne 1f00b00
+ 1f00b10: eaffffd8 b 1f00a78
+ 1f00b14: e3084084 movw r4, #32900 ; 0x8084
+ 1f00b18: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00b1c: e3a00041 mov r0, #65 ; 0x41
+ 1f00b20: eb000b74 bl 1f038f8
+ 1f00b24: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00b28: e3500000 cmp r0, #0
+ 1f00b2c: 1afffffb bne 1f00b20
+ 1f00b30: eaffffd0 b 1f00a78
+ 1f00b34: e308408c movw r4, #32908 ; 0x808c
+ 1f00b38: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00b3c: e3a00055 mov r0, #85 ; 0x55
+ 1f00b40: eb000b6c bl 1f038f8
+ 1f00b44: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00b48: e3500000 cmp r0, #0
+ 1f00b4c: 1afffffb bne 1f00b40
+ 1f00b50: eaffffc8 b 1f00a78
+ 1f00b54: e3084070 movw r4, #32880 ; 0x8070
+ 1f00b58: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00b5c: e3a00046 mov r0, #70 ; 0x46
+ 1f00b60: eb000b64 bl 1f038f8
+ 1f00b64: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00b68: e3500000 cmp r0, #0
+ 1f00b6c: 1afffffb bne 1f00b60
+ 1f00b70: eaffffc0 b 1f00a78
+ 1f00b74: e30840a0 movw r4, #32928 ; 0x80a0
+ 1f00b78: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00b7c: e3a00049 mov r0, #73 ; 0x49
+ 1f00b80: eb000b5c bl 1f038f8
+ 1f00b84: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00b88: e3500000 cmp r0, #0
+ 1f00b8c: 1afffffb bne 1f00b80
+ 1f00b90: eaffffb8 b 1f00a78
+ 1f00b94: e3084024 movw r4, #32804 ; 0x8024
+ 1f00b98: e34041f5 movt r4, #501 ; 0x1f5
+ 1f00b9c: e3a00020 mov r0, #32
+ 1f00ba0: eb000b54 bl 1f038f8
+ 1f00ba4: e1f400d1 ldrsb r0, [r4, #1]!
+ 1f00ba8: e3500000 cmp r0, #0
+ 1f00bac: 1afffffb bne 1f00ba0
+ 1f00bb0: eaffff7b b 1f009a4
+
+01f00bb4 :
+ 1f00bb4: e30820e4 movw r2, #32996 ; 0x80e4
+ 1f00bb8: e34021f5 movt r2, #501 ; 0x1f5
+ 1f00bbc: e3a01004 mov r1, #4
+ 1f00bc0: eafffee2 b 1f00750
+
+01f00bc4 :
+ 1f00bc4: e30820fc movw r2, #33020 ; 0x80fc
+ 1f00bc8: e34021f5 movt r2, #501 ; 0x1f5
+ 1f00bcc: e3a01004 mov r1, #4
+ 1f00bd0: eafffede b 1f00750
+
+01f00bd4 :
+ 1f00bd4: e308210c movw r2, #33036 ; 0x810c
+ 1f00bd8: e34021f5 movt r2, #501 ; 0x1f5
+ 1f00bdc: e3a01008 mov r1, #8
+ 1f00be0: eafffeda b 1f00750
+
+01f00be4 :
+ 1f00be4: e3082118 movw r2, #33048 ; 0x8118
+ 1f00be8: e34021f5 movt r2, #501 ; 0x1f5
+ 1f00bec: e3a01004 mov r1, #4
+ 1f00bf0: eafffed6 b 1f00750
+
+01f00bf4 :
+ 1f00bf4: e30a32d7 movw r3, #41687 ; 0xa2d7
+ 1f00bf8: e340320d movt r3, #525 ; 0x20d
+ 1f00bfc: e92d41f0 push {r4, r5, r6, r7, r8, lr}
+ 1f00c00: e5d33000 ldrb r3, [r3]
+ 1f00c04: e7e06251 ubfx r6, r1, #4, #1
+ 1f00c08: e30ae307 movw lr, #41735 ; 0xa307
+ 1f00c0c: e340e20d movt lr, #525 ; 0x20d
+ 1f00c10: e30a22b4 movw r2, #41652 ; 0xa2b4
+ 1f00c14: e340220d movt r2, #525 ; 0x20d
+ 1f00c18: e30a52bf movw r5, #41663 ; 0xa2bf
+ 1f00c1c: e340520d movt r5, #525 ; 0x20d
+ 1f00c20: e30a431c movw r4, #41756 ; 0xa31c
+ 1f00c24: e340420d movt r4, #525 ; 0x20d
+ 1f00c28: e30a0298 movw r0, #41624 ; 0xa298
+ 1f00c2c: e340020d movt r0, #525 ; 0x20d
+ 1f00c30: e30ac2be movw ip, #41662 ; 0xa2be
+ 1f00c34: e340c20d movt ip, #525 ; 0x20d
+ 1f00c38: e1560003 cmp r6, r3
+ 1f00c3c: e5c26000 strb r6, [r2]
+ 1f00c40: e0233006 eor r3, r3, r6
+ 1f00c44: e7e020d1 ubfx r2, r1, #1, #1
+ 1f00c48: e5ce3000 strb r3, [lr]
+ 1f00c4c: e7e011d1 ubfx r1, r1, #3, #1
+ 1f00c50: e5c52000 strb r2, [r5]
+ 1f00c54: e5c41000 strb r1, [r4]
+ 1f00c58: e5d06000 ldrb r6, [r0]
+ 1f00c5c: e5dc7000 ldrb r7, [ip]
+ 1f00c60: 0a00001b beq 1f00cd4
+ 1f00c64: e30ae2ec movw lr, #41708 ; 0xa2ec
+ 1f00c68: e340e20d movt lr, #525 ; 0x20d
+ 1f00c6c: e30a3305 movw r3, #41733 ; 0xa305
+ 1f00c70: e340320d movt r3, #525 ; 0x20d
+ 1f00c74: e3a08001 mov r8, #1
+ 1f00c78: e59ee000 ldr lr, [lr]
+ 1f00c7c: e5c38000 strb r8, [r3]
+ 1f00c80: e35e0000 cmp lr, #0
+ 1f00c84: 0a000017 beq 1f00ce8
+ 1f00c88: e5de301d ldrb r3, [lr, #29]
+ 1f00c8c: e1833008 orr r3, r3, r8
+ 1f00c90: e5ce301d strb r3, [lr, #29]
+ 1f00c94: e5de301e ldrb r3, [lr, #30]
+ 1f00c98: e3130002 tst r3, #2
+ 1f00c9c: e2033008 and r3, r3, #8
+ 1f00ca0: 03a02001 moveq r2, #1
+ 1f00ca4: 05c52000 strbeq r2, [r5]
+ 1f00ca8: e6ef3073 uxtb r3, r3
+ 1f00cac: e5c02000 strb r2, [r0]
+ 1f00cb0: e3530000 cmp r3, #0
+ 1f00cb4: 03a01001 moveq r1, #1
+ 1f00cb8: 05c41000 strbeq r1, [r4]
+ 1f00cbc: e1560002 cmp r6, r2
+ 1f00cc0: e5cc1000 strb r1, [ip]
+ 1f00cc4: 3a00001c bcc 1f00d3c
+ 1f00cc8: e1570001 cmp r7, r1
+ 1f00ccc: 3a000012 bcc 1f00d1c
+ 1f00cd0: e8bd81f0 pop {r4, r5, r6, r7, r8, pc}
+ 1f00cd4: e30a32ec movw r3, #41708 ; 0xa2ec
+ 1f00cd8: e340320d movt r3, #525 ; 0x20d
+ 1f00cdc: e593e000 ldr lr, [r3]
+ 1f00ce0: e35e0000 cmp lr, #0
+ 1f00ce4: 1affffea bne 1f00c94
+ 1f00ce8: e1560002 cmp r6, r2
+ 1f00cec: e5c02000 strb r2, [r0]
+ 1f00cf0: 330a3305 movwcc r3, #41733 ; 0xa305
+ 1f00cf4: 33a02001 movcc r2, #1
+ 1f00cf8: 3340320d movtcc r3, #525 ; 0x20d
+ 1f00cfc: e5cc1000 strb r1, [ip]
+ 1f00d00: 35c32000 strbcc r2, [r3]
+ 1f00d04: e1570001 cmp r7, r1
+ 1f00d08: 330a32d6 movwcc r3, #41686 ; 0xa2d6
+ 1f00d0c: 33a02001 movcc r2, #1
+ 1f00d10: 3340320d movtcc r3, #525 ; 0x20d
+ 1f00d14: 35c32000 strbcc r2, [r3]
+ 1f00d18: e8bd81f0 pop {r4, r5, r6, r7, r8, pc}
+ 1f00d1c: e30a22d6 movw r2, #41686 ; 0xa2d6
+ 1f00d20: e340220d movt r2, #525 ; 0x20d
+ 1f00d24: e5de301d ldrb r3, [lr, #29]
+ 1f00d28: e3a01001 mov r1, #1
+ 1f00d2c: e5c21000 strb r1, [r2]
+ 1f00d30: e3833004 orr r3, r3, #4
+ 1f00d34: e5ce301d strb r3, [lr, #29]
+ 1f00d38: e8bd81f0 pop {r4, r5, r6, r7, r8, pc}
+ 1f00d3c: e30a2305 movw r2, #41733 ; 0xa305
+ 1f00d40: e340220d movt r2, #525 ; 0x20d
+ 1f00d44: e5de301d ldrb r3, [lr, #29]
+ 1f00d48: e1570001 cmp r7, r1
+ 1f00d4c: e3a01001 mov r1, #1
+ 1f00d50: e3833002 orr r3, r3, #2
+ 1f00d54: e5c21000 strb r1, [r2]
+ 1f00d58: e5ce301d strb r3, [lr, #29]
+ 1f00d5c: 330a22d6 movwcc r2, #41686 ; 0xa2d6
+ 1f00d60: 3340220d movtcc r2, #525 ; 0x20d
+ 1f00d64: 35c21000 strbcc r1, [r2]
+ 1f00d68: 3afffff0 bcc 1f00d30
+ 1f00d6c: e8bd81f0 pop {r4, r5, r6, r7, r8, pc}
+
+01f00d70 :
+ 1f00d70: e3100902 tst r0, #32768 ; 0x8000
+ 1f00d74: 112fff1e bxne lr
+ 1f00d78: e7e23550 ubfx r3, r0, #10, #3
+ 1f00d7c: e3530001 cmp r3, #1
+ 1f00d80: 9a000008 bls 1f00da8
+ 1f00d84: e3530006 cmp r3, #6
+ 1f00d88: 0a00000b beq 1f00dbc
+ 1f00d8c: e3530007 cmp r3, #7
+ 1f00d90: 112fff1e bxne lr
+ 1f00d94: e1a02001 mov r2, r1
+ 1f00d98: e1a01000 mov r1, r0
+ 1f00d9c: e3090314 movw r0, #37652 ; 0x9314
+ 1f00da0: e34001fa movt r0, #506 ; 0x1fa
+ 1f00da4: ea00786b b 1f1ef58
+ 1f00da8: e7ea0050 ubfx r0, r0, #0, #11
+ 1f00dac: e30d3270 movw r3, #53872 ; 0xd270
+ 1f00db0: e34031f9 movt r3, #505 ; 0x1f9
+ 1f00db4: e7c31000 strb r1, [r3, r0]
+ 1f00db8: e12fff1e bx lr
+ 1f00dbc: e1a02001 mov r2, r1
+ 1f00dc0: e1a01000 mov r1, r0
+ 1f00dc4: e30902bc movw r0, #37564 ; 0x92bc
+ 1f00dc8: e34001fa movt r0, #506 ; 0x1fa
+ 1f00dcc: ea007861 b 1f1ef58
+
+01f00dd0 :
+ 1f00dd0: e92d4010 push {r4, lr}
+ 1f00dd4: e1a04000 mov r4, r0
+ 1f00dd8: e5900000 ldr r0, [r0]
+ 1f00ddc: e3500000 cmp r0, #0
+ 1f00de0: 0a000000 beq 1f00de8
+ 1f00de4: ebfffe28 bl 1f0068c
+ 1f00de8: e1a00004 mov r0, r4
+ 1f00dec: e8bd8010 pop {r4, pc}
+
+01f00df0 :
+ 1f00df0: e3100902 tst r0, #32768 ; 0x8000
+ 1f00df4: 112fff1e bxne lr
+ 1f00df8: e2103b06 ands r3, r0, #6144 ; 0x1800
+ 1f00dfc: 0a00000c beq 1f00e34
+ 1f00e00: e3530b06 cmp r3, #6144 ; 0x1800
+ 1f00e04: 112fff1e bxne lr
+ 1f00e08: e52de004 push {lr} ; (str lr, [sp, #-4]!)
+ 1f00e0c: e309c27c movw ip, #37500 ; 0x927c
+ 1f00e10: e340c1fa movt ip, #506 ; 0x1fa
+ 1f00e14: e3a0e058 mov lr, #88 ; 0x58
+ 1f00e18: e7e03550 ubfx r3, r0, #10, #1
+ 1f00e1c: e1a02001 mov r2, r1
+ 1f00e20: e1a01000 mov r1, r0
+ 1f00e24: e020c39e mla r0, lr, r3, ip
+ 1f00e28: e49de004 pop {lr} ; (ldr lr, [sp], #4)
+ 1f00e2c: e2800040 add r0, r0, #64 ; 0x40
+ 1f00e30: ea007848 b 1f1ef58
+ 1f00e34: e30d3270 movw r3, #53872 ; 0xd270
+ 1f00e38: e34031f9 movt r3, #505 ; 0x1f9
+ 1f00e3c: e7c31000 strb r1, [r3, r0]
+ 1f00e40: e12fff1e bx lr
+
+01f00e44 :
+ 1f00e44: e92d4070 push {r4, r5, r6, lr}
+ 1f00e48: e1a01000 mov r1, r0
+ 1f00e4c: e1a05000 mov r5, r0
+ 1f00e50: e24ddf96 sub sp, sp, #600 ; 0x258
+ 1f00e54: e3a02001 mov r2, #1
+ 1f00e58: e28d0010 add r0, sp, #16
+ 1f00e5c: eb002fac bl 1f0cd14
+ 1f00e60: e2504000 subs r4, r0, #0
+ 1f00e64: 0a00000b beq 1f00e98
+ 1f00e68: e3083018 movw r3, #32792 ; 0x8018
+ 1f00e6c: e34031f6 movt r3, #502 ; 0x1f6
+ 1f00e70: e2833802 add r3, r3, #131072 ; 0x20000
+ 1f00e74: e3a04000 mov r4, #0
+ 1f00e78: e1a01005 mov r1, r5
+ 1f00e7c: e3080138 movw r0, #33080 ; 0x8138
+ 1f00e80: e34001f5 movt r0, #501 ; 0x1f5
+ 1f00e84: e5c34800 strb r4, [r3, #2048] ; 0x800
+ 1f00e88: eb00f8af bl 1f3f14c
+ 1f00e8c: e1a00004 mov r0, r4
+ 1f00e90: e28ddf96 add sp, sp, #600 ; 0x258
+ 1f00e94: e8bd8070 pop {r4, r5, r6, pc}
+ 1f00e98: e3a00001 mov r0, #1
+ 1f00e9c: e3086018 movw r6, #32792 ; 0x8018
+ 1f00ea0: e34061f6 movt r6, #502 ; 0x1f6
+ 1f00ea4: eb000e9b bl 1f04918
+ 1f00ea8: e2866802 add r6, r6, #131072 ; 0x20000
+ 1f00eac: e28d300c add r3, sp, #12
+ 1f00eb0: e3a02901 mov r2, #16384 ; 0x4000
+ 1f00eb4: e3081018 movw r1, #32792 ; 0x8018
+ 1f00eb8: e34011f6 movt r1, #502 ; 0x1f6
+ 1f00ebc: e28d0010 add r0, sp, #16
+ 1f00ec0: eb0031b0 bl 1f0d588
+ 1f00ec4: e3a020ff mov r2, #255 ; 0xff
+ 1f00ec8: e1a01005 mov r1, r5
+ 1f00ecc: e1a00006 mov r0, r6
+ 1f00ed0: eb00fccb bl 1f40204
+ 1f00ed4: e1a00004 mov r0, r4
+ 1f00ed8: e3a04001 mov r4, #1
+ 1f00edc: e5c64800 strb r4, [r6, #2048] ; 0x800
+ 1f00ee0: eb000e8c bl 1f04918
+ 1f00ee4: e28d0010 add r0, sp, #16
+ 1f00ee8: eb0037ae bl 1f0eda8
+ 1f00eec: e59d300c ldr r3, [sp, #12]
+ 1f00ef0: e1a01005 mov r1, r5
+ 1f00ef4: e308011c movw r0, #33052 ; 0x811c
+ 1f00ef8: e34001f5 movt r0, #501 ; 0x1f5
+ 1f00efc: e2432901 sub r2, r3, #16384 ; 0x4000
+ 1f00f00: e16f2f12 clz r2, r2
+ 1f00f04: e1a022a2 lsr r2, r2, #5
+ 1f00f08: e58d2000 str r2, [sp]
+ 1f00f0c: e3a02901 mov r2, #16384 ; 0x4000
+ 1f00f10: eb00f88d bl 1f3f14c
+ 1f00f14: e1a00004 mov r0, r4
+ 1f00f18: eaffffdc b 1f00e90
+
+01f00f1c :
+ 1f00f1c: e3100902 tst r0, #32768 ; 0x8000
+ 1f00f20: 1a000008 bne 1f00f48
+ 1f00f24: e7e23550 ubfx r3, r0, #10, #3
+ 1f00f28: e3530001 cmp r3, #1
+ 1f00f2c: 9a00000d bls 1f00f68