diff --git a/bdk/libs/compr/blz.c b/bdk/libs/compr/blz.c new file mode 100644 index 00000000..685ef4af --- /dev/null +++ b/bdk/libs/compr/blz.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018 rajkosto + * Copyright (c) 2018 SciresM + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include +#include + +#include "blz.h" + +const blz_footer *blz_get_footer(const unsigned char *compData, unsigned int compDataLen, blz_footer *outFooter) +{ + if (compDataLen < sizeof(blz_footer)) + return NULL; + + const blz_footer *srcFooter = (const blz_footer*)&compData[compDataLen - sizeof(blz_footer)]; + if (outFooter != NULL) + memcpy(outFooter, srcFooter, sizeof(blz_footer)); // Must be a memcpy because no umaligned accesses on ARMv4. + + return srcFooter; +} + +// From https://github.com/SciresM/hactool/blob/master/kip.c which is exactly how kernel does it, thanks SciresM! +int blz_uncompress_inplace(unsigned char *dataBuf, unsigned int compSize, const blz_footer *footer) +{ + u32 addl_size = footer->addl_size; + u32 header_size = footer->header_size; + u32 cmp_and_hdr_size = footer->cmp_and_hdr_size; + + unsigned char* cmp_start = &dataBuf[compSize] - cmp_and_hdr_size; + u32 cmp_ofs = cmp_and_hdr_size - header_size; + u32 out_ofs = cmp_and_hdr_size + addl_size; + + while (out_ofs) + { + unsigned char control = cmp_start[--cmp_ofs]; + for (unsigned int i=0; i<8; i++) + { + if (control & 0x80) + { + if (cmp_ofs < 2) + return 0; // Out of bounds. + + cmp_ofs -= 2; + u16 seg_val = ((unsigned int)(cmp_start[cmp_ofs + 1]) << 8) | cmp_start[cmp_ofs]; + u32 seg_size = ((seg_val >> 12) & 0xF) + 3; + u32 seg_ofs = (seg_val & 0x0FFF) + 3; + if (out_ofs < seg_size) // Kernel restricts segment copy to stay in bounds. + seg_size = out_ofs; + + out_ofs -= seg_size; + + for (unsigned int j = 0; j < seg_size; j++) + cmp_start[out_ofs + j] = cmp_start[out_ofs + j + seg_ofs]; + } + else + { + // Copy directly. + if (cmp_ofs < 1) + return 0; //out of bounds + + cmp_start[--out_ofs] = cmp_start[--cmp_ofs]; + } + control <<= 1; + if (out_ofs == 0) // Blz works backwards, so if it reaches byte 0, it's done. + return 1; + } + } + + return 1; +} + +int blz_uncompress_srcdest(const unsigned char *compData, unsigned int compDataLen, unsigned char *dstData, unsigned int dstSize) +{ + blz_footer footer; + const blz_footer *compFooterPtr = blz_get_footer(compData, compDataLen, &footer); + if (compFooterPtr == NULL) + return 0; + + // Decompression must be done in-place, so need to copy the relevant compressed data first. + unsigned int numCompBytes = (const unsigned char*)(compFooterPtr)-compData; + memcpy(dstData, compData, numCompBytes); + memset(&dstData[numCompBytes], 0, dstSize - numCompBytes); + + return blz_uncompress_inplace(dstData, compDataLen, &footer); +} diff --git a/bdk/libs/compr/blz.h b/bdk/libs/compr/blz.h new file mode 100644 index 00000000..a1cce376 --- /dev/null +++ b/bdk/libs/compr/blz.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 rajkosto + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _BLZ_H_ +#define _BLZ_H_ + +#include + +typedef struct _blz_footer +{ + u32 cmp_and_hdr_size; + u32 header_size; + u32 addl_size; +} blz_footer; + +// Returns pointer to footer in compData if present, additionally copies it to outFooter if not NULL. +const blz_footer *blz_get_footer(const unsigned char *compData, unsigned int compDataLen, blz_footer *outFooter); +// Returns 0 on failure. +int blz_uncompress_inplace(unsigned char *dataBuf, unsigned int compSize, const blz_footer *footer); +// Returns 0 on failure. +int blz_uncompress_srcdest(const unsigned char *compData, unsigned int compDataLen, unsigned char *dstData, unsigned int dstSize); + +#endif diff --git a/bdk/libs/compr/lz.c b/bdk/libs/compr/lz.c new file mode 100644 index 00000000..94b64c6f --- /dev/null +++ b/bdk/libs/compr/lz.c @@ -0,0 +1,181 @@ +/************************************************************************* +* Name: lz.c +* Author: Marcus Geelnard +* Description: LZ77 coder/decoder implementation. +* Reentrant: Yes +* +* The LZ77 compression scheme is a substitutional compression scheme +* proposed by Abraham Lempel and Jakob Ziv in 1977. It is very simple in +* its design, and uses no fancy bit level compression. +* +* This is my first attempt at an implementation of a LZ77 code/decoder. +* +* The principle of the LZ77 compression algorithm is to store repeated +* occurrences of strings as references to previous occurrences of the same +* string. The point is that the reference consumes less space than the +* string itself, provided that the string is long enough (in this +* implementation, the string has to be at least 4 bytes long, since the +* minimum coded reference is 3 bytes long). Also note that the term +* "string" refers to any kind of byte sequence (it does not have to be +* an ASCII string, for instance). +* +* The coder uses a brute force approach to finding string matches in the +* history buffer (or "sliding window", if you wish), which is very, very +* slow. I recon the complexity is somewhere between O(n^2) and O(n^3), +* depending on the input data. +* +* There is also a faster implementation that uses a large working buffer +* in which a "jump table" is stored, which is used to quickly find +* possible string matches (see the source code for LZ_CompressFast() for +* more information). The faster method is an order of magnitude faster, +* but still quite slow compared to other compression methods. +* +* The upside is that decompression is very fast, and the compression ratio +* is often very good. +* +* The reference to a string is coded as a (length,offset) pair, where the +* length indicates the length of the string, and the offset gives the +* offset from the current data position. To distinguish between string +* references and literal strings (uncompressed bytes), a string reference +* is preceded by a marker byte, which is chosen as the least common byte +* symbol in the input data stream (this marker byte is stored in the +* output stream as the first byte). +* +* Occurrences of the marker byte in the stream are encoded as the marker +* byte followed by a zero byte, which means that occurrences of the marker +* byte have to be coded with two bytes. +* +* The lengths and offsets are coded in a variable length fashion, allowing +* values of any magnitude (up to 4294967295 in this implementation). +* +* With this compression scheme, the worst case compression result is +* (257/256)*insize + 1. +* +*------------------------------------------------------------------------- +* Copyright (c) 2003-2006 Marcus Geelnard +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would +* be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not +* be misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source +* distribution. +* +* Marcus Geelnard +* marcus.geelnard at home.se +*************************************************************************/ + + +/************************************************************************* +* INTERNAL FUNCTIONS * +*************************************************************************/ + + +/************************************************************************* +* _LZ_ReadVarSize() - Read unsigned integer with variable number of +* bytes depending on value. +*************************************************************************/ + +static int _LZ_ReadVarSize( unsigned int * x, const unsigned char * buf ) +{ + unsigned int y, b, num_bytes; + + /* Read complete value (stop when byte contains zero in 8:th bit) */ + y = 0; + num_bytes = 0; + do + { + b = (unsigned int) (*buf ++); + y = (y << 7) | (b & 0x0000007f); + ++ num_bytes; + } + while( b & 0x00000080 ); + + /* Store value in x */ + *x = y; + + /* Return number of bytes read */ + return num_bytes; +} + + + +/************************************************************************* +* PUBLIC FUNCTIONS * +*************************************************************************/ + + +/************************************************************************* +* LZ_Uncompress() - Uncompress a block of data using an LZ77 decoder. +* in - Input (compressed) buffer. +* out - Output (uncompressed) buffer. This buffer must be large +* enough to hold the uncompressed data. +* insize - Number of input bytes. +*************************************************************************/ + +unsigned int LZ_Uncompress( const unsigned char *in, unsigned char *out, + unsigned int insize ) +{ + unsigned char marker, symbol; + unsigned int i, inpos, outpos, length, offset; + + /* Do we have anything to uncompress? */ + if( insize < 1 ) + { + return 0; + } + + /* Get marker symbol from input stream */ + marker = in[ 0 ]; + inpos = 1; + + /* Main decompression loop */ + outpos = 0; + do + { + symbol = in[ inpos ++ ]; + if( symbol == marker ) + { + /* We had a marker byte */ + if( in[ inpos ] == 0 ) + { + /* It was a single occurrence of the marker byte */ + out[ outpos ++ ] = marker; + ++ inpos; + } + else + { + /* Extract true length and offset */ + inpos += _LZ_ReadVarSize( &length, &in[ inpos ] ); + inpos += _LZ_ReadVarSize( &offset, &in[ inpos ] ); + + /* Copy corresponding data from history window */ + for( i = 0; i < length; ++ i ) + { + out[ outpos ] = out[ outpos - offset ]; + ++ outpos; + } + } + } + else + { + /* No marker, plain copy */ + out[ outpos ++ ] = symbol; + } + } + while( inpos < insize ); + + return outpos; +} diff --git a/bdk/libs/compr/lz.h b/bdk/libs/compr/lz.h new file mode 100644 index 00000000..ef670553 --- /dev/null +++ b/bdk/libs/compr/lz.h @@ -0,0 +1,52 @@ +/************************************************************************* +* Name: lz.h +* Author: Marcus Geelnard +* Description: LZ77 coder/decoder interface. +* Reentrant: Yes +*------------------------------------------------------------------------- +* Copyright (c) 2003-2006 Marcus Geelnard +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would +* be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not +* be misrepresented as being the original software. +* +* 3. This notice may not be removed or altered from any source +* distribution. +* +* Marcus Geelnard +* marcus.geelnard at home.se +*************************************************************************/ + +#ifndef _lz_h_ +#define _lz_h_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************************************************************* +* Function prototypes +*************************************************************************/ + +unsigned int LZ_Uncompress( const unsigned char *in, unsigned char *out, + unsigned int insize ); + + +#ifdef __cplusplus +} +#endif + +#endif /* _lz_h_ */ diff --git a/bdk/libs/compr/lz4.c b/bdk/libs/compr/lz4.c new file mode 100644 index 00000000..ddea3d80 --- /dev/null +++ b/bdk/libs/compr/lz4.c @@ -0,0 +1,1672 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2017, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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 + OWNER 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. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define ACCELERATION_DEFAULT 1 + + +/*-************************************ +* Dependency +**************************************/ +#define LZ4_STATIC_LINKING_ONLY +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2_GCC_PPC64LE and LZ4_FORCE_O2_INLINE_GCC_PPC64LE + * Gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) +# define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2"))) +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE +#else +# define LZ4_FORCE_O2_GCC_PPC64LE +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + + +/*-************************************ +* Memory routines +**************************************/ +#include /* malloc, calloc, free */ +#define ALLOC(s) malloc(s) +#define ALLOC_AND_ZERO(s) calloc(1,s) +#define FREEMEM free +#include /* memset, memcpy */ +#define MEM_INIT memset + + +/*-************************************ +* Basic Types +**************************************/ +typedef uint16_t U16; +typedef uint32_t U32; +typedef int32_t S32; +typedef uint64_t U64; +typedef uintptr_t uptrval; +typedef size_t reg_t; /* 32-bits in x32 mode */ + +/*-************************************ +* Reading and writing into memory +**************************************/ +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE +void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,8); d+=8; s+=8; } while (d=2) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + if (LZ4_isLittleEndian()) { + if (sizeof(val)==8) { + return (__builtin_ctzll((U64)val) >> 3); + } else /* 32 bits */ { + return (__builtin_ctz((U32)val) >> 3); + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { /* 64-bits */ + return (__builtin_clzll((U64)val) >> 3); + } else /* 32 bits */ { + return (__builtin_clz((U32)val) >> 3); + } + } +} + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive; +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Like usingExtDict, but everything concerning the preceding + * content is in a separate context, pointed to by + * ctx->dictCtx. ctx->dictionary, ctx->dictSize, and table + * entries in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { full = 0, partial = 1 } earlyEnd_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + + +/*-****************************** +* Compression functions +********************************/ +static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + static const U64 prime5bytes = 889523592379ULL; + static const U64 prime8bytes = 11400714785074694791ULL; + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + else + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +static void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)idx; return; } + } +} + +static void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +static U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h]; } + if (tableType == byU16) { const U16* const hashTable = (const U16*) tableBase; return hashTable[h]; } + return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void LZ4_prepareTable( + LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if (cctx->tableType != clearedTable) { + if (cctx->tableType != tableType + || (tableType == byU16 && cctx->currentOffset + inputSize >= 0xFFFFU) + || (tableType == byU32 && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > MAX_DISTANCE back, is faster + * than compressing without a gap. However, compressing with + * currentOffset == 0 is faster still, so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + const int maxOutputSize, + const limitedOutput_directive outputLimited, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const U32 acceleration) +{ + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary + dictSize; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = dictDirective == usingDictCtx ? + dictionary + dictSize - dictCtx->currentOffset : /* is it possible that dictCtx->currentOffset != dictCtx->dictSize ? Yes if the dictionary context is not reset */ + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, tableType=%u", inputSize, tableType); + /* Init conditions */ + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+MAX_DISTANCE < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) continue; /* match outside of valid area */ + if ((tableType != byU16) && (current - matchIndex > MAX_DISTANCE)) continue; /* too far - note: works even if matchIndex overflows */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputLimited) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) + return 0; + if (litLength >= RUN_MASK) { + int len = (int)litLength-RUN_MASK; + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += MINMATCH + matchCode; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += MINMATCH + matchCode; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ( outputLimited && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) + return 0; + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+MAX_DISTANCE >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && ((tableType==byU16) ? 1 : (current - matchIndex <= MAX_DISTANCE)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t const lastRun = (size_t)(iend - anchor); + if ( (outputLimited) && /* Check output buffer overflow */ + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) + return 0; + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun<internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + LZ4_resetStream((LZ4_stream_t*)state); + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > MAX_DISTANCE)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) {; + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > MAX_DISTANCE)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > MAX_DISTANCE)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > MAX_DISTANCE)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + + return result; +} + + +int LZ4_compress_default(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_fast(source, dest, inputSize, maxOutputSize, 1); +} + + +/* hidden debug function */ +/* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ +int LZ4_compress_fast_force(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t ctx; + LZ4_resetStream(&ctx); + + if (inputSize < LZ4_64Klimit) + return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + else + return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, sizeof(void*)==8 ? byU32 : byPtr, noDict, noDictIssue, acceleration); +} + + +/*-****************************** +* *_destSize() variant +********************************/ + +static int LZ4_compress_destSize_generic( + LZ4_stream_t_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + const int targetDstSize, + const tableType_t tableType) +{ + const BYTE* ip = (const BYTE*) src; + const BYTE* base = (const BYTE*) src; + const BYTE* lowLimit = (const BYTE*) src; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + targetDstSize; + BYTE* const oMaxLit = op + targetDstSize - 2 /* offset */ - 8 /* because 8+MINMATCH==MFLIMIT */ - 1 /* token */; + BYTE* const oMaxMatch = op + targetDstSize - (LASTLITERALS + 1 /* token */); + BYTE* const oMaxSeq = oMaxLit - 1 /* token */; + + U32 forwardH; + + + /* Init conditions */ + if (targetDstSize < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size, too large (or negative) */ + if ((tableType == byU16) && (*srcSizePtr>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + if (*srcSizePtrhashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + + /* Find a match */ + { const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = 1 << LZ4_skipTrigger; + + do { + U32 h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimit)) goto _last_literals; + + match = LZ4_getPositionOnHash(h, ctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, ctx->hashTable, tableType, base); + + } while ( ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + } + + /* Catch up */ + while ((ip>anchor) && (match > lowLimit) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literal length */ + { unsigned litLength = (unsigned)(ip - anchor); + token = op++; + if (op + ((litLength+240)/255) + litLength > oMaxLit) { + /* Not enough space for a last match */ + op--; + goto _last_literals; + } + if (litLength>=RUN_MASK) { + unsigned len = litLength - RUN_MASK; + *token=(RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< oMaxMatch) { + /* Match description too long : reduce it */ + matchLength = (15-1) + (oMaxMatch-op) * 255; + } + ip += MINMATCH + matchLength; + + if (matchLength>=ML_MASK) { + *token += ML_MASK; + matchLength -= ML_MASK; + while (matchLength >= 255) { matchLength-=255; *op++ = 255; } + *op++ = (BYTE)matchLength; + } + else *token += (BYTE)(matchLength); + } + + anchor = ip; + + /* Test end of block */ + if (ip > mflimit) break; + if (op > oMaxSeq) break; + + /* Fill table */ + LZ4_putPosition(ip-2, ctx->hashTable, tableType, base); + + /* Test next position */ + match = LZ4_getPosition(ip, ctx->hashTable, tableType, base); + LZ4_putPosition(ip, ctx->hashTable, tableType, base); + if ( (match+MAX_DISTANCE>=ip) + && (LZ4_read32(match)==LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); + if (op + 1 /* token */ + ((lastRunSize+240)/255) /* litLength */ + lastRunSize /* literals */ > oend) { + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (oend-op) - 1; + lastRunSize -= (lastRunSize+240)/255; + } + ip = anchor + lastRunSize; + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize<= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, byU16); + } else { + tableType_t const tableType = ((sizeof(void*)==4) && ((uptrval)src > MAX_DISTANCE)) ? byPtr : byU32; + return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, tableType); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_resetStream(lz4s); + return lz4s; +} + +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + LZ4_prepareTable(dict, 0, tableType); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - 64 KB - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->currentOffset += 64 KB; + dict->tableType = tableType; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream) { + if (dictionary_stream != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (working_stream->internal_donotuse.currentOffset == 0) { + working_stream->internal_donotuse.currentOffset = 64 KB; + } + working_stream->internal_donotuse.dictCtx = &(dictionary_stream->internal_donotuse); + } else { + working_stream->internal_donotuse.dictCtx = NULL; + } +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + if (LZ4_dict->currentOffset + nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* streamPtr = &LZ4_stream->internal_donotuse; + const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; + + if (streamPtr->initCheck) return 0; /* Uninitialized structure detected */ + LZ4_renormDictT(streamPtr, inputSize); /* avoid index overflow */ + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) source + inputSize; + if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == (const BYTE*)source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + memcpy(streamPtr, streamPtr->dictCtx, sizeof(LZ4_stream_t)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : you don't need to call LZ4_loadDict() afterwards, + * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue(). + * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + + if ((U32)dictSize > 64 KB) dictSize = 64 KB; /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) dictSize = dict->dictSize; + + memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-***************************** +* Decompression functions +*******************************/ +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_O2_GCC_PPC64LE +LZ4_FORCE_INLINE int LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + int endOnInput, /* endOnOutputSize, endOnInputSize */ + int partialDecoding, /* full, partial */ + int targetOutputSize, /* only used if partialDecoding==partial */ + int dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + BYTE* oexit = op + targetOutputSize; + + const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; + const unsigned inc32table[8] = {0, 1, 2, 1, 0, 4, 4, 4}; + const int dec64table[8] = {0, 0, 0, -1, -4, 1, 2, 3}; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Special cases */ + if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => just decode everything */ + if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + + /* Main Loop : decode sequences */ + while (1) { + size_t length; + const BYTE* match; + size_t offset; + + unsigned const token = *ip++; + + /* shortcut for common case : + * in most circumstances, we expect to decode small matches (<= 18 bytes) separated by few literals (<= 14 bytes). + * this shortcut was tested on x86 and x64, where it improves decoding speed. + * it has not yet been benchmarked on ARM, Power, mips, etc. */ + if (((ip + 14 /*maxLL*/ + 2 /*offset*/ <= iend) + & (op + 14 /*maxLL*/ + 18 /*maxML*/ <= oend)) + & ((token < (15<> ML_BITS; + size_t const off = LZ4_readLE16(ip+ll); + const BYTE* const matchPtr = op + ll - off; /* pointer underflow risk ? */ + if ((off >= 8) /* do not deal with overlapping matches */ & (matchPtr >= lowPrefix)) { + size_t const ml = (token & ML_MASK) + MINMATCH; + memcpy(op, ip, 16); op += ll; ip += ll + 2 /*offset*/; + memcpy(op + 0, matchPtr + 0, 8); + memcpy(op + 8, matchPtr + 8, 8); + memcpy(op +16, matchPtr +16, 2); + op += ml; + continue; + } + } + + /* decode literal length */ + if ((length=(token>>ML_BITS)) == RUN_MASK) { + unsigned s; + do { + s = *ip++; + length += s; + } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + if (partialDecoding) { + if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ + if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + } else { + if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + } + memcpy(op, ip, length); + ip += length; + op += length; + break; /* Necessarily EOF, due to parsing restrictions */ + } + LZ4_wildCopy(op, ip, cpy); + ip += length; op = cpy; + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ + + /* get matchlength */ + length = token & ML_MASK; + if (length == ML_MASK) { + unsigned s; + do { + s = *ip++; + if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + length += s; + } while (s==255); + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + + /* check external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + + if (length <= (size_t)(lowPrefix-match)) { + /* match can be copied as a single segment from external dictionary */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match encompass external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix-match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + if (unlikely(offset<8)) { + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { memcpy(op, match, 8); match+=8; } + op += 8; + + if (unlikely(cpy>oend-12)) { + BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op16) LZ4_wildCopy(op+8, match+8, cpy); + } + op = cpy; /* correction */ + } + + /* end of decoding */ + if (endOnInput) + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + else + return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + + /* Overflow error detected */ +_output_error: + return (int) (-(((const char*)ip)-src))-1; +} + + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_partial(const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, partial, targetOutputSize, noDict, (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)(dest - 64 KB), NULL, 64 KB); +} + + +/*===== streaming decompression functions =====*/ + +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); + return lz4s; +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} + +/*! + * LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * Return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t) dictSize; + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixEnd == (BYTE*)dest) { + result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, full, 0, + usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += result; + lz4sd->prefixEnd += result; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, full, 0, + usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixEnd == (BYTE*)dest) { + result = LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, full, 0, + usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, full, 0, + usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +LZ4_FORCE_O2_GCC_PPC64LE +LZ4_FORCE_INLINE int LZ4_decompress_usingDict_generic(const char* source, char* dest, int compressedSize, int maxOutputSize, int safe, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest, NULL, 0); + if (dictStart+dictSize == dest) { + if (dictSize >= (int)(64 KB - 1)) + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, withPrefix64k, (BYTE*)dest-64 KB, NULL, 0); + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest-dictSize, NULL, 0); + } + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + return LZ4_decompress_usingDict_generic(source, dest, compressedSize, maxOutputSize, 1, dictStart, dictSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + return LZ4_decompress_usingDict_generic(source, dest, 0, originalSize, 0, dictStart, dictSize); +} + +/* debug function */ +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) { return LZ4_compress_default(source, dest, inputSize, maxOutputSize); } +int LZ4_compress(const char* source, char* dest, int inputSize) { return LZ4_compress_default(source, dest, inputSize, LZ4_compressBound(inputSize)); } +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); } +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); } +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, maxDstSize, 1); } +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) { return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); } + +/* +These function names are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) { return LZ4_decompress_fast(source, dest, outputSize); } +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) { return LZ4_decompress_safe(source, dest, isize, maxOutputSize); } + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +/* Obsolete streaming decompression functions */ + +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); +} + +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/bdk/libs/compr/lz4.h b/bdk/libs/compr/lz4.h new file mode 100644 index 00000000..0dfa19e0 --- /dev/null +++ b/bdk/libs/compr/lz4.h @@ -0,0 +1,569 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2017, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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 + OWNER 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. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h provides block compression functions. It gives full buffer control to user. + Decompressing an lz4-compressed block also requires metadata (such as compressed size). + Each application is free to encode such metadata in whichever way it wants. + + An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md), + take care of encoding standard metadata alongside LZ4-compressed blocks. + If your application requires interoperability, it's recommended to use it. + A library is provided to take care of it, see lz4frame.h. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 8 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; unseful to check dll version */ + + +/*-************************************ +* Tuning parameter +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio + * Reduced memory usage may improve speed, thanks to cache effect + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE 14 +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + Compresses 'srcSize' bytes from buffer 'src' + into already allocated 'dst' buffer of size 'dstCapacity'. + Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + It also runs faster, so it's a recommended setting. + If the function cannot compress 'src' into a more limited 'dst' budget, + compression stops *immediately*, and the function result is zero. + Note : as a consequence, 'dst' content is not valid. + Note 2 : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + dstCapacity : size of buffer 'dst' (which must be already allocated) + return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + or 0 if compression fails */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + compressedSize : is the exact complete size of the compressed block. + dstCapacity : is the size of destination buffer, which must be already allocated. + return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + If destination buffer is not large enough, decoding will stop and output an error code (negative value). + If the source stream is detected malformed, the function will stop decoding and return a negative result. + This function is protected against malicious data packets. +*/ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! +LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! +LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! +LZ4_compress_fast_extState() : + Same compression function, just using an externally allocated memory space to store compression state. + Use LZ4_sizeofState() to know how much memory must be allocated, + and allocate it on 8-bytes boundaries (using malloc() typically). + Then, provide it as 'void* state' to compression function. +*/ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! +LZ4_compress_destSize() : + Reverse the logic : compresses as much data as possible from 'src' buffer + into already allocated buffer 'dst' of size 'targetDestSize'. + This function either compresses the entire 'src' content into 'dst' if it's large enough, + or fill 'dst' buffer completely with as much data as possible from 'src'. + *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + New value is necessarily <= old value. + return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + or 0 if compression fails +*/ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! +LZ4_decompress_fast() : **unsafe!** +This function is a bit faster than LZ4_decompress_safe(), +but doesn't provide any security guarantee. + originalSize : is the uncompressed size to regenerate + Destination buffer must be already allocated, and its size must be >= 'originalSize' bytes. + return : number of bytes read from source buffer (== compressed size). + If the source stream is detected malformed, the function stops decoding and return a negative result. + note : This function respects memory boundaries for *properly formed* compressed data. + However, it does not provide any protection against malicious input. + It also doesn't know 'src' size, and implies it's >= compressed size. + Use this function in trusted environment **only**. +*/ +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); + +/*! +LZ4_decompress_safe_partial() : + This function decompress a compressed block of size 'srcSize' at position 'src' + into destination buffer 'dst' of size 'dstCapacity'. + The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that. + However, it's not accurate, and may write more than 'targetOutputSize' (but always <= dstCapacity). + @return : the number of bytes decoded in the destination buffer (necessarily <= dstCapacity) + Note : this number can also be < targetOutputSize, if compressed block contains less data. + Therefore, always control how many bytes were decoded. + If source stream is detected malformed, function returns a negative result. + This function is protected against malicious data packets. +*/ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/*! LZ4_createStream() and LZ4_freeStream() : + * LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure. + * LZ4_freeStream() releases its memory. + */ +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure can be allocated once and re-used multiple times. + * Use this function to start compressing a new stream. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to load a static dictionary into LZ4_stream_t. + * Any previous data will be forgotten, only 'dictionary' will remain in memory. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * Important : The previous 64KB of compressed data is assumed to remain present and unmodified in memory! + * + * Special 1 : When input is a double-buffer, they can have any size, including < 64 KB. + * Make sure that buffers are separated by at least one byte. + * This way, each block only depends on previous block. + * Special 2 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * After an error, the stream status is invalid, it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking structure. + * A tracking structure can be re-used multiple times sequentially. */ +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t structure can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionnally be set. Use NULL or size 0 for a reset order. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accept one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded all the data must be present. + * + * Special : if application sets a ring buffer for decompression, it must respect one of the following conditions : + * - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions) + * In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB). + * - Larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * maxBlockSize is implementation dependent. It's the maximum size of any single block. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * - _At least_ 64 KB + 8 bytes + maxBlockSize. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including larger than decoding buffer. + * Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer, + * and indicate where it is saved using LZ4_setStreamDecode() before decompressing next block. +*/ +LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + */ +LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-************************************ + * Unstable declarations + ************************************** + * Declarations in this section should be considered unstable. + * Use at your own peril, etc., etc. + * They may be removed in the future. + * Their signatures may change. + **************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +/*! LZ4_resetStream_fast() : + * When an LZ4_stream_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStream()). + * + * LZ4_streams are guaranteed to be in a valid state when: + * - returned from LZ4_createStream() + * - reset by LZ4_resetStream() + * - memset(stream, 0, sizeof(LZ4_stream_t)) + * - the stream was in a valid state and was reset by LZ4_resetStream_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_fast_extState()) and that + * returned success + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly + * initialized"). From a high level, the difference is that this function + * initializes the provided state with a call to LZ4_resetStream_fast() while + * LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_API void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream); + +#endif + +/*-************************************ + * Private definitions + ************************************** + * Do not use these definitions. + * They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Using these definitions will expose code to API and/or ABI break in future versions of the library. + **************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +#include + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint16_t initCheck; + uint16_t tableType; + const uint8_t* dictionary; + const LZ4_stream_t_internal* dictCtx; + uint32_t dictSize; +}; + +typedef struct { + const uint8_t* externalDict; + size_t extDictSize; + const uint8_t* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#else + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + unsigned int hashTable[LZ4_HASH_SIZE_U32]; + unsigned int currentOffset; + unsigned short initCheck; + unsigned short tableType; + const unsigned char* dictionary; + const LZ4_stream_t_internal* dictCtx; + unsigned int dictSize; +}; + +typedef struct { + const unsigned char* externalDict; + size_t extDictSize; + const unsigned char* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#endif + +/*! + * LZ4_stream_t : + * information structure to track an LZ4 stream. + * init this structure before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * it may change in a future version ! + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_stream_t */ + + +/*! + * LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode (or memset()) before first use + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 4 +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + Should deprecation warnings be a problem, + it is generally possible to disable them, + typically with -Wno-deprecated-declarations for gcc + or _CRT_SECURE_NO_WARNINGS in Visual. + Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif (LZ4_GCC_VERSION >= 301) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") +# define LZ4_DEPRECATED(message) +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/* Obsolete compression functions */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* source, char* dest, int sourceSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete decompression functions */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/* Obsolete streaming decoding functions */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +#endif /* LZ4_H_2983827168210 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/bdk/libs/fatfs/diskio.h b/bdk/libs/fatfs/diskio.h new file mode 100644 index 00000000..6959fb4f --- /dev/null +++ b/bdk/libs/fatfs/diskio.h @@ -0,0 +1,88 @@ +/*-----------------------------------------------------------------------/ +/ Low level disk interface modlue include file (C)ChaN, 2014 / +/-----------------------------------------------------------------------*/ + +#ifndef _DISKIO_DEFINED +#define _DISKIO_DEFINED + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* 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; + +typedef enum { + DRIVE_SD = 0, + DRIVE_RAM = 1, + DRIVE_EMMC = 2, + DRIVE_BIS = 3 +} DDRIVE; + + +/*---------------------------------------*/ +/* Prototypes for disk control functions */ + + +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); +DRESULT disk_set_info (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 FF_FS_READONLY == 0) */ +#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ +#define SET_SECTOR_COUNT 1 /* Set media size (needed at FF_USE_MKFS == 1) */ +#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ +#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ +#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_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/bdk/libs/fatfs/ff.c b/bdk/libs/fatfs/ff.c new file mode 100644 index 00000000..60288911 --- /dev/null +++ b/bdk/libs/fatfs/ff.c @@ -0,0 +1,6861 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2018-2019 CTCaer + * Copyright (c) 2020 Storm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem Module R0.13c (p4) / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2018, 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 */ +#include + +#define EFSPRINTF(text, ...) print_error(); gfx_printf("%k"text"%k\n", 0xFFFFFF00, 0xFFFFFFFF); +//#define EFSPRINTF(...) + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if FF_DEFINED != 86604 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + + +/* Limits and boundaries */ +#define MAX_DIR 0x200000 /* Max size of FAT directory */ +#define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */ +#define MAX_FAT12 0xFF5 /* Max FAT12 clusters (differs from specs, but right for real DOS/Windows behavior) */ +#define MAX_FAT16 0xFFF5 /* Max FAT16 clusters (differs from specs, but right for real DOS/Windows behavior) */ +#define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */ +#define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */ + + +/* 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') +#define IsSurrogate(c) ((c) >= 0xD800 && (c) <= 0xDFFF) +#define IsSurrogateH(c) ((c) >= 0xD800 && (c) <= 0xDBFF) +#define IsSurrogateL(c) ((c) >= 0xDC00 && (c) <= 0xDFFF) + + +/* Additional file access control and file status flags for 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 */ + + +/* Additional file attribute bits for internal use */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_MASK 0x3F /* Mask of defined bits */ + + +/* Name status flags in fn[11] */ +#define NSFLAG 11 /* Index of the name status byte */ +#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 */ + + +/* exFAT directory entry types */ +#define ET_BITMAP 0x81 /* Allocation bitmap */ +#define ET_UPCASE 0x82 /* Up-case table */ +#define ET_VLABEL 0x83 /* Volume label */ +#define ET_FILEDIR 0x85 /* File and directory */ +#define ET_STREAM 0xC0 /* Stream extension */ +#define ET_FILENAME 0xC1 /* Name extension */ + + +/* FatFs refers the FAT structure as simple byte array instead of structure member +/ because the C 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 FAT [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 /* Number of sectors per track 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 /* WindowsNT 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 /* Filesystem 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: Filesystem 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: Filesystem 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 start cluster (DWORD) */ +#define BPB_VolIDEx 100 /* exFAT: Volume serial number (DWORD) */ +#define BPB_FSVerEx 104 /* exFAT: Filesystem version (WORD) */ +#define BPB_VolFlagEx 106 /* exFAT: Volume flags (WORD) */ +#define BPB_BytsPerSecEx 108 /* exFAT: Log2 of sector size in unit of byte (BYTE) */ +#define BPB_SecPerClusEx 109 /* exFAT: Log2 of cluster size in unit of 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 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: LFN order and LLE flag (BYTE) */ +#define LDIR_Attr 11 /* LFN: LFN attribute (BYTE) */ +#define LDIR_Type 12 /* LFN: Entry type (BYTE) */ +#define LDIR_Chksum 13 /* LFN: Checksum of the SFN (BYTE) */ +#define LDIR_FstClusLO 26 /* LFN: MBZ field (WORD) */ +#define XDIR_Type 0 /* exFAT: Type of exFAT directory entry (BYTE) */ +#define XDIR_NumLabel 1 /* exFAT: Number of volume label characters (BYTE) */ +#define XDIR_Label 2 /* exFAT: Volume label (11-WORD) */ +#define XDIR_CaseSum 4 /* exFAT: Sum of case conversion table (DWORD) */ +#define XDIR_NumSec 1 /* exFAT: Number of secondary entries (BYTE) */ +#define XDIR_SetSum 2 /* exFAT: Sum of the set of directory entries (WORD) */ +#define XDIR_Attr 4 /* exFAT: File attribute (WORD) */ +#define XDIR_CrtTime 8 /* exFAT: Created time (DWORD) */ +#define XDIR_ModTime 12 /* exFAT: Modified time (DWORD) */ +#define XDIR_AccTime 16 /* exFAT: Last accessed time (DWORD) */ +#define XDIR_CrtTime10 20 /* exFAT: Created time subsecond (BYTE) */ +#define XDIR_ModTime10 21 /* exFAT: Modified time subsecond (BYTE) */ +#define XDIR_CrtTZ 22 /* exFAT: Created timezone (BYTE) */ +#define XDIR_ModTZ 23 /* exFAT: Modified timezone (BYTE) */ +#define XDIR_AccTZ 24 /* exFAT: Last accessed timezone (BYTE) */ +#define XDIR_GenFlags 33 /* exFAT: General secondary flags (BYTE) */ +#define XDIR_NumName 35 /* exFAT: Number of file name characters (BYTE) */ +#define XDIR_NameHash 36 /* exFAT: Hash of file name (WORD) */ +#define XDIR_ValidFileSize 40 /* exFAT: Valid file size (QWORD) */ +#define XDIR_FstClus 52 /* exFAT: First cluster of the file data (DWORD) */ +#define XDIR_FileSize 56 /* exFAT: File/Directory size (QWORD) */ + +#define SZDIRE 32 /* Size of a directory entry */ +#define DDEM 0xE5 /* Deleted directory entry mark set to DIR_Name[0] */ +#define RDDEM 0x05 /* Replacement of the character collides with DDEM */ +#define LLEF 0x40 /* Last long entry flag in LDIR_Ord */ + +#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 */ + + +/* Post process on fatal error in the file operations */ +#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); } + + +/* Re-entrancy related */ +#if FF_FS_REENTRANT +#if FF_USE_LFN == 1 +#error Static LFN work area cannot be used at thread-safe configuration +#endif +#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; } +#else +#define LEAVE_FF(fs, res) return res +#endif + + +/* Definitions of volume - physical location conversion */ +#if FF_MULTI_PARTITION +#define LD2PD(vol) VolToPart[vol].pd /* Get physical drive number */ +#define LD2PT(vol) VolToPart[vol].pt /* Get partition index */ +#else +#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 + + +/* Definitions of sector size */ +#if (FF_MAX_SS < FF_MIN_SS) || (FF_MAX_SS != 512 && FF_MAX_SS != 1024 && FF_MAX_SS != 2048 && FF_MAX_SS != 4096) || (FF_MIN_SS != 512 && FF_MIN_SS != 1024 && FF_MIN_SS != 2048 && FF_MIN_SS != 4096) +#error Wrong sector size configuration +#endif +#if FF_MAX_SS == FF_MIN_SS +#define SS(fs) ((UINT)FF_MAX_SS) /* Fixed sector size */ +#else +#define SS(fs) ((fs)->ssize) /* Variable sector size */ +#endif + + +/* Timestamp */ +#if FF_FS_NORTC == 1 +#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31 +#error Invalid FF_FS_NORTC settings +#endif +#define GET_FATTIME() ((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16) +#else +#define GET_FATTIME() get_fattime() +#endif + + +/* File lock controls */ +#if FF_FS_LOCK != 0 +#if FF_FS_READONLY +#error FF_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, containing directory (0:root) */ + DWORD ofs; /* Object ID 3, offset in the directory */ + WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ +} FILESEM; +#endif + + +/* SBCS up-case tables (\x80-\xFF) */ +#define TBL_CT437 {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} +#define TBL_CT720 {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} +#define TBL_CT737 {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} +#define TBL_CT771 {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} +#define TBL_CT775 {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} +#define TBL_CT850 {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} +#define TBL_CT852 {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} +#define TBL_CT855 {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} +#define TBL_CT857 {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} +#define TBL_CT860 {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} +#define TBL_CT861 {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} +#define TBL_CT862 {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} +#define TBL_CT863 {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} +#define TBL_CT864 {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} +#define TBL_CT865 {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} +#define TBL_CT866 {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} +#define TBL_CT869 {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} + + +/* DBCS code range |----- 1st byte -----| |----------- 2nd byte -----------| */ +#define TBL_DC932 {0x81, 0x9F, 0xE0, 0xFC, 0x40, 0x7E, 0x80, 0xFC, 0x00, 0x00} +#define TBL_DC936 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0x80, 0xFE, 0x00, 0x00} +#define TBL_DC949 {0x81, 0xFE, 0x00, 0x00, 0x41, 0x5A, 0x61, 0x7A, 0x81, 0xFE} +#define TBL_DC950 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0xA1, 0xFE, 0x00, 0x00} + + +/* Macros for table definitions */ +#define MERGE_2STR(a, b) a ## b +#define MKCVTBL(hd, cp) MERGE_2STR(hd, cp) + + + + +/*-------------------------------------------------------------------------- + + Module Private Work Area + +---------------------------------------------------------------------------*/ +/* Remark: Variables defined here without initial value shall be guaranteed +/ zero/null at start-up. If not, the linker option or start-up routine is +/ not compliance with C standard. */ + +/*--------------------------------*/ +/* File/Volume controls */ +/*--------------------------------*/ + +#if FF_VOLUMES < 1 || FF_VOLUMES > 10 +#error Wrong FF_VOLUMES setting +#endif +static FATFS* FatFs[FF_VOLUMES]; /* Pointer to the filesystem objects (logical drives) */ +static WORD Fsid; /* Filesystem mount ID */ + +#if FF_FS_RPATH != 0 +static BYTE CurrVol; /* Current drive */ +#endif + +#if FF_FS_LOCK != 0 +static FILESEM Files[FF_FS_LOCK]; /* Open object lock semaphores */ +#endif + +#if FF_STR_VOLUME_ID +#ifdef FF_VOLUME_STRS +static const char* const VolumeStr[FF_VOLUMES] = {FF_VOLUME_STRS}; /* Pre-defined volume ID */ +#endif +#endif + + +/*--------------------------------*/ +/* LFN/Directory working buffer */ +/*--------------------------------*/ + +#if FF_USE_LFN == 0 /* Non-LFN configuration */ +#if FF_FS_EXFAT +#error LFN must be enabled when enable exFAT +#endif +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#define LEAVE_MKFS(res) return res + +#else /* LFN configurations */ +#if FF_MAX_LFN < 12 || FF_MAX_LFN > 255 +#error Wrong setting of FF_MAX_LFN +#endif +#if FF_LFN_BUF < FF_SFN_BUF || FF_SFN_BUF < 12 +#error Wrong setting of FF_LFN_BUF or FF_SFN_BUF +#endif +#if FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3 +#error Wrong setting of FF_LFN_UNICODE +#endif +static const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* FAT: Offset of LFN characters in the directory entry */ +#define MAXDIRB(nc) ((nc + 44U) / 15 * SZDIRE) /* exFAT: Size of directory entry block scratchpad buffer needed for the name length */ + +#if FF_USE_LFN == 1 /* LFN enabled with static working buffer */ +#if FF_FS_EXFAT +static BYTE DirBuf[MAXDIRB(FF_MAX_LFN)]; /* Directory entry block scratchpad buffer */ +#endif +static WCHAR LfnBuf[FF_MAX_LFN + 1]; /* LFN working buffer */ +#define DEF_NAMBUF +#define INIT_NAMBUF(fs) +#define FREE_NAMBUF() +#define LEAVE_MKFS(res) return res + +#elif FF_USE_LFN == 2 /* LFN enabled with dynamic working buffer on the stack */ +#if FF_FS_EXFAT +#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; BYTE dbuf[MAXDIRB(FF_MAX_LFN)]; /* LFN working buffer and directory entry block scratchpad buffer */ +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; (fs)->dirbuf = dbuf; } +#define FREE_NAMBUF() +#else +#define DEF_NAMBUF WCHAR lbuf[FF_MAX_LFN+1]; /* LFN working buffer */ +#define INIT_NAMBUF(fs) { (fs)->lfnbuf = lbuf; } +#define FREE_NAMBUF() +#endif +#define LEAVE_MKFS(res) return res + +#elif FF_USE_LFN == 3 /* LFN enabled with dynamic working buffer on the heap */ +#if FF_FS_EXFAT +#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer and directory entry block scratchpad buffer */ +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2 + MAXDIRB(FF_MAX_LFN)); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; (fs)->dirbuf = (BYTE*)(lfn+FF_MAX_LFN+1); } +#define FREE_NAMBUF() ff_memfree(lfn) +#else +#define DEF_NAMBUF WCHAR *lfn; /* Pointer to LFN working buffer */ +#define INIT_NAMBUF(fs) { lfn = ff_memalloc((FF_MAX_LFN+1)*2); if (!lfn) LEAVE_FF(fs, FR_NOT_ENOUGH_CORE); (fs)->lfnbuf = lfn; } +#define FREE_NAMBUF() ff_memfree(lfn) +#endif +#define LEAVE_MKFS(res) { if (!work) ff_memfree(buf); return res; } +#define MAX_MALLOC 0x8000 /* Must be >=FF_MAX_SS */ + +#else +#error Wrong setting of FF_USE_LFN + +#endif /* FF_USE_LFN == 1 */ +#endif /* FF_USE_LFN == 0 */ + + + +/*--------------------------------*/ +/* Code conversion tables */ +/*--------------------------------*/ + +#if FF_CODE_PAGE == 0 /* Run-time code page configuration */ +#define CODEPAGE CodePage +static WORD CodePage; /* Current code page */ +static const BYTE *ExCvt, *DbcTbl; /* Pointer to current SBCS up-case table and DBCS code range table below */ + +static const BYTE Ct437[] = TBL_CT437; +static const BYTE Ct720[] = TBL_CT720; +static const BYTE Ct737[] = TBL_CT737; +static const BYTE Ct771[] = TBL_CT771; +static const BYTE Ct775[] = TBL_CT775; +static const BYTE Ct850[] = TBL_CT850; +static const BYTE Ct852[] = TBL_CT852; +static const BYTE Ct855[] = TBL_CT855; +static const BYTE Ct857[] = TBL_CT857; +static const BYTE Ct860[] = TBL_CT860; +static const BYTE Ct861[] = TBL_CT861; +static const BYTE Ct862[] = TBL_CT862; +static const BYTE Ct863[] = TBL_CT863; +static const BYTE Ct864[] = TBL_CT864; +static const BYTE Ct865[] = TBL_CT865; +static const BYTE Ct866[] = TBL_CT866; +static const BYTE Ct869[] = TBL_CT869; +static const BYTE Dc932[] = TBL_DC932; +static const BYTE Dc936[] = TBL_DC936; +static const BYTE Dc949[] = TBL_DC949; +static const BYTE Dc950[] = TBL_DC950; + +#elif FF_CODE_PAGE < 900 /* Static code page configuration (SBCS) */ +#define CODEPAGE FF_CODE_PAGE +static const BYTE ExCvt[] = MKCVTBL(TBL_CT, FF_CODE_PAGE); + +#else /* Static code page configuration (DBCS) */ +#define CODEPAGE FF_CODE_PAGE +static const BYTE DbcTbl[] = MKCVTBL(TBL_DC, FF_CODE_PAGE); + +#endif + + + + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------*/ +/* Print error header */ +/*-----------------------------------------------------------------------*/ + +void print_error() +{ + gfx_printf("\n\n\n%k[FatFS] Error: %k", 0xFFFFFF00, 0xFFFFFFFF); +} + + +/*-----------------------------------------------------------------------*/ +/* 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 FF_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 !FF_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 FF_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 /* !FF_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 != 0) { + 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; +} + + +/* Test if the character is DBC 1st byte */ +static int dbc_1st (BYTE c) +{ +#if FF_CODE_PAGE == 0 /* Variable code page */ + if (DbcTbl && c >= DbcTbl[0]) { + if (c <= DbcTbl[1]) return 1; /* 1st byte range 1 */ + if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; /* 1st byte range 2 */ + } +#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */ + if (c >= DbcTbl[0]) { + if (c <= DbcTbl[1]) return 1; + if (c >= DbcTbl[2] && c <= DbcTbl[3]) return 1; + } +#else /* SBCS fixed code page */ + if (c != 0) return 0; /* Always false */ +#endif + return 0; +} + + +/* Test if the character is DBC 2nd byte */ +static int dbc_2nd (BYTE c) +{ +#if FF_CODE_PAGE == 0 /* Variable code page */ + if (DbcTbl && c >= DbcTbl[4]) { + if (c <= DbcTbl[5]) return 1; /* 2nd byte range 1 */ + if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; /* 2nd byte range 2 */ + if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; /* 2nd byte range 3 */ + } +#elif FF_CODE_PAGE >= 900 /* DBCS fixed code page */ + if (c >= DbcTbl[4]) { + if (c <= DbcTbl[5]) return 1; + if (c >= DbcTbl[6] && c <= DbcTbl[7]) return 1; + if (c >= DbcTbl[8] && c <= DbcTbl[9]) return 1; + } +#else /* SBCS fixed code page */ + if (c != 0) return 0; /* Always false */ +#endif + return 0; +} + + +#if FF_USE_LFN + +/* Get a character from TCHAR string in defined API encodeing */ +static DWORD tchar2uni ( /* Returns character in UTF-16 encoding (>=0x10000 on double encoding unit, 0xFFFFFFFF on decode error) */ + const TCHAR** str /* Pointer to pointer to TCHAR string in configured encoding */ +) +{ + DWORD uc; + const TCHAR *p = *str; + +#if FF_LFN_UNICODE == 1 /* UTF-16 input */ + WCHAR wc; + + uc = *p++; /* Get a unit */ + if (IsSurrogate(uc)) { /* Surrogate? */ + wc = *p++; /* Get low surrogate */ + if (!IsSurrogateH(uc) || !IsSurrogateL(wc)) return 0xFFFFFFFF; /* Wrong surrogate? */ + uc = uc << 16 | wc; + } + +#elif FF_LFN_UNICODE == 2 /* UTF-8 input */ + BYTE b; + int nf; + + uc = (BYTE)*p++; /* Get a unit */ + if (uc & 0x80) { /* Multiple byte code? */ + if ((uc & 0xE0) == 0xC0) { /* 2-byte sequence? */ + uc &= 0x1F; nf = 1; + } else { + if ((uc & 0xF0) == 0xE0) { /* 3-byte sequence? */ + uc &= 0x0F; nf = 2; + } else { + if ((uc & 0xF8) == 0xF0) { /* 4-byte sequence? */ + uc &= 0x07; nf = 3; + } else { /* Wrong sequence */ + return 0xFFFFFFFF; + } + } + } + do { /* Get trailing bytes */ + b = (BYTE)*p++; + if ((b & 0xC0) != 0x80) return 0xFFFFFFFF; /* Wrong sequence? */ + uc = uc << 6 | (b & 0x3F); + } while (--nf != 0); + if (uc < 0x80 || IsSurrogate(uc) || uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */ + if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ + } + +#elif FF_LFN_UNICODE == 3 /* UTF-32 input */ + uc = (TCHAR)*p++; /* Get a unit */ + if (uc >= 0x110000) return 0xFFFFFFFF; /* Wrong code? */ + if (uc >= 0x010000) uc = 0xD800DC00 | ((uc - 0x10000) << 6 & 0x3FF0000) | (uc & 0x3FF); /* Make a surrogate pair if needed */ + +#else /* ANSI/OEM input */ + BYTE b; + WCHAR wc; + + wc = (BYTE)*p++; /* Get a byte */ + if (dbc_1st((BYTE)wc)) { /* Is it a DBC 1st byte? */ + b = (BYTE)*p++; /* Get 2nd byte */ + if (!dbc_2nd(b)) return 0xFFFFFFFF; /* Invalid code? */ + wc = (wc << 8) + b; /* Make a DBC */ + } + if (wc != 0) { + wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM ==> Unicode */ + if (wc == 0) return 0xFFFFFFFF; /* Invalid code? */ + } + uc = wc; + +#endif + *str = p; /* Next read pointer */ + return uc; +} + + +/* Output a TCHAR string in defined API encoding */ +static BYTE put_utf ( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */ + DWORD chr, /* UTF-16 encoded character (Double encoding unit char if >=0x10000) */ + TCHAR* buf, /* Output buffer */ + UINT szb /* Size of the buffer */ +) +{ +#if FF_LFN_UNICODE == 1 /* UTF-16 output */ + WCHAR hs, wc; + + hs = (WCHAR)(chr >> 16); + wc = (WCHAR)chr; + if (hs == 0) { /* Single encoding unit? */ + if (szb < 1 || IsSurrogate(wc)) return 0; /* Buffer overflow or wrong code? */ + *buf = wc; + return 1; + } + if (szb < 2 || !IsSurrogateH(hs) || !IsSurrogateL(wc)) return 0; /* Buffer overflow or wrong surrogate? */ + *buf++ = hs; + *buf++ = wc; + return 2; + +#elif FF_LFN_UNICODE == 2 /* UTF-8 output */ + DWORD hc; + + if (chr < 0x80) { /* Single byte code? */ + if (szb < 1) return 0; /* Buffer overflow? */ + *buf = (TCHAR)chr; + return 1; + } + if (chr < 0x800) { /* 2-byte sequence? */ + if (szb < 2) return 0; /* Buffer overflow? */ + *buf++ = (TCHAR)(0xC0 | (chr >> 6 & 0x1F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 2; + } + if (chr < 0x10000) { /* 3-byte sequence? */ + if (szb < 3 || IsSurrogate(chr)) return 0; /* Buffer overflow or wrong code? */ + *buf++ = (TCHAR)(0xE0 | (chr >> 12 & 0x0F)); + *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 3; + } + /* 4-byte sequence */ + if (szb < 4) return 0; /* Buffer overflow? */ + hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */ + chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ + if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ + chr = (hc | chr) + 0x10000; + *buf++ = (TCHAR)(0xF0 | (chr >> 18 & 0x07)); + *buf++ = (TCHAR)(0x80 | (chr >> 12 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 6 & 0x3F)); + *buf++ = (TCHAR)(0x80 | (chr >> 0 & 0x3F)); + return 4; + +#elif FF_LFN_UNICODE == 3 /* UTF-32 output */ + DWORD hc; + + if (szb < 1) return 0; /* Buffer overflow? */ + if (chr >= 0x10000) { /* Out of BMP? */ + hc = ((chr & 0xFFFF0000) - 0xD8000000) >> 6; /* Get high 10 bits */ + chr = (chr & 0xFFFF) - 0xDC00; /* Get low 10 bits */ + if (hc >= 0x100000 || chr >= 0x400) return 0; /* Wrong surrogate? */ + chr = (hc | chr) + 0x10000; + } + *buf++ = (TCHAR)chr; + return 1; + +#else /* ANSI/OEM output */ + WCHAR wc; + + wc = ff_uni2oem(chr, CODEPAGE); + if (wc >= 0x100) { /* Is this a DBC? */ + if (szb < 2) return 0; + *buf++ = (char)(wc >> 8); /* Store DBC 1st byte */ + *buf++ = (TCHAR)wc; /* Store DBC 2nd byte */ + return 2; + } + if (wc == 0 || szb < 1) return 0; /* Invalid char or buffer overflow? */ + *buf++ = (TCHAR)wc; /* Store the character */ + return 1; +#endif +} +#endif /* FF_USE_LFN */ + + +#if FF_FS_REENTRANT +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ +static int lock_fs ( /* 1:Ok, 0:timeout */ + FATFS* fs /* Filesystem object */ +) +{ + return ff_req_grant(fs->sobj); +} + + +static void unlock_fs ( + FATFS* fs, /* Filesystem 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 FF_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 mode open, 1:Write mode open, 2:Delete or rename) */ +) +{ + UINT i, be; + + /* Search open object table for the object */ + be = 0; + for (i = 0; i < FF_FS_LOCK; i++) { + if (Files[i].fs) { /* Existing entry */ + if (Files[i].fs == dp->obj.fs && /* Check if the object matches with an open object */ + Files[i].clu == dp->obj.sclust && + Files[i].ofs == dp->dptr) break; + } else { /* Blank entry */ + be = 1; + } + } + if (i == FF_FS_LOCK) { /* The object has not been opened */ + return (!be && acc != 2) ? FR_TOO_MANY_OPEN_FILES : FR_OK; /* Is there a blank entry for new object? */ + } + + /* The object was opened. Reject any open against writing file and all write mode open */ + return (acc != 0 || 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 < FF_FS_LOCK && Files[i].fs; i++) ; + return (i == FF_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 < FF_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 == FF_FS_LOCK) { /* Not opened. Register it as new. */ + for (i = 0; i < FF_FS_LOCK && Files[i].fs; i++) ; + if (i == FF_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 >= 1 && Files[i].ctr) return 0; /* Access violation (int err) */ + + Files[i].ctr = acc ? 0x100 : Files[i].ctr + 1; /* Set semaphore value */ + + return i + 1; /* Index number origin from 1 */ +} + + +static FRESULT dec_lock ( /* Decrement object open counter */ + UINT i /* Semaphore index (1..) */ +) +{ + WORD n; + FRESULT res; + + + if (--i < FF_FS_LOCK) { /* 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 < FF_FS_LOCK; i++) { + if (Files[i].fs == fs) Files[i].fs = 0; + } +} + +#endif /* FF_FS_LOCK != 0 */ + + + +/*-----------------------------------------------------------------------*/ +/* Move/Flush disk access window in the filesystem object */ +/*-----------------------------------------------------------------------*/ +#if !FF_FS_READONLY +static FRESULT sync_window ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs /* Filesystem object */ +) +{ + FRESULT res = FR_OK; + + + if (fs->wflag) { /* Is the disk access window dirty */ + if (disk_write(fs->pdrv, fs->win, fs->winsect, 1) == RES_OK) { /* Write back the window */ + fs->wflag = 0; /* Clear window dirty flag */ + if (fs->winsect - fs->fatbase < fs->fsize) { /* Is it in the 1st FAT? */ + if (fs->n_fats == 2) disk_write(fs->pdrv, fs->win, fs->winsect + fs->fsize, 1); /* Reflect it to 2nd FAT if needed */ + } + } else { + res = FR_DISK_ERR; + } + } + return res; +} +#endif + + +static FRESULT move_window ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs, /* Filesystem object */ + DWORD sector /* Sector number to make appearance in the fs->win[] */ +) +{ + FRESULT res = FR_OK; + + + if (sector != fs->winsect) { /* Window offset changed? */ +#if !FF_FS_READONLY + res = sync_window(fs); /* Write-back changes */ +#endif + if (res == FR_OK) { /* Fill sector window with new data */ + if (disk_read(fs->pdrv, fs->win, sector, 1) != RES_OK) { + sector = 0xFFFFFFFF; /* Invalidate window if read data is not valid */ + res = FR_DISK_ERR; + } + fs->winsect = sector; + } + } + return res; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Synchronize filesystem and data on the storage */ +/*-----------------------------------------------------------------------*/ + +static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS* fs /* Filesystem object */ +) +{ + FRESULT res; + + + res = sync_window(fs); + if (res == FR_OK) { + if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */ + /* Create FSInfo structure */ + mem_set(fs->win, 0, sizeof fs->win); + 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->pdrv, fs->win, fs->winsect, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the lower layer */ + if (disk_ioctl(fs->pdrv, CTRL_SYNC, 0) != RES_OK) res = FR_DISK_ERR; + } + + return res; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Get physical sector number from cluster number */ +/*-----------------------------------------------------------------------*/ + +static DWORD clst2sect ( /* !=0:Sector number, 0:Failed (invalid cluster#) */ + FATFS* fs, /* Filesystem object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; /* Cluster number is origin from 2 */ + if (clst >= fs->n_fatent - 2) return 0; /* Is it invalid cluster number? */ + return fs->database + fs->csize * clst; /* Start sector number of the cluster */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Read value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + +static DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */ + FFOBJID* 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)]; /* Get 1st byte of the entry */ + if (move_window(fs, fs->fatbase + (bc / SS(fs))) != FR_OK) break; + wc |= fs->win[bc % SS(fs)] << 8; /* Merge 2nd byte of the entry */ + val = (clst & 1) ? (wc >> 4) : (wc & 0xFFF); /* Adjust bit position */ + 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)); /* Simple WORD array */ + 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; /* Simple DWORD array but mask out upper 4 bits */ + break; +#if FF_FS_EXFAT + case FS_EXFAT : + if ((obj->objsize != 0 && obj->sclust != 0) || obj->stat == 0) { /* Object except root dir must have valid data length */ + 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 && cofs <= clen) { /* Is it a contiguous chain? */ + val = (cofs == clen) ? 0x7FFFFFFF : clst + 1; /* No data on the FAT, generate the value */ + break; + } + if (obj->stat == 3 && cofs < obj->n_cont) { /* Is it in the 1st fragment? */ + val = clst + 1; /* Generate the value */ + break; + } + if (obj->stat != 2) { /* Get value from FAT if FAT chain is valid */ + if (obj->n_frag != 0) { /* Is it on the growing edge? */ + val = 0x7FFFFFFF; /* Generate EOC */ + } else { + 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 to default */ +#endif + default: + val = 1; /* Internal error */ + } + } + + return val; +} + + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT access - Change value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + +static FRESULT put_fat ( /* FR_OK(0):succeeded, !=0:error */ + FATFS* fs, /* Corresponding filesystem 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 : + bc = (UINT)clst; bc += bc / 2; /* bc: byte offset of the entry */ + 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; /* Put 1st byte */ + 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)); /* Put 2nd byte */ + fs->wflag = 1; + break; + + case FS_FAT16 : + 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); /* Simple WORD array */ + fs->wflag = 1; + break; + + case FS_FAT32 : +#if FF_FS_EXFAT + case FS_EXFAT : +#endif + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + if (!FF_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 /* !FF_FS_READONLY */ + + + + +#if FF_FS_EXFAT && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* exFAT: Accessing FAT and Allocation Bitmap */ +/*-----------------------------------------------------------------------*/ + +/*--------------------------------------*/ +/* Find a contiguous free cluster block */ +/*--------------------------------------*/ + +static DWORD find_bitmap ( /* 0:Not found, 2..:Cluster block found, 0xFFFFFFFF:Disk error */ + FATFS* fs, /* Filesystem 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->bitbase + val / 8 / SS(fs)) != FR_OK) return 0xFFFFFFFF; + 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 = SS(fs); + } + if (bv == 0) { /* Is it a free cluster? */ + if (++ctr == ncl) return scl + 2; /* Check if run length is sufficient for required */ + } else { + scl = val; ctr = 0; /* Encountered a cluster in-use, restart to scan */ + } + if (val == clst) return 0; /* All cluster scanned? */ + } while (bm != 0); + bm = 1; + } while (++i < SS(fs)); + } +} + + +/*----------------------------------------*/ +/* Set/Clear a block of allocation bitmap */ +/*----------------------------------------*/ + +static FRESULT change_bitmap ( + FATFS* fs, /* Filesystem 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->bitbase + clst / 8 / SS(fs); /* Sector address */ + 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; + } +} + + +/*---------------------------------------------*/ +/* Fill the first fragment of the FAT chain */ +/*---------------------------------------------*/ + +static FRESULT fill_first_frag ( + FFOBJID* obj /* Pointer to the corresponding object */ +) +{ + FRESULT res; + DWORD cl, n; + + + if (obj->stat == 3) { /* Has the object been changed 'fragmented' in this session? */ + 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; +} + + +/*---------------------------------------------*/ +/* Fill the last fragment of the FAT chain */ +/*---------------------------------------------*/ + +static FRESULT fill_last_frag ( + FFOBJID* obj, /* Pointer to the corresponding object */ + DWORD lcl, /* Last cluster of the fragment */ + DWORD term /* Value to set the last FAT entry */ +) +{ + FRESULT res; + + + while (obj->n_frag > 0) { /* Create the chain of last fragment */ + res = put_fat(obj->fs, lcl - obj->n_frag + 1, (obj->n_frag > 1) ? lcl - obj->n_frag + 2 : term); + if (res != FR_OK) return res; + obj->n_frag--; + } + return FR_OK; +} + +#endif /* FF_FS_EXFAT && !FF_FS_READONLY */ + + + +#if !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* FAT handling - Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ + +static FRESULT remove_chain ( /* FR_OK(0):succeeded, !=0:error */ + FFOBJID* obj, /* Corresponding object */ + DWORD clst, /* Cluster to remove a chain from */ + DWORD pclst /* Previous cluster of clst (0 if entire chain) */ +) +{ + FRESULT res = FR_OK; + DWORD nxt; + FATFS *fs = obj->fs; +#if FF_FS_EXFAT || FF_USE_TRIM + DWORD scl = clst, ecl = clst; +#endif +#if FF_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 != 0 && (!FF_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 (!FF_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 FF_FS_EXFAT || FF_USE_TRIM + if (ecl + 1 == nxt) { /* Is next cluster contiguous? */ + ecl = nxt; + } else { /* End of contiguous cluster block */ +#if FF_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 FF_USE_TRIM + rt[0] = clst2sect(fs, scl); /* Start of data area freed */ + rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area freed */ + disk_ioctl(fs->pdrv, CTRL_TRIM, rt); /* Inform device the data in the block is no longer needed */ +#endif + scl = ecl = nxt; + } +#endif + clst = nxt; /* Next cluster */ + } while (clst < fs->n_fatent); /* Repeat while not the last link */ + +#if FF_FS_EXFAT + /* Some post processes for chain status */ + if (fs->fs_type == FS_EXFAT) { + if (pclst == 0) { /* Has the entire chain been removed? */ + obj->stat = 0; /* Change the chain status 'initial' */ + } else { + if (obj->stat == 0) { /* Is it a fragmented chain from the beginning of this session? */ + clst = obj->sclust; /* Follow the chain to check if it gets contiguous */ + while (clst != pclst) { + nxt = get_fat(obj, clst); + if (nxt < 2) return FR_INT_ERR; + if (nxt == 0xFFFFFFFF) return FR_DISK_ERR; + if (nxt != clst + 1) break; /* Not contiguous? */ + clst++; + } + if (clst == pclst) { /* Has the chain got contiguous again? */ + obj->stat = 2; /* Change the chain status 'contiguous' */ + } + } else { + if (obj->stat == 3 && pclst >= obj->sclust && pclst <= obj->sclust + obj->n_cont) { /* Was the chain fragmented in this session and got contiguous again? */ + obj->stat = 2; /* Change the chain 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# */ + FFOBJID* 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; /* Suggested cluster to start to find */ + if (scl == 0 || scl >= fs->n_fatent) scl = 1; + } + else { /* Stretch a chain */ + cs = get_fat(obj, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* Test for insanity */ + if (cs == 0xFFFFFFFF) return cs; /* Test for disk error */ + if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */ + scl = clst; /* Cluster to start to find */ + } + if (fs->free_clst == 0) return 0; /* No free cluster */ + +#if FF_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' */ + } else { /* It 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' */ + } + } + if (obj->stat != 2) { /* Is the file non-contiguous? */ + if (ncl == clst + 1) { /* Is the cluster next to previous one? */ + obj->n_frag = obj->n_frag ? obj->n_frag + 1 : 2; /* Increment size of last framgent */ + } else { /* New fragment */ + if (obj->n_frag == 0) obj->n_frag = 1; + res = fill_last_frag(obj, clst, ncl); /* Fill last fragment on the FAT and link it to new one */ + if (res == FR_OK) obj->n_frag = 1; + } + } + } else +#endif + { /* On the FAT/FAT32 volume */ + ncl = 0; + if (scl == clst) { /* Stretching an existing chain? */ + ncl = scl + 1; /* Test if next cluster is free */ + if (ncl >= fs->n_fatent) ncl = 2; + cs = get_fat(obj, ncl); /* Get next cluster status */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */ + if (cs != 0) { /* Not free? */ + cs = fs->last_clst; /* Start at suggested cluster if it is valid */ + if (cs >= 2 && cs < fs->n_fatent) scl = cs; + ncl = 0; + } + } + if (ncl == 0) { /* The new cluster cannot be contiguous and find another fragment */ + 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 found? */ + } + cs = get_fat(obj, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster? */ + if (cs == 1 || cs == 0xFFFFFFFF) return cs; /* Test for error */ + if (ncl == scl) return 0; /* No free cluster found? */ + } + } + res = put_fat(fs, ncl, 0xFFFFFFFF); /* Mark the new cluster 'EOC' */ + if (res == FR_OK && clst != 0) { + 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. Generate error status */ + } + + return ncl; /* Return new cluster number or error status */ +} + +#endif /* !FF_FS_READONLY */ + + + + +#if FF_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 /* FF_USE_FASTSEEK */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Fill a cluster with zeros */ +/*-----------------------------------------------------------------------*/ + +#if !FF_FS_READONLY +static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */ + FATFS *fs, /* Filesystem object */ + DWORD clst /* Directory table to clear */ +) +{ + DWORD sect; + UINT n, szb; + BYTE *ibuf; + + + if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */ + sect = clst2sect(fs, clst); /* Top of the cluster */ + fs->winsect = sect; /* Set window to top of the cluster */ + mem_set(fs->win, 0, sizeof fs->win); /* Clear window buffer */ +#if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */ + /* Allocate a temporary buffer */ + for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ; + if (szb > SS(fs)) { /* Buffer allocated? */ + mem_set(ibuf, 0, szb); + szb /= SS(fs); /* Bytes -> Sectors */ + for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */ + ff_memfree(ibuf); + } else +#endif + { + ibuf = fs->win; szb = 1; /* Use window buffer (many single-sector writes may take a time) */ + for (n = 0; n < fs->csize && disk_write(fs->pdrv, ibuf, sect + n, szb) == RES_OK; n += szb) ; /* Fill the cluster with 0 */ + } + return (n == fs->csize) ? FR_OK : FR_DISK_ERR; +} +#endif /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* 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)((FF_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 (FF_FS_EXFAT) dp->obj.stat = 0; /* exFAT: Root dir has an FAT chain */ + } + + if (clst == 0) { /* Static table (root-directory on the FAT volume) */ + 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 on the FAT32/exFAT volume) */ + 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 = clst2sect(fs, clst); + } + dp->clust = clst; /* Current cluster# */ + if (dp->sect == 0) 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; + + + ofs = dp->dptr + SZDIRE; /* Next entry */ + if (ofs >= (DWORD)((FF_FS_EXFAT && fs->fs_type == FS_EXFAT) ? MAX_DIR_EX : MAX_DIR)) dp->sect = 0; /* Disable it if the offset reached the max value */ + if (dp->sect == 0) return FR_NO_FILE; /* Report EOT if it has been disabled */ + + if (ofs % SS(fs) == 0) { /* Sector changed? */ + dp->sect++; /* Next sector */ + + if (dp->clust == 0) { /* 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) { /* It reached end of dynamic table */ +#if !FF_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 */ + if (dir_clear(fs, clst) != FR_OK) return FR_DISK_ERR; /* Clean up the stretched table */ + if (FF_FS_EXFAT) dp->obj.stat |= 4; /* exFAT: The directory has been stretched */ +#else + if (!stretch) dp->sect = 0; /* (this line is to suppress compiler warning) */ + dp->sect = 0; return FR_NO_FILE; /* Report EOT */ +#endif + } + dp->clust = clst; /* Initialize data for new cluster */ + dp->sect = clst2sect(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 !FF_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 FF_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 /* !FF_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 !FF_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 FF_USE_LFN +/*--------------------------------------------------------*/ +/* 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 != 0) { + if (i >= FF_MAX_LFN + 1 || 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 FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_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 is 0 */ + + i = ((dir[LDIR_Ord] & ~LLEF) - 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 != 0) { + if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ + lfnbuf[i++] = wc = uc; /* Store it */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } + + if (dir[LDIR_Ord] & LLEF && wc != 0) { /* Put terminator if it is the last LFN part and not terminated */ + if (i >= FF_MAX_LFN + 1) return 0; /* Buffer overflow? */ + lfnbuf[i] = 0; + } + + return 1; /* The part of LFN is valid */ +} +#endif + + +#if !FF_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 /* !FF_FS_READONLY */ +#endif /* FF_USE_LFN */ + + + +#if FF_USE_LFN && !FF_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 as hash value */ + 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 to the SFN body */ + for (j = 0; j < i && dst[j] != ' '; j++) { + if (dbc_1st(dst[j])) { + if (j == i - 1) break; + j++; + } + } + do { + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif /* FF_USE_LFN && !FF_FS_READONLY */ + + + +#if FF_USE_LFN +/*-----------------------------------------------------------------------*/ +/* 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 /* FF_USE_LFN */ + + + +#if FF_FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* exFAT: Checksum */ +/*-----------------------------------------------------------------------*/ + +static WORD xdir_sum ( /* Get checksum of the directoly entry block */ + const BYTE* dir /* Directory entry block to be calculated */ +) +{ + UINT i, szblk; + WORD sum; + + + szblk = (dir[XDIR_NumSec] + 1) * SZDIRE; /* Number of bytes of the entry block */ + for (i = sum = 0; i < szblk; i++) { + if (i == XDIR_SetSum) { /* Skip 2-byte 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 file name */ + const WCHAR* name /* File name to be calculated */ +) +{ + WCHAR chr; + WORD sum = 0; + + + while ((chr = *name++) != 0) { + chr = (WCHAR)ff_wtoupper(chr); /* File name needs to be up-case converted */ + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr & 0xFF); + sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (chr >> 8); + } + return sum; +} + + +#if !FF_FS_READONLY && FF_USE_MKFS +static DWORD xsum32 ( /* Returns 32-bit checksum */ + BYTE dat, /* Byte to be calculated (byte-by-byte processing) */ + DWORD sum /* Previous sum value */ +) +{ + sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + dat; + return sum; +} +#endif + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 +/*------------------------------------------------------*/ +/* exFAT: Get object information from a directory block */ +/*------------------------------------------------------*/ + +static void get_xfileinfo ( + BYTE* dirb, /* Pointer to the direcotry entry block 85+C0+C1s */ + FILINFO* fno /* Buffer to store the extracted file information */ +) +{ + WCHAR wc, hs; + UINT di, si, nc; + + /* Get file name from the entry block */ + si = SZDIRE * 2; /* 1st C1 entry */ + nc = 0; hs = 0; di = 0; + while (nc < dirb[XDIR_NumName]) { + if (si >= MAXDIRB(FF_MAX_LFN)) { di = 0; break; } /* Truncated directory block? */ + if ((si % SZDIRE) == 0) si += 2; /* Skip entry type field */ + wc = ld_word(dirb + si); si += 2; nc++; /* Get a character */ + if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ + hs = wc; continue; /* Get low surrogate */ + } + wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in API encoding */ + if (wc == 0) { di = 0; break; } /* Buffer overflow or wrong encoding? */ + di += wc; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + if (di == 0) fno->fname[di++] = '?'; /* Inaccessible object name? */ + fno->fname[di] = 0; /* Terminate the name */ + fno->altname[0] = 0; /* exFAT does not support 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 /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ + + +/*-----------------------------------*/ +/* exFAT: Get a directry entry block */ +/*-----------------------------------*/ + +static FRESULT load_xdir ( /* FR_INT_ERR: invalid entry block */ + DIR* dp /* Reading direcotry object pointing top of the entry block to load */ +) +{ + FRESULT res; + UINT i, sz_ent; + BYTE* dirb = dp->obj.fs->dirbuf; /* Pointer to the on-memory direcotry entry block 85+C0+C1s */ + + + /* Load file-directory entry */ + res = move_window(dp->obj.fs, dp->sect); + if (res != FR_OK) return res; + if (dp->dir[XDIR_Type] != ET_FILEDIR) return FR_INT_ERR; /* Invalid order */ + mem_cpy(dirb + 0 * SZDIRE, dp->dir, SZDIRE); + sz_ent = (dirb[XDIR_NumSec] + 1) * SZDIRE; + if (sz_ent < 3 * SZDIRE || sz_ent > 19 * SZDIRE) return FR_INT_ERR; + + /* Load stream-extension entry */ + res = dir_next(dp, 0); + if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */ + 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] != ET_STREAM) return FR_INT_ERR; /* Invalid order */ + mem_cpy(dirb + 1 * SZDIRE, dp->dir, SZDIRE); + if (MAXDIRB(dirb[XDIR_NumName]) > sz_ent) return FR_INT_ERR; + + /* Load file-name entries */ + i = 2 * SZDIRE; /* Name offset to load */ + do { + res = dir_next(dp, 0); + if (res == FR_NO_FILE) res = FR_INT_ERR; /* It cannot be */ + 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] != ET_FILENAME) return FR_INT_ERR; /* Invalid order */ + if (i < MAXDIRB(FF_MAX_LFN)) mem_cpy(dirb + i, dp->dir, SZDIRE); + } while ((i += SZDIRE) < sz_ent); + + /* Sanity check (do it for only accessible object) */ + if (i <= MAXDIRB(FF_MAX_LFN)) { + if (xdir_sum(dirb) != ld_word(dirb + XDIR_SetSum)) return FR_INT_ERR; + } + return FR_OK; +} + + +/*------------------------------------------------------------------*/ +/* exFAT: Initialize object allocation info with loaded entry block */ +/*------------------------------------------------------------------*/ + +static void init_alloc_info ( + FATFS* fs, /* Filesystem object */ + FFOBJID* obj /* Object allocation information to be initialized */ +) +{ + obj->sclust = ld_dword(fs->dirbuf + XDIR_FstClus); /* Start cluster */ + obj->objsize = ld_qword(fs->dirbuf + XDIR_FileSize); /* Size */ + obj->stat = fs->dirbuf[XDIR_GenFlags] & 2; /* Allocation status */ + obj->n_frag = 0; /* No last fragment info */ +} + + + +#if !FF_FS_READONLY || FF_FS_RPATH != 0 +/*------------------------------------------------*/ +/* exFAT: Load the object's directory entry block */ +/*------------------------------------------------*/ + +static FRESULT load_obj_xdir ( + DIR* dp, /* Blank directory object to be used to access containing direcotry */ + const FFOBJID* obj /* Object with its 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->obj.n_frag = 0; + dp->blk_ofs = obj->c_ofs; + + res = dir_sdi(dp, dp->blk_ofs); /* Goto object's entry block */ + if (res == FR_OK) { + res = load_xdir(dp); /* Load the object's entry block */ + } + return res; +} +#endif + + +#if !FF_FS_READONLY +/*----------------------------------------*/ +/* exFAT: Store the directory entry block */ +/*----------------------------------------*/ + +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 direcotry entry block to the directory */ + 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 object name */ +) +{ + UINT i; + BYTE nc1, nlen; + WCHAR wc; + + + /* Create file-directory and stream-extension entry */ + mem_set(dirb, 0, 2 * SZDIRE); + dirb[0 * SZDIRE + XDIR_Type] = ET_FILEDIR; + dirb[1 * SZDIRE + XDIR_Type] = ET_STREAM; + + /* Create file-name entries */ + i = SZDIRE * 2; /* Top of file_name entries */ + nlen = nc1 = 0; wc = 1; + do { + dirb[i++] = ET_FILENAME; dirb[i++] = 0; + do { /* Fill name field */ + if (wc != 0 && (wc = lfn[nlen]) != 0) nlen++; /* Get a character if exist */ + st_word(dirb + i, wc); /* Store it */ + i += 2; + } while (i % SZDIRE != 0); + nc1++; + } while (lfn[nlen]); /* Fill next entry if any char follows */ + + dirb[XDIR_NumName] = nlen; /* Set name length */ + dirb[XDIR_NumSec] = 1 + nc1; /* Set secondary count (C0 + C1s) */ + st_word(dirb + XDIR_NameHash, xname_sum(lfn)); /* Set name hash */ +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_FS_EXFAT */ + + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 || FF_USE_LABEL || FF_FS_EXFAT +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ + +#define DIR_READ_FILE(dp) dir_read(dp, 0) +#define DIR_READ_LABEL(dp) dir_read(dp, 1) + +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 attr, b; +#if FF_USE_LFN + BYTE ord = 0xFF, sum = 0xFF; +#endif + + while (dp->sect) { + res = move_window(fs, dp->sect); + if (res != FR_OK) break; + b = dp->dir[DIR_Name]; /* Test for the entry type */ + if (b == 0) { + res = FR_NO_FILE; break; /* Reached to end of the directory */ + } +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + if (FF_USE_LABEL && vol) { + if (b == ET_VLABEL) break; /* Volume label entry? */ + } else { + if (b == ET_FILEDIR) { /* 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 FAT/FAT32 volume */ + dp->obj.attr = attr = dp->dir[DIR_Attr] & AM_MASK; /* Get attribute */ +#if FF_USE_LFN /* LFN configuration */ + if (b == DDEM || b == '.' || (int)((attr & ~AM_ARC) == AM_VOL) != vol) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (attr == AM_LFN) { /* An LFN entry is found */ + if (b & LLEF) { /* Is it start of an LFN sequence? */ + sum = dp->dir[LDIR_Chksum]; + b &= (BYTE)~LLEF; ord = b; + dp->blk_ofs = dp->dptr; + } + /* Check LFN validity and capture it */ + ord = (b == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord != 0 || sum != sum_sfn(dp->dir)) { /* Is there a valid LFN? */ + dp->blk_ofs = 0xFFFFFFFF; /* It has no LFN. */ + } + break; + } + } +#else /* Non LFN configuration */ + if (b != DDEM && b != '.' && attr != AM_LFN && (int)((attr & ~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 /* FF_FS_MINIMIZE <= 1 || FF_USE_LABEL || FF_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 FF_USE_LFN + BYTE a, ord, sum; +#endif + + res = dir_sdi(dp, 0); /* Rewind directory object */ + if (res != FR_OK) return res; +#if FF_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_FILE(dp)) == FR_OK) { /* Read an item */ +#if FF_MAX_LFN < 255 + if (fs->dirbuf[XDIR_NumName] > FF_MAX_LFN) continue; /* Skip comparison if inaccessible object name */ +#endif + if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash 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 FAT/FAT32 volume */ +#if FF_USE_LFN + 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 FF_USE_LFN /* 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 == 0 && 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 !FF_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 FF_USE_LFN /* 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 FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + nent = (nlen + 14) / 15 + 2; /* Number of entries to allocate (85+C0+C1s) */ + res = dir_alloc(dp, nent); /* Allocate directory entries */ + if (res != FR_OK) return res; + dp->blk_ofs = dp->dptr - SZDIRE * (nent - 1); /* Set the allocated entry block offset */ + + if (dp->obj.stat & 4) { /* Has the directory been stretched by new allocation? */ + dp->obj.stat &= ~4; + res = fill_first_frag(&dp->obj); /* Fill the first fragment on the FAT if needed */ + if (res != FR_OK) return res; + res = fill_last_frag(&dp->obj, dp->clust, 0xFFFFFFFF); /* Fill the last fragment on the FAT if needed */ + if (res != FR_OK) return res; + if (dp->obj.sclust != 0) { /* Is it a sub-directory? */ + DIR dj; + + res = load_obj_xdir(&dj, &dp->obj); /* Load the object status */ + if (res != FR_OK) return res; + dp->obj.objsize += (DWORD)fs->csize * SS(fs); /* Increase the directory size by cluster size */ + 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 FAT/FAT32 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 FF_USE_LFN + dp->dir[DIR_NTres] = dp->fn[NSFLAG] & (NS_BODY | NS_EXT); /* Put NT flag */ +#endif + fs->wflag = 1; + } + } + + return res; +} + +#endif /* !FF_FS_READONLY */ + + + +#if !FF_FS_READONLY && FF_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 FF_USE_LFN /* 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; + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + dp->dir[XDIR_Type] &= 0x7F; /* Clear the entry InUse flag. */ + } else { /* On the FAT/FAT32 volume */ + dp->dir[DIR_Name] = DDEM; /* Mark the entry 'deleted'. */ + } + 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; /* Mark the entry 'deleted'.*/ + fs->wflag = 1; + } +#endif + + return res; +} + +#endif /* !FF_FS_READONLY && FF_FS_MINIMIZE == 0 */ + + + +#if FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ + +static void get_fileinfo ( + DIR* dp, /* Pointer to the directory object */ + FILINFO* fno /* Pointer to the file information to be filled */ +) +{ + UINT si, di; +#if FF_USE_LFN + BYTE lcf; + WCHAR wc, hs; + FATFS *fs = dp->obj.fs; +#else + TCHAR c; +#endif + + + fno->fname[0] = 0; /* Invaidate file info */ + if (dp->sect == 0) return; /* Exit if read pointer has reached end of directory */ + +#if FF_USE_LFN /* LFN configuration */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + get_xfileinfo(fs->dirbuf, fno); + return; + } else +#endif + { /* On the FAT/FAT32 volume */ + if (dp->blk_ofs != 0xFFFFFFFF) { /* Get LFN if available */ + si = di = hs = 0; + while (fs->lfnbuf[si] != 0) { + wc = fs->lfnbuf[si++]; /* Get an LFN character (UTF-16) */ + if (hs == 0 && IsSurrogate(wc)) { /* Is it a surrogate? */ + hs = wc; continue; /* Get low surrogate */ + } + wc = put_utf((DWORD)hs << 16 | wc, &fno->fname[di], FF_LFN_BUF - di); /* Store it in UTF-16 or UTF-8 encoding */ + if (wc == 0) { di = 0; break; } /* Invalid char or buffer overflow? */ + di += wc; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + fno->fname[di] = 0; /* Terminate the LFN (null string means LFN is invalid) */ + } + } + + si = di = 0; + while (si < 11) { /* Get SFN from SFN entry */ + wc = dp->dir[si++]; /* Get a char */ + if (wc == ' ') continue; /* Skip padding spaces */ + if (wc == RDDEM) wc = DDEM; /* Restore replaced DDEM character */ + if (si == 9 && di < FF_SFN_BUF) fno->altname[di++] = '.'; /* Insert a . if extension is exist */ +#if FF_LFN_UNICODE >= 1 /* Unicode output */ + if (dbc_1st((BYTE)wc) && si != 8 && si != 11 && dbc_2nd(dp->dir[si])) { /* Make a DBC if needed */ + wc = wc << 8 | dp->dir[si++]; + } + wc = ff_oem2uni(wc, CODEPAGE); /* ANSI/OEM -> Unicode */ + if (wc == 0) { di = 0; break; } /* Wrong char in the current code page? */ + wc = put_utf(wc, &fno->altname[di], FF_SFN_BUF - di); /* Store it in Unicode */ + if (wc == 0) { di = 0; break; } /* Buffer overflow? */ + di += wc; +#else /* ANSI/OEM output */ + fno->altname[di++] = (TCHAR)wc; /* Store it without any conversion */ +#endif + } + fno->altname[di] = 0; /* Terminate the SFN (null string means SFN is invalid) */ + + if (fno->fname[0] == 0) { /* If LFN is invalid, altname[] needs to be copied to fname[] */ + if (di == 0) { /* If LFN and SFN both are invalid, this object is inaccesible */ + fno->fname[di++] = '?'; + } else { + for (si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++) { /* Copy altname[] to fname[] with case information */ + wc = (WCHAR)fno->altname[si]; + if (wc == '.') lcf = NS_EXT; + if (IsUpper(wc) && (dp->dir[DIR_NTres] & lcf)) wc += 0x20; + fno->fname[di] = (TCHAR)wc; + } + } + fno->fname[di] = 0; /* Terminate the LFN */ + if (!dp->dir[DIR_NTres]) fno->altname[0] = 0; /* Altname is not needed if neither LFN nor case info is exist. */ + } + +#else /* Non-LFN configuration */ + si = di = 0; + while (si < 11) { /* Copy name body and extension */ + c = (TCHAR)dp->dir[si++]; + if (c == ' ') continue; /* Skip padding spaces */ + if (c == RDDEM) c = DDEM; /* Restore replaced DDEM character */ + if (si == 9) fno->fname[di++] = '.';/* Insert a . if extension is exist */ + fno->fname[di++] = c; + } + fno->fname[di] = 0; +#endif + + fno->fattrib = dp->dir[DIR_Attr]; /* Attribute */ + fno->fsize = ld_dword(dp->dir + DIR_FileSize); /* Size */ + fno->ftime = ld_word(dp->dir + DIR_ModTime + 0); /* Time */ + fno->fdate = ld_word(dp->dir + DIR_ModTime + 2); /* Date */ +} + +#endif /* FF_FS_MINIMIZE <= 1 || FF_FS_RPATH >= 2 */ + + + +#if FF_USE_FIND && FF_FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Pattern matching */ +/*-----------------------------------------------------------------------*/ + +static DWORD get_achar ( /* Get a character and advances ptr */ + const TCHAR** ptr /* Pointer to pointer to the ANSI/OEM or Unicode string */ +) +{ + DWORD chr; + + +#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode input */ + chr = tchar2uni(ptr); + if (chr == 0xFFFFFFFF) chr = 0; /* Wrong UTF encoding is recognized as end of the string */ + chr = ff_wtoupper(chr); + +#else /* ANSI/OEM input */ + chr = (BYTE)*(*ptr)++; /* Get a byte */ + if (IsLower(chr)) chr -= 0x20; /* To upper ASCII char */ +#if FF_CODE_PAGE == 0 + if (ExCvt && chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#elif FF_CODE_PAGE < 900 + if (chr >= 0x80) chr = ExCvt[chr - 0x80]; /* To upper SBCS extended char */ +#endif +#if FF_CODE_PAGE == 0 || FF_CODE_PAGE >= 900 + if (dbc_1st((BYTE)chr)) { /* Get DBC 2nd byte if needed */ + chr = dbc_2nd((BYTE)**ptr) ? chr << 8 | (BYTE)*(*ptr)++ : 0; + } +#endif + +#endif + return chr; +} + + +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; + DWORD 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 == 0 && 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 block */ + 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 /* FF_USE_FIND && FF_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 FF_USE_LFN /* LFN configuration */ + BYTE b, cf; + WCHAR wc, *lfn; + DWORD uc; + UINT i, ni, si, di; + const TCHAR *p; + + + /* Create LFN into LFN working buffer */ + p = *path; lfn = dp->obj.fs->lfnbuf; di = 0; + for (;;) { + uc = tchar2uni(&p); /* Get a character */ + if (uc == 0xFFFFFFFF) return FR_INVALID_NAME; /* Invalid code or UTF decode error */ + if (uc >= 0x10000) lfn[di++] = (WCHAR)(uc >> 16); /* Store high surrogate if needed */ + wc = (WCHAR)uc; + if (wc < ' ' || wc == '/' || wc == '\\') break; /* Break if end of the path or a separator is found */ + if (wc < 0x80 && chk_chr("\"*:<>\?|\x7F", wc)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */ + if (di >= FF_MAX_LFN) return FR_INVALID_NAME; /* Reject too long name */ + lfn[di++] = wc; /* Store the Unicode character */ + } + while (*p == '/' || *p == '\\') p++; /* Skip duplicated separators if exist */ + *path = p; /* Return pointer to the next segment */ + cf = (wc < ' ') ? NS_LAST : 0; /* Set last segment flag if end of the path */ + +#if FF_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 */ + wc = lfn[di - 1]; + if (wc != ' ' && wc != '.') break; + di--; + } + lfn[di] = 0; /* LFN is created into the working buffer */ + if (di == 0) return FR_INVALID_NAME; /* Reject null name */ + + /* Create SFN in directory form */ + for (si = 0; lfn[si] == ' '; si++) ; /* Remove leading spaces */ + if (si > 0 || lfn[si] == '.') cf |= NS_LOSS | NS_LFN; /* Is there any leading space or dot? */ + while (di > 0 && lfn[di - 1] != '.') di--; /* Find last dot (di<=si: no extension) */ + + mem_set(dp->fn, ' ', 11); + i = b = 0; ni = 8; + for (;;) { + wc = lfn[si++]; /* Get an LFN character */ + if (wc == 0) break; /* Break on end of the LFN */ + if (wc == ' ' || (wc == '.' && si != di)) { /* Remove embedded spaces and dots */ + cf |= NS_LOSS | NS_LFN; + continue; + } + + if (i >= ni || si == di) { /* End of field? */ + if (ni == 11) { /* Name extension overflow? */ + cf |= NS_LOSS | NS_LFN; + break; + } + if (si != di) cf |= NS_LOSS | NS_LFN; /* Name body overflow? */ + if (si > di) break; /* No name extension? */ + si = di; i = 8; ni = 11; b <<= 2; /* Enter name extension */ + continue; + } + + if (wc >= 0x80) { /* Is this a non-ASCII character? */ + cf |= NS_LFN; /* LFN entry needs to be created */ +#if FF_CODE_PAGE == 0 + if (ExCvt) { /* At SBCS */ + wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */ + if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */ + } else { /* At DBCS */ + wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */ + } +#elif FF_CODE_PAGE < 900 /* SBCS cfg */ + wc = ff_uni2oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */ + if (wc & 0x80) wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */ +#else /* DBCS cfg */ + wc = ff_uni2oem(ff_wtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */ +#endif + } + + if (wc >= 0x100) { /* Is this a DBC? */ + if (i >= ni - 1) { /* Field overflow? */ + cf |= NS_LOSS | NS_LFN; + i = ni; continue; /* Next field */ + } + dp->fn[i++] = (BYTE)(wc >> 8); /* Put 1st byte */ + } else { /* SBC */ + if (wc == 0 || chk_chr("+,;=[]", wc)) { /* Replace illegal characters for SFN if needed */ + wc = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ + } else { + if (IsUpper(wc)) { /* ASCII upper case? */ + b |= 2; + } + if (IsLower(wc)) { /* ASCII lower case? */ + b |= 1; wc -= 0x20; + } + } + } + dp->fn[i++] = (BYTE)wc; + } + + 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; /* Shift capital flags if no extension */ + if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* LFN entry needs to be created if composite capitals */ + if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */ + if (b & 0x01) cf |= NS_EXT; /* NT flag (Extension has small capital letters only) */ + if (b & 0x04) cf |= NS_BODY; /* NT flag (Body has small capital letters only) */ + } + + dp->fn[NSFLAG] = cf; /* SFN is created into dp->fn[] */ + + return FR_OK; + + +#else /* FF_USE_LFN : 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 FF_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++]; /* Get a byte */ + 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 field overflow? */ + if (ni == 11 || c != '.') return FR_INVALID_NAME; /* Field overflow or invalid dot? */ + i = 8; ni = 11; /* Enter file extension field */ + continue; + } +#if FF_CODE_PAGE == 0 + if (ExCvt && c >= 0x80) { /* Is SBC extended character? */ + c = ExCvt[c & 0x7F]; /* To upper SBC extended character */ + } +#elif FF_CODE_PAGE < 900 + if (c >= 0x80) { /* Is SBC extended character? */ + c = ExCvt[c & 0x7F]; /* To upper SBC extended character */ + } +#endif + if (dbc_1st(c)) { /* Check if it is a DBC 1st byte */ + d = (BYTE)p[si++]; /* Get 2nd byte */ + if (!dbc_2nd(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 /* FF_USE_LFN */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* 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; + FATFS *fs = dp->obj.fs; + + +#if FF_FS_RPATH != 0 + if (*path != '/' && *path != '\\') { /* Without heading separator */ + dp->obj.sclust = fs->cdir; /* Start from current directory */ + } else +#endif + { /* With heading separator */ + while (*path == '/' || *path == '\\') path++; /* Strip heading separator */ + dp->obj.sclust = 0; /* Start from root directory */ + } +#if FF_FS_EXFAT + dp->obj.n_frag = 0; /* Invalidate last fragment counter of the object */ +#if FF_FS_RPATH != 0 + if (fs->fs_type == FS_EXFAT && dp->obj.sclust) { /* exFAT: Retrieve the sub-directory's status */ + DIR dj; + + dp->obj.c_scl = fs->cdc_scl; + dp->obj.c_size = fs->cdc_size; + dp->obj.c_ofs = fs->cdc_ofs; + res = load_obj_xdir(&dj, &dp->obj); + if (res != FR_OK) return res; + dp->obj.objsize = ld_dword(fs->dirbuf + XDIR_FileSize); + dp->obj.stat = fs->dirbuf[XDIR_GenFlags] & 2; + } +#endif +#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 (FF_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 (!(dp->obj.attr & AM_DIR)) { /* It is not a sub-directory and cannot follow */ + res = FR_NO_PATH; break; + } +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* Save containing directory information for next dir */ + dp->obj.c_scl = dp->obj.sclust; + dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat; + dp->obj.c_ofs = dp->blk_ofs; + init_alloc_info(fs, &dp->obj); /* Open next directory */ + } else +#endif + { + dp->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 number or null pointer) */ + const TCHAR** path /* Pointer to pointer to the path name */ +) +{ + const TCHAR *tp, *tt; + TCHAR tc; + int i, vol = -1; +#if FF_STR_VOLUME_ID /* Find string volume ID */ + const char *sp; + char c; +#endif + + tt = tp = *path; + if (!tp) return vol; /* Invalid path name? */ + do tc = *tt++; while ((UINT)tc >= (FF_USE_LFN ? ' ' : '!') && tc != ':'); /* Find a colon in the path */ + + if (tc == ':') { /* DOS/Windows style volume ID? */ + i = FF_VOLUMES; + if (IsDigit(*tp) && tp + 2 == tt) { /* Is there a numeric volume ID + colon? */ + i = (int)*tp - '0'; /* Get the LD number */ + } +#if FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */ + else { + i = 0; + do { + sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */ + do { /* Compare the volume ID with path name */ + c = *sp++; tc = *tp++; + if (IsLower(c)) c -= 0x20; + if (IsLower(tc)) tc -= 0x20; + } while (c && (TCHAR)c == tc); + } while ((c || tp != tt) && ++i < FF_VOLUMES); /* Repeat for each id until pattern match */ + } +#endif + if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */ + vol = i; /* Drive number */ + *path = tt; /* Snip the drive prefix off */ + } + return vol; + } +#if FF_STR_VOLUME_ID == 2 /* Unix style volume ID is enabled */ + if (*tp == '/') { + i = 0; + do { + sp = VolumeStr[i]; tp = *path; /* This string volume ID and path name */ + do { /* Compare the volume ID with path name */ + c = *sp++; tc = *(++tp); + if (IsLower(c)) c -= 0x20; + if (IsLower(tc)) tc -= 0x20; + } while (c && (TCHAR)c == tc); + } while ((c || (tc != '/' && (UINT)tc >= (FF_USE_LFN ? ' ' : '!'))) && ++i < FF_VOLUMES); /* Repeat for each ID until pattern match */ + if (i < FF_VOLUMES) { /* If a volume ID is found, get the drive number and strip it */ + vol = i; /* Drive number */ + *path = tp; /* Snip the drive prefix off */ + return vol; + } + } +#endif + /* No drive prefix is found */ +#if FF_FS_RPATH != 0 + vol = CurrVol; /* Default drive is current drive */ +#else + vol = 0; /* Default drive is 0 */ +#endif + return vol; /* Return the default drive */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load a sector and check if it is an FAT VBR */ +/*-----------------------------------------------------------------------*/ + +static BYTE check_fs ( /* 0:FAT, 1:exFAT, 2:Valid BS but not FAT, 3:Not a BS, 4:Disk error */ + FATFS* fs, /* Filesystem object */ + DWORD sect /* Sector# (lba) to load and 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 here regardless of the sector size) */ + +#if FF_FS_EXFAT + if (!mem_cmp(fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT ", 11)) return 1; /* Check if exFAT VBR */ +#endif + if (fs->win[BS_JmpBoot] == 0xE9 || fs->win[BS_JmpBoot] == 0xEB || fs->win[BS_JmpBoot] == 0xE8) { /* Valid JumpBoot code? */ + if (!mem_cmp(fs->win + BS_FilSysType, "FAT", 3)) return 0; /* Is it an FAT VBR? */ + if (!mem_cmp(fs->win + BS_FilSysType32, "FAT32", 5)) return 0; /* Is it an FAT32 VBR? */ + } + return 2; /* Valid BS but not FAT */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Determine logical drive number and mount the volume if needed */ +/*-----------------------------------------------------------------------*/ + +static FRESULT find_volume ( /* FR_OK(0): successful, !=0: an error occurred */ + const TCHAR** path, /* Pointer to pointer to the path name (drive number) */ + FATFS** rfs, /* Pointer to pointer to the found filesystem 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 filesystem object is valid or not */ + fs = FatFs[vol]; /* Get pointer to the filesystem object */ + if (!fs) return FR_NOT_ENABLED; /* Is the filesystem object available? */ +#if FF_FS_REENTRANT + if (!lock_fs(fs)) return FR_TIMEOUT; /* Lock the volume */ +#endif + *rfs = fs; /* Return pointer to the filesystem object */ + + mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */ + if (fs->fs_type != 0) { /* If the volume has been mounted */ + stat = disk_status(fs->pdrv); + if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */ + if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */ + EFSPRINTF("WPEN1"); + return FR_WRITE_PROTECTED; + } + return FR_OK; /* The filesystem object is valid */ + } + } + + /* The filesystem object is not valid. */ + /* Following code attempts to mount the volume. (analyze BPB and initialize the filesystem object) */ + + fs->fs_type = 0; /* Clear the filesystem object */ + fs->pdrv = LD2PD(vol); /* Bind the logical drive and a physical drive */ + stat = disk_initialize(fs->pdrv); /* Initialize the physical drive */ + if (stat & STA_NOINIT) { /* Check if the initialization succeeded */ + EFSPRINTF("MDNR"); + return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */ + } + if (!FF_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */ + EFSPRINTF("WPEN2"); + return FR_WRITE_PROTECTED; + } +#if FF_MAX_SS != FF_MIN_SS /* Get sector size (multiple sector size cfg only) */ + if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR; + if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR; +#endif + + /* Find an FAT partition on the drive. Supports only generic partitioning rules, FDISK (MBR) and SFD (w/o partition). */ + 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 != 0) i--; + do { /* Find an FAT volume */ + bsect = br[i]; + fmt = bsect ? check_fs(fs, bsect) : 3; /* Check the partition */ + } while (LD2PT(vol) == 0 && fmt >= 2 && ++i < 4); + } + if (fmt == 4) { + EFSPRINTF("BRNL"); + return FR_DISK_ERR; /* An error occured in the disk I/O layer */ + } + if (fmt >= 2) { + EFSPRINTF("NOFAT"); + return FR_NO_FILESYSTEM; /* No FAT volume is found */ + } + + /* An FAT volume is found (bsect). Following code initializes the filesystem object */ + +#if FF_FS_EXFAT + if (fmt == 1) { + QWORD maxlba; + DWORD so, cv, bcl; + + 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 version (must be version 1.0) */ + + if (1 << fs->win[BPB_BytsPerSecEx] != SS(fs)) { /* (BPB_BytsPerSecEx must be equal to the physical sector size) */ + EFSPRINTF("EXSPS"); + 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) { + EFSPRINTF("EXFNF"); + 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); + + /* Get bitmap location and check if it is contiguous (implementation assumption) */ + so = i = 0; + for (;;) { /* Find the bitmap entry in the root directory (in only first cluster) */ + if (i == 0) { + if (so >= fs->csize) return FR_NO_FILESYSTEM; /* Not found? */ + if (move_window(fs, clst2sect(fs, fs->dirbase) + so) != FR_OK) { + EFSPRINTF("EXBM1C"); + return FR_DISK_ERR; + } + so++; + } + if (fs->win[i] == ET_BITMAP) break; /* Is it a bitmap entry? */ + i = (i + SZDIRE) % SS(fs); /* Next entry */ + } + bcl = ld_dword(fs->win + i + 20); /* Bitmap cluster */ + if (bcl < 2 || bcl >= fs->n_fatent) { + EFSPRINTF("EXBMM"); + return FR_NO_FILESYSTEM; + } + fs->bitbase = fs->database + fs->csize * (bcl - 2); /* Bitmap sector */ + for (;;) { /* Check if bitmap is contiguous */ + if (move_window(fs, fs->fatbase + bcl / (SS(fs) / 4)) != FR_OK) return FR_DISK_ERR; + cv = ld_dword(fs->win + bcl % (SS(fs) / 4) * 4); + if (cv == 0xFFFFFFFF) break; /* Last link? */ + if (cv != ++bcl) { + EFSPRINTF("EXBMM"); + return FR_NO_FILESYSTEM; /* Fragmented? */ + } + } + +#if !FF_FS_READONLY + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ +#endif + fmt = FS_EXFAT; /* FAT sub-type */ + } else +#endif /* FF_FS_EXFAT */ + { + if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) { + EFSPRINTF("32SPS"); + 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 = 0; + if (nclst <= MAX_FAT32) fmt = FS_FAT32; + if (nclst <= MAX_FAT16) fmt = FS_FAT16; + if (nclst <= MAX_FAT12) fmt = FS_FAT12; + if (fmt == 0) return FR_NO_FILESYSTEM; + + /* 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 != 0) 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 !FF_FS_READONLY + /* Get FSInfo if available */ + fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ + fs->fsi_flag = 0x80; +#if (FF_FS_NOFSINFO & 3) != 3 + if (fmt == FS_FAT32 /* Allow to update FSInfo only if 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 (FF_FS_NOFSINFO & 1) == 0 + fs->free_clst = ld_dword(fs->win + FSI_Free_Count); +#endif +#if (FF_FS_NOFSINFO & 2) == 0 + fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free); +#endif + } + } +#endif /* (FF_FS_NOFSINFO & 3) != 3 */ +#endif /* !FF_FS_READONLY */ + } + + fs->fs_type = fmt; /* FAT sub-type */ + fs->id = ++Fsid; /* Volume mount ID */ +#if FF_USE_LFN == 1 + fs->lfnbuf = LfnBuf; /* Static LFN working buffer */ +#if FF_FS_EXFAT + fs->dirbuf = DirBuf; /* Static directory block scratchpad buffer */ +#endif +#endif +#if FF_FS_RPATH != 0 + fs->cdir = 0; /* Initialize current directory */ +#endif +#if FF_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 */ + FFOBJID* obj, /* Pointer to the FFOBJID, the 1st member in the FIL/DIR object, to check validity */ + FATFS** rfs /* Pointer to pointer to the owner filesystem object to return */ +) +{ + FRESULT res = FR_INVALID_OBJECT; + + + if (obj && obj->fs && obj->fs->fs_type && obj->id == obj->fs->id) { /* Test if the object is valid */ +#if FF_FS_REENTRANT + if (lock_fs(obj->fs)) { /* Obtain the filesystem object */ + if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */ + res = FR_OK; + } else { + unlock_fs(obj->fs, FR_OK); + } + } else { + res = FR_TIMEOUT; + } +#else + if (!(disk_status(obj->fs->pdrv) & STA_NOINIT)) { /* Test if the phsical drive is kept initialized */ + res = FR_OK; + } +#endif + } + *rfs = (res == FR_OK) ? obj->fs : 0; /* Corresponding filesystem object */ + return res; +} + + + + +/*--------------------------------------------------------------------------- + + Public Functions (FatFs API) + +----------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Logical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + FATFS* fs, /* Pointer to the filesystem 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) { + EFSPRINTF("IDRIVE!"); + return FR_INVALID_DRIVE; + } + cfs = FatFs[vol]; /* Pointer to fs object */ + + if (cfs) { +#if FF_FS_LOCK != 0 + clear_lock(cfs); +#endif +#if FF_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 FF_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 (opt == 0) 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 !FF_FS_READONLY + DWORD dw, cl, bcs, clst, sc; + FSIZE_t ofs; +#endif + DEF_NAMBUF + + + if (!fp) return FR_INVALID_OBJECT; + + /* Get logical drive number */ + mode &= FF_FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND; + 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 !FF_FS_READONLY /* Read/Write configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */ + res = FR_INVALID_NAME; + } +#if FF_FS_LOCK != 0 + else { + res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Check if the file can be used */ + } +#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 FF_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 with the same name 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 the file if overwrite mode */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + /* Get current allocation info */ + fp->obj.fs = fs; + init_alloc_info(fs, &fp->obj); + /* Set directory entry block initial state */ + mem_set(fs->dirbuf + 2, 0, 30); /* Clear 85 entry except for NumSec */ + mem_set(fs->dirbuf + 38, 0, 26); /* Clear C0 entry except for NumName and NameHash */ + fs->dirbuf[XDIR_Attr] = AM_ARC; + st_dword(fs->dirbuf + XDIR_CrtTime, GET_FATTIME()); + fs->dirbuf[XDIR_GenFlags] = 1; + res = store_xdir(&dj); + if (res == FR_OK && fp->obj.sclust != 0) { /* 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 + { + /* Set directory entry initial state */ + cl = ld_clust(fs, dj.dir); /* Get current cluster chain */ + st_dword(dj.dir + DIR_CrtTime, GET_FATTIME()); /* Set created time */ + dj.dir[DIR_Attr] = AM_ARC; /* Reset attribute */ + st_clust(fs, dj.dir, 0); /* Reset file allocation info */ + st_dword(dj.dir + DIR_FileSize, 0); + fs->wflag = 1; + if (cl != 0) { /* 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) { /* Is the object exsiting? */ + if (dj.obj.attr & AM_DIR) { /* File open against a directory */ + res = FR_NO_FILE; + } else { + if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* Write mode open against R/O file */ + res = FR_DENIED; + } + } + } + } + if (res == FR_OK) { + if (mode & FA_CREATE_ALWAYS) mode |= FA_MODIFIED; /* Set file change flag if created or overwritten */ + fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dj.dir; +#if FF_FS_LOCK != 0 + fp->obj.lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); /* Lock the file for this session */ + if (fp->obj.lockid == 0) res = FR_INT_ERR; +#endif + } +#else /* R/O configuration */ + if (res == FR_OK) { + if (dj.fn[NSFLAG] & NS_NONAME) { /* Is it origin directory itself? */ + res = FR_INVALID_NAME; + } else { + if (dj.obj.attr & AM_DIR) { /* Is it a directory? */ + res = FR_NO_FILE; + } + } + } +#endif + + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + fp->obj.c_scl = dj.obj.sclust; /* Get containing directory info */ + fp->obj.c_size = ((DWORD)dj.obj.objsize & 0xFFFFFF00) | dj.obj.stat; + fp->obj.c_ofs = dj.blk_ofs; + init_alloc_info(fs, &fp->obj); + } else +#endif + { + fp->obj.sclust = ld_clust(fs, dj.dir); /* Get object allocation info */ + fp->obj.objsize = ld_dword(dj.dir + DIR_FileSize); + } +#if FF_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 !FF_FS_READONLY +#if !FF_FS_TINY + mem_set(fp->buf, 0, sizeof fp->buf); /* 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 = clst2sect(fs, clst)) == 0) { + res = FR_INT_ERR; + } else { + fp->sect = sc + (DWORD)(ofs / SS(fs)); +#if !FF_FS_TINY + if (disk_read(fs->pdrv, 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; + + UINT br_tmp; + if (!br) + br = &br_tmp; + *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) { + EFSPRINTF("FOV"); + 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 btr bytes read */ + btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += 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 FF_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) { + EFSPRINTF("CCHK"); + ABORT(fs, FR_INT_ERR); + } + if (clst == 0xFFFFFFFF) { + EFSPRINTF("DSKC"); + ABORT(fs, FR_DISK_ERR); + } + fp->clust = clst; /* Update current cluster */ + } + sect = clst2sect(fs, fp->clust); /* Get current sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btr / SS(fs); /* When remaining bytes >= sector size, */ + if (cc > 0) { /* Read maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_read(fs->pdrv, rbuff, sect, cc) != RES_OK) { + EFSPRINTF("RLIO"); + ABORT(fs, FR_DISK_ERR); + } +#if !FF_FS_READONLY && FF_FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ +#if FF_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 !FF_FS_TINY + if (fp->sect != sect) { /* Load data sector if not in cache */ +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) { + EFSPRINTF("RDC"); + ABORT(fs, FR_DISK_ERR); + } + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, sect, 1) != RES_OK) { + EFSPRINTF("RSC"); + 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 FF_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 FF_FASTFS && FF_USE_FASTSEEK +/*-----------------------------------------------------------------------*/ +/* Fast Read Aligned Sized File Without a Cache */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read_fast ( + FIL* fp, /* Pointer to the file object */ + const void* buff, /* Pointer to the data to be written */ + UINT btr /* Number of bytes to read */ +) +{ + FRESULT res; + FATFS *fs; + UINT csize_bytes; + DWORD clst; + UINT count = 0; + FSIZE_t work_sector = 0; + FSIZE_t sector_base = 0; + BYTE *wbuff = (BYTE*)buff; + + // TODO support sector reading inside a cluster + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) { + EFSPRINTF("FOV"); + LEAVE_FF(fs, res); /* Check validity */ + } + + if (!(fp->flag & FA_READ)) LEAVE_FF(fs, FR_DENIED); /* Check access mode */ + FSIZE_t remain = fp->obj.objsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + csize_bytes = fs->csize * SS(fs); + + if (!fp->fptr) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow from the origin */ + } else { + if (fp->cltbl) clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + else { EFSPRINTF("CLTBL"); ABORT(fs, FR_CLTBL_NO_INIT); } + } + if (clst < 2) { EFSPRINTF("CCHK"); ABORT(fs, FR_INT_ERR); } + else if (clst == 0xFFFFFFFF) { EFSPRINTF("DSKC"); ABORT(fs, FR_DISK_ERR); } + + fp->clust = clst; /* Set working cluster */ + + sector_base = clst2sect(fs, fp->clust); + count += fs->csize; + btr -= csize_bytes; + fp->fptr += csize_bytes; + + while (btr) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + + if (clst < 2) { EFSPRINTF("CCHK2"); ABORT(fs, FR_INT_ERR); } + else if (clst == 0xFFFFFFFF) { EFSPRINTF("DSKC"); ABORT(fs, FR_DISK_ERR); } + + fp->clust = clst; + + work_sector = clst2sect(fs, fp->clust); + if ((work_sector - sector_base) == count) count += fs->csize; + else { + if (disk_read(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR); + wbuff += count * SS(fs); + + sector_base = work_sector; + count = fs->csize; + } + + fp->fptr += MIN(btr, csize_bytes); + btr -= MIN(btr, csize_bytes); + + // TODO: what about if data is smaller than cluster? + // Must read-write back that cluster. + + if (!btr) { /* Final cluster/sectors read. */ + if (disk_read(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR); + } + } + + LEAVE_FF(fs, FR_OK); +} +#endif + + + + +#if !FF_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; + + UINT bw_tmp; + if (!bw) + bw = &bw_tmp; + *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) { + EFSPRINTF("FOV"); + 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 4 GiB at FAT volume) */ + if ((!FF_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 */ + btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize) { + 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 FF_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) { + EFSPRINTF("DSKFULL"); + break; /* Could not allocate a new cluster (disk full) */ + } + if (clst == 1) { + EFSPRINTF("CCHK"); + ABORT(fs, FR_INT_ERR); + } + if (clst == 0xFFFFFFFF) { + EFSPRINTF("DERR"); + 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 FF_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->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + sect = clst2sect(fs, fp->clust); /* Get current sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; + cc = btw / SS(fs); /* When remaining bytes >= sector size, */ + if (cc > 0) { /* Write maximum contiguous sectors directly */ + if (csect + cc > fs->csize) { /* Clip at cluster boundary */ + cc = fs->csize - csect; + } + if (disk_write(fs->pdrv, wbuff, sect, cc) != RES_OK) { + EFSPRINTF("WLIO"); + ABORT(fs, FR_DISK_ERR); + } +#if FF_FS_MINIMIZE <= 2 +#if FF_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 FF_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->pdrv, 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 FF_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); +} + + + + +#if FF_FASTFS && FF_USE_FASTSEEK +/*-----------------------------------------------------------------------*/ +/* Fast Write Aligned Sized File Without a Cache */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write_fast ( + FIL* fp, /* Pointer to the file object */ + const void* buff, /* Pointer to the data to be written */ + UINT btw /* Number of bytes to write */ +) +{ + FRESULT res; + FATFS *fs; + UINT csize_bytes; + DWORD clst; + UINT count = 0; + FSIZE_t work_sector = 0; + FSIZE_t sector_base = 0; + const BYTE *wbuff = (const BYTE*)buff; + + // TODO support sector writing inside a cluster + + res = validate(&fp->obj, &fs); /* Check validity of the file object */ + if (res != FR_OK || (res = (FRESULT)fp->err) != FR_OK) { + EFSPRINTF("FOV"); + 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 4 GiB at FAT volume) */ + if ((!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr) { + btw = (UINT)(0xFFFFFFFF - (DWORD)fp->fptr); + } + + csize_bytes = fs->csize * SS(fs); + + if (!fp->fptr) { /* On the top of the file? */ + clst = fp->obj.sclust; /* Follow from the origin */ + } else { + if (fp->cltbl) clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + else { EFSPRINTF("CLTBL"); ABORT(fs, FR_CLTBL_NO_INIT); } + } + + if (clst < 2) { EFSPRINTF("CCHK"); ABORT(fs, FR_INT_ERR); } + else if (clst == 0xFFFFFFFF) { EFSPRINTF("DERR"); ABORT(fs, FR_DISK_ERR); } + + fp->clust = clst; /* Set working cluster */ + + sector_base = clst2sect(fs, fp->clust); + count += fs->csize; + btw -= csize_bytes; + fp->fptr += csize_bytes; + + while (btw) { + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + + if (clst < 2) { EFSPRINTF("CCHK2"); ABORT(fs, FR_INT_ERR); } + else if (clst == 0xFFFFFFFF) { EFSPRINTF("DERR"); ABORT(fs, FR_DISK_ERR); } + + fp->clust = clst; + + work_sector = clst2sect(fs, fp->clust); + if ((work_sector - sector_base) == count) count += fs->csize; + else { + if (disk_write(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR); + wbuff += count * SS(fs); + + sector_base = work_sector; + count = fs->csize; + } + + fp->fptr += MIN(btw, csize_bytes); + btw -= MIN(btw, csize_bytes); + + // what about if data is smaller than cluster? + // Probably must read-write back that cluster. + if (!btw) { /* Final cluster/sectors write. */ + if (disk_write(fs->pdrv, wbuff, sector_base, count) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } + } + + fp->flag |= FA_MODIFIED; /* Set file change flag */ + + LEAVE_FF(fs, FR_OK); +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD tm; + BYTE *dir; + + + 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 !FF_FS_TINY + if (fp->flag & FA_DIRTY) { /* Write-back cached data if needed */ + if (disk_write(fs->pdrv, 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 FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + res = fill_first_frag(&fp->obj); /* Fill first fragment on the FAT if needed */ + if (res == FR_OK) { + res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ + } + if (res == FR_OK) { + DIR dj; + DEF_NAMBUF + + INIT_NAMBUF(fs); + res = load_obj_xdir(&dj, &fp->obj); /* Load directory entry block */ + if (res == FR_OK) { + fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute to indicate that the file has been changed */ + fs->dirbuf[XDIR_GenFlags] = fp->obj.stat | 1; /* Update file allocation information */ + 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 attribute to indicate that the file has been changed */ + st_clust(fp->obj.fs, dir, fp->obj.sclust); /* Update file allocation information */ + 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 /* !FF_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL* fp /* Pointer to the file object to be closed */ +) +{ + FRESULT res; + FATFS *fs; + +#if !FF_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 FF_FS_LOCK != 0 + res = dec_lock(fp->obj.lockid); /* Decrement file open counter */ + if (res == FR_OK) fp->obj.fs = 0; /* Invalidate file object */ +#else + fp->obj.fs = 0; /* Invalidate file object */ +#endif +#if FF_FS_REENTRANT + unlock_fs(fs, FR_OK); /* Unlock volume */ +#endif + } + } + return res; +} + + + + +#if FF_FS_RPATH >= 1 +/*-----------------------------------------------------------------------*/ +/* Change Current Directory or Current Drive, Get Current Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chdrive ( + const TCHAR* path /* Drive number to set */ +) +{ + 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; +} + + + +FRESULT f_chdir ( + const TCHAR* path /* Pointer to the directory path */ +) +{ +#if FF_STR_VOLUME_ID == 2 + UINT i; +#endif + 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) { /* Is it the start directory itself? */ + fs->cdir = dj.obj.sclust; +#if FF_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 FF_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; +#if FF_STR_VOLUME_ID == 2 /* Also current drive is changed at Unix style volume ID */ + if (res == FR_OK) { + for (i = FF_VOLUMES - 1; i && fs != FatFs[i]; i--) ; /* Set current drive */ + CurrVol = (BYTE)i; + } +#endif + } + + LEAVE_FF(fs, res); +} + + +#if FF_FS_RPATH >= 2 +FRESULT f_getcwd ( + TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of buff in unit of TCHAR */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + UINT i, n; + DWORD ccl; + TCHAR *tp = buff; +#if FF_VOLUMES >= 2 + UINT vl; +#if FF_STR_VOLUME_ID + const char *vp; +#endif +#endif + FILINFO fno; + DEF_NAMBUF + + + /* Get logical drive */ + buff[0] = 0; /* Set null string to get current volume */ + res = find_volume((const TCHAR**)&buff, &fs, 0); /* Get current volume */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + + /* Follow parent directories and create the path */ + i = len; /* Bottom of buffer (directory stack base) */ + if (!FF_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_FILE(&dj); + 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++) ; /* Name length */ + if (i < n + 1) { /* Insufficient space to store the path name? */ + res = FR_NOT_ENOUGH_CORE; break; + } + while (n) buff[--i] = fno.fname[--n]; /* Stack the name */ + buff[--i] = '/'; + } + } + if (res == FR_OK) { + if (i == len) buff[--i] = '/'; /* Is it the root-directory? */ +#if FF_VOLUMES >= 2 /* Put drive prefix */ + vl = 0; +#if FF_STR_VOLUME_ID >= 1 /* String volume ID */ + for (n = 0, vp = (const char*)VolumeStr[CurrVol]; vp[n]; n++) ; + if (i >= n + 2) { + if (FF_STR_VOLUME_ID == 2) *tp++ = (TCHAR)'/'; + for (vl = 0; vl < n; *tp++ = (TCHAR)vp[vl], vl++) ; + if (FF_STR_VOLUME_ID == 1) *tp++ = (TCHAR)':'; + vl++; + } +#else /* Numeric volume ID */ + if (i >= 3) { + *tp++ = (TCHAR)'0' + CurrVol; + *tp++ = (TCHAR)':'; + vl = 2; + } +#endif + if (vl == 0) res = FR_NOT_ENOUGH_CORE; +#endif + /* Add current directory path */ + if (res == FR_OK) { + do *tp++ = buff[i++]; while (i < len); /* Copy stacked path string */ + } + } + FREE_NAMBUF(); + } + + *tp = 0; + LEAVE_FF(fs, res); +} + +#endif /* FF_FS_RPATH >= 2 */ +#endif /* FF_FS_RPATH >= 1 */ + + + +#if FF_FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File Read/Write 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 FF_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; +#if FF_FS_EXFAT && !FF_FS_READONLY + if (res == FR_OK && fs->fs_type == FS_EXFAT) { + res = fill_last_frag(&fp->obj, fp->clust, 0xFFFFFFFF); /* Fill last fragment on the FAT if needed */ + } +#endif + if (res != FR_OK) LEAVE_FF(fs, res); + +#if FF_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 != 0) { + 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 > 0) { + fp->clust = clmt_clust(fp, ofs - 1); + dsc = clst2sect(fs, fp->clust); + if (dsc == 0) 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 !FF_FS_TINY +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, dsc, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Load current sector */ +#endif + fp->sect = dsc; + } + } + } + } else +#endif + + /* Normal Seek */ + { +#if FF_FS_EXFAT + if (fs->fs_type != FS_EXFAT && ofs >= 0x100000000) ofs = 0xFFFFFFFF; /* Clip at 4 GiB - 1 if at FATxx */ +#endif + if (ofs > fp->obj.objsize && (FF_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 > 0) { + 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 !FF_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 !FF_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + if (FF_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 = clst2sect(fs, clst); /* Current sector */ + if (nsect == 0) ABORT(fs, FR_INT_ERR); + nsect += (DWORD)(ofs / SS(fs)); + } + } + } + if (!FF_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 !FF_FS_TINY +#if !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, fp->buf, nsect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ +#endif + fp->sect = nsect; + } + } + + LEAVE_FF(fs, res); +} + + + +#if FF_FASTFS && FF_USE_FASTSEEK +/*-----------------------------------------------------------------------*/ +/* Seek File Read/Write Pointer */ +/*-----------------------------------------------------------------------*/ + +DWORD *f_expand_cltbl ( + FIL* fp, /* Pointer to the file object */ + UINT tblsz, /* Size of table */ + FSIZE_t ofs /* File pointer from top of file */ +) +{ + if (fp->flag & FA_WRITE) f_lseek(fp, ofs); /* Expand file if write is enabled */ + if (!fp->cltbl) { /* Allocate memory for cluster link table */ + fp->cltbl = (DWORD *)ff_memalloc(tblsz); + fp->cltbl[0] = tblsz; + } + if (f_lseek(fp, CREATE_LINKMAP)) { /* Create cluster link table */ + ff_memfree(fp->cltbl); + fp->cltbl = NULL; + EFSPRINTF("CLTBLSZ"); + return NULL; + } + f_lseek(fp, 0); + + return fp->cltbl; +} +#endif + + + + +#if FF_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; + DEF_NAMBUF + + + if (!dp) return FR_INVALID_OBJECT; + + /* Get logical drive */ + res = find_volume(&path, &fs, 0); + if (res == FR_OK) { + dp->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 (dp->obj.attr & AM_DIR) { /* This object is a sub-directory */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + dp->obj.c_scl = dp->obj.sclust; /* Get containing directory inforamation */ + dp->obj.c_size = ((DWORD)dp->obj.objsize & 0xFFFFFF00) | dp->obj.stat; + dp->obj.c_ofs = dp->blk_ofs; + init_alloc_info(fs, &dp->obj); /* Get object allocation info */ + } else +#endif + { + dp->obj.sclust = ld_clust(fs, dp->dir); /* Get object allocation info */ + } + } else { /* This object is a file */ + res = FR_NO_PATH; + } + } + if (res == FR_OK) { + dp->obj.id = fs->id; + res = dir_sdi(dp, 0); /* Rewind directory */ +#if FF_FS_LOCK != 0 + if (res == FR_OK) { + if (dp->obj.sclust != 0) { + dp->obj.lockid = inc_lock(dp, 0); /* Lock the sub directory */ + if (!dp->obj.lockid) res = FR_TOO_MANY_OPEN_FILES; + } else { + dp->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) dp->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 FF_FS_LOCK != 0 + if (dp->obj.lockid) res = dec_lock(dp->obj.lockid); /* Decrement sub-directory open counter */ + if (res == FR_OK) dp->obj.fs = 0; /* Invalidate directory object */ +#else + dp->obj.fs = 0; /* Invalidate directory object */ +#endif +#if FF_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_FILE(dp); /* 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 FF_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 FF_USE_LFN && FF_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 /* FF_USE_FIND */ + + + +#if FF_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 !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + const TCHAR* path, /* Logical drive number */ + DWORD* nclst, /* Pointer to a variable to return number of free clusters */ + FATFS** fatfs /* Pointer to return pointer to corresponding filesystem object */ +) +{ + FRESULT res; + FATFS *fs; + DWORD nfree, clst, sect, stat; + UINT i; + FFOBJID obj; + + + /* Get logical drive */ + res = find_volume(&path, &fs, 0); + if (res == FR_OK) { + if (fatfs) *fatfs = fs; /* Return ptr to the fs object */ + /* If free_clst is valid, return it without full FAT scan */ + if (fs->free_clst <= fs->n_fatent - 2) { + *nclst = fs->free_clst; + } else { + /* Scan FAT to obtain number of free clusters */ + nfree = 0; + if (fs->fs_type == FS_FAT12) { /* FAT12: Scan bit field 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 FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* exFAT: Scan allocation bitmap */ + BYTE bm; + UINT b; + + clst = fs->n_fatent - 2; /* Number of clusters */ + sect = fs->bitbase; /* Bitmap sector */ + i = 0; /* Offset in the sector */ + do { /* Counts numbuer of bits with zero in the bitmap */ + if (i == 0) { + res = move_window(fs, sect++); + if (res != 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: Scan WORD/DWORD FAT entries */ + clst = fs->n_fatent; /* Number of entries */ + sect = fs->fatbase; /* Top of the FAT */ + i = 0; /* Offset in the sector */ + do { /* Counts numbuer of entries with zero in the FAT */ + if (i == 0) { + res = move_window(fs, sect++); + if (res != FR_OK) break; + } + if (fs->fs_type == FS_FAT16) { + if (ld_word(fs->win + i) == 0) nfree++; + i += 2; + } else { + if ((ld_dword(fs->win + i) & 0x0FFFFFFF) == 0) nfree++; + i += 4; + } + i %= SS(fs); + } while (--clst); + } + } + *nclst = nfree; /* Return the free clusters */ + fs->free_clst = nfree; /* Now free_clst is valid */ + fs->fsi_flag |= 1; /* FAT32: 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->fptr < fp->obj.objsize) { /* Process when fptr is not on the eof */ + 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 read/write point */ + fp->flag |= FA_MODIFIED; +#if !FF_FS_TINY + if (res == FR_OK && (fp->flag & FA_DIRTY)) { + if (disk_write(fs->pdrv, 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 FF_FS_EXFAT + FFOBJID obj; +#endif + DEF_NAMBUF + + + /* Get logical drive */ + res = find_volume(&path, &fs, FA_WRITE); + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (FF_FS_RPATH && res == FR_OK && (dj.fn[NSFLAG] & NS_DOT)) { + res = FR_INVALID_NAME; /* Cannot remove dot entry */ + } +#if FF_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 FF_FS_EXFAT + obj.fs = fs; + if (fs->fs_type == FS_EXFAT) { + init_alloc_info(fs, &obj); + dclst = obj.sclust; + } else +#endif + { + dclst = ld_clust(fs, dj.dir); + } + if (dj.obj.attr & AM_DIR) { /* Is it a sub-directory? */ +#if FF_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 FF_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_FILE(&sdj); /* Test if the directory is empty */ + if (res == FR_OK) res = FR_OK; // Not empty? Copyright(c) 2019 Storm, if (res == FR_OK) res = FR_DENIED; // Not empty? //geändert in FR_OK + 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 != 0) { /* Remove the cluster chain if exist */ +#if FF_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; + FFOBJID sobj; + FATFS *fs; + DWORD dcl, pcl, tm; + DEF_NAMBUF + + + res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + INIT_NAMBUF(fs); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Name collision? */ + if (FF_FS_RPATH && res == FR_NO_FILE && (dj.fn[NSFLAG] & NS_DOT)) { /* Invalid name? */ + res = FR_INVALID_NAME; + } + if (res == FR_NO_FILE) { /* It is clear to create a new directory */ + sobj.fs = fs; /* New object id to create a new chain */ + dcl = create_chain(&sobj, 0); /* Allocate a cluster for the new directory */ + res = FR_OK; + if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster? */ + if (dcl == 1) res = FR_INT_ERR; /* Any insanity? */ + if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; /* Disk error? */ + tm = GET_FATTIME(); + if (res == FR_OK) { + res = dir_clear(fs, dcl); /* Clean up the new table */ + if (res == FR_OK) { + if (!FF_FS_EXFAT || fs->fs_type != FS_EXFAT) { /* Create dot entries (FAT only) */ + mem_set(fs->win + DIR_Name, ' ', 11); /* Create "." entry */ + fs->win[DIR_Name] = '.'; + fs->win[DIR_Attr] = AM_DIR; + st_dword(fs->win + DIR_ModTime, tm); + st_clust(fs, fs->win, dcl); + mem_cpy(fs->win + SZDIRE, fs->win, SZDIRE); /* Create ".." entry */ + fs->win[SZDIRE + 1] = '.'; pcl = dj.obj.sclust; + st_clust(fs, fs->win + SZDIRE, pcl); + fs->wflag = 1; + } + res = dir_register(&dj); /* Register the object to the parent directoy */ + } + } + if (res == FR_OK) { +#if FF_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)fs->csize * SS(fs)); /* File size needs to be valid */ + st_dword(fs->dirbuf + XDIR_ValidFileSize, (DWORD)fs->csize * SS(fs)); + fs->dirbuf[XDIR_GenFlags] = 3; /* Initialize the object flag */ + fs->dirbuf[XDIR_Attr] = AM_DIR; /* Attribute */ + res = store_xdir(&dj); + } else +#endif + { + st_dword(dj.dir + DIR_ModTime, tm); /* Created time */ + st_clust(fs, dj.dir, dcl); /* Table start cluster */ + dj.dir[DIR_Attr] = AM_DIR; /* Attribute */ + fs->wflag = 1; + } + if (res == FR_OK) { + res = sync_fs(fs); + } + } else { + remove_chain(&sobj, dcl, 0); /* Could not register, remove the allocated cluster */ + } + } + 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[FF_FS_EXFAT ? SZDIRE * 2 : SZDIRE], *dir; + DWORD dw; + DEF_NAMBUF + + + get_ldnumber(&path_new); /* Snip the drive number of new name off */ + 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 FF_FS_LOCK != 0 + if (res == FR_OK) { + res = chk_lock(&djo, 2); + } +#endif + if (res == FR_OK) { /* Object to be renamed is found */ +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* At exFAT volume */ + 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); /* Restore 85+C0 entry */ + fs->dirbuf[XDIR_NumSec] = nf; fs->dirbuf[XDIR_NumName] = nn; + st_word(fs->dirbuf + XDIR_NameHash, nh); + if (!(fs->dirbuf[XDIR_Attr] & AM_DIR)) fs->dirbuf[XDIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ +/* Start of critical section where an interruption can cause a cross-link */ + res = store_xdir(&djn); + } + } + } else +#endif + { /* At FAT/FAT32 volume */ + mem_cpy(buf, djo.dir, SZDIRE); /* Save directory entry of the object */ + 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 directory entry of the object except name */ + mem_cpy(dir + 13, buf + 13, SZDIRE - 13); + dir[DIR_Attr] = buf[DIR_Attr]; + if (!(dir[DIR_Attr] & AM_DIR)) dir[DIR_Attr] |= AM_ARC; /* Set archive attribute if it is a file */ + fs->wflag = 1; + if ((dir[DIR_Attr] & AM_DIR) && djo.obj.sclust != djn.obj.sclust) { /* Update .. entry in the sub-directory if needed */ + dw = clst2sect(fs, ld_clust(fs, dir)); + if (dw == 0) { + res = FR_INT_ERR; + } else { +/* Start of critical section where an 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 the critical section */ + } + FREE_NAMBUF(); + } + + LEAVE_FF(fs, res); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_FS_MINIMIZE == 0 */ +#endif /* FF_FS_MINIMIZE <= 1 */ +#endif /* FF_FS_MINIMIZE <= 2 */ + + + +#if FF_USE_CHMOD && !FF_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 */ + if (res == FR_OK) { + dj.obj.fs = fs; + 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 FF_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 timestamp to be set */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + DEF_NAMBUF + + + res = find_volume(&path, &fs, FA_WRITE); /* Get logical drive */ + if (res == FR_OK) { + dj.obj.fs = fs; + 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 FF_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 /* FF_USE_CHMOD && !FF_FS_READONLY */ + + + +#if FF_USE_LABEL +/*-----------------------------------------------------------------------*/ +/* Get Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getlabel ( + const TCHAR* path, /* Logical drive number */ + TCHAR* label, /* Buffer to store the volume label */ + DWORD* vsn /* Variable to store the volume serial number */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + UINT si, di; + WCHAR wc; + + /* 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_LABEL(&dj); /* Find a volume label entry */ + if (res == FR_OK) { +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { + WCHAR hs; + + for (si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++) { /* Extract volume label from 83 entry */ + wc = ld_word(dj.dir + XDIR_Label + si * 2); + if (hs == 0 && IsSurrogate(wc)) { /* Is the code a surrogate? */ + hs = wc; continue; + } + wc = put_utf((DWORD)hs << 16 | wc, &label[di], 4); + if (wc == 0) { di = 0; break; } + di += wc; + hs = 0; + } + if (hs != 0) di = 0; /* Broken surrogate pair? */ + label[di] = 0; + } else +#endif + { + si = di = 0; /* Extract volume label from AM_VOL entry */ + while (si < 11) { + wc = dj.dir[si++]; +#if FF_USE_LFN && FF_LFN_UNICODE >= 1 /* Unicode output */ + if (dbc_1st((BYTE)wc) && si < 11) wc = wc << 8 | dj.dir[si++]; /* Is it a DBC? */ + wc = ff_oem2uni(wc, CODEPAGE); /* Convert it into Unicode */ + if (wc != 0) wc = put_utf(wc, &label[di], 4); /* Put it in Unicode */ + if (wc == 0) { di = 0; break; } + di += wc; +#else /* ANSI/OEM output */ + label[di++] = (TCHAR)wc; +#endif + } + 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 !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Set Volume Label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setlabel ( + const TCHAR* label /* Volume label to set with heading logical drive number */ +) +{ + FRESULT res; + DIR dj; + FATFS *fs; + BYTE dirvn[22]; + UINT di; + WCHAR wc; + static const char badchr[] = "+.,;=[]/\\\"*:<>\?|\x7F"; /* [0..] for FAT, [7..] for exFAT */ +#if FF_USE_LFN + DWORD dc; +#endif + + /* Get logical drive */ + res = find_volume(&label, &fs, FA_WRITE); + if (res != FR_OK) LEAVE_FF(fs, res); + +#if FF_FS_EXFAT + if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */ + mem_set(dirvn, 0, 22); + di = 0; + while ((UINT)*label >= ' ') { /* Create volume label */ + dc = tchar2uni(&label); /* Get a Unicode character */ + if (dc >= 0x10000) { + if (dc == 0xFFFFFFFF || di >= 10) { /* Wrong surrogate or buffer overflow */ + dc = 0; + } else { + st_word(dirvn + di * 2, (WCHAR)(dc >> 16)); di++; + } + } + if (dc == 0 || chk_chr(badchr + 7, (int)dc) || di >= 11) { /* Check validity of the volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + st_word(dirvn + di * 2, (WCHAR)dc); di++; + } + } else +#endif + { /* On the FAT/FAT32 volume */ + mem_set(dirvn, ' ', 11); + di = 0; + while ((UINT)*label >= ' ') { /* Create volume label */ +#if FF_USE_LFN + dc = tchar2uni(&label); + wc = (dc < 0x10000) ? ff_uni2oem(ff_wtoupper(dc), CODEPAGE) : 0; +#else /* ANSI/OEM input */ + wc = (BYTE)*label++; + if (dbc_1st((BYTE)wc)) wc = dbc_2nd((BYTE)*label) ? wc << 8 | (BYTE)*label++ : 0; + if (IsLower(wc)) wc -= 0x20; /* To upper ASCII characters */ +#if FF_CODE_PAGE == 0 + if (ExCvt && wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */ +#elif FF_CODE_PAGE < 900 + if (wc >= 0x80) wc = ExCvt[wc - 0x80]; /* To upper extended characters (SBCS cfg) */ +#endif +#endif + if (wc == 0 || chk_chr(badchr + 0, (int)wc) || di >= (UINT)((wc >= 0x100) ? 10 : 11)) { /* Reject invalid characters for volume label */ + LEAVE_FF(fs, FR_INVALID_NAME); + } + if (wc >= 0x100) dirvn[di++] = (BYTE)(wc >> 8); + dirvn[di++] = (BYTE)wc; + } + if (dirvn[0] == DDEM) LEAVE_FF(fs, FR_INVALID_NAME); /* Reject illegal name (heading DDEM) */ + while (di && dirvn[di - 1] == ' ') di--; /* Snip trailing spaces */ + } + + /* Set volume label */ + dj.obj.fs = fs; dj.obj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = DIR_READ_LABEL(&dj); /* Get volume label entry */ + if (res == FR_OK) { + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_NumLabel] = (BYTE)di; /* Change the volume label */ + mem_cpy(dj.dir + XDIR_Label, dirvn, 22); + } else { + if (di != 0) { + 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 or an error */ + if (res == FR_NO_FILE) { + res = FR_OK; + if (di != 0) { /* Create a volume label entry */ + res = dir_alloc(&dj, 1); /* Allocate an entry */ + if (res == FR_OK) { + mem_set(dj.dir, 0, SZDIRE); /* Clean the entry */ + if (FF_FS_EXFAT && fs->fs_type == FS_EXFAT) { + dj.dir[XDIR_Type] = ET_VLABEL; /* Create volume label entry */ + dj.dir[XDIR_NumLabel] = (BYTE)di; + mem_cpy(dj.dir + XDIR_Label, dirvn, 22); + } 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 /* !FF_FS_READONLY */ +#endif /* FF_USE_LABEL */ + + + +#if FF_USE_EXPAND && !FF_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 FF_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 FF_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) { /* A contiguous free area is found */ + if (opt) { /* Allocate it now */ + res = change_bitmap(fs, scl, tcl, 1); /* Mark the cluster block 'in use' */ + lclst = scl + tcl - 1; + } else { /* Set it as suggested point for next allocation */ + 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) { /* A contiguous free area is found */ + if (opt) { /* Allocate it now */ + 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 { /* Set it as suggested point for next allocation */ + lclst = scl - 1; + } + } + } + + if (res == FR_OK) { + fs->last_clst = lclst; /* Set suggested start cluster to start next */ + if (opt) { /* Is it allocated now? */ + fp->obj.sclust = scl; /* Update object allocation information */ + fp->obj.objsize = fsz; + if (FF_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 /* FF_USE_EXPAND && !FF_FS_READONLY */ + + + +#if FF_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 = clst2sect(fs, fp->clust); /* Get current data sector */ + if (sect == 0) ABORT(fs, FR_INT_ERR); + sect += csect; +#if FF_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 !FF_FS_READONLY + if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fs->pdrv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); + fp->flag &= (BYTE)~FA_DIRTY; + } +#endif + if (disk_read(fs->pdrv, 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 == 0) ABORT(fs, FR_INT_ERR); + } + + LEAVE_FF(fs, FR_OK); +} +#endif /* FF_USE_FORWARD */ + + + +#if FF_USE_MKFS && !FF_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Create an FAT/exFAT volume */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkfs ( + const TCHAR* path, /* Logical drive number */ + BYTE opt, /* Format option */ + DWORD au, /* Size of allocation unit (cluster) [byte] */ + void* work, /* Pointer to working buffer (null: use heap memory) */ + UINT len /* Size of working buffer [byte] */ +) +{ + const UINT n_fats = 2; /* Number of FATs for FAT/FAT32 volume (1 or 2) */ + const UINT n_rootdir = 512; /* Number of root directory entries for FAT volume */ + static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT 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; /* Sector size */ + 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 FF_USE_TRIM || FF_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 the volume if mounted */ + 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 FF_MAX_SS != FF_MIN_SS /* Get sector size of the medium if variable sector size cfg. */ + if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &ss) != RES_OK) return FR_DISK_ERR; + if (ss > FF_MAX_SS || ss < FF_MIN_SS || (ss & (ss - 1))) return FR_DISK_ERR; +#else + ss = FF_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 */ +#if FF_USE_LFN == 3 + if (!work) { /* Use heap memory for working buffer */ + for (szb_buf = MAX_MALLOC, buf = 0; szb_buf >= ss && (buf = ff_memalloc(szb_buf)) == 0; szb_buf /= 2) ; + sz_buf = szb_buf / ss; /* Size of working buffer (sector) */ + } else +#endif + { + 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 (!buf || sz_buf == 0) return FR_NOT_ENOUGH_CORE; + + /* Determine where the volume to be located (b_vol, sz_vol) */ + if (FF_MULTI_PARTITION && part != 0) { + /* Get partition information from partition table in the MBR */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Load MBR */ + if (ld_word(buf + BS_55AA) != 0xAA55) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if MBR is valid */ + pte = buf + (MBR_Table + (part - 1) * SZ_PTE); + if (pte[PTE_System] == 0) LEAVE_MKFS(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) LEAVE_MKFS(FR_DISK_ERR); + b_vol = (opt & FM_SFD) ? 0 : 32768; /* Volume start sector. Align to 16MB */ + if (sz_vol < b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); + sz_vol -= b_vol; /* Volume size */ + } + if (sz_vol < 128) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=128s */ + + /* Pre-determine the FAT type */ + do { + if (FF_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) LEAVE_MKFS(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)) LEAVE_MKFS(FR_INVALID_PARAMETER); /* no-FAT? */ + fmt = FS_FAT16; + } while (0); + +#if FF_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) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ +#if FF_USE_TRIM + tbl[0] = b_vol; tbl[1] = b_vol + sz_vol - 1; /* Inform the device the volume area may be erased */ + disk_ioctl(pdrv, CTRL_TRIM, tbl); +#endif + /* Determine FAT location, data location and number of clusters */ + if (au == 0) { /* 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 - b_vol >= sz_vol / 2) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ + n_clst = (sz_vol - (b_data - b_vol)) / au; /* Number of clusters */ + if (n_clst <16) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too few clusters? */ + if (n_clst > MAX_EXFAT) LEAVE_MKFS(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 = 0; si = 0; i = 0; j = 0; szb_case = 0; + do { + switch (st) { + case 0: + ch = (WCHAR)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 */ + /* go to next case */ + case 1: + ch = si++; /* Fill the short run */ + if (--j == 0) st = 0; + break; + + default: + ch = (WCHAR)j; si += (WCHAR)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 == 0 || 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) LEAVE_MKFS(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 != 0 && 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) LEAVE_MKFS(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 != 0 && i < szb_buf) { /* Create a chain */ + st_dword(buf + i, (nb > 1) ? cl + 1 : 0xFFFFFFFF); + i += 4; cl++; nb--; + } + if (nb == 0 && j < 3) nb = tbl[j++]; /* Next chain */ + } while (nb != 0 && i < szb_buf); + n = (nsect > sz_buf) ? sz_buf : nsect; /* Write the buffered data */ + if (disk_write(pdrv, buf, sect, n) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + + /* Initialize the root directory */ + mem_set(buf, 0, szb_buf); + buf[SZDIRE * 0 + 0] = ET_VLABEL; /* Volume label entry */ + buf[SZDIRE * 1 + 0] = ET_BITMAP; /* Bitmap entry */ + st_dword(buf + SZDIRE * 1 + 20, 2); /* cluster */ + st_dword(buf + SZDIRE * 1 + 24, szb_bit); /* size */ + buf[SZDIRE * 2 + 0] = ET_UPCASE; /* Up-case table entry */ + st_dword(buf + SZDIRE * 2 + 4, sum); /* sum */ + st_dword(buf + SZDIRE * 2 + 20, 2 + tbl[0]); /* cluster */ + st_dword(buf + SZDIRE * 2 + 24, szb_case); /* size */ + 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) LEAVE_MKFS(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); /* Filesystem 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) LEAVE_MKFS(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) LEAVE_MKFS(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) LEAVE_MKFS(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) LEAVE_MKFS(FR_DISK_ERR); + } + + } else +#endif /* FF_FS_EXFAT */ + { /* Create an FAT/FAT32 volume */ + do { + pau = au; + /* Pre-determine number of clusters and FAT sub-type */ + if (fmt == FS_FAT32) { /* FAT32 volume */ + if (pau == 0) { /* 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) LEAVE_MKFS(FR_MKFS_ABORTED); + } else { /* FAT volume */ + if (pau == 0) { /* 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 { /* FAT: Expand FAT size */ + if (n % n_fats) { /* Adjust fractional error if needed */ + n--; sz_rsv++; b_fat++; + } + 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) LEAVE_MKFS(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 == 0 && (au = pau / 2) != 0) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + } + if (fmt == FS_FAT16) { + if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */ + if (au == 0 && (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 == 0 && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */ + if (au == 0 && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ + LEAVE_MKFS(FR_MKFS_ABORTED); + } + } + if (fmt == FS_FAT12 && n_clst > MAX_FAT12) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too many clusters for FAT12 */ + + /* Ok, it is the valid cluster configuration */ + break; + } while (1); + +#if FF_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, "SWITCH SD " "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, "SWITCH SD " "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) LEAVE_MKFS(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) LEAVE_MKFS(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) LEAVE_MKFS(FR_DISK_ERR); + sect += n; nsect -= n; + } while (nsect); + } + + /* Determine system ID in the partition table */ + if (FF_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 (large) */ + } else { + sys = (fmt == FS_FAT16) ? 0x04 : 0x01; /* FAT16 : FAT12 */ + } + } + } + + /* Update partition information */ + if (FF_MULTI_PARTITION && part != 0) { /* Created in the existing partition */ + /* Update system ID in the partition table */ + if (disk_read(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Read the MBR */ + buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys; /* Set system ID */ + if (disk_write(pdrv, buf, 0, 1) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); /* Write it back to the MBR */ + } else { /* Created as a new single partition */ + if (!(opt & FM_SFD)) { /* Create partition table if 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 may be invalid) */ + pte[PTE_EdHead] = 254; /* End head */ + pte[PTE_EdSec] = (BYTE)(((n >> 2) & 0xC0) | 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) LEAVE_MKFS(FR_DISK_ERR); /* Write it to the MBR */ + } + } + + if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) LEAVE_MKFS(FR_DISK_ERR); + + LEAVE_MKFS(FR_OK); +} + + + +#if FF_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 (null: use heap memory) */ +) +{ + 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; + FRESULT res; + + + 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; + + buf = (BYTE*)work; +#if FF_USE_LFN == 3 + if (!buf) buf = ff_memalloc(FF_MAX_SS); /* Use heap memory for working buffer */ +#endif + if (!buf) return FR_NOT_ENOUGH_CORE; + + /* Determine the CHS without any consideration of the drive geometry */ + for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2) ; + if (n == 256) n--; + e_hd = (BYTE)(n - 1); + sz_cyl = 63 * n; + tot_cyl = sz_disk / sz_cyl; + + /* Create partition table */ + mem_set(buf, 0, FF_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; /* Number of cylinders */ + if (p_cyl == 0) 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; /* End cylinder */ + if (e_cyl >= tot_cyl) LEAVE_MKFS(FR_INVALID_PARAMETER); + + /* Set partition table */ + p[1] = s_hd; /* Start head */ + p[2] = (BYTE)(((b_cyl >> 2) & 0xC0) | 1); /* Start sector */ + p[3] = (BYTE)b_cyl; /* Start cylinder */ + p[4] = 0x07; /* System type (temporary setting) */ + p[5] = e_hd; /* End head */ + p[6] = (BYTE)(((e_cyl >> 2) & 0xC0) | 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); /* Number of sectors */ + + /* Next partition */ + b_cyl += p_cyl; + } + st_word(p, 0xAA55); /* MBR signature (always at offset 510) */ + + /* Write it to the MBR */ + res = (disk_write(pdrv, buf, 0, 1) == RES_OK && disk_ioctl(pdrv, CTRL_SYNC, 0) == RES_OK) ? FR_OK : FR_DISK_ERR; + LEAVE_MKFS(res); +} + +#endif /* FF_MULTI_PARTITION */ +#endif /* FF_USE_MKFS && !FF_FS_READONLY */ + + + + +#if FF_USE_STRFUNC +#if FF_USE_LFN && FF_LFN_UNICODE && (FF_STRF_ENCODE < 0 || FF_STRF_ENCODE > 3) +#error Wrong FF_STRF_ENCODE setting +#endif +/*-----------------------------------------------------------------------*/ +/* Get a String from the File */ +/*-----------------------------------------------------------------------*/ + +TCHAR* f_gets ( + TCHAR* buff, /* Pointer to the string buffer to read */ + int len, /* Size of string buffer (items) */ + FIL* fp /* Pointer to the file object */ +) +{ + int nc = 0; + TCHAR *p = buff; + BYTE s[4]; + UINT rc; + DWORD dc; +#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE <= 2 + WCHAR wc; +#endif +#if FF_USE_LFN && FF_LFN_UNICODE && FF_STRF_ENCODE == 3 + UINT ct; +#endif + +#if FF_USE_LFN && FF_LFN_UNICODE /* With code conversion (Unicode API) */ + /* Make a room for the character and terminator */ + if (FF_LFN_UNICODE == 1) len -= (FF_STRF_ENCODE == 0) ? 1 : 2; + if (FF_LFN_UNICODE == 2) len -= (FF_STRF_ENCODE == 0) ? 3 : 4; + if (FF_LFN_UNICODE == 3) len -= 1; + while (nc < len) { +#if FF_STRF_ENCODE == 0 /* Read a character in ANSI/OEM */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + wc = s[0]; + if (dbc_1st((BYTE)wc)) { + f_read(fp, s, 1, &rc); + if (rc != 1 || !dbc_2nd(s[0])) continue; + wc = wc << 8 | s[0]; + } + dc = ff_oem2uni(wc, CODEPAGE); + if (dc == 0) continue; +#elif FF_STRF_ENCODE == 1 || FF_STRF_ENCODE == 2 /* Read a character in UTF-16LE/BE */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + dc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1]; + if (IsSurrogateL(dc)) continue; + if (IsSurrogateH(dc)) { + f_read(fp, s, 2, &rc); + if (rc != 2) break; + wc = (FF_STRF_ENCODE == 1) ? ld_word(s) : s[0] << 8 | s[1]; + if (!IsSurrogateL(wc)) continue; + dc = ((dc & 0x3FF) + 0x40) << 10 | (wc & 0x3FF); + } +#else /* Read a character in UTF-8 */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + dc = s[0]; + if (dc >= 0x80) { /* Multi-byte character? */ + ct = 0; + if ((dc & 0xE0) == 0xC0) { dc &= 0x1F; ct = 1; } /* 2-byte? */ + if ((dc & 0xF0) == 0xE0) { dc &= 0x0F; ct = 2; } /* 3-byte? */ + if ((dc & 0xF8) == 0xF0) { dc &= 0x07; ct = 3; } /* 4-byte? */ + if (ct == 0) continue; + f_read(fp, s, ct, &rc); /* Get trailing bytes */ + if (rc != ct) break; + rc = 0; + do { /* Merge trailing bytes */ + if ((s[rc] & 0xC0) != 0x80) break; + dc = dc << 6 | (s[rc] & 0x3F); + } while (++rc < ct); + if (rc != ct || dc < 0x80 || IsSurrogate(dc) || dc >= 0x110000) continue; /* Wrong encoding? */ + } +#endif + if (FF_USE_STRFUNC == 2 && dc == '\r') continue; /* Strip \r off if needed */ +#if FF_LFN_UNICODE == 1 || FF_LFN_UNICODE == 3 /* Output it in UTF-16/32 encoding */ + if (FF_LFN_UNICODE == 1 && dc >= 0x10000) { /* Out of BMP at UTF-16? */ + *p++ = (TCHAR)(0xD800 | ((dc >> 10) - 0x40)); nc++; /* Make and output high surrogate */ + dc = 0xDC00 | (dc & 0x3FF); /* Make low surrogate */ + } + *p++ = (TCHAR)dc; nc++; + if (dc == '\n') break; /* End of line? */ +#elif FF_LFN_UNICODE == 2 /* Output it in UTF-8 encoding */ + if (dc < 0x80) { /* 1-byte */ + *p++ = (TCHAR)dc; + nc++; + if (dc == '\n') break; /* End of line? */ + } else { + if (dc < 0x800) { /* 2-byte */ + *p++ = (TCHAR)(0xC0 | (dc >> 6 & 0x1F)); + *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + nc += 2; + } else { + if (dc < 0x10000) { /* 3-byte */ + *p++ = (TCHAR)(0xE0 | (dc >> 12 & 0x0F)); + *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + nc += 3; + } else { /* 4-byte */ + *p++ = (TCHAR)(0xF0 | (dc >> 18 & 0x07)); + *p++ = (TCHAR)(0x80 | (dc >> 12 & 0x3F)); + *p++ = (TCHAR)(0x80 | (dc >> 6 & 0x3F)); + *p++ = (TCHAR)(0x80 | (dc >> 0 & 0x3F)); + nc += 4; + } + } + } +#endif + } + +#else /* Byte-by-byte without any conversion (ANSI/OEM API) */ + len -= 1; /* Make a room for the terminator */ + while (nc < len) { + f_read(fp, s, 1, &rc); + if (rc != 1) break; + dc = s[0]; + if (FF_USE_STRFUNC == 2 && dc == '\r') continue; + *p++ = (TCHAR)dc; nc++; + if (dc == '\n') break; + } +#endif + + *p = 0; /* Terminate the string */ + return nc ? buff : 0; /* When no data read due to EOF or error, return with error. */ +} + + + + +#if !FF_FS_READONLY +#include +/*-----------------------------------------------------------------------*/ +/* Put a Character to the File */ +/*-----------------------------------------------------------------------*/ + +typedef struct { /* Putchar output buffer and work area */ + FIL *fp; /* Ptr to the writing file */ + int idx, nchr; /* Write index of buf[] (-1:error), number of encoding units written */ +#if FF_USE_LFN && FF_LFN_UNICODE == 1 + WCHAR hs; +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 + BYTE bs[4]; + UINT wi, ct; +#endif + BYTE buf[64]; /* Write buffer */ +} putbuff; + + +static void putc_bfd ( /* Buffered write with code conversion */ + putbuff* pb, + TCHAR c +) +{ + UINT n; + int i, nc; +#if FF_USE_LFN && FF_LFN_UNICODE + WCHAR hs, wc; +#if FF_LFN_UNICODE == 2 + DWORD dc; + TCHAR *tp; +#endif +#endif + + if (FF_USE_STRFUNC == 2 && c == '\n') { /* LF -> CRLF conversion */ + putc_bfd(pb, '\r'); + } + + i = pb->idx; /* Write index of pb->buf[] */ + if (i < 0) return; + nc = pb->nchr; /* Write unit counter */ + +#if FF_USE_LFN && FF_LFN_UNICODE +#if FF_LFN_UNICODE == 1 /* UTF-16 input */ + if (IsSurrogateH(c)) { + pb->hs = c; return; + } + hs = pb->hs; pb->hs = 0; + if (hs != 0) { + if (!IsSurrogateL(c)) hs = 0; + } else { + if (IsSurrogateL(c)) return; + } + wc = c; +#elif FF_LFN_UNICODE == 2 /* UTF-8 input */ + for (;;) { + if (pb->ct == 0) { /* Out of multi-byte sequence? */ + pb->bs[pb->wi = 0] = (BYTE)c; /* Save 1st byte */ + if ((BYTE)c < 0x80) break; /* 1-byte? */ + if (((BYTE)c & 0xE0) == 0xC0) pb->ct = 1; /* 2-byte? */ + if (((BYTE)c & 0xF0) == 0xE0) pb->ct = 2; /* 3-byte? */ + if (((BYTE)c & 0xF1) == 0xF0) pb->ct = 3; /* 4-byte? */ + return; + } else { /* In the multi-byte sequence */ + if (((BYTE)c & 0xC0) != 0x80) { /* Broken sequence? */ + pb->ct = 0; continue; + } + pb->bs[++pb->wi] = (BYTE)c; /* Save the trailing byte */ + if (--pb->ct == 0) break; /* End of multi-byte sequence? */ + return; + } + } + tp = (TCHAR*)pb->bs; + dc = tchar2uni(&tp); /* UTF-8 ==> UTF-16 */ + if (dc == 0xFFFFFFFF) return; + wc = (WCHAR)dc; + hs = (WCHAR)(dc >> 16); +#elif FF_LFN_UNICODE == 3 /* UTF-32 input */ + if (IsSurrogate(c) || c >= 0x110000) return; + if (c >= 0x10000) { + hs = (WCHAR)(0xD800 | ((c >> 10) - 0x40)); /* Make high surrogate */ + wc = 0xDC00 | (c & 0x3FF); /* Make low surrogate */ + } else { + hs = 0; + wc = (WCHAR)c; + } +#endif + +#if FF_STRF_ENCODE == 1 /* Write a character in UTF-16LE */ + if (hs != 0) { + st_word(&pb->buf[i], hs); + i += 2; + nc++; + } + st_word(&pb->buf[i], wc); + i += 2; +#elif FF_STRF_ENCODE == 2 /* Write a character in UTF-16BE */ + if (hs != 0) { + pb->buf[i++] = (BYTE)(hs >> 8); + pb->buf[i++] = (BYTE)hs; + nc++; + } + pb->buf[i++] = (BYTE)(wc >> 8); + pb->buf[i++] = (BYTE)wc; +#elif FF_STRF_ENCODE == 3 /* Write it in UTF-8 */ + if (hs != 0) { /* 4-byte */ + nc += 3; + hs = (hs & 0x3FF) + 0x40; + pb->buf[i++] = (BYTE)(0xF0 | hs >> 8); + pb->buf[i++] = (BYTE)(0x80 | (hs >> 2 & 0x3F)); + pb->buf[i++] = (BYTE)(0x80 | (hs & 3) << 4 | (wc >> 6 & 0x0F)); + pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F)); + } else { + if (wc < 0x80) { /* 1-byte */ + pb->buf[i++] = (BYTE)wc; + } else { + if (wc < 0x800) { /* 2-byte */ + nc += 1; + pb->buf[i++] = (BYTE)(0xC0 | wc >> 6); + } else { /* 3-byte */ + nc += 2; + pb->buf[i++] = (BYTE)(0xE0 | wc >> 12); + pb->buf[i++] = (BYTE)(0x80 | (wc >> 6 & 0x3F)); + } + pb->buf[i++] = (BYTE)(0x80 | (wc & 0x3F)); + } + } +#else /* Write it in ANSI/OEM */ + if (hs != 0) return; + wc = ff_uni2oem(wc, CODEPAGE); /* UTF-16 ==> ANSI/OEM */ + if (wc == 0) return; + if (wc >= 0x100) { + pb->buf[i++] = (BYTE)(wc >> 8); nc++; + } + pb->buf[i++] = (BYTE)wc; +#endif + +#else /* ANSI/OEM input (without re-encode) */ + pb->buf[i++] = (BYTE)c; +#endif + + if (i >= (int)(sizeof pb->buf) - 4) { /* Write buffered characters to the file */ + f_write(pb->fp, pb->buf, (UINT)i, &n); + i = (n == (UINT)i) ? 0 : -1; + } + pb->idx = i; + pb->nchr = nc + 1; +} + + +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 +) +{ + mem_set(pb, 0, sizeof (putbuff)); + pb->fp = fp; +} + + + +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++; + } + } + if (c == '*') { /* Minimum width by argument */ + w = va_arg(arp, int); + c = *fmt++; + } else { + while (IsDigit(c)) { /* Minimum width */ + w = w * 10 + c - '0'; + c = *fmt++; + } + } + if (c == 'l' || c == 'L') { /* Type prefix: Size is long int */ + f |= 4; c = *fmt++; + } + if (c == 0) break; + d = c; + if (IsLower(d)) d -= 0x20; + switch (d) { /* Atgument type is... */ + case 'S' : /* String */ + p = va_arg(arp, TCHAR*); + for (j = 0; p[j]; j++) ; + if (!(f & 2)) { /* Right padded */ + while (j++ < w) putc_bfd(&pb, ' ') ; + } + while (*p) putc_bfd(&pb, *p++) ; /* String body */ + while (j++ < w) putc_bfd(&pb, ' ') ; /* Left padded */ + continue; + + case 'C' : /* Character */ + putc_bfd(&pb, (TCHAR)va_arg(arp, int)); continue; + + case 'B' : /* Unsigned binary */ + r = 2; break; + + case 'O' : /* Unsigned octal */ + r = 8; break; + + case 'D' : /* Signed decimal */ + case 'U' : /* Unsigned decimal */ + r = 10; break; + + case 'X' : /* Unsigned 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); + if (f & 8) str[i++] = '-'; + j = i; d = (f & 1) ? '0' : ' '; + if (!(f & 2)) { + while (j++ < w) putc_bfd(&pb, d); /* Right pad */ + } + do { + putc_bfd(&pb, str[--i]); /* Number body */ + } while (i); + while (j++ < w) putc_bfd(&pb, d); /* Left pad */ + } + + va_end(arp); + + return putc_flush(&pb); +} + +#endif /* !FF_FS_READONLY */ +#endif /* FF_USE_STRFUNC */ + + + +#if FF_CODE_PAGE == 0 +/*-----------------------------------------------------------------------*/ +/* Set Active Codepage for the Path Name */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setcp ( + WORD cp /* Value to be set as active code page */ +) +{ + static const WORD validcp[] = { 437, 720, 737, 771, 775, 850, 852, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950, 0}; + static const BYTE* const tables[] = {Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0}; + UINT i; + + + for (i = 0; validcp[i] != 0 && validcp[i] != cp; i++) ; /* Find the code page */ + if (validcp[i] != cp) return FR_INVALID_PARAMETER; /* Not found? */ + + CodePage = cp; + if (cp >= 900) { /* DBCS */ + ExCvt = 0; + DbcTbl = tables[i]; + } else { /* SBCS */ + ExCvt = tables[i]; + DbcTbl = 0; + } + return FR_OK; +} +#endif /* FF_CODE_PAGE == 0 */ diff --git a/bdk/libs/fatfs/ff.h b/bdk/libs/fatfs/ff.h new file mode 100644 index 00000000..a83cf63d --- /dev/null +++ b/bdk/libs/fatfs/ff.h @@ -0,0 +1,390 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem module R0.13c / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2018, 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 FF_DEFINED +#define FF_DEFINED 86604 /* Revision ID */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* Basic integer types */ +#include /* FatFs configuration options */ + +#if FF_DEFINED != FFCONF_DEF +#error Wrong configuration file (ffconf.h). +#endif + + + +/* Definitions of volume management */ + +#if FF_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 */ +#endif + +#if FF_STR_VOLUME_ID +#ifndef FF_VOLUME_STRS +extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ +#endif +#endif + + + +/* Type of path name strings on FatFs API */ + +#ifndef _INC_TCHAR +#define _INC_TCHAR + +#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ +typedef char TCHAR; +#define _T(x) u8 ## x +#define _TEXT(x) u8 ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ +typedef DWORD TCHAR; +#define _T(x) U ## x +#define _TEXT(x) U ## x +#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) +#error Wrong FF_LFN_UNICODE setting +#else /* ANSI/OEM code in SBCS/DBCS */ +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + +#endif + + + +/* Type of file size variables */ + +#if FF_FS_EXFAT +typedef QWORD FSIZE_t; +#else +typedef DWORD FSIZE_t; +#endif + + + +/* Filesystem object structure (FATFS) */ + +typedef struct { + BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ + BYTE fs_type; /* Filesystem type (0:not mounted) */ + BYTE pdrv; /* Associated physical drive */ + 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; /* Volume mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD csize; /* Cluster size [sectors] */ +#if FF_MAX_SS != FF_MIN_SS + WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ +#endif +#if FF_USE_LFN + WCHAR* lfnbuf; /* LFN working buffer */ +#endif +#if FF_FS_EXFAT + BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ +#endif +#if FF_FS_REENTRANT + FF_SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !FF_FS_READONLY + DWORD last_clst; /* Last allocated cluster */ + DWORD free_clst; /* Number of free clusters */ +#endif +#if FF_FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#if FF_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 */ +#if FF_FS_EXFAT + DWORD bitbase; /* Allocation bitmap base sector */ +#endif + DWORD winsect; /* Current sector appearing in the win[] */ +} FATFS; + + + +/* Object ID and allocation information (FFOBJID) */ + +typedef struct { + FATFS* fs; /* Pointer to the hosting volume of this object */ + WORD id; /* Hosting volume mount ID */ + BYTE attr; /* Object attribute */ + BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ + DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ + FSIZE_t objsize; /* Object size (valid when sclust != 0) */ +#if FF_FS_EXFAT + DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ + DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ + 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 file object and sclust != 0) */ +#endif +#if FF_FS_LOCK + UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ +#endif +} FFOBJID; + + + +/* File object structure (FIL) */ + +typedef struct { +#if !FF_FS_TINY + BYTE buf[FF_MAX_SS]; /* File private data read/write window */ +#endif + FFOBJID 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 fptr is 0) */ + DWORD sect; /* Sector number appearing in buf[] (0:invalid) */ +#if !FF_FS_READONLY + DWORD dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ + BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ +#endif +#if FF_USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + FFOBJID obj; /* Object identifier */ + DWORD dptr; /* Current read/write offset */ + DWORD clust; /* Current cluster */ + DWORD sect; /* Current sector (0:Read operation has terminated) */ + BYTE* dir; /* Pointer to the directory item in the win[] */ + BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ +#if FF_USE_LFN + DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ +#endif +#if FF_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 FF_USE_LFN + TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ + TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ +#else + TCHAR fname[12 + 1]; /* 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 > FF_FS_LOCK */ +#if FF_FASTFS + FR_INVALID_PARAMETER, /* (19) Given parameter is invalid */ + FR_CLTBL_NO_INIT /* (20) The cluster table for fast seek/read/write was not created */ +#else + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +#endif +} 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_read_fast (FIL* fp, const void* buff, UINT btr); /* Fast read data from the file */ +FRESULT f_write_fast (FIL* fp, const void* buff, UINT btw); /* Fast 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 */ +DWORD *f_expand_cltbl (FIL* fp, UINT tblsz, FSIZE_t ofs); /* Expand file and populate cluster table */ +FRESULT f_expand (FIL* fp, FSIZE_t fsz, 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 */ +FRESULT f_setcp (WORD cp); /* Set current code page */ +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) +#define f_rmdir(path) f_unlink(path) +#define f_unmount(path) f_mount(0, path, 0) + +#ifndef EOF +#define EOF (-1) +#endif + + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !FF_FS_READONLY && !FF_FS_NORTC +DWORD get_fattime (void); +#endif + +/* LFN support functions */ +#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */ +WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ +WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ +DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ +#endif +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif + +/* Sync functions */ +#if FF_FS_REENTRANT +int ff_cre_syncobj (BYTE vol, FF_SYNC_t* sobj); /* Create a sync object */ +int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */ +void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */ +int ff_del_syncobj (FF_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_VOL 0x08 /* Volume */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ +#define AM_DEV 0x40 /* Device */ +#define AM_RVD 0x80 /* Reserved */ + + +#ifdef __cplusplus +} +#endif + +#endif /* FF_DEFINED */ diff --git a/bdk/libs/fatfs/ffunicode.c b/bdk/libs/fatfs/ffunicode.c new file mode 100644 index 00000000..9f039637 --- /dev/null +++ b/bdk/libs/fatfs/ffunicode.c @@ -0,0 +1,625 @@ +/*------------------------------------------------------------------------*/ +/* Unicode handling functions for FatFs R0.13c */ +/*------------------------------------------------------------------------*/ +/* This module will occupy a huge memory in the .const section when the / +/ FatFs is configured for LFN with DBCS. If the system has any Unicode / +/ utilitiy for the code conversion, this module should be modified to use / +/ that function to avoid silly memory consumption. / +/-------------------------------------------------------------------------*/ +/* +/ Copyright (C) 2018, 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" + +#if FF_USE_LFN /* This module will be blanked at non-LFN configuration */ + +#if FF_DEFINED != 86604 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + +#define MERGE2(a, b) a ## b +#define CVTBL(tbl, cp) MERGE2(tbl, cp) + +/*------------------------------------------------------------------------*/ +/* Code Conversion Tables */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE == 437 || FF_CODE_PAGE == 0 +static const WCHAR uc437[] = { /* CP437(U.S.) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 720 || FF_CODE_PAGE == 0 +static const WCHAR uc720[] = { /* CP720(Arabic) to Unicode conversion table */ + 0x0000, 0x0000, 0x00E9, 0x00E2, 0x0000, 0x00E0, 0x0000, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0651, 0x0652, 0x00F4, 0x00A4, 0x0640, 0x00FB, 0x00F9, 0x0621, 0x0622, 0x0623, 0x0624, 0x00A3, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 0x0641, 0x00B5, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, + 0x2261, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 737 || FF_CODE_PAGE == 0 +static const WCHAR uc737[] = { /* CP737(Greek) to Unicode conversion table */ + 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, + 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, + 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, 0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E, + 0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 771 || FF_CODE_PAGE == 0 +static const WCHAR uc771[] = { /* CP771(KBL) to Unicode conversion table */ + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x0104, 0x0105, 0x010C, 0x010D, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x0118, 0x0119, 0x0116, 0x0117, 0x012E, 0x012F, 0x0160, 0x0161, 0x0172, 0x0173, 0x016A, 0x016B, 0x017D, 0x017E, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 775 || FF_CODE_PAGE == 0 +static const WCHAR uc775[] = { /* CP775(Baltic) to Unicode conversion table */ + 0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, 0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4, + 0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, 0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, 0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D, + 0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, 0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, 0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019, + 0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, 0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 850 || FF_CODE_PAGE == 0 +static const WCHAR uc850[] = { /* CP850(Latin 1) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 852 || FF_CODE_PAGE == 0 +static const WCHAR uc852[] = { /* CP852(Latin 2) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106, + 0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4, + 0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 855 || FF_CODE_PAGE == 0 +static const WCHAR uc855[] = { /* CP855(Cyrillic) to Unicode conversion table */ + 0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408, + 0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A, + 0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580, + 0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116, + 0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 857 || FF_CODE_PAGE == 0 +static const WCHAR uc857[] = { /* CP857(Turkish) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0131, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x0130, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x015E, 0x015F, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x011E, 0x011F, 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00BA, 0x00AA, 0x00CA, 0x00CB, 0x00C8, 0x0000, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x0000, 0x00D7, 0x00DA, 0x00DB, 0x00D9, 0x00EC, 0x00FF, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x0000, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 860 || FF_CODE_PAGE == 0 +static const WCHAR uc860[] = { /* CP860(Portuguese) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E3, 0x00E0, 0x00C1, 0x00E7, 0x00EA, 0x00CA, 0x00E8, 0x00CD, 0x00D4, 0x00EC, 0x00C3, 0x00C2, + 0x00C9, 0x00C0, 0x00C8, 0x00F4, 0x00F5, 0x00F2, 0x00DA, 0x00F9, 0x00CC, 0x00D5, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x20A7, 0x00D3, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x00D2, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 861 || FF_CODE_PAGE == 0 +static const WCHAR uc861[] = { /* CP861(Icelandic) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00D0, 0x00F0, 0x00DE, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00FE, 0x00FB, 0x00DD, 0x00FD, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00C1, 0x00CD, 0x00D3, 0x00DA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 862 || FF_CODE_PAGE == 0 +static const WCHAR uc862[] = { /* CP862(Hebrew) to Unicode conversion table */ + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 863 || FF_CODE_PAGE == 0 +static const WCHAR uc863[] = { /* CP863(Canadian French) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00C2, 0x00E0, 0x00B6, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x2017, 0x00C0, + 0x00C9, 0x00C8, 0x00CA, 0x00F4, 0x00CB, 0x00CF, 0x00FB, 0x00F9, 0x00A4, 0x00D4, 0x00DC, 0x00A2, 0x00A3, 0x00D9, 0x00DB, 0x0192, + 0x00A6, 0x00B4, 0x00F3, 0x00FA, 0x00A8, 0x00BB, 0x00B3, 0x00AF, 0x00CE, 0x3210, 0x00AC, 0x00BD, 0x00BC, 0x00BE, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2219, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 864 || FF_CODE_PAGE == 0 +static const WCHAR uc864[] = { /* CP864(Arabic) to Unicode conversion table */ + 0x00B0, 0x00B7, 0x2219, 0x221A, 0x2592, 0x2500, 0x2502, 0x253C, 0x2524, 0x252C, 0x251C, 0x2534, 0x2510, 0x250C, 0x2514, 0x2518, + 0x03B2, 0x221E, 0x03C6, 0x00B1, 0x00BD, 0x00BC, 0x2248, 0x00AB, 0x00BB, 0xFEF7, 0xFEF8, 0x0000, 0x0000, 0xFEFB, 0xFEFC, 0x0000, + 0x00A0, 0x00AD, 0xFE82, 0x00A3, 0x00A4, 0xFE84, 0x0000, 0x20AC, 0xFE8E, 0xFE8F, 0xFE95, 0xFE99, 0x060C, 0xFE9D, 0xFEA1, 0xFEA5, + 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0xFED1, 0x061B, 0xFEB1, 0xFEB5, 0xFEB9, 0x061F, + 0x00A2, 0xFE80, 0xFE81, 0xFE83, 0xFE85, 0xFECA, 0xFE8B, 0xFE8D, 0xFE91, 0xFE93, 0xFE97, 0xFE9B, 0xFE9F, 0xFEA3, 0xFEA7, 0xFEA9, + 0xFEAB, 0xFEAD, 0xFEAF, 0xFEB3, 0xFEB7, 0xFEBB, 0xFEBF, 0xFEC1, 0xFEC5, 0xFECB, 0xFECF, 0x00A6, 0x00AC, 0x00F7, 0x00D7, 0xFEC9, + 0x0640, 0xFED3, 0xFED7, 0xFEDB, 0xFEDF, 0xFEE3, 0xFEE7, 0xFEEB, 0xFEED, 0xFEEF, 0xFEF3, 0xFEBD, 0xFECC, 0xFECE, 0xFECD, 0xFEE1, + 0xFE7D, 0x0651, 0xFEE5, 0xFEE9, 0xFEEC, 0xFEF0, 0xFEF2, 0xFED0, 0xFED5, 0xFEF5, 0xFEF6, 0xFEDD, 0xFED9, 0xFEF1, 0x25A0, 0x0000 +}; +#endif +#if FF_CODE_PAGE == 865 || FF_CODE_PAGE == 0 +static const WCHAR uc865[] = { /* CP865(Nordic) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C5, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00A4, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x2558, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 866 || FF_CODE_PAGE == 0 +static const WCHAR uc866[] = { /* CP866(Russian) to Unicode conversion table */ + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0 +}; +#endif +#if FF_CODE_PAGE == 869 || FF_CODE_PAGE == 0 +static const WCHAR uc869[] = { /* CP869(Greek 2) to Unicode conversion table */ + 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x00B7, 0x0386, 0x00B7, 0x00B7, 0x00AC, 0x00A6, 0x2018, 0x2019, 0x0388, 0x2015, 0x0389, + 0x038A, 0x03AA, 0x038C, 0x00B7, 0x00B7, 0x038E, 0x03AB, 0x00A9, 0x038F, 0x00B2, 0x00B3, 0x03AC, 0x00A3, 0x03AD, 0x03AE, 0x03AF, + 0x03CA, 0x0390, 0x03CC, 0x03CD, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x00BD, 0x0398, 0x0399, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x039A, 0x039B, 0x039C, 0x039D, 0x2563, 0x2551, 0x2557, 0x255D, 0x039E, 0x039F, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0A30, 0x03A1, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x03A3, + 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x2518, 0x250C, 0x2588, 0x2584, 0x03B4, 0x03B5, 0x2580, + 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x0384, + 0x00AD, 0x00B1, 0x03C5, 0x03C6, 0x03C7, 0x00A7, 0x03C8, 0x0385, 0x00B0, 0x00A8, 0x03C9, 0x03CB, 0x03B0, 0x03CE, 0x25A0, 0x00A0 +}; +#endif + + + + +/*------------------------------------------------------------------------*/ +/* OEM <==> Unicode conversions for static code page configuration */ +/* SBCS fixed code page */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE != 0 && FF_CODE_PAGE < 900 +WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */ + DWORD uni, /* UTF-16 encoded character to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + WCHAR c = 0; + const WCHAR *p = CVTBL(uc, FF_CODE_PAGE); + + + if (uni < 0x80) { /* ASCII? */ + c = (WCHAR)uni; + + } else { /* Non-ASCII */ + if (uni < 0x10000 && cp == FF_CODE_PAGE) { /* Is it in BMP and valid code page? */ + for (c = 0; c < 0x80 && uni != p[c]; c++) ; + c = (c + 0x80) & 0xFF; + } + } + + return c; +} + +WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */ + WCHAR oem, /* OEM code to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + WCHAR c = 0; + const WCHAR *p = CVTBL(uc, FF_CODE_PAGE); + + + if (oem < 0x80) { /* ASCII? */ + c = oem; + + } else { /* Extended char */ + if (cp == FF_CODE_PAGE) { /* Is it a valid code page? */ + if (oem < 0x100) c = p[oem - 0x80]; + } + } + + return c; +} + +#endif + + + +/*------------------------------------------------------------------------*/ +/* OEM <==> Unicode conversions for static code page configuration */ +/* DBCS fixed code page */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE >= 900 +WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */ + DWORD uni, /* UTF-16 encoded character to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0, uc; + UINT i = 0, n, li, hi; + + + if (uni < 0x80) { /* ASCII? */ + c = (WCHAR)uni; + + } else { /* Non-ASCII */ + if (uni < 0x10000 && cp == FF_CODE_PAGE) { /* Is it in BMP and valid code page? */ + uc = (WCHAR)uni; + p = CVTBL(uni2oem, FF_CODE_PAGE); + hi = sizeof CVTBL(uni2oem, FF_CODE_PAGE) / 4 - 1; + li = 0; + for (n = 16; n; n--) { + i = li + (hi - li) / 2; + if (uc == p[i * 2]) break; + if (uc > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + + return c; +} + + +WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */ + WCHAR oem, /* OEM code to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0; + UINT i = 0, n, li, hi; + + + if (oem < 0x80) { /* ASCII? */ + c = oem; + + } else { /* Extended char */ + if (cp == FF_CODE_PAGE) { /* Is it valid code page? */ + p = CVTBL(oem2uni, FF_CODE_PAGE); + hi = sizeof CVTBL(oem2uni, FF_CODE_PAGE) / 4 - 1; + li = 0; + for (n = 16; n; n--) { + i = li + (hi - li) / 2; + if (oem == p[i * 2]) break; + if (oem > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + + return c; +} +#endif + + + +/*------------------------------------------------------------------------*/ +/* OEM <==> Unicode conversions for dynamic code page configuration */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE == 0 + +static const WORD cp_code[] = { 437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 0}; +static const WCHAR* const cp_table[] = {uc437, uc720, uc737, uc771, uc775, uc850, uc852, uc855, uc857, uc860, uc861, uc862, uc863, uc864, uc865, uc866, uc869, 0}; + + +WCHAR ff_uni2oem ( /* Returns OEM code character, zero on error */ + DWORD uni, /* UTF-16 encoded character to be converted */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0, uc; + UINT i, n, li, hi; + + + if (uni < 0x80) { /* ASCII? */ + c = (WCHAR)uni; + + } else { /* Non-ASCII */ + if (uni < 0x10000) { /* Is it in BMP? */ + uc = (WCHAR)uni; + p = 0; + if (cp < 900) { /* SBCS */ + for (i = 0; cp_code[i] != 0 && cp_code[i] != cp; i++) ; /* Get conversion table */ + p = cp_table[i]; + if (p) { /* Is it valid code page ? */ + for (c = 0; c < 0x80 && uc != p[c]; c++) ; /* Find OEM code in the table */ + c = (c + 0x80) & 0xFF; + } + } else { /* DBCS */ + switch (cp) { /* Get conversion table */ + case 932 : p = uni2oem932; hi = sizeof uni2oem932 / 4 - 1; break; + case 936 : p = uni2oem936; hi = sizeof uni2oem936 / 4 - 1; break; + case 949 : p = uni2oem949; hi = sizeof uni2oem949 / 4 - 1; break; + case 950 : p = uni2oem950; hi = sizeof uni2oem950 / 4 - 1; break; + } + if (p) { /* Is it valid code page? */ + li = 0; + for (n = 16; n; n--) { /* Find OEM code */ + i = li + (hi - li) / 2; + if (uc == p[i * 2]) break; + if (uc > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + } + } + + return c; +} + + +WCHAR ff_oem2uni ( /* Returns Unicode character, zero on error */ + WCHAR oem, /* OEM code to be converted (DBC if >=0x100) */ + WORD cp /* Code page for the conversion */ +) +{ + const WCHAR *p; + WCHAR c = 0; + UINT i, n, li, hi; + + + if (oem < 0x80) { /* ASCII? */ + c = oem; + + } else { /* Extended char */ + p = 0; + if (cp < 900) { /* SBCS */ + for (i = 0; cp_code[i] != 0 && cp_code[i] != cp; i++) ; /* Get table */ + p = cp_table[i]; + if (p) { /* Is it a valid CP ? */ + if (oem < 0x100) c = p[oem - 0x80]; + } + } else { /* DBCS */ + switch (cp) { + case 932 : p = oem2uni932; hi = sizeof oem2uni932 / 4 - 1; break; + case 936 : p = oem2uni936; hi = sizeof oem2uni936 / 4 - 1; break; + case 949 : p = oem2uni949; hi = sizeof oem2uni949 / 4 - 1; break; + case 950 : p = oem2uni950; hi = sizeof oem2uni950 / 4 - 1; break; + } + if (p) { + li = 0; + for (n = 16; n; n--) { + i = li + (hi - li) / 2; + if (oem == p[i * 2]) break; + if (oem > p[i * 2]) { + li = i; + } else { + hi = i; + } + } + if (n != 0) c = p[i * 2 + 1]; + } + } + } + + return c; +} +#endif + + + +/*------------------------------------------------------------------------*/ +/* Unicode up-case conversion */ +/*------------------------------------------------------------------------*/ + +DWORD ff_wtoupper ( /* Returns up-converted code point */ + DWORD uni /* Unicode code point to be up-converted */ +) +{ + const WORD *p; + WORD uc, bc, nc, cmd; + static const WORD cvt1[] = { /* Compressed up conversion table for U+0000 - U+0FFF */ + /* Basic Latin */ + 0x0061,0x031A, + /* Latin-1 Supplement */ + 0x00E0,0x0317, + 0x00F8,0x0307, + 0x00FF,0x0001,0x0178, + /* Latin Extended-A */ + 0x0100,0x0130, + 0x0132,0x0106, + 0x0139,0x0110, + 0x014A,0x012E, + 0x0179,0x0106, + /* Latin Extended-B */ + 0x0180,0x004D,0x0243,0x0181,0x0182,0x0182,0x0184,0x0184,0x0186,0x0187,0x0187,0x0189,0x018A,0x018B,0x018B,0x018D,0x018E,0x018F,0x0190,0x0191,0x0191,0x0193,0x0194,0x01F6,0x0196,0x0197,0x0198,0x0198,0x023D,0x019B,0x019C,0x019D,0x0220,0x019F,0x01A0,0x01A0,0x01A2,0x01A2,0x01A4,0x01A4,0x01A6,0x01A7,0x01A7,0x01A9,0x01AA,0x01AB,0x01AC,0x01AC,0x01AE,0x01AF,0x01AF,0x01B1,0x01B2,0x01B3,0x01B3,0x01B5,0x01B5,0x01B7,0x01B8,0x01B8,0x01BA,0x01BB,0x01BC,0x01BC,0x01BE,0x01F7,0x01C0,0x01C1,0x01C2,0x01C3,0x01C4,0x01C5,0x01C4,0x01C7,0x01C8,0x01C7,0x01CA,0x01CB,0x01CA, + 0x01CD,0x0110, + 0x01DD,0x0001,0x018E, + 0x01DE,0x0112, + 0x01F3,0x0003,0x01F1,0x01F4,0x01F4, + 0x01F8,0x0128, + 0x0222,0x0112, + 0x023A,0x0009,0x2C65,0x023B,0x023B,0x023D,0x2C66,0x023F,0x0240,0x0241,0x0241, + 0x0246,0x010A, + /* IPA Extensions */ + 0x0253,0x0040,0x0181,0x0186,0x0255,0x0189,0x018A,0x0258,0x018F,0x025A,0x0190,0x025C,0x025D,0x025E,0x025F,0x0193,0x0261,0x0262,0x0194,0x0264,0x0265,0x0266,0x0267,0x0197,0x0196,0x026A,0x2C62,0x026C,0x026D,0x026E,0x019C,0x0270,0x0271,0x019D,0x0273,0x0274,0x019F,0x0276,0x0277,0x0278,0x0279,0x027A,0x027B,0x027C,0x2C64,0x027E,0x027F,0x01A6,0x0281,0x0282,0x01A9,0x0284,0x0285,0x0286,0x0287,0x01AE,0x0244,0x01B1,0x01B2,0x0245,0x028D,0x028E,0x028F,0x0290,0x0291,0x01B7, + /* Greek, Coptic */ + 0x037B,0x0003,0x03FD,0x03FE,0x03FF, + 0x03AC,0x0004,0x0386,0x0388,0x0389,0x038A, + 0x03B1,0x0311, + 0x03C2,0x0002,0x03A3,0x03A3, + 0x03C4,0x0308, + 0x03CC,0x0003,0x038C,0x038E,0x038F, + 0x03D8,0x0118, + 0x03F2,0x000A,0x03F9,0x03F3,0x03F4,0x03F5,0x03F6,0x03F7,0x03F7,0x03F9,0x03FA,0x03FA, + /* Cyrillic */ + 0x0430,0x0320, + 0x0450,0x0710, + 0x0460,0x0122, + 0x048A,0x0136, + 0x04C1,0x010E, + 0x04CF,0x0001,0x04C0, + 0x04D0,0x0144, + /* Armenian */ + 0x0561,0x0426, + + 0x0000 /* EOT */ + }; + static const WORD cvt2[] = { /* Compressed up conversion table for U+1000 - U+FFFF */ + /* Phonetic Extensions */ + 0x1D7D,0x0001,0x2C63, + /* Latin Extended Additional */ + 0x1E00,0x0196, + 0x1EA0,0x015A, + /* Greek Extended */ + 0x1F00,0x0608, + 0x1F10,0x0606, + 0x1F20,0x0608, + 0x1F30,0x0608, + 0x1F40,0x0606, + 0x1F51,0x0007,0x1F59,0x1F52,0x1F5B,0x1F54,0x1F5D,0x1F56,0x1F5F, + 0x1F60,0x0608, + 0x1F70,0x000E,0x1FBA,0x1FBB,0x1FC8,0x1FC9,0x1FCA,0x1FCB,0x1FDA,0x1FDB,0x1FF8,0x1FF9,0x1FEA,0x1FEB,0x1FFA,0x1FFB, + 0x1F80,0x0608, + 0x1F90,0x0608, + 0x1FA0,0x0608, + 0x1FB0,0x0004,0x1FB8,0x1FB9,0x1FB2,0x1FBC, + 0x1FCC,0x0001,0x1FC3, + 0x1FD0,0x0602, + 0x1FE0,0x0602, + 0x1FE5,0x0001,0x1FEC, + 0x1FF3,0x0001,0x1FFC, + /* Letterlike Symbols */ + 0x214E,0x0001,0x2132, + /* Number forms */ + 0x2170,0x0210, + 0x2184,0x0001,0x2183, + /* Enclosed Alphanumerics */ + 0x24D0,0x051A, + 0x2C30,0x042F, + /* Latin Extended-C */ + 0x2C60,0x0102, + 0x2C67,0x0106, 0x2C75,0x0102, + /* Coptic */ + 0x2C80,0x0164, + /* Georgian Supplement */ + 0x2D00,0x0826, + /* Full-width */ + 0xFF41,0x031A, + + 0x0000 /* EOT */ + }; + + + if (uni < 0x10000) { /* Is it in BMP? */ + uc = (WORD)uni; + p = uc < 0x1000 ? cvt1 : cvt2; + for (;;) { + bc = *p++; /* Get the block base */ + if (bc == 0 || uc < bc) break; /* Not matched? */ + nc = *p++; cmd = nc >> 8; nc &= 0xFF; /* Get processing command and block size */ + if (uc < bc + nc) { /* In the block? */ + switch (cmd) { + case 0: uc = p[uc - bc]; break; /* Table conversion */ + case 1: uc -= (uc - bc) & 1; break; /* Case pairs */ + case 2: uc -= 16; break; /* Shift -16 */ + case 3: uc -= 32; break; /* Shift -32 */ + case 4: uc -= 48; break; /* Shift -48 */ + case 5: uc -= 26; break; /* Shift -26 */ + case 6: uc += 8; break; /* Shift +8 */ + case 7: uc -= 80; break; /* Shift -80 */ + case 8: uc -= 0x1C60; break; /* Shift -0x1C60 */ + } + break; + } + if (cmd == 0) p += nc; /* Skip table if needed */ + } + uni = uc; + } + + return uni; +} + +#endif /* #if FF_USE_LFN */ diff --git a/bdk/libs/lv_conf.h b/bdk/libs/lv_conf.h new file mode 100644 index 00000000..22590f7b --- /dev/null +++ b/bdk/libs/lv_conf.h @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2018 CTCaer + * Copyright (c) 2020 Storm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 LV_CONF_H +#define LV_CONF_H + +#include +#include +/*=================== + Dynamic memory + *===================*/ + +/* Memory size which will be used by the library + * to store the graphical objects and other data */ +#define LV_MEM_CUSTOM 0 /*1: use custom malloc/free, 0: use the built-in lv_mem_alloc/lv_mem_free*/ +#if LV_MEM_CUSTOM == 0 +# define LV_MEM_SIZE NYX_LV_MEM_SZ /*Size memory used by `lv_mem_alloc` in bytes (>= 2kB)*/ +# define LV_MEM_ATTR /*Complier prefix for big array declaration*/ +# define LV_MEM_ADR NYX_LV_MEM_ADR /*Set an address for memory pool instead of allocation it as an array. Can be in external SRAM too.*/ +# define LV_MEM_AUTO_DEFRAG 1 /*Automatically defrag on free*/ +#else /*LV_MEM_CUSTOM*/ +# define LV_MEM_CUSTOM_INCLUDE "../../../mem/heap.h" /*Header for the dynamic memory function*/ +# define LV_MEM_CUSTOM_ALLOC malloc /*Wrapper to malloc*/ +# define LV_MEM_CUSTOM_FREE free /*Wrapper to free*/ +#endif /*LV_MEM_CUSTOM*/ + +/* Garbage Collector settings + * Used if lvgl is binded to higher language and the memory is managed by that language */ +#define LV_ENABLE_GC 0 +#if LV_ENABLE_GC != 0 +# define LV_MEM_CUSTOM_REALLOC your_realloc /*Wrapper to realloc*/ +# define LV_MEM_CUSTOM_GET_SIZE your_mem_get_size /*Wrapper to lv_mem_get_size*/ +# define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/ +#endif /* LV_ENABLE_GC */ + +/*=================== + Graphical settings + *===================*/ + +/* Horizontal and vertical resolution of the library.*/ +#define LV_HOR_RES (1280) +#define LV_VER_RES (720) + +#define LV_HOR_RES_MAX (1280) +#define LV_VER_RES_MAX (720) + +/* Dot Per Inch: used to initialize default sizes. E.g. a button with width = LV_DPI / 2 -> half inch wide + * (Not so important, you can adjust it to modify default sizes and spaces)*/ +#define LV_DPI 100 + +/* Enable anti-aliasing (lines, and radiuses will be smoothed) */ +#define LV_ANTIALIAS 1 /*1: Enable anti-aliasing*/ + +/*Screen refresh period in milliseconds*/ +#define LV_REFR_PERIOD 33 + +/*----------------- + * VDB settings + *----------------*/ + +/* VDB (Virtual Display Buffer) is an internal graphics buffer. + * The GUI will be drawn into this buffer first and then + * the buffer will be passed to your `disp_drv.disp_flush` function to + * copy it to your frame buffer. + * VDB is required for: buffered drawing, opacity, anti-aliasing and shadows + * Learn more: https://docs.littlevgl.com/#Drawing*/ + +/* Size of the VDB in pixels. Typical size: ~1/10 screen. Must be >= LV_HOR_RES + * Setting it to 0 will disable VDB and `disp_drv.disp_fill` and `disp_drv.disp_map` functions + * will be called to draw to the frame buffer directly*/ +#define LV_VDB_SIZE (LV_VER_RES * LV_HOR_RES) + + /* Bit-per-pixel of VDB. Useful for monochrome or non-standard color format displays. + * Special formats are handled with `disp_drv.vdb_wr`)*/ +#define LV_VDB_PX_BPP LV_COLOR_SIZE /*LV_COLOR_SIZE comes from LV_COLOR_DEPTH below to set 8, 16 or 32 bit pixel size automatically */ + + /* Place VDB to a specific address (e.g. in external RAM) + * 0: allocate automatically into RAM + * LV_VDB_ADR_INV: to replace it later with `lv_vdb_set_adr()`*/ +#define LV_VDB_ADR NYX_LV_VDB_ADR + +/* Use two Virtual Display buffers (VDB) to parallelize rendering and flushing + * The flushing should use DMA to write the frame buffer in the background */ +#define LV_VDB_DOUBLE 0 + +/* Place VDB2 to a specific address (e.g. in external RAM) + * 0: allocate automatically into RAM + * LV_VDB_ADR_INV: to replace it later with `lv_vdb_set_adr()`*/ +#define LV_VDB2_ADR 0 + +/* Using true double buffering in `disp_drv.disp_flush` you will always get the image of the whole screen. + * Your only task is to set the rendered image (`color_p` parameter) as frame buffer address or send it to your display. + * The best if you do in the blank period of you display to avoid tearing effect. + * Requires: + * - LV_VDB_SIZE = LV_HOR_RES * LV_VER_RES + * - LV_VDB_DOUBLE = 1 + */ +#define LV_VDB_TRUE_DOUBLE_BUFFERED 0 + +/*================= + Misc. setting + *=================*/ + +/*Input device settings*/ +#define LV_INDEV_READ_PERIOD 33 /*Input device read period in milliseconds*/ +#define LV_INDEV_POINT_MARKER 0 /*Mark the pressed points (required: USE_LV_REAL_DRAW = 1)*/ +#define LV_INDEV_DRAG_LIMIT 10 /*Drag threshold in pixels */ +#define LV_INDEV_DRAG_THROW 20 /*Drag throw slow-down in [%]. Greater value means faster slow-down */ +#define LV_INDEV_LONG_PRESS_TIME 400 /*Long press time in milliseconds*/ +#define LV_INDEV_LONG_PRESS_REP_TIME 1000 //Fix keyb /*Repeated trigger period in long press [ms] */ + +/*Color settings*/ +#define LV_COLOR_DEPTH 32 /*Color depth: 1/8/16/32*/ +#define LV_COLOR_16_SWAP 0 /*Swap the 2 bytes of RGB565 color. Useful if the display has a 8 bit interface (e.g. SPI)*/ +#define LV_COLOR_SCREEN_TRANSP 0 /*1: Enable screen transparency. Useful for OSD or other overlapping GUIs. Requires ARGB8888 colors*/ +#define LV_COLOR_TRANSP LV_COLOR_LIME /*Images pixels with this color will not be drawn (with chroma keying)*/ + +/*Text settings*/ +#define LV_TXT_UTF8 0 /*Enable UTF-8 coded Unicode character usage */ +#define LV_TXT_BREAK_CHARS " ,.;:-_" /*Can break texts on these chars*/ +#define LV_TXT_LINE_BREAK_LONG_LEN 12 /* If a character is at least this long, will break wherever "prettiest" */ +#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 /* Minimum number of characters of a word to put on a line before a break */ +#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 1 /* Minimum number of characters of a word to put on a line after a break */ + +/*Feature usage*/ +#define USE_LV_ANIMATION 1 /*1: Enable all animations*/ +#define USE_LV_SHADOW 1 /*1: Enable shadows*/ +#define USE_LV_GROUP 0 /*1: Enable object groups (for keyboards)*/ +#define USE_LV_GPU 0 /*1: Enable GPU interface*/ +#define USE_LV_REAL_DRAW 0 /*1: Enable function which draw directly to the frame buffer instead of VDB (required if LV_VDB_SIZE = 0)*/ +#define USE_LV_FILESYSTEM 0 /*1: Enable file system (might be required for images*/ +#define USE_LV_MULTI_LANG 0 /* Number of languages for labels to store (0: to disable this feature)*/ + +/*Compiler settings*/ +#define LV_ATTRIBUTE_TICK_INC /* Define a custom attribute to `lv_tick_inc` function */ +#define LV_ATTRIBUTE_TASK_HANDLER /* Define a custom attribute to `lv_task_handler` function */ +#define LV_COMPILER_VLA_SUPPORTED 1 /* 1: Variable length array is supported*/ + +/*HAL settings*/ +#define LV_TICK_CUSTOM 1 /*1: use a custom tick source (removing the need to manually update the tick with `lv_tick_inc`) */ +#if LV_TICK_CUSTOM == 1 +#define LV_TICK_CUSTOM_INCLUDE "../../../utils/util.h" /*Header for the sys time function*/ +#define LV_TICK_CUSTOM_SYS_TIME_EXPR (get_tmr_ms()) /*Expression evaluating to current systime in ms*/ +#endif /*LV_TICK_CUSTOM*/ + + +/*Log settings*/ +#define USE_LV_LOG 0 /*Enable/disable the log module*/ +#if USE_LV_LOG +/* How important log should be added: + * LV_LOG_LEVEL_TRACE A lot of logs to give detailed information + * LV_LOG_LEVEL_INFO Log important events + * LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't caused problem + * LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail + */ +# define LV_LOG_LEVEL LV_LOG_LEVEL_WARN +/* 1: Print the log with 'printf'; 0: user need to register a callback*/ +# define LV_LOG_PRINTF 1 +#endif /*USE_LV_LOG*/ + + +/*================ + * THEME USAGE + *================*/ +#define LV_THEME_LIVE_UPDATE 0 /*1: Allow theme switching at run time. Uses 8..10 kB of RAM*/ + +#define USE_LV_THEME_STORM 1 //Theme initialisieren + + +/*================== + * FONT USAGE + *===================*/ + +/* More info about fonts: https://docs.littlevgl.com/#Fonts + * To enable a built-in font use 1,2,4 or 8 values + * which will determine the bit-per-pixel. Higher value means smoother fonts */ +#define LV_FONT_QUALITY 8 + +#define USE_UBUNTU_MONO LV_FONT_QUALITY + +#define USE_INTERUI_20 LV_FONT_QUALITY +#define USE_INTERUI_30 LV_FONT_QUALITY + +#define USE_HEKATE_SYMBOL_20 USE_INTERUI_20 +#define USE_HEKATE_SYMBOL_30 USE_INTERUI_30 +#define USE_HEKATE_SYMBOL_120 LV_FONT_QUALITY + + +#define USE_MABOLT_12 2 +#define USE_NUM_110 2 + +/* Optionally declare your custom fonts here. + * You can use these fonts as default font too + * and they will be available globally. E.g. + * #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) \ + * LV_FONT_DECLARE(my_font_2) \ + */ +#define LV_FONT_CUSTOM_DECLARE + +#define LV_FONT_DEFAULT &interui_30 /*Always set a default font from the built-in fonts*/ + +/*=================== + * LV_OBJ SETTINGS + *==================*/ +#define LV_OBJ_FREE_NUM_TYPE uint32_t /*Type of free number attribute (comment out disable free number)*/ +#define LV_OBJ_FREE_PTR 1 /*Enable the free pointer attribute*/ +#define LV_OBJ_REALIGN 1 // 0 in OG gui /*Enable `lv_obj_realaign()` based on `lv_obj_align()` parameters*/ + +/*================== + * LV OBJ X USAGE + *================*/ +/* + * Documentation of the object types: https://docs.littlevgl.com/#Object-types + */ + +/***************** + * Simple object + *****************/ + +/*Label (dependencies: -*/ +#define USE_LV_LABEL 1 +#if USE_LV_LABEL != 0 +# define LV_LABEL_SCROLL_SPEED 25 /*Hor, or ver. scroll speed [px/sec] in 'LV_LABEL_LONG_SCROLL/ROLL' mode*/ +#endif + +/*Image (dependencies: lv_label*/ +#define USE_LV_IMG 1 +#if USE_LV_IMG != 0 +# define LV_IMG_CF_INDEXED 0 /*Enable indexed (palette) images*/ +# define LV_IMG_CF_ALPHA 0 /*Enable alpha indexed images*/ +#endif + +/*Line (dependencies: -*/ +#define USE_LV_LINE 1 + +/*Arc (dependencies: -)*/ +#define USE_LV_ARC 0 + +/******************* + * Container objects + *******************/ + +/*Container (dependencies: -*/ +#define USE_LV_CONT 1 + +/*Page (dependencies: lv_cont)*/ +#define USE_LV_PAGE 1 + +/*Window (dependencies: lv_cont, lv_btn, lv_label, lv_img, lv_page)*/ +#define USE_LV_WIN 1 + +/*Tab (dependencies: lv_page, lv_btnm)*/ +#define USE_LV_TABVIEW 1 +# if USE_LV_TABVIEW != 0 +# define LV_TABVIEW_ANIM_TIME 0 /*Time of slide animation [ms] (0: no animation)*/ +#endif + +/*Tileview (dependencies: lv_page) */ +#define USE_LV_TILEVIEW 0 +#if USE_LV_TILEVIEW +# define LV_TILEVIEW_ANIM_TIME 0 /*Time of slide animation [ms] (0: no animation)*/ +#endif + +/************************* + * Data visualizer objects + *************************/ + +/*Bar (dependencies: -)*/ +#define USE_LV_BAR 1 + +/*Line meter (dependencies: *;)*/ +#define USE_LV_LMETER 0 + +/*Gauge (dependencies:lv_bar, lv_lmeter)*/ +#define USE_LV_GAUGE 0 + +/*Chart (dependencies: -)*/ +#define USE_LV_CHART 0 + +/*Table (dependencies: lv_label)*/ +#define USE_LV_TABLE 1 +#if USE_LV_TABLE +# define LV_TABLE_COL_MAX 12 +#endif + +/*LED (dependencies: -)*/ +#define USE_LV_LED 0 + +/*Message box (dependencies: lv_rect, lv_btnm, lv_label)*/ +#define USE_LV_MBOX 1 + +/*Text area (dependencies: lv_label, lv_page)*/ +#define USE_LV_TA 1 +#if USE_LV_TA != 0 +# define LV_TA_CURSOR_BLINK_TIME 400 /*ms*/ +# define LV_TA_PWD_SHOW_TIME 1500 /*ms*/ +#endif + +/*Spinbox (dependencies: lv_ta)*/ +#define USE_LV_SPINBOX 0 + +/*Calendar (dependencies: -)*/ +#define USE_LV_CALENDAR 0 + +/*Preload (dependencies: lv_arc)*/ +#define USE_LV_PRELOAD 0 +#if USE_LV_PRELOAD != 0 +# define LV_PRELOAD_DEF_ARC_LENGTH 60 /*[deg]*/ +# define LV_PRELOAD_DEF_SPIN_TIME 1000 /*[ms]*/ +# define LV_PRELOAD_DEF_ANIM LV_PRELOAD_TYPE_SPINNING_ARC +#endif + +/*Canvas (dependencies: lv_img)*/ +#define USE_LV_CANVAS 0 +/************************* + * User input objects + *************************/ + +/*Button (dependencies: lv_cont*/ +#define USE_LV_BTN 1 +#if USE_LV_BTN != 0 +# define LV_BTN_INK_EFFECT 0 /*Enable button-state animations - draw a circle on click (dependencies: USE_LV_ANIMATION)*/ +#endif + +/*Image Button (dependencies: lv_btn*/ +#define USE_LV_IMGBTN 1 +#if USE_LV_IMGBTN +# define LV_IMGBTN_TILED 0 /*1: The imgbtn requires left, mid and right parts and the width can be set freely*/ +#endif + +/*Button matrix (dependencies: -)*/ +#define USE_LV_BTNM 1 + +/*Keyboard (dependencies: lv_btnm)*/ +#define USE_LV_KB 1 + +/*Check box (dependencies: lv_btn, lv_label)*/ +#define USE_LV_CB 1 + +/*List (dependencies: lv_page, lv_btn, lv_label, (lv_img optionally for icons ))*/ +#define USE_LV_LIST 1 +#if USE_LV_LIST != 0 +# define LV_LIST_FOCUS_TIME 100 /*Default animation time of focusing to a list element [ms] (0: no animation) */ +#endif + +/*Drop down list (dependencies: lv_page, lv_label, lv_symbol_def.h)*/ +#define USE_LV_DDLIST 1 +#if USE_LV_DDLIST != 0 +# define LV_DDLIST_ANIM_TIME 100 /*Open and close default animation time [ms] (0: no animation)*/ +#endif + +/*Roller (dependencies: lv_ddlist)*/ +#define USE_LV_ROLLER 1 +#if USE_LV_ROLLER != 0 +# define LV_ROLLER_ANIM_TIME 200 /*Focus animation time [ms] (0: no animation)*/ +#endif + +/*Slider (dependencies: lv_bar)*/ +#define USE_LV_SLIDER 1 + +/*Switch (dependencies: lv_slider)*/ +#define USE_LV_SW 1 + + + + + + +#endif /*LV_CONF_H*/ + diff --git a/bdk/thermal/fan.c b/bdk/thermal/fan.c new file mode 100644 index 00000000..d149b360 --- /dev/null +++ b/bdk/thermal/fan.c @@ -0,0 +1,106 @@ +/* + * Fan driver for Nintendo Switch + * + * Copyright (c) 2018-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include +#include +#include +#include +#include +#include + +void set_fan_duty(u32 duty) +{ + static bool fan_init = false; + static u16 curr_duty = -1; + + if (curr_duty == duty) + return; + + if (!fan_init) + { + // Fan tachometer. + PINMUX_AUX(PINMUX_AUX_CAM1_PWDN) = PINMUX_TRISTATE | PINMUX_INPUT_ENABLE | PINMUX_PULL_UP | 1; + gpio_config(GPIO_PORT_S, GPIO_PIN_7, GPIO_MODE_GPIO); + gpio_output_enable(GPIO_PORT_S, GPIO_PIN_7, GPIO_OUTPUT_DISABLE); + + PWM(PWM_CONTROLLER_PWM_CSR_1) = PWM_CSR_EN | (0x100 << 16); // Max PWM to disable fan. + + PINMUX_AUX(PINMUX_AUX_LCD_GPIO2) = 1; // Set source to PWM1. + gpio_config(GPIO_PORT_V, GPIO_PIN_4, GPIO_MODE_SPIO); // Fan power mode. + + fan_init = true; + } + + if (duty > 236) + duty = 236; + + // Inverted polarity. + u32 inv_duty = 236 - duty; + + // If disabled send a 0 duty. + if (inv_duty == 236) + { + PWM(PWM_CONTROLLER_PWM_CSR_1) = PWM_CSR_EN | (0x100 << 16); // Bit 24 is absolute 0%. + regulator_5v_disable(REGULATOR_5V_FAN); + + // Disable fan. + PINMUX_AUX(PINMUX_AUX_LCD_GPIO2) = + PINMUX_INPUT_ENABLE | PINMUX_PARKED | PINMUX_TRISTATE | PINMUX_PULL_DOWN; // Set source to PWM1. + } + else // Set PWM duty. + { + // Fan power supply. + regulator_5v_enable(REGULATOR_5V_FAN); + PWM(PWM_CONTROLLER_PWM_CSR_1) = PWM_CSR_EN | (inv_duty << 16); + + // Enable fan. + PINMUX_AUX(PINMUX_AUX_LCD_GPIO2) = 1; // Set source to PWM1. + } + + curr_duty = duty; +} + +void get_fan_speed(u32 *duty, u32 *rpm) +{ + if (rpm) + { + u32 irq_count = 1; + bool should_read = true; + bool irq_val = 0; + + // Poll irqs for 2 seconds. + int timer = get_tmr_us() + 1000000; + while (timer - get_tmr_us()) + { + irq_val = gpio_read(GPIO_PORT_S, GPIO_PIN_7); + if (irq_val && should_read) + { + irq_count++; + should_read = false; + } + else if (!irq_val) + should_read = true; + } + + // Calculate rpm based on triggered interrupts. + *rpm = 60000000 / ((1000000 * 2) / irq_count); + } + + if (duty) + *duty = 236 - ((PWM(PWM_CONTROLLER_PWM_CSR_1) >> 16) & 0xFF); +} diff --git a/bdk/thermal/fan.h b/bdk/thermal/fan.h new file mode 100644 index 00000000..e8279758 --- /dev/null +++ b/bdk/thermal/fan.h @@ -0,0 +1,29 @@ +/* + * Fan driver for Nintendo Switch + * + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __FAN_H_ +#define __FAN_H_ + +#include + +// Disable: 0 (0 RPM), min duty: 1 (960 RPM), max duty 235 (11000 RPM). +void set_fan_duty(u32 duty); +// Passing NULL ptr on either of the two, disables parsing of it. +void get_fan_speed(u32 *duty, u32 *rpm); + +#endif /* __FAN_H_ */ diff --git a/bdk/thermal/tmp451.c b/bdk/thermal/tmp451.c new file mode 100644 index 00000000..65f2fd24 --- /dev/null +++ b/bdk/thermal/tmp451.c @@ -0,0 +1,87 @@ +/* + * SOC/PCB Temperature driver for Nintendo Switch's TI TMP451 + * + * Copyright (c) 2018-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include +#include +#include +#include + +u16 tmp451_get_soc_temp(bool intenger) +{ + u8 val; + u16 temp = 0; + + val = i2c_recv_byte(I2C_1, TMP451_I2C_ADDR, TMP451_SOC_TEMP_REG); + if (intenger) + return val; + + temp = val << 8; + val = i2c_recv_byte(I2C_1, TMP451_I2C_ADDR, TMP451_SOC_TMP_DEC_REG); + temp |= ((val >> 4) * 625) / 100; + + return temp; +} + +u16 tmp451_get_pcb_temp(bool intenger) +{ + u8 val; + u16 temp = 0; + + val = i2c_recv_byte(I2C_1, TMP451_I2C_ADDR, TMP451_PCB_TEMP_REG); + if (intenger) + return val; + + temp = val << 8; + val = i2c_recv_byte(I2C_1, TMP451_I2C_ADDR, TMP451_PCB_TMP_DEC_REG); + temp |= ((val >> 4) * 625) / 100; + + return temp; +} + +void tmp451_init() +{ + // Disable ALARM and Range to 0 - 127 oC. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_CONFIG_REG, 0x80); + + // Set remote sensor offsets based on SoC. + if (hw_get_chip_id() == GP_HIDREV_MAJOR_T210) + { + // Set offset to 0 oC for Erista. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_SOC_TMP_OFH_REG, 0); + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_SOC_TMP_OFL_REG, 0); + } + else + { + // Set offset to -12.5 oC for Mariko. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_SOC_TMP_OFH_REG, 0xF3); // - 13 oC. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_SOC_TMP_OFL_REG, 0x80); // + 0.5 oC. + } + + // Set conversion rate to 32/s and make a read to update the reg. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_CNV_RATE_REG, 9); + tmp451_get_soc_temp(false); + + // Set rate to every 4 seconds. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_CNV_RATE_REG, 2); +} + +void tmp451_end() +{ + // Place into shutdown mode to conserve power. + i2c_send_byte(I2C_1, TMP451_I2C_ADDR, TMP451_CONFIG_REG, 0xC0); +} diff --git a/bdk/thermal/tmp451.h b/bdk/thermal/tmp451.h new file mode 100644 index 00000000..6258fbde --- /dev/null +++ b/bdk/thermal/tmp451.h @@ -0,0 +1,46 @@ +/* + * SOC/PCB Temperature driver for Nintendo Switch's TI TMP451 + * + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 __TMP451_H_ +#define __TMP451_H_ + +#include + +#define TMP451_I2C_ADDR 0x4C + +#define TMP451_PCB_TEMP_REG 0x00 +#define TMP451_SOC_TEMP_REG 0x01 + +#define TMP451_CONFIG_REG 0x09 +#define TMP451_CNV_RATE_REG 0x0A + +#define TMP451_SOC_TMP_DEC_REG 0x10 +#define TMP451_PCB_TMP_DEC_REG 0x15 + +#define TMP451_SOC_TMP_OFH_REG 0x11 +#define TMP451_SOC_TMP_OFL_REG 0x12 + +// If input is false, the return value is packed. MSByte is the integer in oC +// and the LSByte is the decimal point truncated to 2 decimal places. +// Otherwise it's an integer oC. +u16 tmp451_get_soc_temp(bool integer); +u16 tmp451_get_pcb_temp(bool integer); +void tmp451_init(); +void tmp451_end(); + +#endif /* __TMP451_H_ */ diff --git a/bdk/usb/usb_descriptor_types.h b/bdk/usb/usb_descriptor_types.h new file mode 100644 index 00000000..9f86e9d6 --- /dev/null +++ b/bdk/usb/usb_descriptor_types.h @@ -0,0 +1,238 @@ +/* + * USB driver for Tegra X1 + * + * Copyright (c) 2019-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _USB_DESCRIPTORS_TYPES_H_ +#define _USB_DESCRIPTORS_TYPES_H_ + +#include + +typedef enum { + USB_DESCRIPTOR_DEVICE = 1, + USB_DESCRIPTOR_CONFIGURATION = 2, + USB_DESCRIPTOR_STRING = 3, + USB_DESCRIPTOR_INTERFACE = 4, + USB_DESCRIPTOR_ENDPOINT = 5, + USB_DESCRIPTOR_DEVICE_QUALIFIER = 6, + USB_DESCRIPTOR_OTHER_SPEED_CONFIGURATION = 7, + USB_DESCRIPTOR_INTERFACE_POWER = 8, + USB_DESCRIPTOR_INTERFACE_OTG = 9, + USB_DESCRIPTOR_DEVICE_BINARY_OBJECT = 15, + USB_DESCRIPTOR_DEVICE_BINARY_OBJECT_CAP = 16, + USB_DESCRIPTOR_HID = 33, + USB_DESCRIPTOR_HID_REPORT = 34 +} usb_desc_type_t; + +typedef enum { + USB_DESCRIPTOR_MS_COMPAT_ID = 4, + USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES = 5 +} usb_vendor_desc_type_t; + +typedef enum { + USB_ATTR_REMOTE_WAKE_UP = 0x20, + USB_ATTR_SELF_POWERED = 0x40, + USB_ATTR_BUS_POWERED_RSVD = 0x80 +} usb_cfg_attr_type_t; + +typedef enum +{ + USB_EP_TYPE_CTRL = 0, + USB_EP_TYPE_ISO = 1, + USB_EP_TYPE_BULK = 2, + USB_EP_TYPE_INTR = 3 +} usb_cfg_ep_type_t; + +/* Device descriptor structure */ +typedef struct _usb_dev_descr_t +{ + u8 bLength; // Size of this descriptor in bytes. + u8 bDescriptorType; // Device Descriptor Type. (USB_DESCRIPTOR_DEVICE) + u16 bcdUSB; // USB Spec. Release number (2.1). + u8 bDeviceClass; // Class is specified in the interface descriptor. + u8 bDeviceSubClass; // SubClass is specified in the interface descriptor. + u8 bDeviceProtocol; // Protocol is specified in the interface descriptor. + u8 bMaxPacketSize; // Maximum packet size for EP0. + u16 idVendor; // Vendor ID assigned by USB forum. + u16 idProduct; // Product ID assigned by Organization. + u16 bcdDevice; // Device Release number in BCD. + u8 iManufacturer; // Index of String descriptor describing Manufacturer. + u8 iProduct; // Index of String descriptor describing Product. + u8 iSerialNumber; // Index of String descriptor describing Serial number. + u8 bNumConfigs; // Number of possible configuration. +} __attribute__((packed)) usb_dev_descr_t; + +/* Device Qualifier descriptor structure */ +typedef struct _usb_dev_qual_descr_t +{ + u8 bLength; // Size of this descriptor in bytes. + u8 bDescriptorType; // Device Descriptor Type. (USB_DESCRIPTOR_DEVICE_QUALIFIER) + u16 bcdUSB; // USB Spec. Release number (2.1). + u8 bDeviceClass; // Class is specified in the interface descriptor. + u8 bDeviceSubClass; // SubClass is specified in the interface descriptor. + u8 bDeviceProtocol; // Protocol is specified in the interface descriptor. + u8 bMaxPacketSize; // Maximum packet size for EP0. + u8 bNumOtherConfigs; // Number of possible other-speed configurations. + u8 bReserved; // Reserved for future use, must be zero +} __attribute__((packed)) usb_dev_qual_descr_t; + +/* Configuration descriptor structure */ +typedef struct _usb_cfg_descr_t +{ + u8 bLength; // Length of this descriptor. + u8 bDescriptorType; // CONFIGURATION descriptor type (USB_DESCRIPTOR_CONFIGURATION). + u16 wTotalLength; // Total length of all descriptors for this configuration. + u8 bNumInterfaces; // Number of interfaces in this configuration. + u8 bConfigurationValue; // Value of this configuration (1 based). + u8 iConfiguration; // Index of String Descriptor describing the configuration. + u8 bmAttributes; // Configuration characteristics. + u8 bMaxPower; // Maximum power consumed by this configuration. +} __attribute__((packed)) usb_cfg_descr_t; + +/* Interface descriptor structure */ +typedef struct _usb_inter_descr_t +{ + u8 bLength; // Length of this descriptor. + u8 bDescriptorType; // INTERFACE descriptor type (USB_DESCRIPTOR_INTERFACE). + u8 bInterfaceNumber; // Number of this interface (0 based). + u8 bAlternateSetting; // Value of this alternate interface setting. + u8 bNumEndpoints; // Number of endpoints in this interface. + u8 bInterfaceClass; // Class code (assigned by the USB-IF). + u8 bInterfaceSubClass; // Subclass code (assigned by the USB-IF). + u8 bInterfaceProtocol; // Protocol code (assigned by the USB-IF). + u8 iInterface; // Index of String Descriptor describing the interface. +} __attribute__((packed)) usb_inter_descr_t; + +/* HID descriptor structure */ +typedef struct _usb_hid_descr_t +{ + u8 bLength; // Length of this descriptor. + u8 bDescriptorType; // INTERFACE descriptor type (USB_DESCRIPTOR_HID). + u16 bcdHID; // HID class specification release + u8 bCountryCode; // Country code. + u8 bNumDescriptors; // Number of descriptors. + u8 bClassDescriptorType; // Type of class descriptor (USB_DESCRIPTOR_HID_REPORT). + u16 bDescriptorLength; // Report descriptor length. +} __attribute__((packed)) usb_hid_descr_t; + +/* Endpoint descriptor structure */ +typedef struct _usb_ep_descr_t +{ + u8 bLength; // Length of this descriptor. + u8 bDescriptorType; // ENDPOINT descriptor type (USB_DESCRIPTOR_ENDPOINT). + u8 bEndpointAddress; // Endpoint address. bit7 indicates direction (0=OUT, 1=IN). + u8 bmAttributes; // Endpoint transfer type. + u16 wMaxPacketSize; // Maximum packet size. + u8 bInterval; // Polling interval in frames. For Interrupt and Isochronous data transfer only. +} __attribute__((packed)) usb_ep_descr_t; + +typedef struct _usb_cfg_simple_descr_t +{ + usb_cfg_descr_t config; + usb_inter_descr_t interface; + usb_ep_descr_t endpoint[2]; +} __attribute__((packed)) usb_cfg_simple_descr_t; + +typedef struct _usb_cfg_hid_descr_t +{ + usb_cfg_descr_t config; + usb_inter_descr_t interface; + usb_hid_descr_t hid; + usb_ep_descr_t endpoint[2]; +} __attribute__((packed)) usb_cfg_hid_descr_t; + +typedef struct _usb_dev_bot_t +{ + u8 bLength; // Size of this descriptor in bytes. + u8 bDescriptorType; // Device Descriptor Type. (USB_DESCRIPTOR_DEVICE_BINARY_OBJECT) + u16 wTotalLength; // Size of this descriptor in bytes. + u8 bNumDeviceCaps; // Number of device capabilities in this descriptor. + + /* Device Capability USB 2.0 Extension Descriptor */ + /* Needed for a USB2.10 device. */ + u8 bLengthCap0; // Size of this capability descriptor in bytes. + u8 bDescriptorTypeCap0; // Device Capability Descriptor Type. (USB_DESCRIPTOR_DEVICE_BINARY_OBJECT_CAP) + u8 bDevCapabilityTypeCap0; // USB2: 2. + u32 bmAttributesCap0; // bit1: Link Power Management (LPM). + + u8 bLengthCap1; // Size of this capability descriptor in bytes. + u8 bDescriptorTypeCap1; // Device Capability Descriptor Type. (USB_DESCRIPTOR_DEVICE_BINARY_OBJECT_CAP) + u8 bDevCapabilityTypeCap1; // USB3: 3. + u8 bmAttributesCap1; // bit1: Latency Tolerance Messaging (LTM). + u16 wSpeedsSupported; // Supported bus speeds. 1: Low Speed, 2: Full Speed, 4: High Speed, 8: Super Speed. + u8 bFunctionalitySupport; // Lowest speed at which all the functionality is available. 1: Full speed and above. + u8 bU1DevExitLat; // USB3.0 U1 exit latency. + u16 wU2DevExitLat; // USB3.0 U2 exit latency. + +} __attribute__((packed)) usb_dev_bot_t; + +/* Microsoft OS String descriptor structure */ +typedef struct _usb_ms_os_descr_t +{ + u8 bLength; // 0x12 + u8 bDescriptorType; // 3 + u16 wSignature[7]; // "MSFT100" UTF16 LE + u8 bVendorCode; // + u8 bPadding; +} __attribute__((packed)) usb_ms_os_descr_t; + +/* Microsoft Compatible ID Feature descriptor structure */ +typedef struct _usb_ms_cid_descr_t +{ + u32 dLength; + u16 wVersion; + u16 wCompatibilityId; + u8 bSections; + u8 bReserved0[7]; + u8 bInterfaceNumber; + u8 bReserved1; + u8 bCompatibleId[8]; + u8 bSubCompatibleId[8]; + u8 bReserved2[6]; +} __attribute__((packed)) usb_ms_cid_descr_t; + +/* Microsoft Extended Properties Feature descriptor structure */ +typedef struct _usb_ms_ext_prop_descr_t +{ + u32 dLength; + u16 wVersion; + u16 wExtendedProperty; + u16 wSections; + u32 dPropertySize; + u32 dPropertyType; + u16 wPropertyNameLength; + u16 wPropertyName[22]; // UTF16 LE + u32 dPropertyDataLength; + u16 wPropertyData[2]; // UTF16 LE +} __attribute__((packed)) usb_ms_ext_prop_descr_t; + +typedef struct _usb_desc_t +{ + usb_dev_descr_t *dev; + usb_dev_qual_descr_t *dev_qual; + usb_cfg_simple_descr_t *cfg; + usb_cfg_simple_descr_t *cfg_other; + usb_dev_bot_t *dev_bot; + u8 *vendor; + u8 *product; + u8 *serial; + u8 *lang_id; + usb_ms_os_descr_t *ms_os; + usb_ms_cid_descr_t *ms_cid; + usb_ms_ext_prop_descr_t *mx_ext; +} usb_desc_t; + +#endif diff --git a/bdk/usb/usb_descriptors.c b/bdk/usb/usb_descriptors.c new file mode 100644 index 00000000..389a70d5 --- /dev/null +++ b/bdk/usb/usb_descriptors.c @@ -0,0 +1,558 @@ +/* + * USB driver for Tegra X1 + * + * Copyright (c) 2019-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include +#include + +static usb_dev_descr_t usb_device_descriptor_ums = +{ + .bLength = 18, + .bDescriptorType = USB_DESCRIPTOR_DEVICE, + .bcdUSB = 0x210, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize = 0x40, + .idVendor = 0x11EC, // Nintendo: 0x057E, Nvidia: 0x0955 + .idProduct = 0xA7E0, // Switch: 0x2000, usbd: 0x3000 + .bcdDevice = 0x0101, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigs = 1 +}; + +static usb_dev_qual_descr_t usb_device_qualifier_descriptor = +{ + .bLength = 10, + .bDescriptorType = USB_DESCRIPTOR_DEVICE_QUALIFIER, + .bcdUSB = 0x210, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize = 0x40, + .bNumOtherConfigs = 0x01, + .bReserved = 0x00 +}; + +static usb_cfg_simple_descr_t usb_configuration_descriptor_ums = +{ + /* Configuration descriptor structure */ + .config.bLength = 9, + .config.bDescriptorType = USB_DESCRIPTOR_CONFIGURATION, + .config.wTotalLength = 0x20, + .config.bNumInterfaces = 0x01, + .config.bConfigurationValue = 0x01, + .config.iConfiguration = 0x00, + .config.bmAttributes = USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED_RSVD, + .config.bMaxPower = 32 / 2, + + /* Interface descriptor structure */ + .interface.bLength = 9, + .interface.bDescriptorType = USB_DESCRIPTOR_INTERFACE, + .interface.bInterfaceNumber = 0, + .interface.bAlternateSetting = 0, + .interface.bNumEndpoints = 2, + .interface.bInterfaceClass = 0x08, // Mass Storage Class. + .interface.bInterfaceSubClass = 0x06, // SCSI Transparent Command Set. + .interface.bInterfaceProtocol = 0x50, // Bulk-Only Transport. + .interface.iInterface = 0x00, + + /* Endpoint descriptor structure EP1 IN */ + .endpoint[0].bLength = 7, + .endpoint[0].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[0].bEndpointAddress = 0x81, // USB_EP_ADDR_BULK_IN. + .endpoint[0].bmAttributes = USB_EP_TYPE_BULK, + .endpoint[0].wMaxPacketSize = 0x200, + .endpoint[0].bInterval = 0x00, + + /* Endpoint descriptor structure EP1 OUT */ + .endpoint[1].bLength = 7, + .endpoint[1].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[1].bEndpointAddress = 0x01, // USB_EP_ADDR_BULK_OUT. + .endpoint[1].bmAttributes = USB_EP_TYPE_BULK, + .endpoint[1].wMaxPacketSize = 0x200, + .endpoint[1].bInterval = 0x00 +}; + +static usb_cfg_simple_descr_t usb_other_speed_config_descriptor_ums = +{ + /* Other Speed Configuration descriptor structure */ + .config.bLength = 9, + .config.bDescriptorType = USB_DESCRIPTOR_OTHER_SPEED_CONFIGURATION, + .config.wTotalLength = 0x20, + .config.bNumInterfaces = 0x01, + .config.bConfigurationValue = 0x01, + .config.iConfiguration = 0x00, + .config.bmAttributes = USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED_RSVD, + .config.bMaxPower = 32 / 2, + + /* Interface descriptor structure */ + .interface.bLength = 9, + .interface.bDescriptorType = USB_DESCRIPTOR_INTERFACE, + .interface.bInterfaceNumber = 0x00, + .interface.bAlternateSetting = 0x00, + .interface.bNumEndpoints = 2, + .interface.bInterfaceClass = 0x08, // Mass Storage Class. + .interface.bInterfaceSubClass = 0x06, // SCSI Transparent Command Set. + .interface.bInterfaceProtocol = 0x50, // Bulk-Only Transport. + .interface.iInterface = 0x00, + + /* Endpoint descriptor structure EP1 IN */ + .endpoint[0].bLength = 7, + .endpoint[0].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[0].bEndpointAddress = 0x81, // USB_EP_ADDR_BULK_IN. + .endpoint[0].bmAttributes = USB_EP_TYPE_BULK, + .endpoint[0].wMaxPacketSize = 0x40, + .endpoint[0].bInterval = 0, + + /* Endpoint descriptor structure EP1 OUT */ + .endpoint[1].bLength = 7, + .endpoint[1].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[1].bEndpointAddress = 0x01, // USB_EP_ADDR_BULK_OUT. + .endpoint[1].bmAttributes = USB_EP_TYPE_BULK, + .endpoint[1].wMaxPacketSize = 0x40, + .endpoint[1].bInterval = 0 +}; + +static usb_dev_bot_t usb_device_binary_object_descriptor = +{ + .bLength = 5, + .bDescriptorType = USB_DESCRIPTOR_DEVICE_BINARY_OBJECT, + .wTotalLength = 22, + .bNumDeviceCaps = 2, + + /* Device Capability USB 2.0 Extension Descriptor */ + .bLengthCap0 = 7, + .bDescriptorTypeCap0 = USB_DESCRIPTOR_DEVICE_BINARY_OBJECT_CAP, + .bDevCapabilityTypeCap0 = 2, // USB2. + .bmAttributesCap0 = 0, + + /* Device Capability SuperSpeed Descriptor */ + /* Needed for a USB2.10 device. */ + .bLengthCap1 = 10, + .bDescriptorTypeCap1 = USB_DESCRIPTOR_DEVICE_BINARY_OBJECT_CAP, + .bDevCapabilityTypeCap1 = 3, // USB3. + .bmAttributesCap1 = 0, + .wSpeedsSupported = 0x6, // FS | HS. + .bFunctionalitySupport = 1, // FS and above. + .bU1DevExitLat = 0, + .wU2DevExitLat = 0 +}; + +static u8 usb_lang_id_string_descriptor[4] = +{ + 4, 3, + 0x09, 0x04 +}; + +static u8 usb_serial_string_descriptor[26] = +{ + 26, 0x03, + 'C', 0x00, '7', 0x00, 'C', 0x00, '0', 0x00, + '9', 0x00, '2', 0x00, '4', 0x00, '2', 0x00, 'F', 0x00, '7', 0x00, '0', 0x00, '3', 0x00 +}; + +static u8 usb_vendor_string_descriptor_ums[32] = +{ + 26, 0x03, + 'N', 0, 'y', 0, 'x', 0, ' ', 0, 'U', 0, 'S', 0, 'B', 0, ' ', 0, + 'D', 0, 'i', 0, 's', 0, 'k', 0 +}; + +static u8 usb_product_string_descriptor_ums[22] = +{ + 8, 0x03, + 'U', 0, 'M', 0, 'S', 0 +}; + +static usb_ms_os_descr_t usb_ms_os_descriptor = +{ + .bLength = 0x28, + .bDescriptorType = 0x03, + .wSignature[0] = 'M', + .wSignature[1] = 'S', + .wSignature[2] = 'F', + .wSignature[3] = 'T', + .wSignature[4] = '1', + .wSignature[5] = '0', + .wSignature[6] = '0', + .bVendorCode = 0x99, +}; + +static usb_ms_cid_descr_t usb_ms_cid_descriptor = +{ + .dLength = 0x28, + .wVersion = 0x100, + .wCompatibilityId = USB_DESCRIPTOR_MS_COMPAT_ID, + .bSections = 1, + .bInterfaceNumber = 0, + .bReserved1 = 1, + + .bCompatibleId[0] = 'N', + .bCompatibleId[1] = 'Y', + .bCompatibleId[2] = 'X', + .bCompatibleId[3] = 'U', + .bCompatibleId[4] = 'S', + .bCompatibleId[5] = 'B', +}; + +static usb_ms_ext_prop_descr_t usb_ms_ext_prop_descriptor_ums = +{ + .dLength = 0x48, + .wVersion = 0x100, + .wExtendedProperty = USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES, + .wSections = 1, + + .dPropertySize = 0x3E, + .dPropertyType = 4, // DWORD + + .wPropertyNameLength = 0x2C, + .wPropertyName[0] = 'M', // MaximumTransferLength. + .wPropertyName[1] = 'a', + .wPropertyName[2] = 'x', + .wPropertyName[3] = 'i', + .wPropertyName[4] = 'm', + .wPropertyName[5] = 'u', + .wPropertyName[6] = 'm', + .wPropertyName[7] = 'T', + .wPropertyName[8] = 'r', + .wPropertyName[9] = 'a', + .wPropertyName[10] = 'n', + .wPropertyName[11] = 's', + .wPropertyName[12] = 'f', + .wPropertyName[13] = 'e', + .wPropertyName[14] = 'r', + .wPropertyName[15] = 'L', + .wPropertyName[16] = 'e', + .wPropertyName[17] = 'n', + .wPropertyName[18] = 'g', + .wPropertyName[19] = 't', + .wPropertyName[20] = 'h', + .wPropertyName[21] = 0, + + .dPropertyDataLength = 0x4, + .wPropertyData[0] = 0x00, // 1MB. + .wPropertyData[1] = 0x10, +}; + +static usb_ms_ext_prop_descr_t usb_ms_ext_prop_descriptor_hid = +{ + .dLength = 7, + .wVersion = 0x100, + .wExtendedProperty = USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES, + .wSections = 0, +}; + +static usb_dev_descr_t usb_device_descriptor_hid_jc = +{ + .bLength = 18, + .bDescriptorType = USB_DESCRIPTOR_DEVICE, + .bcdUSB = 0x210, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize = 0x40, + .idVendor = 0x11EC, // Nintendo: 0x057E, Nvidia: 0x0955 + .idProduct = 0xA7E1, // Switch: 0x2000, usbd: 0x3000 + .bcdDevice = 0x0101, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigs = 1 +}; + +static usb_dev_descr_t usb_device_descriptor_hid_touch = +{ + .bLength = 18, + .bDescriptorType = USB_DESCRIPTOR_DEVICE, + .bcdUSB = 0x210, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize = 0x40, + .idVendor = 0x11EC, // Nintendo: 0x057E, Nvidia: 0x0955 + .idProduct = 0xA7E2, // Switch: 0x2000, usbd: 0x3000 + .bcdDevice = 0x0101, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigs = 1 +}; + +u8 hid_report_descriptor_jc[] = +{ + 0x05, 0x01, // USAGE_PAGE (Generic Desktop), + 0x09, 0x04, // USAGE (Joystick), + 0xa1, 0x01, // COLLECTION (Application), + 0xa1, 0x02, // COLLECTION (Logical), + 0x75, 0x08, // REPORT_SIZE (8), + 0x95, 0x04, // REPORT_COUNT (4), + 0x15, 0x00, // LOGICAL_MINIMUM (0), + 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255), + 0x35, 0x00, // PHYSICAL_MINIMUM (0), + 0x46, 0xff, 0x00, // PHYSICAL_MAXIMUM (255), + 0x09, 0x30, // USAGE (X_ID), + 0x09, 0x31, // USAGE (Y_ID), + 0x09, 0x32, // USAGE (Z_ID), + 0x09, 0x35, // USAGE (Rz_ID), + 0x81, 0x02, // INPUT (IOF_Variable), + 0x75, 0x04, // REPORT_SIZE (4), + 0x95, 0x01, // REPORT_COUNT (1), + 0x25, 0x07, // LOGICAL_MAXIMUM (7), + 0x46, 0x3b, 0x01, // PHYSICAL_MAXIMUM (315), + 0x65, 0x14, // UNIT (Eng_Rot_Angular_Pos), + 0x09, 0x39, // USAGE (Hat_Switch), + 0x81, 0x42, // INPUT (IOF_NullposVar), + 0x65, 0x00, // UNIT (Unit_None), + 0x75, 0x01, // REPORT_SIZE (1), + 0x95, 0x0c, // REPORT_COUNT (12), + 0x25, 0x01, // LOGICAL_MAXIMUM (1), + 0x45, 0x01, // PHYSICAL_MAXIMUM (1), + 0x05, 0x09, // USAGE_PAGE (Button_ID), + 0x19, 0x01, // USAGE_MINIMUM (1), + 0x29, 0x0c, // USAGE_MAXIMUM (12), + 0x81, 0x02, // INPUT (IOF_Variable), + 0xc0, // END_COLLECTION(), + 0xc0 // END_COLLECTION(), +}; + +u32 hid_report_descriptor_jc_size = sizeof(hid_report_descriptor_jc); + +u8 hid_report_descriptor_touch[] = +{ + 0x05, 0x0d, // USAGE_PAGE (Digitizers) + 0x09, 0x05, // USAGE (Touch Pad) + 0xa1, 0x01, // COLLECTION (Application) + 0x85, 0x05, // REPORT_ID (Touch pad) + 0x09, 0x22, // USAGE (Finger) + 0xa1, 0x02, // COLLECTION (Logical) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x09, 0x42, // USAGE (Tip switch) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x15, 0x00, // LOGICAL_MINIMUM (1) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x07, // REPORT_COUNT (7) + 0x09, 0x54, // USAGE (Contact Count) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x08, // REPORT_SIZE (8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x0A, // LOGICAL_MAXIMUM (10) + 0x09, 0x51, // USAGE (Contact Identifier) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // 0x15, 0x00, // LOGICAL_MINIMUM (0) + // 0x26, 0xF8, 0x2A, // LOGICAL_MAXIMUM (11000) + // 0x95, 0x01, // REPORT_COUNT (1) + // 0x75, 0x08, // REPORT_SIZE (16) + // 0x09, 0x30, // USAGE (Pressure) + // 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x05, 0x01, // USAGE_PAGE (Generic Desk.. + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xff, 0x04, // LOGICAL_MAXIMUM (1279) + 0x75, 0x10, // REPORT_SIZE (16) + 0x55, 0x0e, // UNIT_EXPONENT (-2) + 0x65, 0x13, // UNIT(Inch,EngLinear) + 0x09, 0x30, // USAGE (X) + 0x35, 0x00, // PHYSICAL_MINIMUM (0) + 0x46, 0xFF, 0x04, // PHYSICAL_MAXIMUM (1279) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x26, 0xCF, 0x02, // LOGICAL_MAXIMUM (719) + 0x46, 0xCF, 0x02, // PHYSICAL_MAXIMUM (719) + 0x09, 0x31, // USAGE (Y) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + 0x05, 0x0d, // USAGE PAGE (Digitizers) + 0xc0, // END_COLLECTION + 0xc0, // END_COLLECTION +}; +u32 hid_report_descriptor_touch_size = sizeof(hid_report_descriptor_touch); + +static usb_cfg_hid_descr_t usb_configuration_descriptor_hid_jc = +{ + /* Configuration descriptor structure */ + .config.bLength = 9, + .config.bDescriptorType = USB_DESCRIPTOR_CONFIGURATION, + .config.wTotalLength = sizeof(usb_cfg_hid_descr_t), + .config.bNumInterfaces = 0x01, + .config.bConfigurationValue = 0x01, + .config.iConfiguration = 0x00, + .config.bmAttributes = USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED_RSVD, + .config.bMaxPower = 32 / 2, + + /* Interface descriptor structure */ + .interface.bLength = 9, + .interface.bDescriptorType = USB_DESCRIPTOR_INTERFACE, + .interface.bInterfaceNumber = 0, + .interface.bAlternateSetting = 0, + .interface.bNumEndpoints = 2, + .interface.bInterfaceClass = 0x03, // Human Interface Device Class. + .interface.bInterfaceSubClass = 0x00, // SCSI Transparent Command Set. + .interface.bInterfaceProtocol = 0x00, // Bulk-Only Transport. + .interface.iInterface = 0x00, + + .hid.bLength = 9, + .hid.bDescriptorType = USB_DESCRIPTOR_HID, + .hid.bcdHID = 0x110, + .hid.bCountryCode = 0, + .hid.bNumDescriptors = 1, + .hid.bClassDescriptorType = USB_DESCRIPTOR_HID_REPORT, + .hid.bDescriptorLength = sizeof(hid_report_descriptor_jc), + + /* Endpoint descriptor structure EP1 IN */ + .endpoint[0].bLength = 7, + .endpoint[0].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[0].bEndpointAddress = 0x81, // USB_EP_ADDR_BULK_IN. + .endpoint[0].bmAttributes = USB_EP_TYPE_INTR, + .endpoint[0].wMaxPacketSize = 0x200, + .endpoint[0].bInterval = 4, // 8ms on HS. + + /* Endpoint descriptor structure EP1 OUT */ + .endpoint[1].bLength = 7, + .endpoint[1].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[1].bEndpointAddress = 0x01, // USB_EP_ADDR_BULK_OUT. + .endpoint[1].bmAttributes = USB_EP_TYPE_INTR, + .endpoint[1].wMaxPacketSize = 0x200, + .endpoint[1].bInterval = 4 // 8ms on HS. +}; + +static u8 usb_vendor_string_descriptor_hid[22] = +{ + 16, 0x03, + 'N', 0, 'y', 0, 'x', 0, ' ', 0, + 'U', 0, 'S', 0, 'B', 0 +}; + +static u8 usb_product_string_descriptor_hid_jc[24] = +{ + 24, 0x03, + 'N', 0, 'y', 0, 'x', 0, ' ', 0, + 'J', 0, 'o', 0, 'y', 0, '-', 0, 'C', 0, 'o', 0, 'n', 0 +}; + +static u8 usb_product_string_descriptor_hid_touch[26] = +{ + 26, 0x03, + 'N', 0, 'y', 0, 'x', 0, ' ', 0, + 'T', 0, 'o', 0, 'u', 0, 'c', 0, 'h', 0, 'p', 0, 'a', 0, 'd', 0 +}; + +static usb_cfg_hid_descr_t usb_configuration_descriptor_hid_touch = +{ + /* Configuration descriptor structure */ + .config.bLength = 9, + .config.bDescriptorType = USB_DESCRIPTOR_CONFIGURATION, + .config.wTotalLength = sizeof(usb_cfg_hid_descr_t), + .config.bNumInterfaces = 0x01, + .config.bConfigurationValue = 0x01, + .config.iConfiguration = 0x00, + .config.bmAttributes = USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED_RSVD, + .config.bMaxPower = 32 / 2, + + /* Interface descriptor structure */ + .interface.bLength = 9, + .interface.bDescriptorType = USB_DESCRIPTOR_INTERFACE, + .interface.bInterfaceNumber = 0, + .interface.bAlternateSetting = 0, + .interface.bNumEndpoints = 2, + .interface.bInterfaceClass = 0x03, // Human Interface Device Class. + .interface.bInterfaceSubClass = 0x00, // SCSI Transparent Command Set. + .interface.bInterfaceProtocol = 0x00, // Bulk-Only Transport. + .interface.iInterface = 0x00, + + .hid.bLength = 9, + .hid.bDescriptorType = USB_DESCRIPTOR_HID, + .hid.bcdHID = 0x111, + .hid.bCountryCode = 0, + .hid.bNumDescriptors = 1, + .hid.bClassDescriptorType = USB_DESCRIPTOR_HID_REPORT, + .hid.bDescriptorLength = sizeof(hid_report_descriptor_touch), + + /* Endpoint descriptor structure EP1 IN */ + .endpoint[0].bLength = 7, + .endpoint[0].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[0].bEndpointAddress = 0x81, // USB_EP_ADDR_BULK_IN. + .endpoint[0].bmAttributes = USB_EP_TYPE_INTR, + .endpoint[0].wMaxPacketSize = 0x200, + .endpoint[0].bInterval = 3, // 4ms on HS. + + /* Endpoint descriptor structure EP1 OUT */ + .endpoint[1].bLength = 7, + .endpoint[1].bDescriptorType = USB_DESCRIPTOR_ENDPOINT, + .endpoint[1].bEndpointAddress = 0x01, // USB_EP_ADDR_BULK_OUT. + .endpoint[1].bmAttributes = USB_EP_TYPE_INTR, + .endpoint[1].wMaxPacketSize = 0x200, + .endpoint[1].bInterval = 3 // 4ms on HS. +}; + +usb_desc_t usb_gadget_ums_descriptors = +{ + .dev = &usb_device_descriptor_ums, + .dev_qual = &usb_device_qualifier_descriptor, + .cfg = &usb_configuration_descriptor_ums, + .cfg_other = &usb_other_speed_config_descriptor_ums, + .dev_bot = &usb_device_binary_object_descriptor, + .vendor = usb_vendor_string_descriptor_ums, + .product = usb_product_string_descriptor_ums, + .serial = usb_serial_string_descriptor, + .lang_id = usb_lang_id_string_descriptor, + .ms_os = &usb_ms_os_descriptor, + .ms_cid = &usb_ms_cid_descriptor, + .mx_ext = &usb_ms_ext_prop_descriptor_ums +}; + +usb_desc_t usb_gadget_hid_jc_descriptors = +{ + .dev = &usb_device_descriptor_hid_jc, + .dev_qual = &usb_device_qualifier_descriptor, + .cfg = (usb_cfg_simple_descr_t *)&usb_configuration_descriptor_hid_jc, + .cfg_other = NULL, + .dev_bot = &usb_device_binary_object_descriptor, + .vendor = usb_vendor_string_descriptor_hid, + .product = usb_product_string_descriptor_hid_jc, + .serial = usb_serial_string_descriptor, + .lang_id = usb_lang_id_string_descriptor, + .ms_os = &usb_ms_os_descriptor, + .ms_cid = &usb_ms_cid_descriptor, + .mx_ext = &usb_ms_ext_prop_descriptor_hid +}; + +usb_desc_t usb_gadget_hid_touch_descriptors = +{ + .dev = &usb_device_descriptor_hid_touch, + .dev_qual = &usb_device_qualifier_descriptor, + .cfg = (usb_cfg_simple_descr_t *)&usb_configuration_descriptor_hid_touch, + .cfg_other = NULL, + .dev_bot = &usb_device_binary_object_descriptor, + .vendor = usb_vendor_string_descriptor_hid, + .product = usb_product_string_descriptor_hid_touch, + .serial = usb_serial_string_descriptor, + .lang_id = usb_lang_id_string_descriptor, + .ms_os = &usb_ms_os_descriptor, + .ms_cid = &usb_ms_cid_descriptor, + .mx_ext = &usb_ms_ext_prop_descriptor_hid +}; diff --git a/bdk/usb/usb_gadget_hid.c b/bdk/usb/usb_gadget_hid.c new file mode 100644 index 00000000..b7c2e24d --- /dev/null +++ b/bdk/usb/usb_gadget_hid.c @@ -0,0 +1,441 @@ +/* + * USB Gadget HID driver for Tegra X1 + * + * Copyright (c) 2019-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +//#define DPRINTF(...) gfx_printf(__VA_ARGS__) +#define DPRINTF(...) + +typedef struct _gamepad_report_t +{ + u8 x; + u8 y; + u8 z; + u8 rz; + + u8 hat:4; + u8 btn1:1; + u8 btn2:1; + u8 btn3:1; + u8 btn4:1; + + u8 btn5:1; + u8 btn6:1; + u8 btn7:1; + u8 btn8:1; + u8 btn9:1; + u8 btn10:1; + u8 btn11:1; + u8 btn12:1; +} __attribute__((packed)) gamepad_report_t; + +typedef struct _jc_cal_t +{ + bool cl_done; + bool cr_done; + u16 clx_max; + u16 clx_min; + u16 cly_max; + u16 cly_min; + u16 crx_max; + u16 crx_min; + u16 cry_max; + u16 cry_min; +} jc_cal_t; + +static jc_cal_t jc_cal_ctx; +static usb_ops_t usb_ops; + +static bool _jc_calibration(jc_gamepad_rpt_t *jc_pad) +{ + // Calibrate left stick. + if (!jc_cal_ctx.cl_done) + { + if (jc_pad->conn_l + && jc_pad->lstick_x > 0x400 && jc_pad->lstick_y > 0x400 + && jc_pad->lstick_x < 0xC00 && jc_pad->lstick_y < 0xC00) + { + jc_cal_ctx.clx_max = jc_pad->lstick_x + 0x72; + jc_cal_ctx.clx_min = jc_pad->lstick_x - 0x72; + jc_cal_ctx.cly_max = jc_pad->lstick_y + 0x72; + jc_cal_ctx.cly_min = jc_pad->lstick_y - 0x72; + jc_cal_ctx.cl_done = true; + } + else + return false; + } + + // Calibrate right stick. + if (!jc_cal_ctx.cr_done) + { + if (jc_pad->conn_r + && jc_pad->rstick_x > 0x400 && jc_pad->rstick_y > 0x400 + && jc_pad->rstick_x < 0xC00 && jc_pad->rstick_y < 0xC00) + { + jc_cal_ctx.crx_max = jc_pad->rstick_x + 0x72; + jc_cal_ctx.crx_min = jc_pad->rstick_x - 0x72; + jc_cal_ctx.cry_max = jc_pad->rstick_y + 0x72; + jc_cal_ctx.cry_min = jc_pad->rstick_y - 0x72; + jc_cal_ctx.cr_done = true; + } + else + return false; + } + + return true; +} + +static bool _jc_poll(gamepad_report_t *rpt) +{ + // Poll Joy-Con. + jc_gamepad_rpt_t *jc_pad = joycon_poll(); + + if (!jc_pad) + return false; + + // Exit emulation if Left stick and Home are pressed. + if (jc_pad->l3 && jc_pad->home) + return true; + + if (!jc_cal_ctx.cl_done || !jc_cal_ctx.cr_done) + { + if (!_jc_calibration(jc_pad)) + return false; + } + + // Re-calibrate on disconnection. + if (!jc_pad->conn_l) + jc_cal_ctx.cl_done = false; + if (!jc_pad->conn_r) + jc_cal_ctx.cr_done = false; + + // Calculate left analog stick. + if (jc_pad->lstick_x <= jc_cal_ctx.clx_max && jc_pad->lstick_x >= jc_cal_ctx.clx_min) + rpt->x = 0x7F; + else if (jc_pad->lstick_x > jc_cal_ctx.clx_max) + { + u16 x_raw = (jc_pad->lstick_x - jc_cal_ctx.clx_max) / 7; + if (x_raw > 0x7F) + x_raw = 0x7F; + rpt->x = 0x7F + x_raw; + } + else + { + u16 x_raw = (jc_cal_ctx.clx_min - jc_pad->lstick_x) / 7; + if (x_raw > 0x7F) + x_raw = 0x7F; + rpt->x = 0x7F - x_raw; + } + + if (jc_pad->lstick_y <= jc_cal_ctx.cly_max && jc_pad->lstick_y >= jc_cal_ctx.cly_min) + rpt->y = 0x7F; + else if (jc_pad->lstick_y > jc_cal_ctx.cly_max) + { + u16 y_raw = (jc_pad->lstick_y - jc_cal_ctx.cly_max) / 7; + if (y_raw > 0x7F) + y_raw = 0x7F; + rpt->y = 0x7F - y_raw; + } + else + { + u16 y_raw = (jc_cal_ctx.cly_min - jc_pad->lstick_y) / 7; + if (y_raw > 0x7F) + y_raw = 0x7F; + rpt->y = 0x7F + y_raw; + } + + // Calculate right analog stick. + if (jc_pad->rstick_x <= jc_cal_ctx.crx_max && jc_pad->rstick_x >= jc_cal_ctx.crx_min) + rpt->z = 0x7F; + else if (jc_pad->rstick_x > jc_cal_ctx.crx_max) + { + u16 x_raw = (jc_pad->rstick_x - jc_cal_ctx.crx_max) / 7; + if (x_raw > 0x7F) + x_raw = 0x7F; + rpt->z = 0x7F + x_raw; + } + else + { + u16 x_raw = (jc_cal_ctx.crx_min - jc_pad->rstick_x) / 7; + if (x_raw > 0x7F) + x_raw = 0x7F; + rpt->z = 0x7F - x_raw; + } + + if (jc_pad->rstick_y <= jc_cal_ctx.cry_max && jc_pad->rstick_y >= jc_cal_ctx.cry_min) + rpt->rz = 0x7F; + else if (jc_pad->rstick_y > jc_cal_ctx.cry_max) + { + u16 y_raw = (jc_pad->rstick_y - jc_cal_ctx.cry_max) / 7; + if (y_raw > 0x7F) + y_raw = 0x7F; + rpt->rz = 0x7F - y_raw; + } + else + { + u16 y_raw = (jc_cal_ctx.cry_min - jc_pad->rstick_y) / 7; + if (y_raw > 0x7F) + y_raw = 0x7F; + rpt->rz = 0x7F + y_raw; + } + + // Set D-pad. + switch ((jc_pad->buttons >> 16) & 0xF) + { + case 0: // none + rpt->hat = 0xF; + break; + case 1: // down + rpt->hat = 4; + break; + case 2: // up + rpt->hat = 0; + break; + case 4: // right + rpt->hat = 2; + break; + case 5: // down + right + rpt->hat = 3; + break; + case 6: // up + right + rpt->hat = 1; + break; + case 8: // left + rpt->hat = 6; + break; + case 9: // down + left + rpt->hat = 5; + break; + case 10: // up + left + rpt->hat = 7; + break; + default: + rpt->hat = 0xF; + break; + } + + // Set buttons. + rpt->btn1 = jc_pad->b; // x. + rpt->btn2 = jc_pad->a; // a. + rpt->btn3 = jc_pad->y; // b. + rpt->btn4 = jc_pad->x; // y. + + rpt->btn5 = jc_pad->l; + rpt->btn6 = jc_pad->r; + rpt->btn7 = jc_pad->zl; + rpt->btn8 = jc_pad->zr; + rpt->btn9 = jc_pad->minus; + rpt->btn10 = jc_pad->plus; + rpt->btn11 = jc_pad->l3; + rpt->btn12 = jc_pad->r3; + + //rpt->btn13 = jc_pad->cap; + //rpt->btn14 = jc_pad->home; + + return false; +} + +typedef struct _touchpad_report_t +{ + u8 rpt_id; + u8 tip_switch:1; + u8 count:7; + + u8 id; + + //u16 z; + u16 x; + u16 y; +} __attribute__((packed)) touchpad_report_t; + +static bool _fts_touch_read(touchpad_report_t *rpt) +{ + static touch_event touchpad; + + touch_poll(&touchpad); + + rpt->rpt_id = 5; + rpt->count = 1; + + // Decide touch enable. + switch (touchpad.type & STMFTS_MASK_EVENT_ID) + { + //case STMFTS_EV_MULTI_TOUCH_ENTER: + case STMFTS_EV_MULTI_TOUCH_MOTION: + rpt->x = touchpad.x; + rpt->y = touchpad.y; + //rpt->z = touchpad.z; + rpt->id = touchpad.fingers ? touchpad.fingers - 1 : 0; + rpt->tip_switch = 1; + break; + case STMFTS_EV_MULTI_TOUCH_LEAVE: + rpt->x = touchpad.x; + rpt->y = touchpad.y; + //rpt->z = touchpad.z; + rpt->id = touchpad.fingers ? touchpad.fingers - 1 : 0; + rpt->tip_switch = 0; + break; + case STMFTS_EV_NO_EVENT: + return false; + } + + return true; +} + +static u8 _hid_transfer_start(usb_ctxt_t *usbs, u32 len) +{ + u8 status = usb_ops.usb_device_ep1_in_write((u8 *)USB_EP_BULK_IN_BUF_ADDR, len, NULL, USB_XFER_SYNCED_CMD); + if (status == USB_ERROR_XFER_ERROR) + { + usbs->set_text(usbs->label, "#FFDD00 Error:# EP IN transfer!"); + if (usb_ops.usbd_flush_endpoint) + usb_ops.usbd_flush_endpoint(USB_EP_BULK_IN); + } + + // Linux mitigation: If timed out, clear status. + if (status == USB_ERROR_TIMEOUT) + return 0; + + return status; +} + +static bool _hid_poll_jc(usb_ctxt_t *usbs) +{ + if (_jc_poll((gamepad_report_t *)USB_EP_BULK_IN_BUF_ADDR)) + return true; + + // Send HID report. + if (_hid_transfer_start(usbs, sizeof(gamepad_report_t))) + return true; // EP Error. + + return false; +} + +static bool _hid_poll_touch(usb_ctxt_t *usbs) +{ + _fts_touch_read((touchpad_report_t *)USB_EP_BULK_IN_BUF_ADDR); + + // Send HID report. + if (_hid_transfer_start(usbs, sizeof(touchpad_report_t))) + return true; // EP Error. + + return false; +} + +int usb_device_gadget_hid(usb_ctxt_t *usbs) +{ + int res = 0; + u32 gadget_type; + u32 polling_time; + + // Get USB Controller ops. + if (hw_get_chip_id() == GP_HIDREV_MAJOR_T210) + usb_device_get_ops(&usb_ops); + else + xusb_device_get_ops(&usb_ops); + + if (usbs->type == USB_HID_GAMEPAD) + { + polling_time = 8000; + gadget_type = USB_GADGET_HID_GAMEPAD; + } + else + { + polling_time = 4000; + gadget_type = USB_GADGET_HID_TOUCHPAD; + } + + usbs->set_text(usbs->label, "#C7EA46 Status:# Started USB"); + + if (usb_ops.usb_device_init()) + { + usb_ops.usbd_end(false, true); + return 1; + } + + usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for connection"); + + // Initialize Control Endpoint. + if (usb_ops.usb_device_enumerate(gadget_type)) + goto error; + + usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for HID report request"); + + if (usb_ops.usb_device_class_send_hid_report()) + goto error; + + usbs->set_text(usbs->label, "#C7EA46 Status:# Started HID emulation"); + + u32 timer_sys = get_tmr_ms() + 5000; + while (true) + { + u32 timer = get_tmr_us(); + + // Parse input device. + if (usbs->type == USB_HID_GAMEPAD) + { + if (_hid_poll_jc(usbs)) + break; + } + else + { + if (_hid_poll_touch(usbs)) + break; + } + + // Check for suspended USB in case the cable was pulled. + if (usb_ops.usb_device_get_suspended()) + break; // Disconnected. + + // Handle control endpoint. + usb_ops.usbd_handle_ep0_ctrl_setup(); + + // Wait max gadget timing. + timer = get_tmr_us() - timer; + if (timer < polling_time) + usleep(polling_time - timer); + + if (timer_sys < get_tmr_ms()) + { + usbs->system_maintenance(true); + timer_sys = get_tmr_ms() + 5000; + } + } + + usbs->set_text(usbs->label, "#C7EA46 Status:# HID ended"); + goto exit; + +error: + usbs->set_text(usbs->label, "#FFDD00 Error:# Timed out or canceled"); + res = 1; + +exit: + usb_ops.usbd_end(true, false); + + return res; +} diff --git a/bdk/usb/usb_gadget_ums.c b/bdk/usb/usb_gadget_ums.c new file mode 100644 index 00000000..380cfa98 --- /dev/null +++ b/bdk/usb/usb_gadget_ums.c @@ -0,0 +1,1925 @@ +/* + * USB Gadget UMS driver for Tegra X1 + * + * Copyright (c) 2003-2008 Alan Stern + * Copyright (c) 2009 Samsung Electronics + * Author: Michal Nazarewicz + * Copyright (c) 2019-2020 CTCaer + * Copyright (c) 2020 Storm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//#define DPRINTF(...) gfx_printf(__VA_ARGS__) +#define DPRINTF(...) + +#define UMS_MAX_LUN 1 // Only 1 disk/partition for now. + +#define USB_BULK_CB_WRAP_LEN 31 +#define USB_BULK_CB_SIG 0x43425355 // USBC. +#define USB_BULK_IN_FLAG 0x80 + +#define USB_BULK_CS_WRAP_LEN 13 +#define USB_BULK_CS_SIG 0x53425355 // USBS. + +#define USB_STATUS_PASS 0 +#define USB_STATUS_FAIL 1 +#define USB_STATUS_PHASE_ERROR 2 + +#define UMS_DISK_LBA_SHIFT 9 +#define UMS_DISK_LBA_SIZE (1 << UMS_DISK_LBA_SHIFT) + +#define UMS_DISK_MAX_IO_TRANSFER_64K (USB_EP_BUFFER_MAX_SIZE >> UMS_DISK_LBA_SHIFT) +#define UMS_DISK_MAX_IO_TRANSFER_32K (UMS_DISK_MAX_IO_TRANSFER_64K / 2) + +#define UMS_SCSI_TRANSFER_512K (0x80000 >> UMS_DISK_LBA_SHIFT) + +#define UMS_EP_OUT_MAX_XFER (USB_EP_BULK_OUT_MAX_XFER) + +// Length of a SCSI Command Data Block. +#define SCSI_MAX_CMD_SZ 16 + +// SCSI device types +#define SCSI_TYPE_DISK 0x00 + +// SCSI commands. +#define SC_FORMAT_UNIT 0x04 +#define SC_INQUIRY 0x12 +#define SC_LOG_SENSE 0x4D +#define SC_MODE_SELECT_6 0x15 +#define SC_MODE_SELECT_10 0x55 +#define SC_MODE_SENSE_6 0x1A +#define SC_MODE_SENSE_10 0x5A +#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define SC_READ_6 0x08 +#define SC_READ_10 0x28 +#define SC_READ_12 0xA8 +#define SC_READ_CAPACITY 0x25 +#define SC_READ_FORMAT_CAPACITIES 0x23 +#define SC_READ_HEADER 0x44 +#define SC_READ_TOC 0x43 +#define SC_RELEASE 0x17 +#define SC_REQUEST_SENSE 0x03 +#define SC_RESERVE 0x16 +#define SC_SEND_DIAGNOSTIC 0x1D +#define SC_START_STOP_UNIT 0x1B +#define SC_SYNCHRONIZE_CACHE 0x35 +#define SC_TEST_UNIT_READY 0x00 +#define SC_VERIFY 0x2F +#define SC_WRITE_6 0x0A +#define SC_WRITE_10 0x2A +#define SC_WRITE_12 0xAA + +// SCSI Sense Key/Additional Sense Code/ASC Qualifier values. +#define SS_NO_SENSE 0x0 +#define SS_COMMUNICATION_FAILURE 0x40800 +#define SS_INVALID_COMMAND 0x52000 +#define SS_INVALID_FIELD_IN_CDB 0x52400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x52100 +#define SS_MEDIUM_NOT_PRESENT 0x23A00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x55302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x62800 +#define SS_RESET_OCCURRED 0x62900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x53900 +#define SS_UNRECOVERED_READ_ERROR 0x31100 +#define SS_WRITE_ERROR 0x30C02 +#define SS_WRITE_PROTECTED 0x72700 + +#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((u8) ((x) >> 8)) +#define ASCQ(x) ((u8) (x)) + +enum ums_state { + UMS_STATE_NORMAL = 0, + UMS_STATE_ABORT_BULK_OUT, + UMS_STATE_PROTOCOL_RESET, + UMS_STATE_EXIT, + UMS_STATE_TERMINATED +}; + +enum ums_result { + UMS_RES_OK = 0, + UMS_RES_IO_ERROR = -5, + UMS_RES_TIMEOUT = -3, + UMS_RES_PROT_FATAL = -4, + UMS_RES_INVALID_ARG = -22 +}; + + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + +enum buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +typedef struct _bulk_recv_pkt_t { + u32 Signature; // 'USBC'. + u32 Tag; // Unique per command id. + u32 DataTransferLength; // Size of the data. + u8 Flags; // Direction in bit 7. + u8 Lun; // LUN (normally 0). + u8 Length; // Of the CDB, <= SCSI_MAX_CMD_SZ. + u8 CDB[16]; // Command Data Block. +} bulk_recv_pkt_t; + +typedef struct _bulk_send_pkt_t { + u32 Signature; // 'USBS'. + u32 Tag; // Same as original command. + u32 Residue; // Amount not transferred. + u8 Status; +} bulk_send_pkt_t; + +typedef struct _logical_unit_t +{ + sdmmc_t *sdmmc; + sdmmc_storage_t *storage; + + u32 num_sectors; + u32 offset; + + int unmounted; + + u32 ro; + u32 type; + u32 partition; + u32 removable; + u32 prevent_medium_removal; + + u32 info_valid; + + u32 sense_data; + u32 sense_data_info; + u32 unit_attention_data; +} logical_unit_t; + +typedef struct _bulk_ctxt_t { + u32 bulk_in; + int bulk_in_status; + u32 bulk_in_length; + u32 bulk_in_length_actual; + u8 *bulk_in_buf; + enum buffer_state bulk_in_buf_state; + + u32 bulk_out; + int bulk_out_status; + u32 bulk_out_length; + u32 bulk_out_length_actual; + int bulk_out_ignore; + u8 *bulk_out_buf; + enum buffer_state bulk_out_buf_state; +} bulk_ctxt_t; + +typedef struct _usbd_gadget_ums_t { + bulk_ctxt_t bulk_ctxt; + + int cmnd_size; + u8 cmnd[SCSI_MAX_CMD_SZ]; + + u32 lun_idx; // lun index + logical_unit_t lun; + + enum ums_state state; // For exception handling. + + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + u32 residue; + u32 usb_amount_left; + + u32 phase_error; + u32 short_packet_received; + + int thread_wakeup_needed; + int can_stall; + + u32 timeouts; + bool xusb; + + void (*system_maintenance)(bool); + void *label; + void (*set_text)(void *, const char *); +} usbd_gadget_ums_t; + +static usb_ops_t usb_ops; + +static inline void put_array_le_to_be16(u16 val, void *p) +{ + u8 *_p = p; + _p[0] = val >> 8; + _p[1] = val; +} + +static inline void put_array_le_to_be32(u32 val, void *p) +{ + u8 *_p = p; + _p[0] = val >> 24; + _p[1] = val >> 16; + _p[2] = val >> 8; + _p[3] = val; +} + +static inline u16 get_array_be_to_le16(const void *p) +{ + const u8 *_p = p; + u16 val = _p[0] << 8 | _p[1]; + return val; +} + +static inline u32 get_array_be_to_le24(const void *p) +{ + const u8 *_p = p; + u32 val = (_p[0] << 16) | (_p[1] << 8) | _p[2]; + return val; +} + +static inline u32 get_array_be_to_le32(const void *p) +{ + const u8 *_p = p; + u32 val = (_p[0] << 24) | (_p[1] << 16) | (_p[2] << 8) | _p[3]; + return val; +} + +static void raise_exception(usbd_gadget_ums_t *ums, enum ums_state new_state) +{ + /* Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. */ + if (ums->state <= new_state) { + ums->state = new_state; + ums->thread_wakeup_needed = 1; + } +} + +static void ums_handle_ep0_ctrl(usbd_gadget_ums_t *ums) +{ + if (usb_ops.usbd_handle_ep0_ctrl_setup()) + raise_exception(ums, UMS_STATE_PROTOCOL_RESET); +} + +static int ums_wedge_bulk_in_endpoint(usbd_gadget_ums_t *ums) +{ + /* usbd_set_ep_wedge(bulk_ctxt->bulk_in); */ + + return UMS_RES_OK; +} + +static int ums_set_stall(u32 ep) +{ + usb_ops.usbd_set_ep_stall(ep, USB_EP_CFG_STALL); + + return UMS_RES_OK; +} + +static int ums_clear_stall(u32 ep) +{ + usb_ops.usbd_set_ep_stall(ep, USB_EP_CFG_CLEAR); + + return UMS_RES_OK; +} + +static void ums_flush_endpoint(u32 ep) +{ + if (usb_ops.usbd_flush_endpoint) + usb_ops.usbd_flush_endpoint(ep); +} + +static void _ums_transfer_start(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt, u32 ep, u32 sync_timeout) +{ + if (ep == bulk_ctxt->bulk_in) + { + bulk_ctxt->bulk_in_status = usb_ops.usb_device_ep1_in_write( + bulk_ctxt->bulk_in_buf, bulk_ctxt->bulk_in_length, + &bulk_ctxt->bulk_in_length_actual, sync_timeout); + + if (bulk_ctxt->bulk_in_status == USB_ERROR_XFER_ERROR) + { + ums->set_text(ums->label, "#C7EA46 Error:# EP IN transfer!"); + ums_flush_endpoint(bulk_ctxt->bulk_in); + } + else if (bulk_ctxt->bulk_in_status == USB2_ERROR_XFER_NOT_ALIGNED) + ums->set_text(ums->label, "#C7EA46 Error:# EP IN Buffer not aligned!"); + + if (sync_timeout) + bulk_ctxt->bulk_in_buf_state = BUF_STATE_EMPTY; + } + else + { + bulk_ctxt->bulk_out_status = usb_ops.usb_device_ep1_out_read( + bulk_ctxt->bulk_out_buf, bulk_ctxt->bulk_out_length, + &bulk_ctxt->bulk_out_length_actual, sync_timeout); + + if (bulk_ctxt->bulk_out_status == USB_ERROR_XFER_ERROR) + { + ums->set_text(ums->label, "#C7EA46 Error:# EP OUT transfer!"); + ums_flush_endpoint(bulk_ctxt->bulk_out); + } + else if (bulk_ctxt->bulk_out_status == USB2_ERROR_XFER_NOT_ALIGNED) + ums->set_text(ums->label, "#C7EA46 Error:# EP OUT Buffer not aligned!"); + + if (sync_timeout) + bulk_ctxt->bulk_out_buf_state = BUF_STATE_FULL; + } +} + +static void _ums_transfer_out_big_read(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + bulk_ctxt->bulk_out_status = usb_ops.usb_device_ep1_out_read_big( + bulk_ctxt->bulk_out_buf, bulk_ctxt->bulk_out_length, + &bulk_ctxt->bulk_out_length_actual); + + if (bulk_ctxt->bulk_out_status == USB_ERROR_XFER_ERROR) + { + ums->set_text(ums->label, "#C7EA46 Error:# EP OUT transfer!"); + ums_flush_endpoint(bulk_ctxt->bulk_out); + } + + bulk_ctxt->bulk_out_buf_state = BUF_STATE_FULL; +} + +static void _ums_transfer_finish(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt, u32 ep) +{ + if (ep == bulk_ctxt->bulk_in) + { + bulk_ctxt->bulk_in_status = usb_ops.usb_device_ep1_in_writing_finish( + &bulk_ctxt->bulk_in_length_actual); + + if (bulk_ctxt->bulk_in_status == USB_ERROR_XFER_ERROR) + { + ums->set_text(ums->label, "#C7EA46 Error:# EP IN transfer!"); + ums_flush_endpoint(bulk_ctxt->bulk_in); + } + + bulk_ctxt->bulk_in_buf_state = BUF_STATE_EMPTY; + } + else + { + bulk_ctxt->bulk_out_status = usb_ops.usb_device_ep1_out_reading_finish( + &bulk_ctxt->bulk_out_length_actual); + + if (bulk_ctxt->bulk_out_status == USB_ERROR_XFER_ERROR) + { + ums->set_text(ums->label, "#C7EA46 Error:# EP OUT transfer!"); + ums_flush_endpoint(bulk_ctxt->bulk_out); + } + + bulk_ctxt->bulk_out_buf_state = BUF_STATE_FULL; + } +} + +static void _ums_reset_buffer(bulk_ctxt_t *bulk_ctxt, u32 ep) +{ + if (ep == bulk_ctxt->bulk_in) + bulk_ctxt->bulk_in_buf = (u8 *)USB_EP_BULK_IN_BUF_ADDR; + else + bulk_ctxt->bulk_out_buf = (u8 *)USB_EP_BULK_OUT_BUF_ADDR; +} + +/* + * The following are old data based on max 64KB SCSI transfers. + * The endpoint xfer is actually 41.2 MB/s and SD card max 39.2 MB/s, with higher SCSI + * transfers, but the concurrency still helps and increases speeds by 20%. + * + * Concurrency of the SDMMC and USB xfers is very important with no cache. + * The worst offender being the SD card. We are already limited by bus, so + * concurrency helps minimize the SDMMC overhead. + * Max achieved bulk endpoint rate on a Tegra X1 and USB2.0 is 39.4 MB/s. + * + * USB bulk endpoint raw max transfer rate: + * 39.4MB/S - SCSI 128KB. + * 38.2MB/s - SCSI 64KB. + * + * 128 KB, 64 KB, 32 KB, 16 KB, 8 KB - Internal SDMMC I\O Sizes + * ------------------------------------------------------------------------------------- + * eMMC - Toshiba - 4MB reads: 314.8 MB/s: + * 225.9 MB/s, 168.6 MB/s, 114.7 MB/s, 86.4 MB/s, 50.3 MB/s - RAW SDMMC. + * 33.5 MB/s, 31.9 MB/s, 29.3 MB/s, 27.1 MB/s, 22.1 MB/s - SCSI 128KB, No concurrency. + * 33.5 MB/s, 35.3 MB/s, 36.3 MB/s, 37.3 MB/s, 37.8 MB/s - SCSI 128KB, Concurrency. + * --.- --/-, 31.1 MB/s, 28.7 MB/s, 26.5 MB/s, 21.7 MB/s - SCSI 64KB, No concurrency. + * --.- --/-, 31.1 MB/s, 32.7 MB/s, 34.4 MB/s, 35.0 MB/s - SCSI 64KB, Concurrency. + * + * SD Card - Samsung Evo+ 128GB - 4MB reads: 91.6 MB/s: + * 72.6 MB/s, 62.8 MB/s, 47.4 MB/s, 31.1 MB/s, 18.5 MB/s - RAW SDMMC. + * 25.5 MB/s, 24.2 MB/s, 21.5 MB/s, 17.4 MB/s, 12.6 MB/s - SCSI 128KB, No concurrency. + * 25.5 MB/s, 30.0 MB/s, 32.6 MB/s, 28.3 MB/s, 18.0 MB/s - SCSI 128KB, Concurrency. + * --.- --/-, 23.8 MB/s, 21.2 MB/s, 17.1 MB/s, 12.5 MB/s - SCSI 64KB, No concurrency. + * --.- --/-, 23.8 MB/s, 27.2 MB/s, 25.8 MB/s, 17.5 MB/s - SCSI 64KB, Concurrency. + */ + +static int _scsi_read(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u32 lba_offset; + bool first_read = true; + u8 *sdmmc_buf = (u8 *)SDXC_BUF_ALIGNED; + + // Get the starting LBA and check that it's not too big. + if (ums->cmnd[0] == SC_READ_6) + lba_offset = get_array_be_to_le24(&ums->cmnd[1]); + else + { + lba_offset = get_array_be_to_le32(&ums->cmnd[2]); + + // We allow DPO and FUA bypass cache bits, but we don't use them. + if ((ums->cmnd[1] & ~0x18) != 0) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + } + if (lba_offset >= ums->lun.num_sectors) + { + ums->lun.sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + + return UMS_RES_INVALID_ARG; + } + + // Check that request data size is not 0. + u32 amount_left = ums->data_size_from_cmnd >> UMS_DISK_LBA_SHIFT; + if (!amount_left) + return UMS_RES_IO_ERROR; // No default reply. + + // Limit IO transfers based on request for faster concurrent reads. + u32 max_io_transfer = (amount_left >= UMS_SCSI_TRANSFER_512K) ? + UMS_DISK_MAX_IO_TRANSFER_64K : UMS_DISK_MAX_IO_TRANSFER_32K; + + while (true) + { + // Max io size and end sector limits. + u32 amount = MIN(amount_left, max_io_transfer); + amount = MIN(amount, ums->lun.num_sectors - lba_offset); + + // Check if it is a read past the end sector. + if (!amount) + { + ums->lun.sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + ums->lun.sense_data_info = lba_offset; + ums->lun.info_valid = 1; + bulk_ctxt->bulk_in_length = 0; + bulk_ctxt->bulk_in_buf_state = BUF_STATE_FULL; + break; + } + + // Do the SDMMC read. + if (!sdmmc_storage_read(ums->lun.storage, ums->lun.offset + lba_offset, amount, sdmmc_buf)) + amount = 0; + + // Wait for the async USB transfer to finish. + if (!first_read) + _ums_transfer_finish(ums, bulk_ctxt, bulk_ctxt->bulk_in); + + lba_offset += amount; + amount_left -= amount; + ums->residue -= amount << UMS_DISK_LBA_SHIFT; + + bulk_ctxt->bulk_in_length = amount << UMS_DISK_LBA_SHIFT; + bulk_ctxt->bulk_in_buf_state = BUF_STATE_FULL; + bulk_ctxt->bulk_in_buf = sdmmc_buf; + + // If an error occurred, report it and its position. + if (!amount) + { + ums->set_text(ums->label, "#C7EA46 Error:# SDMMC Read!"); + ums->lun.sense_data = SS_UNRECOVERED_READ_ERROR; + ums->lun.sense_data_info = lba_offset; + ums->lun.info_valid = 1; + break; + } + + // Last SDMMC read. Last part will be sent by the finish reply function. + if (!amount_left) + break; + + // Start the USB transfer. + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_in, USB_XFER_START); + first_read = false; + + // Increment our buffer to read new data. + sdmmc_buf += amount << UMS_DISK_LBA_SHIFT; + } + + return UMS_RES_IO_ERROR; // No default reply. +} + +/* + * Writes are another story. + * Tests showed that big writes are faster than concurrent 32K usb reads + writes. + * The only thing that can help here is caching the writes. But for the simplicity + * of this implementation it will not be implemented yet. + */ + +static int _scsi_write(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + static char txt_buf[256]; + u32 amount_left_to_req, amount_left_to_write; + u32 usb_lba_offset, lba_offset; + u32 amount; + + if (ums->lun.ro) + { + ums->lun.sense_data = SS_WRITE_PROTECTED; + + return UMS_RES_INVALID_ARG; + } + + if (ums->cmnd[0] == SC_WRITE_6) + lba_offset = get_array_be_to_le24(&ums->cmnd[1]); + else + { + lba_offset = get_array_be_to_le32(&ums->cmnd[2]); + + // We allow DPO and FUA bypass cache bits. We only implement FUA by performing synchronous output. + if (ums->cmnd[1] & ~0x18) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + } + + // Check that starting LBA is not past the end sector offset. + if (lba_offset >= ums->lun.num_sectors) + { + ums->lun.sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + + return UMS_RES_INVALID_ARG; + } + + /* Carry out the file writes */ + usb_lba_offset = lba_offset; + amount_left_to_req = ums->data_size_from_cmnd; + amount_left_to_write = ums->data_size_from_cmnd; + + while (amount_left_to_write > 0) + { + + /* Queue a request for more data from the host */ + if (amount_left_to_req) + { + + // Limit write to max supported read from EP OUT. + amount = MIN(amount_left_to_req, UMS_EP_OUT_MAX_XFER); + + if (usb_lba_offset >= ums->lun.num_sectors) //////////Check if it works with concurrency + { + ums->set_text(ums->label, "#C7EA46 Error:# Write - Past last sector!"); + amount_left_to_req = 0; + ums->lun.sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + ums->lun.sense_data_info = usb_lba_offset; + ums->lun.info_valid = 1; + continue; + } + + // Get the next buffer. + usb_lba_offset += amount >> UMS_DISK_LBA_SHIFT; + ums->usb_amount_left -= amount; + amount_left_to_req -= amount; + + bulk_ctxt->bulk_out_length = amount; + + _ums_transfer_out_big_read(ums, bulk_ctxt); + } + + if (bulk_ctxt->bulk_out_buf_state == BUF_STATE_FULL) + { + bulk_ctxt->bulk_out_buf_state = BUF_STATE_EMPTY; + + // Did something go wrong with the transfer?. + if (bulk_ctxt->bulk_out_status != 0) + { + ums->lun.sense_data = SS_COMMUNICATION_FAILURE; + ums->lun.sense_data_info = lba_offset; + ums->lun.info_valid = 1; + s_printf(txt_buf, "#C7EA46 Error:# Write - Comm failure %d!", bulk_ctxt->bulk_out_status); + ums->set_text(ums->label, txt_buf); + break; + } + + amount = bulk_ctxt->bulk_out_length_actual; + + if ((ums->lun.num_sectors - lba_offset) < (amount >> UMS_DISK_LBA_SHIFT)) + { + DPRINTF("write %X @ %X beyond end %X\n", amount, lba_offset, ums->lun.num_sectors); + amount = (ums->lun.num_sectors - lba_offset) << UMS_DISK_LBA_SHIFT; + } + + /* + * Don't accept excess data. The spec doesn't say + * what to do in this case. We'll ignore the error. + */ + amount = MIN(amount, bulk_ctxt->bulk_out_length); + + /* Don't write a partial block */ + amount -= (amount & 511); + if (amount == 0) + goto empty_write; + + /* Perform the write */ + if (!sdmmc_storage_write(ums->lun.storage, ums->lun.offset + lba_offset, + amount >> UMS_DISK_LBA_SHIFT, (u8 *)bulk_ctxt->bulk_out_buf)) + amount = 0; + +DPRINTF("file write %X @ %X\n", amount, lba_offset); + + lba_offset += amount >> UMS_DISK_LBA_SHIFT; + amount_left_to_write -= amount; + ums->residue -= amount; + + /* If an error occurred, report it and its position */ + if (!amount) + { + ums->set_text(ums->label, "#C7EA46 Error:# SDMMC Write!"); + ums->lun.sense_data = SS_WRITE_ERROR; + ums->lun.sense_data_info = lba_offset; + ums->lun.info_valid = 1; + break; + } + + empty_write: + // Did the host decide to stop early? + if (bulk_ctxt->bulk_out_length_actual < bulk_ctxt->bulk_out_length) + { + ums->set_text(ums->label, "#C7EA46 Error:# Empty Write!"); + ums->short_packet_received = 1; + break; + } + } + } + + return UMS_RES_IO_ERROR; // No default reply. +} + +static int _scsi_verify(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + // Check that start LBA is past the end sector offset. + u32 lba_offset = get_array_be_to_le32(&ums->cmnd[2]); + if (lba_offset >= ums->lun.num_sectors) + { + ums->lun.sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + + return UMS_RES_INVALID_ARG; + } + + // We allow DPO but we don't implement it. Check that nothing else is enabled. + if (ums->cmnd[1] & ~0x10) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + u32 verification_length = get_array_be_to_le16(&ums->cmnd[7]); + if (verification_length == 0) + return UMS_RES_IO_ERROR; // No default reply. + + u32 amount; + while (verification_length > 0) + { + + // Limit to EP buffer size and end sector offset. + amount = MIN(verification_length, USB_EP_BUFFER_MAX_SIZE >> UMS_DISK_LBA_SHIFT); + amount = MIN(amount, ums->lun.num_sectors - lba_offset); + if (amount == 0) { + ums->lun.sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + ums->lun.sense_data_info = lba_offset; + ums->lun.info_valid = 1; + break; + } + + if (!sdmmc_storage_read(ums->lun.storage, ums->lun.offset + lba_offset, amount, bulk_ctxt->bulk_in_buf)) + amount = 0; + +DPRINTF("File read %X @ %X\n", amount, lba_offset); + + if (!amount) + { + ums->set_text(ums->label, "#C7EA46 Error:# File verify!"); + ums->lun.sense_data = SS_UNRECOVERED_READ_ERROR; + ums->lun.sense_data_info = lba_offset; + ums->lun.info_valid = 1; + break; + } + lba_offset += amount; + verification_length -= amount; + } + return UMS_RES_OK; +} + +static int _scsi_inquiry(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 *buf = (u8 *)bulk_ctxt->bulk_in_buf; + + memset(buf, 0, 36); + + // Enable Vital Product Data (EVPD) and Unit Serial Number. + if (ums->cmnd[1] == 1 && ums->cmnd[2] == 0x80) + { + buf[0] = 0; + buf[1] = ums->cmnd[2]; + buf[2] = 0; + buf[3] = 20; // Additional length. + + buf += 4; + s_printf((char *)buf, "%04X%s", + ums->lun.storage->cid.serial, ums->lun.type == MMC_SD ? " SD " : " eMMC "); + + switch (ums->lun.partition) + { + case 0: + strcpy((char *)buf + strlen((char *)buf), "RAW"); + break; + case EMMC_GPP + 1: + s_printf((char *)buf + strlen((char *)buf), "GPP"); + break; + case EMMC_BOOT0 + 1: + s_printf((char *)buf + strlen((char *)buf), "BOOT0"); + break; + case EMMC_BOOT1 + 1: + s_printf((char *)buf + strlen((char *)buf), "BOOT1"); + break; + } + + for (u32 i = strlen((char *)buf); i < 20; i++) + buf[i] = ' '; + + return 24; + } + else /* if (ums->cmnd[1] == 0 && ums->cmnd[2] == 0) */ // Standard inquiry. + { + buf[0] = SCSI_TYPE_DISK; + buf[1] = ums->lun.removable ? 0x80 : 0; + buf[2] = 6; // ANSI INCITS 351-2001 (SPC-2).////////SPC2: 4, SPC4: 6 + buf[3] = 2; // SCSI-2 INQUIRY data format. + buf[4] = 31; // Additional length. + // buf5-7: No special options. + + // Vendor ID. Max 8 chars. + buf += 8; + strcpy((char *)buf, "hekate"); + + // Product ID. Max 16 chars. + buf += 8; + switch (ums->lun.partition) + { + case 0: + s_printf((char *)buf, "%s", "SD RAW"); + break; + case EMMC_GPP + 1: + s_printf((char *)buf, "%s%s", + ums->lun.type == MMC_SD ? "SD " : "eMMC ", "GPP"); + break; + case EMMC_BOOT0 + 1: + s_printf((char *)buf, "%s%s", + ums->lun.type == MMC_SD ? "SD " : "eMMC ", "BOOT0"); + break; + case EMMC_BOOT1 + 1: + s_printf((char *)buf, "%s%s", + ums->lun.type == MMC_SD ? "SD " : "eMMC ", "BOOT1"); + break; + } + + // Rev ID. Max 4 chars. + buf += 16; + strcpy((char *)buf, "1.00"); + + return 36; + } +} + +static int _scsi_request_sense(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 *buf = (u8 *)bulk_ctxt->bulk_in_buf; + u32 sd, sdinfo; + int valid; + + sd = ums->lun.sense_data; + sdinfo = ums->lun.sense_data_info; + valid = ums->lun.info_valid << 7; + ums->lun.sense_data = SS_NO_SENSE; + ums->lun.sense_data_info = 0; + ums->lun.info_valid = 0; + + memset(buf, 0, 18); + buf[0] = valid | 0x70; // Valid, current error. + buf[2] = SK(sd); + put_array_le_to_be32(sdinfo, &buf[3]); // Sense information. + buf[7] = 18 - 8; // Additional sense length. + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + + return 18; +} + +static int _scsi_read_capacity(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 *buf = (u8 *)bulk_ctxt->bulk_in_buf; + u32 lba = get_array_be_to_le32(&ums->cmnd[2]); + int pmi = ums->cmnd[8]; + + // Check the PMI and LBA fields. + if (pmi > 1 || (pmi == 0 && lba != 0)) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + put_array_le_to_be32(ums->lun.num_sectors - 1, &buf[0]); // Max logical block. + put_array_le_to_be32(UMS_DISK_LBA_SIZE, &buf[4]); // Block length. + + return 8; +} + +static int _scsi_log_sense(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 *buf = (u8 *)bulk_ctxt->bulk_in_buf; + u8 *buf0 = buf; + bool valid_page = false; + + u8 pc = ums->cmnd[2] >> 6; + u8 page_code = ums->cmnd[2] & 0x3F; + u8 sub_page_code = ums->cmnd[3]; + + if (ums->cmnd[1] & 1) + { + ums->lun.sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + + return UMS_RES_INVALID_ARG; + } + + if (pc != 1) // Current cumulative values. + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + memset(buf, 0, 8); + if (page_code == 0x00 && !sub_page_code) // Supported pages. + { + valid_page = true; + buf[0] = 0x00; // Page code. + buf += 4; + + buf[0] = 0x00; // Page 0. + buf[1] = 0x0D; // Page 1. + + buf += 2; + } + else if (page_code == 0x0d && !sub_page_code) // Temperature. + { + valid_page = true; + buf[0] = 0x0D; + buf += 4; + + put_array_le_to_be16(0, &buf[0]); // Param code. + buf[2] = 1; // Param control byte. + buf[3] = 2; // Param length. + buf[4] = 0; // Reserved. + buf[5] = 35; // Temperature (C) current (PCB here). + + put_array_le_to_be16(0, &buf[6]); // PARAMETER CODE + buf[8] = 1; // Param control byte. + buf[9] = 2; // Param length. + buf[10] = 0; // Reserved. + buf[11] = 60; // Temperature (C) reference. + + buf += 12; + } + + // Check that a valid page mode data length was requested. + u32 len = buf - buf0; + if (!valid_page) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + put_array_le_to_be16(len - 4, &buf0[2]); + + return len; +} + +static int _scsi_mode_sense(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 *buf = (u8 *)bulk_ctxt->bulk_in_buf; + u8 *buf0 = buf; + bool valid_page = false; + + u8 pc = ums->cmnd[2] >> 6; + u8 page_code = ums->cmnd[2] & 0x3F; + bool changeable_values = pc == 1; + bool all_pages = page_code == 0x3F; + + if ((ums->cmnd[1] & ~0x08) != 0) // Mask away DBD. + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + if (pc == 3) + { + ums->lun.sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + + return UMS_RES_INVALID_ARG; + } + + /* Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. */ + memset(buf, 0, 8); + if (ums->cmnd[0] == SC_MODE_SENSE_6) + { + buf[2] = (ums->lun.ro ? 0x80 : 0x00); // WP, DPOFUA. + buf += 4; + } + else // SC_MODE_SENSE_10. + { + buf[3] = (ums->lun.ro ? 0x80 : 0x00); // WP, DPOFUA. + buf += 8; + } + + // The only page we support is the Caching page. + // What about x1C + if (page_code == 0x08 || all_pages) + { + valid_page = true; + buf[0] = 0x08; // Page code. + buf[1] = 18; // Page length. + memset(buf + 2, 0, 18); // Set all parameters to 0. + + // None of the fields are changeable. + if (!changeable_values) + { + // Write Cache enable, Read Cache not disabled, Multiplication Factor off. + buf[2] = 0x04; + + // Multiplication Factor is disabled, so all values below are 1x LBA. + put_array_le_to_be16(0xFFFF, &buf[4]); // Disable Prefetch if >32MB. + put_array_le_to_be16(0x0000, &buf[6]); // Minimum Prefetch 0MB. + put_array_le_to_be16(0xFFFF, &buf[8]); // Maximum Prefetch 32MB. + put_array_le_to_be16(0xFFFF, &buf[10]); // Maximum Prefetch ceiling 32MB. + } + + buf += 20; + } + + // Check that a valid page mode data length was requested. + u32 len = buf - buf0; + if (!valid_page) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + /* Store the mode data length */ + if (ums->cmnd[0] == SC_MODE_SENSE_6) + buf0[0] = len - 1; + else + put_array_le_to_be16(len - 2, buf0); + + return len; +} + +static int _scsi_start_stop(usbd_gadget_ums_t *ums) +{ + int loej, start; + + if (!ums->lun.removable) + { + ums->lun.sense_data = SS_INVALID_COMMAND; + + return UMS_RES_INVALID_ARG; + } + else if ((ums->cmnd[1] & ~0x01) != 0 || // Mask away Immed. + (ums->cmnd[4] & ~0x03) != 0) // Mask LoEj, Start. + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + loej = ums->cmnd[4] & 0x02; + start = ums->cmnd[4] & 0x01; + + // We do not support re-mounting. + if (start) + { + if (ums->lun.unmounted) + { + ums->lun.sense_data = SS_MEDIUM_NOT_PRESENT; + + return UMS_RES_INVALID_ARG; + } + + return UMS_RES_OK; + } + + // Check if we are allowed to unload the media. + if (ums->lun.prevent_medium_removal) + { + ums->set_text(ums->label, "#C7EA46 Status:# Unload attempt prevented"); + ums->lun.sense_data = SS_MEDIUM_REMOVAL_PREVENTED; + + return UMS_RES_INVALID_ARG; + } + + if (!loej) + return UMS_RES_OK; + + // Unmount means we exit UMS because of ejection. + ums->lun.unmounted = 1; + + return UMS_RES_OK; +} + +static int _scsi_prevent_allow_removal(usbd_gadget_ums_t *ums) +{ + int prevent; + + if (!ums->lun.removable) + { + ums->lun.sense_data = SS_INVALID_COMMAND; + + return UMS_RES_INVALID_ARG; + } + + prevent = ums->cmnd[4] & 0x01; + if ((ums->cmnd[4] & ~0x01) != 0) // Mask away Prevent. + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + + // Notify for possible unmounting? + // Normally we sync here but we do synced writes to SDMMC. + if (ums->lun.prevent_medium_removal && !prevent) + ; + + ums->lun.prevent_medium_removal = prevent; + + return UMS_RES_OK; +} + +static int _scsi_read_format_capacities(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 *buf = (u8 *)bulk_ctxt->bulk_in_buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; // Only the Current/Maximum Capacity Descriptor. + buf += 4; + + put_array_le_to_be32(ums->lun.num_sectors, &buf[0]); // Number of blocks. + put_array_le_to_be32(UMS_DISK_LBA_SIZE, &buf[4]); // Block length. + buf[4] = 0x02; // Current capacity. + + return 12; +} + +// Check whether the command is properly formed and whether its data size +// and direction agree with the values we already have. +static int _ums_check_scsi_cmd(usbd_gadget_ums_t *ums, int cmnd_size, + enum data_direction data_dir, u32 mask, int needs_medium) +{ +//const char dirletter[4] = {'u', 'o', 'i', 'n'}; +DPRINTF("SCSI command: %X; Dc=%d, D%c=%X; Hc=%d, H%c=%X\n", + ums->cmnd[0], cmnd_size, dirletter[(int)ums->data_dir], + ums->data_size_from_cmnd, ums->cmnd_size, + dirletter[(int)data_dir], ums->data_size); + + // We can't reply if we don't know the direction and size. + if (ums->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + + // This is a phase error but we continue and only transfer as much we can. + if (ums->data_size < ums->data_size_from_cmnd) + { + ums->data_size_from_cmnd = ums->data_size; + ums->phase_error = 1; + } + + ums->residue = ums->data_size; + ums->usb_amount_left = ums->data_size; + + if (ums->data_dir != data_dir && ums->data_size_from_cmnd > 0) + { + ums->phase_error = 1; + + return UMS_RES_INVALID_ARG; + } + + // Cmd length verification. + if (cmnd_size != ums->cmnd_size) + { + + // Special case workaround for Windows and Xbox 360. + if (cmnd_size <= ums->cmnd_size) + cmnd_size = ums->cmnd_size; + else + { + ums->phase_error = 1; + + return UMS_RES_INVALID_ARG; + } + } + + // check that LUN ums->cmnd[1] >> 5 is 0 because of only one. + + if (ums->cmnd[0] != SC_REQUEST_SENSE) + { + ums->lun.sense_data = SS_NO_SENSE; + ums->lun.sense_data_info = 0; + ums->lun.info_valid = 0; + } + + // If a unit attention condition exists, only INQUIRY and REQUEST SENSE + // commands are allowed. + if (ums->lun.unit_attention_data != SS_NO_SENSE && ums->cmnd[0] != SC_INQUIRY && + ums->cmnd[0] != SC_REQUEST_SENSE) + { + ums->lun.sense_data = ums->lun.unit_attention_data; + ums->lun.unit_attention_data = SS_NO_SENSE; + + return UMS_RES_INVALID_ARG; + } + + // Check that only command bytes listed in the mask are set. + ums->cmnd[1] &= 0x1F; // Mask away the LUN. + for (u32 i = 1; i < cmnd_size; ++i) + { + if (ums->cmnd[i] && !(mask & BIT(i))) + { + ums->lun.sense_data = SS_INVALID_FIELD_IN_CDB; + + return UMS_RES_INVALID_ARG; + } + } + + // If the medium isn't mounted and the command needs to access it, return an error. + if (ums->lun.unmounted && needs_medium) + { + ums->lun.sense_data = SS_MEDIUM_NOT_PRESENT; + + return UMS_RES_INVALID_ARG; + } + + return UMS_RES_OK; +} + +static int _ums_parse_scsi_cmd(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u32 len; + int reply = UMS_RES_INVALID_ARG; + + ums->phase_error = 0; + ums->short_packet_received = 0; + + switch (ums->cmnd[0]) + { + case SC_INQUIRY: + ums->data_size_from_cmnd = ums->cmnd[4]; + u32 mask = (1<<4); + if (ums->cmnd[1] == 1 && ums->cmnd[2] == 0x80) // Inquiry S/N. + mask = (1<<1) | (1<<2) | (1<<4); + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_TO_HOST, mask, 0); + if (reply == 0) + reply = _scsi_inquiry(ums, bulk_ctxt); + break; + + case SC_LOG_SENSE: + ums->data_size_from_cmnd = get_array_be_to_le16(&ums->cmnd[7]); + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_TO_HOST, (1<<1) | (1<<2) | (3<<7), 0); + if (reply == 0) + reply = _scsi_log_sense(ums, bulk_ctxt); + break; + + case SC_MODE_SELECT_6: + ums->data_size_from_cmnd = ums->cmnd[4]; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_FROM_HOST, (1<<1) | (1<<4), 0); + if (reply == 0) + { + // We don't support MODE SELECT. + ums->lun.sense_data = SS_INVALID_COMMAND; + reply = UMS_RES_INVALID_ARG; + } + break; + + case SC_MODE_SELECT_10: + ums->data_size_from_cmnd = get_array_be_to_le16(&ums->cmnd[7]); + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_FROM_HOST, (1<<1) | (3<<7), 0); + if (reply == 0) + { + // We don't support MODE SELECT. + ums->lun.sense_data = SS_INVALID_COMMAND; + reply = UMS_RES_INVALID_ARG; + } + break; + + case SC_MODE_SENSE_6: + ums->data_size_from_cmnd = ums->cmnd[4]; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_TO_HOST, (1<<1) | (1<<2) | (1<<4), 0); + if (reply == 0) + reply = _scsi_mode_sense(ums, bulk_ctxt); + break; + + case SC_MODE_SENSE_10: + ums->data_size_from_cmnd = get_array_be_to_le16(&ums->cmnd[7]); + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_TO_HOST, (1<<1) | (1<<2) | (3<<7), 0); + if (reply == 0) + reply = _scsi_mode_sense(ums, bulk_ctxt); + break; + + case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: + ums->data_size_from_cmnd = 0; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_NONE, (1<<4), 0); + if (reply == 0) + reply = _scsi_prevent_allow_removal(ums); + break; + + case SC_READ_6: + len = ums->cmnd[4]; + ums->data_size_from_cmnd = (len == 0 ? 256 : len) << UMS_DISK_LBA_SHIFT; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_TO_HOST, (7<<1) | (1<<4), 1); + if (reply == 0) + reply = _scsi_read(ums, bulk_ctxt); + break; + + case SC_READ_10: + ums->data_size_from_cmnd = get_array_be_to_le16(&ums->cmnd[7]) << UMS_DISK_LBA_SHIFT; + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_TO_HOST, (1<<1) | (0xf<<2) | (3<<7), 1); + if (reply == 0) + reply = _scsi_read(ums, bulk_ctxt); + break; + + case SC_READ_12: + ums->data_size_from_cmnd = get_array_be_to_le32(&ums->cmnd[6]) << UMS_DISK_LBA_SHIFT; + reply = _ums_check_scsi_cmd(ums, 12, DATA_DIR_TO_HOST, (1<<1) | (0xf<<2) | (0xf<<6), 1); + if (reply == 0) + reply = _scsi_read(ums, bulk_ctxt); + break; + + case SC_READ_CAPACITY: + ums->data_size_from_cmnd = 8; + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_TO_HOST, (0xf<<2) | (1<<8), 1); + if (reply == 0) + reply = _scsi_read_capacity(ums, bulk_ctxt); + break; + case SC_READ_FORMAT_CAPACITIES: + ums->data_size_from_cmnd = get_array_be_to_le16(&ums->cmnd[7]); + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_TO_HOST, (3<<7), 1); + if (reply == 0) + reply = _scsi_read_format_capacities(ums, bulk_ctxt); + break; + + case SC_REQUEST_SENSE: + ums->data_size_from_cmnd = ums->cmnd[4]; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_TO_HOST, (1<<4), 0); + if (reply == 0) + reply = _scsi_request_sense(ums, bulk_ctxt); + break; + + case SC_START_STOP_UNIT: + ums->data_size_from_cmnd = 0; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_NONE, (1<<1) | (1<<4), 0); + if (reply == 0) + reply = _scsi_start_stop(ums); + break; + + case SC_SYNCHRONIZE_CACHE: + ums->data_size_from_cmnd = 0; + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_NONE, (0xf<<2) | (3<<7), 1); + if (reply == 0) + reply = 0; // Don't bother + break; + + case SC_TEST_UNIT_READY: + ums->data_size_from_cmnd = 0; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_NONE, 0, 1); + break; + + // This command is used by Windows. We support a minimal version and BytChk must be 0. + case SC_VERIFY: + ums->data_size_from_cmnd = 0; + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_NONE, (1<<1) | (0xf<<2) | (3<<7), 1); + if (reply == 0) + reply = _scsi_verify(ums, bulk_ctxt); + break; + + case SC_WRITE_6: + len = ums->cmnd[4]; + ums->data_size_from_cmnd = (len == 0 ? 256 : len) << UMS_DISK_LBA_SHIFT; + reply = _ums_check_scsi_cmd(ums, 6, DATA_DIR_FROM_HOST, (7<<1) | (1<<4), 1); + if (reply == 0) + reply = _scsi_write(ums, bulk_ctxt); + break; + + case SC_WRITE_10: + ums->data_size_from_cmnd = get_array_be_to_le16(&ums->cmnd[7]) << UMS_DISK_LBA_SHIFT; + reply = _ums_check_scsi_cmd(ums, 10, DATA_DIR_FROM_HOST, (1<<1) | (0xf<<2) | (3<<7), 1); + if (reply == 0) + reply = _scsi_write(ums, bulk_ctxt); + break; + + case SC_WRITE_12: + ums->data_size_from_cmnd = get_array_be_to_le32(&ums->cmnd[6]) << UMS_DISK_LBA_SHIFT; + reply = _ums_check_scsi_cmd(ums, 12, DATA_DIR_FROM_HOST, (1<<1) | (0xf<<2) | (0xf<<6), 1); + if (reply == 0) + reply = _scsi_write(ums, bulk_ctxt); + break; + + // Mandatory commands that we don't implement. No need. + case SC_READ_HEADER: + case SC_READ_TOC: + case SC_FORMAT_UNIT: + case SC_RELEASE: + case SC_RESERVE: + case SC_SEND_DIAGNOSTIC: + default: + ums->data_size_from_cmnd = 0; + reply = _ums_check_scsi_cmd(ums, ums->cmnd_size, DATA_DIR_UNKNOWN, 0xFF, 0); + if (reply == 0) + { + ums->lun.sense_data = SS_INVALID_COMMAND; + reply = UMS_RES_INVALID_ARG; + } + break; + } + + if (reply == UMS_RES_INVALID_ARG) + reply = 0; // Error reply length. + + // Set up reply buffer for finish_reply(). Otherwise it's already set. + if (reply >= 0 && ums->data_dir == DATA_DIR_TO_HOST) + { + reply = MIN((u32)reply, ums->data_size_from_cmnd); + bulk_ctxt->bulk_in_length = reply; + bulk_ctxt->bulk_in_buf_state = BUF_STATE_FULL; + ums->residue -= reply; + } + + return UMS_RES_OK; +} + +static int pad_with_zeros(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + bulk_ctxt->bulk_in_buf_state = BUF_STATE_EMPTY; // For the first iteration. + u32 current_len_to_keep = bulk_ctxt->bulk_in_length; + ums->usb_amount_left = current_len_to_keep + ums->residue; + + while (ums->usb_amount_left > 0) + { + u32 nsend = MIN(ums->usb_amount_left, USB_EP_BUFFER_MAX_SIZE); + memset(bulk_ctxt->bulk_in_buf + current_len_to_keep, 0, nsend - current_len_to_keep); + bulk_ctxt->bulk_in_length = nsend; + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_in, USB_XFER_SYNCED_DATA); + ums->usb_amount_left -= nsend; + current_len_to_keep = 0; + } + + return UMS_RES_OK; +} + +static int throw_away_data(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + if (bulk_ctxt->bulk_out_buf_state != BUF_STATE_EMPTY || ums->usb_amount_left > 0) + { + // Try to submit another request if we need one. + if (bulk_ctxt->bulk_out_buf_state == BUF_STATE_EMPTY && ums->usb_amount_left > 0) + { + u32 amount = MIN(ums->usb_amount_left, USB_EP_BUFFER_MAX_SIZE); + + bulk_ctxt->bulk_out_length = amount; + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_out, USB_XFER_SYNCED_DATA); + ums->usb_amount_left -= amount; + + return UMS_RES_OK; + } + + // Throw away the data in a filled buffer. + if (bulk_ctxt->bulk_out_buf_state == BUF_STATE_FULL) + bulk_ctxt->bulk_out_buf_state = BUF_STATE_EMPTY; + + // A short packet or an error ends everything. + if (bulk_ctxt->bulk_out_length_actual != bulk_ctxt->bulk_out_length || + bulk_ctxt->bulk_out_status != USB_RES_OK) + { + raise_exception(ums, UMS_STATE_ABORT_BULK_OUT); + return UMS_RES_PROT_FATAL; + } + } + return UMS_RES_OK; +} + +static int finish_reply(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + int rc = UMS_RES_OK; + + switch (ums->data_dir) { + case DATA_DIR_NONE: + break; // Nothing to send. + + // If this is a CB or CBI with an unknown command, we mustn't + // try to send or receive any data. Stall if we can and wait reset. + case DATA_DIR_UNKNOWN: + if (ums->can_stall) + { + ums_set_stall(bulk_ctxt->bulk_out); + rc = ums_set_stall(bulk_ctxt->bulk_in); + ums->set_text(ums->label, "#C7EA46 Error:# Direction unknown. Stalled both EP!"); + } // Else do nothing. + break; + + // All but the last buffer of data have already been sent. + case DATA_DIR_TO_HOST: + if (ums->data_size) + { + // If there's no residue, simply send the last buffer. + if (!ums->residue) + { + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_in, USB_XFER_SYNCED_DATA); + + /* For Bulk-only, if we're allowed to stall then send the + * short packet and halt the bulk-in endpoint. If we can't + * stall, pad out the remaining data with 0's. */ + } + else if (ums->can_stall) + { + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_in, USB_XFER_SYNCED_DATA); + rc = ums_set_stall(bulk_ctxt->bulk_in); + ums->set_text(ums->label, "#C7EA46 Error:# Residue. Stalled EP IN!"); + } + else + rc = pad_with_zeros(ums, bulk_ctxt); + } + + // In case we used SDMMC transfer, reset the buffer address. + _ums_reset_buffer(bulk_ctxt, bulk_ctxt->bulk_in); + break; + + // We have processed all we want from the data the host has sent. + // There may still be outstanding bulk-out requests. + case DATA_DIR_FROM_HOST: + if (ums->residue) + { + if (ums->short_packet_received) // Did the host stop sending unexpectedly early? + { + raise_exception(ums, UMS_STATE_ABORT_BULK_OUT); + rc = UMS_RES_PROT_FATAL; + } + else // We can't stall. Read in the excess data and throw it away. + rc = throw_away_data(ums, bulk_ctxt); + } + + break; + } + + return rc; +} + +/* + * Medium ejection heuristics. + * + * Windows: + * Uses Start/Stop Unit. Only Stop with LoEj. Observed ONLY on very specific windows machines. + * Uses Prevent/Allow Medium Removal. (For big reads and ANY write.) //////Except trivial writes. Needs check with prefetch ON + * Sends Test Unit Ready every 1s at idle. (Needs 1 EP Timeout protection: 2s) + * Does not send data when ejects. In the case it does, + * it loops into Request Sense and Test Unit Ready when ejects. + * Line always at SE0 and only goes in J-State when it ejects. + * + * Linux: + * Uses Start/Stop Unit. Stops with LoEj when Media prevention is off. + * Uses Prevent/Allow Medium Removal. (For big read and any write.) + * Sends Test Unit Ready every 2s at idle. (Needs 2 EP Timeouts protection: 4s) + * Loops into Request Sense and Test Unit Ready when ejects. + * Line always at SE0. + * + * Mac OS: + * Uses Start/Stop. Stops with LoEj when Allow Medium Removal is enabled. + * Uses Prevent/Allow Medium Removal. (Properly. Enables at mount and only disables it when ejects.) + * Does not send Test Unit Ready at idle. But Prevent Medium Removal is enabled. + * Loops into Request Sense and Test Unit Ready when ejects. + * Line always at SE0. + */ + +static int received_cbw(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + /* Was this a real packet? Should it be ignored? */ + if (bulk_ctxt->bulk_out_status || bulk_ctxt->bulk_out_ignore || ums->lun.unmounted) + { + if (bulk_ctxt->bulk_out_status || ums->lun.unmounted) + { + DPRINTF("USB: EP timeout\n"); + // In case we disconnected, exit UMS. + // Raise timeout if removable and didn't got a unit ready command inside 4s. + if (bulk_ctxt->bulk_out_status == USB2_ERROR_XFER_EP_DISABLED || + (bulk_ctxt->bulk_out_status == USB_ERROR_TIMEOUT && ums->lun.removable && !ums->lun.prevent_medium_removal)) + { + if (bulk_ctxt->bulk_out_status == USB_ERROR_TIMEOUT) + { + if (usb_ops.usb_device_get_port_in_sleep()) + { + ums->set_text(ums->label, "#C7EA46 Status:# #FFFFFF EP in sleep#"); + ums->timeouts += 14; + } + else if (!ums->xusb) // Timeout only on USB2. + { + ums->timeouts += 4; + DPRINTF("USB: EP removable\n"); + } + } + else + { + gfx_printf("USB: EP disabled\n"); + msleep(500); + ums->timeouts += 4; + } + } + + if (ums->lun.unmounted) + { + ums->set_text(ums->label, "#C7EA46 Status:# #FFFFFF Medium unmounted#"); + ums->timeouts++; + } + + if (ums->timeouts > 20) + raise_exception(ums, UMS_STATE_EXIT); + } + + if (bulk_ctxt->bulk_out_status || bulk_ctxt->bulk_out_ignore) + return UMS_RES_INVALID_ARG; + } + + /* Is the CBW valid? */ + bulk_recv_pkt_t *cbw = (bulk_recv_pkt_t *)bulk_ctxt->bulk_out_buf; + if (bulk_ctxt->bulk_out_length_actual != USB_BULK_CB_WRAP_LEN || cbw->Signature != USB_BULK_CB_SIG) + { + gfx_printf("USB: invalid CBW: len %X sig 0x%X\n", bulk_ctxt->bulk_out_length_actual, cbw->Signature); + + // The Bulk-only spec says we MUST stall the IN endpoint + // (6.6.1), so it's unavoidable. It also says we must + // retain this state until the next reset, but there's + // no way to tell the controller driver it should ignore + // Clear-Feature(HALT) requests. + // + // We aren't required to halt the OUT endpoint; instead + // we can simply accept and discard any data received + // until the next reset. + ums_wedge_bulk_in_endpoint(ums); + bulk_ctxt->bulk_out_ignore = 1; + return UMS_RES_INVALID_ARG; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= UMS_MAX_LUN || cbw->Flags & ~USB_BULK_IN_FLAG || + cbw->Length <= 0 || cbw->Length > SCSI_MAX_CMD_SZ) + { + gfx_printf("USB: non-meaningful CBW: lun = %X, flags = 0x%X, cmdlen %X\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. */ + if (ums->can_stall) + { + ums_set_stall(bulk_ctxt->bulk_out); + ums_set_stall(bulk_ctxt->bulk_in); + ums->set_text(ums->label, "#C7EA46 Status:# #FFFFFF CBW unknown - Stalled both EP#"); + } + + return UMS_RES_INVALID_ARG; + } + + /* Save the command for later */ + ums->cmnd_size = cbw->Length; + memcpy(ums->cmnd, cbw->CDB, ums->cmnd_size); + + if (cbw->Flags & USB_BULK_IN_FLAG) + ums->data_dir = DATA_DIR_TO_HOST; + else + ums->data_dir = DATA_DIR_FROM_HOST; + + ums->data_size = cbw->DataTransferLength; + + if (ums->data_size == 0) + ums->data_dir = DATA_DIR_NONE; + + ums->lun_idx = cbw->Lun; + ums->tag = cbw->Tag; + + if (!ums->lun.unmounted) + ums->timeouts = 0; + + return UMS_RES_OK; +} + +static int get_next_command(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + int rc = UMS_RES_OK; + + /* Wait for the next buffer to become available */ + // while (bulk_ctxt->bulk_out_buf_state != BUF_STATE_EMPTY) + // { + // //wait irq. + // } + + bulk_ctxt->bulk_out_length = USB_BULK_CB_WRAP_LEN; + + /* Queue a request to read a Bulk-only CBW */ + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_out, USB_XFER_SYNCED_CMD); + + /* We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* Wait for the CBW to arrive */ + // while (bulk_ctxt->bulk_out_buf_state != BUF_STATE_FULL) + // { + // //wait irq. + // } + + rc = received_cbw(ums, bulk_ctxt); + bulk_ctxt->bulk_out_buf_state = BUF_STATE_EMPTY; + + return rc; +} + +static void send_status(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + u8 status = USB_STATUS_PASS; + u32 sd = ums->lun.sense_data; + + if (ums->phase_error) + { + ums->set_text(ums->label, "#C7EA46 Error:# Phase-error!"); + status = USB_STATUS_PHASE_ERROR; + sd = SS_INVALID_COMMAND; + } + else if (sd != SS_NO_SENSE) + { + DPRINTF("USB: CMD fail\n"); + status = USB_STATUS_FAIL; + DPRINTF("USB: Sense: SK x%02X, ASC x%02X, ASCQ x%02X; info x%X\n", + SK(sd), ASC(sd), ASCQ(sd), ums->lun.sense_data_info); + } + + /* Store and send the Bulk-only CSW */ + bulk_send_pkt_t *csw = (bulk_send_pkt_t *)bulk_ctxt->bulk_in_buf; + + csw->Signature = USB_BULK_CS_SIG; + csw->Tag = ums->tag; + csw->Residue = ums->residue; + csw->Status = status; + + bulk_ctxt->bulk_in_length = USB_BULK_CS_WRAP_LEN; + _ums_transfer_start(ums, bulk_ctxt, bulk_ctxt->bulk_in, USB_XFER_SYNCED_CMD); +} + +static void handle_exception(usbd_gadget_ums_t *ums, bulk_ctxt_t *bulk_ctxt) +{ + enum ums_state old_state; + + /* Clear out the controller's fifos */ + ums_flush_endpoint(bulk_ctxt->bulk_in); + ums_flush_endpoint(bulk_ctxt->bulk_out); + + /* Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. */ + + bulk_ctxt->bulk_in_buf_state = BUF_STATE_EMPTY; + bulk_ctxt->bulk_out_buf_state = BUF_STATE_EMPTY; + + old_state = ums->state; + + if (old_state != UMS_STATE_ABORT_BULK_OUT) + { + ums->lun.prevent_medium_removal = 0; + ums->lun.sense_data = SS_NO_SENSE; + ums->lun.unit_attention_data = SS_NO_SENSE; + ums->lun.sense_data_info = 0; + ums->lun.info_valid = 0; + } + + ums->state = UMS_STATE_NORMAL; + + /* Carry out any extra actions required for the exception */ + switch (old_state) + { + case UMS_STATE_NORMAL: + break; + case UMS_STATE_ABORT_BULK_OUT: + send_status(ums, bulk_ctxt); + break; + + case UMS_STATE_PROTOCOL_RESET: + /* In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) */ + if (bulk_ctxt->bulk_out_ignore) + { + bulk_ctxt->bulk_out_ignore = 0; + ums_clear_stall(bulk_ctxt->bulk_in); + } + ums->lun.unit_attention_data = SS_RESET_OCCURRED; + break; + + case UMS_STATE_EXIT: + ums->state = UMS_STATE_TERMINATED; /* Stop the thread */ + break; + + default: + break; + } +} + +static inline void _system_maintainance(usbd_gadget_ums_t *ums) +{ + static u32 timer_dram = 0; + static u32 timer_status_bar = 0; + + u32 time = get_tmr_ms(); + + if (timer_status_bar < time) + { + ums->system_maintenance(true); + timer_status_bar = get_tmr_ms() + 30000; + } + else if (timer_dram < time) + { + minerva_periodic_training(); + timer_dram = get_tmr_ms() + EMC_PERIODIC_TRAIN_MS; + } +} + +int usb_device_gadget_ums(usb_ctxt_t *usbs) +{ + int res = 0; + sdmmc_t sdmmc; + sdmmc_storage_t storage; + usbd_gadget_ums_t ums = {0}; + + // Get USB Controller ops. + if (hw_get_chip_id() == GP_HIDREV_MAJOR_T210) + usb_device_get_ops(&usb_ops); + else + { + ums.xusb = true; + xusb_device_get_ops(&usb_ops); + } + + usbs->set_text(usbs->label, "#C7EA46 Status:# #FFFFFF Started USB#"); + + if (usb_ops.usb_device_init()) + { + usb_ops.usbd_end(false, true); + return 1; + } + + ums.state = UMS_STATE_NORMAL; + ums.can_stall = 0; + + ums.bulk_ctxt.bulk_in = USB_EP_BULK_IN; + ums.bulk_ctxt.bulk_in_buf = (u8 *)USB_EP_BULK_IN_BUF_ADDR; + + ums.bulk_ctxt.bulk_out = USB_EP_BULK_OUT; + ums.bulk_ctxt.bulk_out_buf = (u8 *)USB_EP_BULK_OUT_BUF_ADDR; + + // Set LUN parameters. + ums.lun.ro = usbs->ro; + ums.lun.type = usbs->type; + ums.lun.partition = usbs->partition; + ums.lun.offset = usbs->offset; + ums.lun.removable = 1; // Always removable to force OSes to use prevent media removal. + ums.lun.unit_attention_data = SS_RESET_OCCURRED; + + // Set system functions + ums.label = usbs->label; + ums.set_text = usbs->set_text; + ums.system_maintenance = usbs->system_maintenance; + + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Mounting disk#"); + + // Initialize sdmmc. + if (usbs->type == MMC_SD) + { + sd_mount(); + sd_unmount(); + ums.lun.sdmmc = &sd_sdmmc; + ums.lun.storage = &sd_storage; + } + else + { + ums.lun.sdmmc = &sdmmc; + ums.lun.storage = &storage; + sdmmc_storage_init_mmc(ums.lun.storage, ums.lun.sdmmc, SDMMC_BUS_WIDTH_8, SDHCI_TIMING_MMC_HS400); + sdmmc_storage_set_mmc_partition(ums.lun.storage, ums.lun.partition - 1); + } + + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Waiting for connection#"); + + // Initialize Control Endpoint. + if (usb_ops.usb_device_enumerate(USB_GADGET_UMS)) + goto error; + + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Waiting for LUN#"); + + if (usb_ops.usb_device_class_send_max_lun(0)) // One device for now. + goto error; + + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Started UMS#"); + + if (usbs->sectors) + ums.lun.num_sectors = usbs->sectors; + else + ums.lun.num_sectors = ums.lun.storage->sec_cnt; + + do + { + // Do DRAM training and update system tasks. + _system_maintainance(&ums); + + // Check for force unmount button combo. + if (btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + { + // Check if we are allowed to unload the media. + if (ums.lun.prevent_medium_removal) + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Unload attempt prevented#"); + else + break; + } + + if (ums.state != UMS_STATE_NORMAL) + { + handle_exception(&ums, &ums.bulk_ctxt); + continue; + } + + ums_handle_ep0_ctrl(&ums); + + if (get_next_command(&ums, &ums.bulk_ctxt) || (ums.state > UMS_STATE_NORMAL)) + continue; + + ums_handle_ep0_ctrl(&ums); + + if (_ums_parse_scsi_cmd(&ums, &ums.bulk_ctxt) || (ums.state > UMS_STATE_NORMAL)) + continue; + + ums_handle_ep0_ctrl(&ums); + + if (finish_reply(&ums, &ums.bulk_ctxt) || (ums.state > UMS_STATE_NORMAL)) + continue; + + send_status(&ums, &ums.bulk_ctxt); + } while (ums.state != UMS_STATE_TERMINATED); + + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Disk ejected#"); + goto exit; + +error: + ums.set_text(ums.label, "#C7EA46 Status:# #FFFFFF Timed out or canceled#"); + res = 1; + +exit: + if (ums.lun.type == MMC_EMMC) + sdmmc_storage_end(ums.lun.storage); + + usb_ops.usbd_end(true, false); + + return res; +} diff --git a/bdk/usb/usb_t210.h b/bdk/usb/usb_t210.h new file mode 100644 index 00000000..e677a5f3 --- /dev/null +++ b/bdk/usb/usb_t210.h @@ -0,0 +1,293 @@ +/* + * Enhanced & eXtensible USB device (EDCI & XDCI) driver for Tegra X1 + * + * Copyright (c) 2019-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _USB_T210_H_ +#define _USB_T210_H_ + +#include + +/* EHCI USB */ + +/* General USB registers */ +#define USB1_IF_USB_SUSP_CTRL 0x400 +#define SUSP_CTRL_USB_WAKE_ON_CNNT_EN_DEV BIT(3) +#define SUSP_CTRL_USB_WAKE_ON_DISCON_EN_DEV BIT(4) +#define SUSP_CTRL_USB_PHY_CLK_VALID BIT(7) +#define SUSP_CTRL_UTMIP_RESET BIT(11) +#define SUSP_CTRL_UTMIP_PHY_ENB BIT(12) +#define SUSP_CTRL_UTMIP_UTMIP_SUSPL1_SET BIT(25) +#define USB1_IF_USB_PHY_VBUS_SENSORS 0x404 +#define USB1_UTMIP_XCVR_CFG0 0x808 +#define USB1_UTMIP_BIAS_CFG0 0x80C +#define USB1_UTMIP_HSRX_CFG0 0x810 +#define USB1_UTMIP_HSRX_CFG1 0x814 +#define USB1_UTMIP_TX_CFG0 0x820 +#define USB1_UTMIP_MISC_CFG1 0x828 +#define USB1_UTMIP_DEBOUNCE_CFG0 0x82C +#define USB1_UTMIP_BAT_CHRG_CFG0 0x830 +#define BAT_CHRG_CFG0_PWRDOWN_CHRG BIT(0) +#define BAT_CHRG_CFG0_OP_SRC_EN BIT(3) +#define USB1_UTMIP_SPARE_CFG0 0x834 +#define USB1_UTMIP_XCVR_CFG1 0x838 +#define USB1_UTMIP_BIAS_CFG1 0x83C +#define USB1_UTMIP_BIAS_CFG2 0x850 +#define USB1_UTMIP_XCVR_CFG2 0x854 +#define USB1_UTMIP_XCVR_CFG3 0x858 + +/* USB Queue Head Descriptor */ +#define USB2_QH_USB2D_QH_EP_BASE (USB_BASE + 0x1000) +#define USB_QHD_EP_CAP_IOS_ENABLE BIT(15) +#define USB_QHD_EP_CAP_MAX_PKT_LEN_MASK 0x7FF +#define USB_QHD_EP_CAP_ZERO_LEN_TERM_DIS BIT(29) +#define USB_QHD_EP_CAP_MULTI_NON_ISO (0 << 30) +#define USB_QHD_EP_CAP_MULTI_1 (1 << 30) +#define USB_QHD_EP_CAP_MULTI_2 (2 << 30) +#define USB_QHD_EP_CAP_MULTI_3 (3 << 30) + +#define USB_QHD_TOKEN_XFER_ERROR BIT(3) +#define USB_QHD_TOKEN_BUFFER_ERROR BIT(5) +#define USB_QHD_TOKEN_HALTED BIT(6) +#define USB_QHD_TOKEN_ACTIVE BIT(7) +#define USB_QHD_TOKEN_MULT_OVERR_MASK (2 << 10) +#define USB_QHD_TOKEN_IRQ_ON_COMPLETE BIT(15) +#define USB_QHD_TOKEN_TOTAL_BYTES_SHIFT 16 + +/* USB_OTG/USB_1 controllers register bits */ +#define USB2D_PORTSC1_SUSP BIT(7) + +#define USB2D_USBCMD_RUN BIT(0) +#define USB2D_USBCMD_RESET BIT(1) +#define USB2D_USBCMD_ITC_MASK (0xFF << 16) + +#define USB2D_USBSTS_UI BIT(0) +#define USB2D_USBSTS_UEI BIT(1) +#define USB2D_USBSTS_PCI BIT(2) +#define USB2D_USBSTS_FRI BIT(3) +#define USB2D_USBSTS_SEI BIT(4) +#define USB2D_USBSTS_AAI BIT(5) +#define USB2D_USBSTS_URI BIT(6) +#define USB2D_USBSTS_SRI BIT(7) +#define USB2D_USBSTS_SLI BIT(8) + +#define USB2D_USBMODE_CM_MASK (3 << 0) +#define USB2D_USBMODE_CM_IDLE 0 +#define USB2D_USBMODE_CM_RSVD 1 +#define USB2D_USBMODE_CM_DEVICE 2 +#define USB2D_USBMODE_CM_HOST 3 + +#define USB2D_ENDPT_STATUS_RX_OFFSET BIT(0) +#define USB2D_ENDPT_STATUS_TX_OFFSET BIT(16) + +#define USB2D_ENDPTCTRL_RX_EP_STALL BIT(0) +#define USB2D_ENDPTCTRL_RX_EP_TYPE_CTRL (0 << 2) +#define USB2D_ENDPTCTRL_RX_EP_TYPE_ISO (1 << 2) +#define USB2D_ENDPTCTRL_RX_EP_TYPE_BULK (2 << 2) +#define USB2D_ENDPTCTRL_RX_EP_TYPE_INTR (3 << 2) +#define USB2D_ENDPTCTRL_RX_EP_TYPE_MASK (3 << 2) +#define USB2D_ENDPTCTRL_RX_EP_INHIBIT BIT(5) +#define USB2D_ENDPTCTRL_RX_EP_RESET BIT(6) +#define USB2D_ENDPTCTRL_RX_EP_ENABLE BIT(7) +#define USB2D_ENDPTCTRL_TX_EP_STALL BIT(16) +#define USB2D_ENDPTCTRL_TX_EP_TYPE_CTRL (0 << 18) +#define USB2D_ENDPTCTRL_TX_EP_TYPE_ISO (1 << 18) +#define USB2D_ENDPTCTRL_TX_EP_TYPE_BULK (2 << 18) +#define USB2D_ENDPTCTRL_TX_EP_TYPE_INTR (3 << 18) +#define USB2D_ENDPTCTRL_TX_EP_TYPE_MASK (3 << 18) +#define USB2D_ENDPTCTRL_TX_EP_INHIBIT BIT(21) +#define USB2D_ENDPTCTRL_TX_EP_RESET BIT(22) +#define USB2D_ENDPTCTRL_TX_EP_ENABLE BIT(23) + +#define USB2D_HOSTPC1_DEVLC_ASUS BIT(17) +#define USB2D_HOSTPC1_DEVLC_PHCD BIT(22) +#define USB2D_HOSTPC1_DEVLC_PSPD_MASK (3 << 25) + +#define USB2D_OTGSC_USB_ID_PULLUP BIT(5) +#define USB2D_OTGSC_USB_IRQ_STS_MASK (0x7F << 16) + +/* USB_OTG/USB_1 controllers registers */ +typedef struct _t210_usb2d_t +{ + vu32 id; + vu32 unk0; + vu32 hw_host; + vu32 hw_device; + vu32 hw_txbuf; + vu32 hw_rxbuf; + vu32 unk1[26]; + vu32 gptimer0ld; + vu32 gptimer0ctrl; + vu32 gptimer1ld; + vu32 gptimer1ctrl; + vu32 unk2[28]; + vu16 caplength; + vu16 hciversion; + vu32 hcsparams; + vu32 hccparams; + vu32 unk3[5]; + vu32 dciversion; + vu32 dccparams; + vu32 extsts; + vu32 usbextintr; + vu32 usbcmd; + vu32 usbsts; + vu32 usbintr; + vu32 frindex; + vu32 unk4; + vu32 periodiclistbase; + vu32 asynclistaddr; + vu32 asyncttsts; + vu32 burstsize; + vu32 txfilltuning; + vu32 unk6; + vu32 icusb_ctrl; + vu32 ulpi_viewport; + vu32 rsvd0[4]; + vu32 portsc1; + vu32 rsvd1[15]; + vu32 hostpc1_devlc; + vu32 rsvd2[15]; + vu32 otgsc; + vu32 usbmode; + vu32 unk10; + vu32 endptnak; + vu32 endptnak_enable; + vu32 endptsetupstat; + vu32 endptprime; + vu32 endptflush; + vu32 endptstatus; + vu32 endptcomplete; + vu32 endptctrl[16]; +} t210_usb2d_t; + + +/* XHCI USB */ + +/* XUSB DEV XHCI registers */ +#define XUSB_DEV_XHCI_DB 0x4 +#define XUSB_DEV_XHCI_ERSTSZ 0x8 +#define XUSB_DEV_XHCI_ERST0BALO 0x10 +#define XUSB_DEV_XHCI_ERST0BAHI 0x14 +#define XUSB_DEV_XHCI_ERST1BALO 0x18 +#define XUSB_DEV_XHCI_ERST1BAHI 0x1C +#define XUSB_DEV_XHCI_ERDPLO 0x20 +#define XHCI_ERDPLO_EHB BIT(3) +#define XUSB_DEV_XHCI_ERDPHI 0x24 +#define XUSB_DEV_XHCI_EREPLO 0x28 +#define XCHI_ECS BIT(0) +#define XUSB_DEV_XHCI_EREPHI 0x2C +#define XUSB_DEV_XHCI_CTRL 0x30 +#define XHCI_CTRL_RUN BIT(0) +#define XHCI_CTRL_LSE BIT(1) +#define XHCI_CTRL_IE BIT(4) +#define XHCI_CTRL_ENABLE BIT(31) +#define XUSB_DEV_XHCI_ST 0x34 +#define XHCI_ST_RC BIT(0) +#define XHCI_ST_IP BIT(4) +#define XUSB_DEV_XHCI_RT_IMOD 0x38 +#define XUSB_DEV_XHCI_PORTSC 0x3C +#define XHCI_PORTSC_PR BIT(4) +#define XHCI_PORTSC_PLS_MASK (0xF << 5) +#define XHCI_PORTSC_PLS_U0 (0 << 5) +#define XHCI_PORTSC_PLS_U1 (1 << 5) +#define XHCI_PORTSC_PLS_U2 (2 << 5) +#define XHCI_PORTSC_PLS_U3 (3 << 5) +#define XHCI_PORTSC_PLS_DISABLED (4 << 5) +#define XHCI_PORTSC_PLS_RXDETECT (5 << 5) +#define XHCI_PORTSC_PLS_INACTIVE (6 << 5) +#define XHCI_PORTSC_PLS_POLLING (7 << 5) +#define XHCI_PORTSC_PLS_RECOVERY (8 << 5) +#define XHCI_PORTSC_PLS_HOTRESET (9 << 5) +#define XHCI_PORTSC_PLS_COMPLIANCE (10 << 5) +#define XHCI_PORTSC_PLS_LOOPBACK (11 << 5) +#define XHCI_PORTSC_PLS_RESUME (15 << 5) +#define XHCI_PORTSC_PS (0xF << 10) +#define XHCI_PORTSC_LWS BIT(16) +#define XHCI_PORTSC_CSC BIT(17) +#define XHCI_PORTSC_WRC BIT(19) +#define XHCI_PORTSC_PRC BIT(21) +#define XHCI_PORTSC_PLC BIT(22) +#define XHCI_PORTSC_CEC BIT(23) +#define XHCI_PORTSC_WPR BIT(30) +#define XUSB_DEV_XHCI_ECPLO 0x40 +#define XUSB_DEV_XHCI_ECPHI 0x44 +#define XUSB_DEV_XHCI_EP_HALT 0x50 +#define XHCI_EP_HALT_DCI BIT(0) +#define XUSB_DEV_XHCI_EP_PAUSE 0x54 +#define XUSB_DEV_XHCI_EP_RELOAD 0x58 +#define XUSB_DEV_XHCI_EP_STCHG 0x5C +#define XUSB_DEV_XHCI_PORTHALT 0x6C +#define XHCI_PORTHALT_HALT_LTSSM BIT(0) +#define XHCI_PORTHALT_STCHG_REQ BIT(20) +#define XUSB_DEV_XHCI_CFG_DEV_FE 0x85C +#define XHCI_CFG_DEV_FE_PORTREGSEL_MASK (3 << 0) +#define XHCI_CFG_DEV_FE_PORTREGSEL_SS (1 << 0) +#define XHCI_CFG_DEV_FE_PORTREGSEL_HSFS (2 << 0) + +/* XUSB DEV PCI registers */ +#define XUSB_CFG_1 0x4 +#define CFG_1_IO_SPACE BIT(0) +#define CFG_1_MEMORY_SPACE BIT(1) +#define CFG_1_BUS_MASTER BIT(2) +#define XUSB_CFG_4 0x10 +#define CFG_4_ADDRESS_TYPE_32_BIT (0 << 1) +#define CFG_4_ADDRESS_TYPE_64_BIT (2 << 1) + +/* XUSB DEV Device registers */ +#define XUSB_DEV_CONFIGURATION 0x180 +#define DEV_CONFIGURATION_EN_FPCI BIT(0) +#define XUSB_DEV_INTR_MASK 0x188 +#define DEV_INTR_MASK_IP_INT_MASK BIT(16) + +/* XUSB Pad Control registers */ +#define XUSB_PADCTL_USB2_PAD_MUX 0x4 +#define PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_USB2 (0 << 0) +#define PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_XUSB (1 << 0) +#define PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_MASK (3 << 0) +#define PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_USB2 (0 << 18) +#define PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB (1 << 18) +#define PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK (3 << 18) +#define XUSB_PADCTL_USB2_PORT_CAP 0x8 +#define PADCTL_USB2_PORT_CAP_PORT_0_CAP_DIS (0 << 0) +#define PADCTL_USB2_PORT_CAP_PORT_0_CAP_HOST (1 << 0) +#define PADCTL_USB2_PORT_CAP_PORT_0_CAP_DEV (2 << 0) +#define PADCTL_USB2_PORT_CAP_PORT_0_CAP_OTG (3 << 0) +#define PADCTL_USB2_PORT_CAP_PORT_0_CAP_MASK (3 << 0) +#define XUSB_PADCTL_SS_PORT_MAP 0x14 +#define PADCTL_SS_PORT_MAP_PORT0_MASK (0xF << 0) +#define XUSB_PADCTL_ELPG_PROGRAM_0 0x20 +#define XUSB_PADCTL_ELPG_PROGRAM_1 0x24 +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL0 0x80 +#define XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL1 0x84 +#define XUSB_PADCTL_USB2_OTG_PAD0_CTL_0 0x88 +#define XUSB_PADCTL_USB2_OTG_PAD0_CTL_1 0x8C +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL_0 0x284 +#define XUSB_PADCTL_USB2_BIAS_PAD_CTL_1 0x288 +#define XUSB_PADCTL_USB2_VBUS_ID 0xC60 +#define PADCTL_USB2_VBUS_ID_VBUS_OVR_EN (1 << 12) +#define PADCTL_USB2_VBUS_ID_VBUS_OVR_MASK (3 << 12) +#define PADCTL_USB2_VBUS_ID_VBUS_ON BIT(14) +#define PADCTL_USB2_VBUS_ID_SRC_ID_OVR_EN (1 << 16) +#define PADCTL_USB2_VBUS_ID_SRC_MASK (3 << 16) +#define PADCTL_USB2_VBUS_ID_OVR_GND (0 << 18) +#define PADCTL_USB2_VBUS_ID_OVR_C (1 << 18) +#define PADCTL_USB2_VBUS_ID_OVR_B (2 << 18) +#define PADCTL_USB2_VBUS_ID_OVR_A (4 << 18) +#define PADCTL_USB2_VBUS_ID_OVR_FLOAT (8 << 18) +#define PADCTL_USB2_VBUS_ID_OVR_MASK (0xF << 18) + +#endif diff --git a/bdk/usb/usbd.c b/bdk/usb/usbd.c new file mode 100644 index 00000000..95d1ed5c --- /dev/null +++ b/bdk/usb/usbd.c @@ -0,0 +1,1595 @@ +/* + * Enhanced USB Device (EDCI) driver for Tegra X1 + * + * Copyright (c) 2019-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef enum +{ + USB_HW_EP0 = 0, + USB_HW_EP1 = 1 +} usb_hw_ep_t; + +typedef enum +{ + USB_EP_STATUS_IDLE = 0, + USB_EP_STATUS_ACTIVE = 1, + USB_EP_STATUS_ERROR = 2, + USB_EP_STATUS_NO_CONFIG = 3, + USB_EP_STATUS_STALLED = 4, + USB_EP_STATUS_DISABLED = 5 +} usb_ep_status_t; + +typedef enum { + USB_LOW_SPEED = 0, + USB_FULL_SPEED = 1, + USB_HIGH_SPEED = 2, + USB_SUPER_SPEED = 3, +} usb_speed_t; + +typedef struct _dTD_t +{ + vu32 next_dTD; + vu32 info; + vu32 pages[5]; + vu32 reserved; +} dTD_t; + +typedef struct _dQH_t +{ + vu32 ep_capabilities; + vu32 curr_dTD_ptr; + vu32 next_dTD_ptr; + vu32 token; + vu32 buffers[5]; // hmmm. + vu32 reserved; + vu32 setup[2]; + vu32 gap[4]; +} dQH_t; + +typedef struct _usbd_t +{ + volatile dTD_t dtds[4 * 4]; // 4 dTD per endpoint. + volatile dQH_t *qhs; + int ep_configured[4]; + int ep_bytes_requested[4]; +} usbd_t; + +typedef struct _usbd_controller_t +{ + u32 port_speed; + t210_usb2d_t *regs; + usb_ctrl_setup_t control_setup; + usb_desc_t *desc; + usb_gadget_type gadget; + u8 config_num; + u8 interface_num; + u8 max_lun; + bool usb_phy_ready; + bool configuration_set; + bool max_lun_set; + bool bulk_reset_req; + bool hid_report_sent; + u32 charger_detect; +} usbd_controller_t; + +extern u8 hid_report_descriptor_jc[]; +extern u8 hid_report_descriptor_touch[]; +extern u32 hid_report_descriptor_jc_size; +extern u32 hid_report_descriptor_touch_size; + +extern usb_desc_t usb_gadget_hid_jc_descriptors; +extern usb_desc_t usb_gadget_hid_touch_descriptors; +extern usb_desc_t usb_gadget_ums_descriptors; + +usbd_t *usbdaemon; + +usbd_controller_t *usbd_otg; +usbd_controller_t usbd_usb_otg_controller_ctxt; + +bool usb_init_done = false; + +u8 *usb_ep0_ctrl_buf = (u8 *)USB_EP_CONTROL_BUF_ADDR; + +static int _usbd_reset_usb_otg_phy_device_mode() +{ + usbd_otg->usb_phy_ready = false; + + // Clear UTMIP reset. + USB(USB1_IF_USB_SUSP_CTRL) &= ~SUSP_CTRL_UTMIP_RESET; + + // Wait for PHY clock to get validated. + u32 retries = 100000; // 200ms timeout. + while (!(USB(USB1_IF_USB_SUSP_CTRL) & SUSP_CTRL_USB_PHY_CLK_VALID)) + { + retries--; + if (!retries) + return USB_ERROR_INIT; + usleep(1); + } + usbd_otg->usb_phy_ready = true; + + // Clear all device addresses, enabled setup requests and transmit events. + usbd_otg->regs->periodiclistbase = 0; + usbd_otg->regs->endptsetupstat = usbd_otg->regs->endptsetupstat; + usbd_otg->regs->endptcomplete = usbd_otg->regs->endptcomplete; + + // Stop device controller. + usbd_otg->regs->usbcmd &= ~USB2D_USBCMD_RUN; + + // Set controller mode to idle. + usbd_otg->regs->usbmode &= ~USB2D_USBMODE_CM_MASK; + + // Reset the controller. + usbd_otg->regs->usbcmd |= USB2D_USBCMD_RESET; + + // Wait for the reset to complete. + retries = 100000; // 200ms timeout. + while (usbd_otg->regs->usbcmd & USB2D_USBCMD_RESET) + { + retries--; + if (!retries) + return USB_ERROR_INIT; + usleep(1); + } + + // Wait for PHY clock to get validated after reset. + retries = 100000; // 200ms timeout. + while (!(USB(USB1_IF_USB_SUSP_CTRL) & SUSP_CTRL_USB_PHY_CLK_VALID)) + { + retries--; + if (!retries) + return USB_ERROR_INIT; + usleep(1); + } + + // Set controller to Device mode. + usbd_otg->regs->usbmode = (usbd_otg->regs->usbmode & ~USB2D_USBMODE_CM_MASK) | USB2D_USBMODE_CM_DEVICE; + + // Wait for the selected mode to be enabled. + retries = 100000; // 200ms timeout. + while ((usbd_otg->regs->usbmode & USB2D_USBMODE_CM_MASK) != USB2D_USBMODE_CM_DEVICE) + { + retries--; + if (!retries) + return USB_ERROR_INIT; + usleep(1); + } + + // Disable all interrupts. + usbd_otg->regs->usbintr = 0; + + // Set the ID pullup and disable all OTGSC interrupts. + usbd_otg->regs->otgsc = USB2D_OTGSC_USB_ID_PULLUP; + + // Clear all relevant interrupt statuses. + usbd_otg->regs->usbsts = + USB2D_USBSTS_UI | USB2D_USBSTS_UEI | USB2D_USBSTS_PCI | + USB2D_USBSTS_FRI | USB2D_USBSTS_SEI | USB2D_USBSTS_AAI | + USB2D_USBSTS_URI | USB2D_USBSTS_SRI | USB2D_USBSTS_SLI; + + // Disable and clear all OTGSC interrupts. + usbd_otg->regs->otgsc = USB2D_OTGSC_USB_IRQ_STS_MASK; + + // Clear EP0, EP1, EP2 setup requests. + usbd_otg->regs->endptsetupstat = 7; //TODO: Shouldn't this be endptsetupstat = endptsetupstat? + + // Set all interrupts to immediate. + usbd_otg->regs->usbcmd &= ~USB2D_USBCMD_ITC_MASK; + + return USB_RES_OK; +} + +static void _usb_charger_detect() +{ + // Charger detect init. + usbd_otg->charger_detect = 0; + bool charger_detect_enable = FUSE(FUSE_RESERVED_SW) & 0x10; // Disabled on Switch production. + if (charger_detect_enable) + { + usbd_otg->charger_detect |= 1; + // Configure detect pin. + PINMUX_AUX(PINMUX_AUX_LCD_GPIO1) &= ~(PINMUX_PARKED | PINMUX_TRISTATE | PINMUX_PULL_MASK); + gpio_config(GPIO_PORT_V, GPIO_PIN_3, GPIO_MODE_GPIO); + + // Configure charger pin. + PINMUX_AUX(PINMUX_AUX_USB_VBUS_EN1) &= + ~(PINMUX_INPUT_ENABLE | PINMUX_PARKED | PINMUX_TRISTATE | PINMUX_PULL_MASK); + gpio_config(GPIO_PORT_CC, GPIO_PIN_5, GPIO_MODE_GPIO); + gpio_output_enable(GPIO_PORT_CC, GPIO_PIN_5, GPIO_OUTPUT_ENABLE); + + // Enable charger. + if (gpio_read(GPIO_PORT_V, GPIO_PIN_3)) + { + usbd_otg->charger_detect |= 2; + gpio_write(GPIO_PORT_CC, GPIO_PIN_5, GPIO_HIGH); + usbd_otg->charger_detect |= 0x100; + USB(USB1_UTMIP_BAT_CHRG_CFG0) = BAT_CHRG_CFG0_OP_SRC_EN; // Clears UTMIP_PD_CHRG and enables charger detect. + usleep(5000); + } + } +} + +static void _usb_init_phy() +{ + // Configure and enable PLLU. + clock_enable_pllu(); + + // Enable USBD clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_SET) = BIT(CLK_L_USBD); + usleep(2); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = BIT(CLK_L_USBD); + usleep(2); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_CLR) = BIT(CLK_L_USBD); + usleep(2); + + // Clear XUSB_PADCTL reset + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB_PADCTL); + + // Enable USB PHY and reset for programming. + u32 usb_susp_ctrl = USB(USB1_IF_USB_SUSP_CTRL); + USB(USB1_IF_USB_SUSP_CTRL) = usb_susp_ctrl | SUSP_CTRL_UTMIP_RESET; + USB(USB1_IF_USB_SUSP_CTRL) = usb_susp_ctrl | SUSP_CTRL_UTMIP_PHY_ENB | SUSP_CTRL_UTMIP_RESET; + + // Enable IDDQ control by software and disable UTMIPLL IDDQ. + CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) = (CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) & 0xFFFFFFFC) | 1; + usleep(10); + + // Disable crystal clock. + USB(USB1_UTMIP_MISC_CFG1) &= 0xBFFFFFFF; + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) &= 0xBFFFFFFF; + + // Set B_SESS_VLD. + USB(USB1_IF_USB_PHY_VBUS_SENSORS) |= 0x1000; + USB(USB1_IF_USB_PHY_VBUS_SENSORS) |= 0x800; + + // Set UTMIPLL dividers and config based on OSC and enable it to 960 MHz. + clock_enable_utmipll(); + + // Configure UTMIP Transceiver Cells. + u32 fuse_usb_calib = FUSE(FUSE_USB_CALIB); + USB(USB1_UTMIP_XCVR_CFG0) = (((USB(USB1_UTMIP_XCVR_CFG0) & 0xFFFFFFF0) | (fuse_usb_calib & 0xF)) & 0xFE3FFFFF) | ((fuse_usb_calib & 0x3F) << 25 >> 29 << 22); + USB(USB1_UTMIP_XCVR_CFG1) = (USB(USB1_UTMIP_XCVR_CFG1) & 0xFFC3FFFF) | ((fuse_usb_calib << 21) >> 28 << 18); + USB(USB1_UTMIP_XCVR_CFG3) = (USB(USB1_UTMIP_XCVR_CFG3) & 0xFFFFC1FF) | ((FUSE(FUSE_USB_CALIB_EXT) & 0x1F) << 9); + USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFDFFFFF; + USB(USB1_UTMIP_XCVR_CFG2) = (USB(USB1_UTMIP_XCVR_CFG2) & 0xFFFFF1FF) | 0x400; + usleep(1); + + // Configure misc UTMIP. + USB(USB1_UTMIP_DEBOUNCE_CFG0) = (USB(USB1_UTMIP_DEBOUNCE_CFG0) & 0xFFFF0000) | 0xBB80; + USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFFFFC0FF) | 0x100; // when osc is 38.4KHz + + //USB(USB1_UTMIP_SPARE_CFG0) &= 0xFFFFFEE7; unpatched0 + USB(USB1_UTMIP_BIAS_CFG2) |= 2; //patched0 - UTMIP_HSSQUELCH_LEVEL_NEW: 2. + USB(USB1_UTMIP_SPARE_CFG0) &= 0xFFFFFE67; //patched0 - FUSE_HS_IREF_CAP_CFG + USB(USB1_UTMIP_TX_CFG0) |= 0x80000; + + //USB(USB1_UTMIP_HSRX_CFG0) = (USB(USB1_UTMIP_HSRX_CFG0) & 0xFFF003FF) | 0x88000 | 0x4000; unpatched1 + USB(USB1_UTMIP_HSRX_CFG0) = (USB(USB1_UTMIP_HSRX_CFG0) & 0xF0F003FF) | 0x88000 | 0x4000; //patched1 - reset UTMIP_PCOUNT_UPDN_DIV: From 1 to 0. + USB(USB1_UTMIP_BIAS_CFG2) &= 0xFFFFFFF8; //patched1 - UTMIP_HSSQUELCH_LEVEL_NEW: 0 + + USB(USB1_UTMIP_HSRX_CFG1) = (USB(USB1_UTMIP_HSRX_CFG1) & 0xFFFFFFC1) | 0x12; + USB(USB1_UTMIP_MISC_CFG1) |= 0x40000000; + + // Enable crystal clock. + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) |= 0x40000000; + + // Enable USB2 tracking clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_SET) = BIT(CLK_Y_USB2_TRK); + CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) & 0xFFFFFF00) | 6; // Set trank divisor to 4. + + USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFFC03F07) | 0x78000 | 0x50; // Set delays. + USB(USB1_UTMIP_BIAS_CFG0) &= 0xFFFFFBFF; // Disable Power down bias circuit. + usleep(1); + + // Force PDTRK input into power up. + USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFFFFFFFE) | 2; + usleep(100); + + // TRK cycle done. Force PDTRK input into power down. + USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFF7FFFFF) | 1; + usleep(3); + + // Force PDTRK input into power up. + USB(USB1_UTMIP_BIAS_CFG1) = USB(USB1_UTMIP_BIAS_CFG1) & 0xFFFFFFFE; + usleep(100); + + // TRK cycle done. Force PDTRK input into power down. + USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFF7FFFFF) | 1; + + // Disable USB2 tracking clock and configure UTMIP misc. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_CLR) = BIT(CLK_Y_USB2_TRK); + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) = (CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) & 0xFEFFFFEA) | 0x2000000 | 0x28 | 2; + usleep(1); + + USB(USB1_UTMIP_BIAS_CFG0) &= 0xFF3FF7FF; + usleep(1); + + // Clear power downs on UTMIP ID and VBUS wake up, PD, PD2, PDZI, PDCHRP, PDDR. + PMC(APBDEV_PMC_USB_AO) &= 0xFFFFFFF3; // UTMIP ID and VBUS wake up. + usleep(1); + USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFFFBFFF; // UTMIP_FORCE_PD_POWERDOWN. + usleep(1); + USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFFEFFFF; // UTMIP_FORCE_PD2_POWERDOWN. + usleep(1); + USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFFBFFFF; // UTMIP_FORCE_PDZI_POWERDOWN. + usleep(1); + USB(USB1_UTMIP_XCVR_CFG1) &= 0xFFFFFFFB; // UTMIP_FORCE_PDCHRP_POWERDOWN. + usleep(1); + USB(USB1_UTMIP_XCVR_CFG1) &= 0xFFFFFFEF; // UTMIP_FORCE_PDDR_POWERDOWN. + usleep(1); +} + +int usb_device_init() +{ + if (usb_init_done) + return USB_RES_OK; + + // Initialize USB2 controller PHY. + _usb_init_phy(); + + // AHB USB performance cfg. + AHB_GIZMO(AHB_GIZMO_AHB_MEM) |= AHB_MEM_DONT_SPLIT_AHB_WR | AHB_MEM_ENB_FAST_REARBITRATE; + AHB_GIZMO(AHB_GIZMO_USB) |= AHB_GIZMO_IMMEDIATE; + AHB_GIZMO(AHB_ARBITRATION_PRIORITY_CTRL) = PRIORITY_CTRL_WEIGHT(7) | PRIORITY_SELECT_USB; + AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) = + MEM_PREFETCH_ENABLE | MEM_PREFETCH_USB_MST_ID | MEM_PREFETCH_ADDR_BNDRY(12) | 0x1000; // Addr boundary 64KB, Inactivity 4096 cycles. + + // Set software and hardware context storage and clear it. + usbdaemon = (usbd_t *)USBD_ADDR; // Depends on USB_TD_BUFFER_PAGE_SIZE aligned address. + usbd_otg = &usbd_usb_otg_controller_ctxt; + memset(usbd_otg, 0, sizeof(usbd_controller_t)); + memset(usbdaemon, 0, sizeof(usbd_t)); + + usbd_otg->regs = (t210_usb2d_t *)USB_OTG_BASE; + usbd_otg->usb_phy_ready = false; + + // Initialize USB PHY on the USB_OTG Controller (#1) in Device mode. + int res = _usbd_reset_usb_otg_phy_device_mode(); + usbd_otg->configuration_set = false; + + _usb_charger_detect(); + + if (!res) + usb_init_done = true; + + return res; +} + +static void _usb_device_power_down() +{ + // Enable PHY low power suspend. + usbd_otg->regs->hostpc1_devlc |= USB2D_HOSTPC1_DEVLC_PHCD; + // Do not use any controller regs after the above! + // A reset or clear of the PHCD suspend bit must happen. + + // Power down OTG and Bias circuits. + USB(USB1_UTMIP_BIAS_CFG0) |= BIT(11) | BIT(10); // UTMIP_OTGPD, UTMIP_BIASPD. + + // Power down ID detectors. + USB(USB1_UTMIP_BIAS_CFG0) |= BIT(23) | BIT(22); // UTMIP_IDPD_SEL, UTMIP_IDPD_VAL. + + if (usbd_otg->charger_detect) + { + USB(USB1_UTMIP_BAT_CHRG_CFG0) = 1; //UTMIP_PD_CHRG + usbd_otg->charger_detect = 0; + } + + // Power down the UTMIP transceivers. + // UTMIP_FORCE_PDZI_POWERDOWN, UTMIP_FORCE_PD2_POWERDOWN, UTMIP_FORCE_PD_POWERDOWN. + USB(USB1_UTMIP_XCVR_CFG0) |= BIT(18) | BIT(16) |BIT(14); + // UTMIP_FORCE_PDDR_POWERDOWN, UTMIP_FORCE_PDCHRP_POWERDOWN, UTMIP_FORCE_PDDISC_POWERDOWN. + USB(USB1_UTMIP_XCVR_CFG1) |= BIT(4) | BIT(2) | BIT(0); + + // Keep UTMIP in reset. + USB(USB1_IF_USB_SUSP_CTRL) |= SUSP_CTRL_UTMIP_RESET; + + // Power down PD trunk. + USB(USB1_UTMIP_BIAS_CFG1) |= BIT(0); //UTMIP_FORCE_PDTRK_POWERDOWN. + + // Force UTMIP_PLL power down. + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) |= BIT(14); // UTMIP_FORCE_PLL_ENABLE_POWERDOWN. + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) |= BIT(12); // UTMIP_FORCE_PLL_ACTIVE_POWERDOWN. + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) |= BIT(4) | BIT(0); // UTMIP_FORCE_PD_SAMP_A/C_POWERDOWN. + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) |= BIT(16); // UTMIP_FORCE_PLLU_POWERDOWN. + + // Disable crystal clock. + USB(USB1_UTMIP_MISC_CFG1) &= 0xBFFFFFFF; + + // Force enable UTMIPLL IDDQ. + CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) |= 3; + + // Set XUSB_PADCTL reset + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB_PADCTL); + + // Disable USBD clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = BIT(CLK_L_USBD); + + // Disable PLLU. + clock_disable_pllu(); + + usb_init_done = false; +} + +static void _usbd_stall_reset_ep1(usb_dir_t direction, usb_ep_cfg_t stall) +{ + stall &= 1; + if (direction == USB_DIR_IN) + { + usbd_otg->regs->endptctrl[1] = (usbd_otg->regs->endptctrl[1] & ~USB2D_ENDPTCTRL_TX_EP_STALL) | ((u32)stall << 16); + if (!stall) + usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_TX_EP_RESET; + } + else + { + usbd_otg->regs->endptctrl[1] = (usbd_otg->regs->endptctrl[1] & ~USB2D_ENDPTCTRL_RX_EP_STALL) | stall; + if (!stall) + usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_RX_EP_RESET; + } +} + +void usb_device_stall_ep1_bulk_out() +{ + _usbd_stall_reset_ep1(USB_DIR_OUT, USB_EP_CFG_STALL); +} + +void usb_device_stall_ep1_bulk_in() +{ + _usbd_stall_reset_ep1(USB_DIR_IN, USB_EP_CFG_STALL); +} + +static int _usbd_get_max_pkt_length(int endpoint) +{ + switch (endpoint) + { + case USB_EP_CTRL_OUT: + case USB_EP_CTRL_IN: + return 64; + case USB_EP_BULK_OUT: + case USB_EP_BULK_IN: + if (usbd_otg->port_speed == USB_HIGH_SPEED) + return 512; + else + return 64; + default: + return 64; + } +} + +static void _usbd_initialize_ep_ctrl(u32 endpoint) +{ + usb_hw_ep_t actual_ep = (endpoint & 2) >> 1; + usb_dir_t direction = endpoint & 1; + + memset((void *)&usbdaemon->qhs[endpoint], 0, sizeof(dQH_t)); + + if (!endpoint) + usbdaemon->qhs[endpoint].ep_capabilities = USB_QHD_EP_CAP_IOS_ENABLE; + + usbdaemon->qhs[endpoint].next_dTD_ptr = 1; // TERMINATE_SET + + u32 max_packet_len = _usbd_get_max_pkt_length(endpoint) & USB_QHD_EP_CAP_MAX_PKT_LEN_MASK; + usbdaemon->qhs[endpoint].ep_capabilities |= max_packet_len << 16; + + if (direction == USB_DIR_IN) + { + u32 endpoint_type = usbd_otg->regs->endptctrl[actual_ep] & ~USB2D_ENDPTCTRL_TX_EP_TYPE_MASK; + if (actual_ep) + endpoint_type |= usbd_otg->gadget ? USB2D_ENDPTCTRL_TX_EP_TYPE_INTR : USB2D_ENDPTCTRL_TX_EP_TYPE_BULK; + else + endpoint_type |= USB2D_ENDPTCTRL_TX_EP_TYPE_CTRL; + + usbd_otg->regs->endptctrl[actual_ep] = endpoint_type; + + usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_TX_EP_STALL; + + if (actual_ep == USB_HW_EP1) + usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_TX_EP_RESET; + + usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_TX_EP_ENABLE; + } + else // EP Bulk OUT. + { + u32 endpoint_type = usbd_otg->regs->endptctrl[actual_ep] & ~USB2D_ENDPTCTRL_RX_EP_TYPE_MASK; + if (actual_ep) + { + endpoint_type |= usbd_otg->gadget ? USB2D_ENDPTCTRL_RX_EP_TYPE_INTR : USB2D_ENDPTCTRL_RX_EP_TYPE_BULK; + } + else + endpoint_type |= USB2D_ENDPTCTRL_RX_EP_TYPE_CTRL; + + usbd_otg->regs->endptctrl[actual_ep] = endpoint_type; + usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_RX_EP_STALL; + + if (actual_ep == USB_HW_EP1) + usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_RX_EP_RESET; + + usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_RX_EP_ENABLE; + } +} + +static int _usbd_initialize_ep0() +{ + memset((void *)usbdaemon->qhs, 0, sizeof(dQH_t) * 4); // Clear all used EP queue heads. + memset((void *)usbdaemon->dtds, 0, sizeof(dTD_t) * 4); // Clear all used EP0 token heads. + + usbd_otg->regs->asynclistaddr = (u32)usbdaemon->qhs; + + _usbd_initialize_ep_ctrl(USB_EP_CTRL_OUT); + _usbd_initialize_ep_ctrl(USB_EP_CTRL_IN); + + // Disable Auto Low Power. + usbd_otg->regs->hostpc1_devlc &= ~USB2D_HOSTPC1_DEVLC_ASUS; + + // Initiate an attach event. + usbd_otg->regs->usbcmd |= USB2D_USBCMD_RUN; + + u32 retries = 100000; // 200ms timeout. + while (!(usbd_otg->regs->usbcmd & USB2D_USBCMD_RUN)) + { + retries--; + if (!retries) + return USB_ERROR_TIMEOUT; + usleep(1); + } + + return USB_RES_OK; +} + +// static void _disable_usb_wdt4() +// { +// if (TIMER_WDT4_STATUS & 1)// active +// { +// TIMER_TMR0_TMR_PTV &= 0x7FFFFFFF; // Disable timer +// TIMER_WDT4_UNLOCK_PATTERN = 0xC45A; // Alow writes to disable counter bit. +// TIMER_WDT4_COMMAND |= 2; // Disable counter +// TIMER_TMR0_TMR_PCR |= 0x40000000;// INTR_CLR +// } +// } + +int usbd_flush_endpoint(u32 endpoint) +{ + + usb_hw_ep_t actual_ep = (endpoint & 2) >> 1; + usb_dir_t direction = endpoint & 1; + u32 reg_mask = endpoint; + + // Flash all endpoints or 1. + if (endpoint != USB_EP_ALL) + { + if (direction == USB_DIR_IN) + reg_mask = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep; + else + reg_mask = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep; + } + usbd_otg->regs->endptflush = reg_mask; + + u32 retries = 100000; // 200ms timeout. + while (usbd_otg->regs->endptflush & reg_mask) + { + retries--; + if (!retries) + return USB_ERROR_TIMEOUT; + usleep(1); + } + + // Wait for the endpoint to finish all transactions (buffer not ready). + retries = 100000; // 200ms timeout. + while (usbd_otg->regs->endptstatus & reg_mask) + { + retries--; + if (!retries) + return USB_ERROR_TIMEOUT; + usleep(1); + } + + // Wait for the endpoint to clear the primed status. + retries = 100000; // 200ms timeout. + while (usbd_otg->regs->endptprime & reg_mask) + { + retries--; + if (!retries) + return USB_ERROR_TIMEOUT; + usleep(1); + } + + return USB_RES_OK; +} + +void usbd_end(bool reset_ep, bool only_controller) +{ + if (reset_ep) + { + usbd_flush_endpoint(USB_EP_ALL); + _usbd_stall_reset_ep1(USB_DIR_OUT, USB_EP_CFG_RESET); // EP1 Bulk OUT. + _usbd_stall_reset_ep1(USB_DIR_IN, USB_EP_CFG_RESET); // EP1 Bulk IN. + + usbd_otg->config_num = 0; + usbd_otg->interface_num = 0; + usbd_otg->configuration_set = false; + usbd_otg->max_lun_set = false; + } + + // Stop device controller. + usbd_otg->regs->usbcmd &= ~USB2D_USBCMD_RUN; + + // Enable PHY auto low power suspend. + usbd_otg->regs->hostpc1_devlc |= USB2D_HOSTPC1_DEVLC_ASUS; + + if (!only_controller) + _usb_device_power_down(); +} + +static void _usbd_mark_ep_complete(u32 endpoint) +{ + u32 complete_bit; + usb_hw_ep_t actual_ep = (endpoint & 2) >> 1; + usb_dir_t direction = endpoint & 1; + + usbd_flush_endpoint(endpoint); + memset((void *)&usbdaemon->dtds[endpoint * 4], 0, sizeof(dTD_t) * 4); + memset((void *)&usbdaemon->qhs[endpoint], 0, sizeof(dQH_t)); + usbdaemon->ep_configured[endpoint] = 0; + usbdaemon->ep_bytes_requested[endpoint] = 0; + + if (direction == USB_DIR_IN) + complete_bit = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep; + else + complete_bit = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep; + + usbd_otg->regs->endptcomplete |= complete_bit; +} + +static usb_ep_status_t _usbd_get_ep_status(usb_ep_t endpoint) +{ + bool status; + u32 reg_val; + u32 reg_mask; + u32 actual_ep = (endpoint & 2) >> 1; + usb_dir_t direction = endpoint & 1; + + if (direction == USB_DIR_IN) + reg_mask = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep; + else + reg_mask = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep; + + if (actual_ep == USB_HW_EP1) + reg_val = usbd_otg->regs->endptctrl[1]; + else + reg_val = usbd_otg->regs->endptctrl[0]; + + // Check stalled status. + if (direction == USB_DIR_IN) + status = reg_val & USB2D_ENDPTCTRL_TX_EP_STALL; + else + status = reg_val & USB2D_ENDPTCTRL_RX_EP_STALL; + + if (status) + return USB_EP_STATUS_STALLED; + + // Check enabled status. + if (direction == USB_DIR_IN) + status = reg_val & USB2D_ENDPTCTRL_TX_EP_ENABLE; + else + status = reg_val & USB2D_ENDPTCTRL_RX_EP_ENABLE; + + if (!status) + return USB_EP_STATUS_DISABLED; + + // CHeck qHD error status. + u32 token_error_mask = USB_QHD_TOKEN_HALTED | USB_QHD_TOKEN_BUFFER_ERROR | USB_QHD_TOKEN_XFER_ERROR; + if (usbdaemon->qhs[endpoint].token & token_error_mask) + return USB_EP_STATUS_ERROR; + + // Check if endpoint has a request or a ready buffer. + if ((usbd_otg->regs->endptprime & reg_mask) || (usbd_otg->regs->endptstatus & reg_mask)) + return USB_EP_STATUS_ACTIVE; // RX/TX active. + + // Return idle or not configured status. + if (!usbdaemon->ep_configured[endpoint]) + return USB_EP_STATUS_NO_CONFIG; + + return USB_EP_STATUS_IDLE; +} + +static int _usbd_ep_operation(usb_ep_t endpoint, u8 *buf, u32 len, u32 sync_timeout) +{ + if (!buf) + len = 0; + + u32 prime_bit; + usb_hw_ep_t actual_ep = (endpoint & 2) >> 1; + usb_dir_t direction = endpoint & 1; + u32 length_left = len; + u32 dtd_ep_idx = endpoint * 4; + + _usbd_mark_ep_complete(endpoint); + + if (endpoint == USB_EP_CTRL_OUT) + usbdaemon->qhs[endpoint].ep_capabilities = USB_QHD_EP_CAP_IOS_ENABLE; + + u32 max_packet_len = _usbd_get_max_pkt_length(endpoint) & USB_QHD_EP_CAP_MAX_PKT_LEN_MASK; + usbdaemon->qhs[endpoint].ep_capabilities |= (max_packet_len << 16) | USB_QHD_EP_CAP_ZERO_LEN_TERM_DIS; + usbdaemon->qhs[endpoint].next_dTD_ptr = 0; // Clear terminate bit. + //usbdaemon->qhs[endpoint].ep_capabilities |= USB_QHD_TOKEN_IRQ_ON_COMPLETE; + + usbdaemon->ep_configured[endpoint] = 1; + usbdaemon->ep_bytes_requested[endpoint] = len; + + // Configure dTD. + u32 dtd_idx = 0; + do + { + if (dtd_idx) + usbdaemon->dtds[dtd_ep_idx + dtd_idx - 1].next_dTD = (u32)&usbdaemon->dtds[dtd_ep_idx + dtd_idx]; + + u32 dtd_size = MIN(length_left, USB_TD_BUFFER_MAX_SIZE); // 16KB max per dTD. + usbdaemon->dtds[dtd_ep_idx + dtd_idx].info = (dtd_size << 16) | USB_QHD_TOKEN_ACTIVE; + // usbdaemon->dtds[dtd_ep_idx + dtd_idx].info |= USB_QHD_TOKEN_IRQ_ON_COMPLETE; + + // Set buffers addresses to all page pointers. + u32 dt_buffer_offset = dtd_idx * USB_TD_BUFFER_MAX_SIZE; + for (u32 i = 0; i < 4; i++) + usbdaemon->dtds[dtd_ep_idx + dtd_idx].pages[i] = + (u32)&buf[dt_buffer_offset + (USB_TD_BUFFER_PAGE_SIZE * i)]; + + //usbdaemon->dtds[dtd_ep_idx + dtd_idx].pages[5] = + // (u32)&buf[dt_buffer_offset + (USB_TD_BUFFER_PAGE_SIZE * 4)]; // Last buffer. Unused. + + length_left -= dtd_size; + if (length_left) + dtd_idx++; + } + while (length_left); + + // Last dTD, terminate it. + usbdaemon->dtds[dtd_ep_idx + dtd_idx].next_dTD = 1; + + // Set first dTD address to queue head next dTD. + usbdaemon->qhs[endpoint].next_dTD_ptr |= (u32)&usbdaemon->dtds[dtd_ep_idx] & 0xFFFFFFE0; + + // Flush AHB prefetcher. + AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) &= ~MEM_PREFETCH_ENABLE; + AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) |= MEM_PREFETCH_ENABLE; + + if (direction == USB_DIR_IN) + { + prime_bit = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep; + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + } + else + prime_bit = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep; + + // Prime endpoint. + usbd_otg->regs->endptprime |= prime_bit; // USB2_CONTROLLER_USB2D_ENDPTPRIME. + + int res = USB_RES_OK; + usb_ep_status_t ep_status; + if (sync_timeout) + { + ep_status = _usbd_get_ep_status(endpoint); + if (ep_status == USB_EP_STATUS_ACTIVE) + { + u32 retries = sync_timeout; + while (retries) + { + ep_status = _usbd_get_ep_status(endpoint); + if (ep_status != USB_EP_STATUS_ACTIVE) + { + if (ep_status == USB_EP_STATUS_DISABLED) + res = USB2_ERROR_XFER_EP_DISABLED; + goto out; + } + retries--; + usleep(1); + } + res = USB_ERROR_TIMEOUT; + } + else if (ep_status == USB_EP_STATUS_DISABLED) + res = USB2_ERROR_XFER_EP_DISABLED; +out: + if (res) + _usbd_mark_ep_complete(endpoint); + else if (_usbd_get_ep_status(endpoint) != USB_EP_STATUS_IDLE) + res = USB_ERROR_XFER_ERROR; + + if (direction == USB_DIR_OUT) + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + } + + return res; +} + +static int _usbd_ep_ack(usb_ep_t ep) +{ + return _usbd_ep_operation(ep, NULL, 0, USB_XFER_SYNCED_ENUM); +} + +static void _usbd_set_ep0_stall() +{ + // EP Control endpoints must be always stalled together. + usbd_otg->regs->endptctrl[0] = + USB2D_ENDPTCTRL_TX_EP_ENABLE | USB2D_ENDPTCTRL_TX_EP_STALL | + USB2D_ENDPTCTRL_RX_EP_ENABLE | USB2D_ENDPTCTRL_RX_EP_STALL; +} + +int usbd_set_ep_stall(u32 endpoint, int ep_stall) +{ + usb_hw_ep_t actual_ep = (endpoint & 2) >> 1; + usb_dir_t direction = endpoint & 1; + + if (ep_stall) + { + if (direction == USB_DIR_IN) + usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_TX_EP_STALL; // Stall EP Bulk IN. + else + usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_RX_EP_STALL; // Stall EP Bulk OUT. + } + else + { + if (direction == USB_DIR_IN) + usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_TX_EP_STALL; // Clear stall EP Bulk IN. + else + usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_RX_EP_STALL; // Clear stall EP Bulk OUT. + } + + return USB_RES_OK; +} + +static void _usbd_handle_get_class_request(bool *transmit_data, u8 *descriptor, int *size, bool *ep_stall) +{ + u8 _bRequest = usbd_otg->control_setup.bRequest; + u16 _wIndex = usbd_otg->control_setup.wIndex; + u16 _wValue = usbd_otg->control_setup.wValue; + u16 _wLength = usbd_otg->control_setup.wLength; + + bool valid_interface = _wIndex == usbd_otg->interface_num; + bool valid_len = (_bRequest == USB_REQUEST_BULK_GET_MAX_LUN) ? 1 : 0; + + if (!valid_interface || _wValue != 0 || _wLength != valid_len) + { + *ep_stall = true; + return; + } + + switch (_bRequest) + { + case USB_REQUEST_BULK_RESET: + _usbd_ep_ack(USB_EP_CTRL_IN); + usbd_otg->bulk_reset_req = true; + break; // DELAYED_STATUS; + case USB_REQUEST_BULK_GET_MAX_LUN: + *transmit_data = true; + *size = 1; + descriptor[0] = usbd_otg->max_lun; // Set 0 LUN for 1 drive supported. + usbd_otg->max_lun_set = true; + break; + default: + *ep_stall = true; + break; + } +} + +static void _usbd_handle_get_descriptor(bool *transmit_data, void **descriptor, int *size, bool *ep_stall) +{ + u8 descriptor_type = usbd_otg->control_setup.wValue >> 8; + u8 descriptor_subtype = usbd_otg->control_setup.wValue & 0xFF; + + switch (descriptor_type) + { + case USB_DESCRIPTOR_DEVICE: + { +/* + u32 soc_rev = APB_MISC(APB_MISC_GP_HIDREV); + usb_device_descriptor.idProduct = (soc_rev >> 8) & 0xFF; // chip_id. + usb_device_descriptor.idProduct |= ((soc_rev << 4) | (FUSE(FUSE_SKU_INFO) & 0xF)) << 8; // HIDFAM. + usb_device_descriptor.bcdDevice = (soc_rev >> 16) & 0xF; // MINORREV. + usb_device_descriptor.bcdDevice |= ((soc_rev >> 4) & 0xF) << 8; // MAJORREV. +*/ + *descriptor = usbd_otg->desc->dev; + *size = usbd_otg->desc->dev->bLength; + *transmit_data = true; + return; + } + case USB_DESCRIPTOR_CONFIGURATION: + if (usbd_otg->gadget == USB_GADGET_UMS) + { + if (usbd_otg->port_speed == USB_HIGH_SPEED) // High speed. 512 bytes. + { + usbd_otg->desc->cfg->endpoint[0].wMaxPacketSize = 0x200; + usbd_otg->desc->cfg->endpoint[1].wMaxPacketSize = 0x200; + } + else // Full speed. 64 bytes. + { + usbd_otg->desc->cfg->endpoint[0].wMaxPacketSize = 0x40; + usbd_otg->desc->cfg->endpoint[1].wMaxPacketSize = 0x40; + } + } + else + { + usb_cfg_hid_descr_t *tmp = (usb_cfg_hid_descr_t *)usbd_otg->desc->cfg; + if (usbd_otg->port_speed == USB_HIGH_SPEED) // High speed. 512 bytes. + { + tmp->endpoint[0].wMaxPacketSize = 0x200; + tmp->endpoint[1].wMaxPacketSize = 0x200; + tmp->endpoint[0].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms. + tmp->endpoint[1].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms. + } + else // Full speed. 64 bytes. + { + tmp->endpoint[0].wMaxPacketSize = 0x40; + tmp->endpoint[1].wMaxPacketSize = 0x40; + tmp->endpoint[0].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms. + tmp->endpoint[1].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms. + } + } + *descriptor = usbd_otg->desc->cfg; + *size = usbd_otg->desc->cfg->config.wTotalLength; + *transmit_data = true; + return; + case USB_DESCRIPTOR_STRING: + switch (descriptor_subtype) + { + case 1: + *descriptor = usbd_otg->desc->vendor; + *size = usbd_otg->desc->vendor[0]; + break; + case 2: + *descriptor = usbd_otg->desc->product; + *size = usbd_otg->desc->product[0]; + break; + case 3: + *descriptor = usbd_otg->desc->serial; + *size = usbd_otg->desc->serial[0]; + break; + case 0xEE: + *descriptor = usbd_otg->desc->ms_os; + *size = usbd_otg->desc->ms_os->bLength; + break; + default: + *descriptor = usbd_otg->desc->lang_id; + *size = 4; + break; + } + *transmit_data = true; + return; + case USB_DESCRIPTOR_DEVICE_QUALIFIER: + if (!usbd_otg->desc->dev_qual) + goto exit; + usbd_otg->desc->dev_qual->bNumOtherConfigs = 1; + *descriptor = usbd_otg->desc->dev_qual; + *size = usbd_otg->desc->dev_qual->bLength; + *transmit_data = true; + return; + case USB_DESCRIPTOR_OTHER_SPEED_CONFIGURATION: + if (!usbd_otg->desc->cfg_other) + goto exit; + if (usbd_otg->port_speed == USB_HIGH_SPEED) + { + usbd_otg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x40; + usbd_otg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x40; + } + else + { + usbd_otg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x200; + usbd_otg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x200; + } + if ((usbd_otg->charger_detect & 1) && (usbd_otg->charger_detect & 2)) + usbd_otg->desc->cfg_other->config.bMaxPower = 500 / 2; + *descriptor = usbd_otg->desc->cfg_other; + *size = usbd_otg->desc->cfg_other->config.wTotalLength; + *transmit_data = true; + return; + case USB_DESCRIPTOR_DEVICE_BINARY_OBJECT: + *descriptor = usbd_otg->desc->dev_bot; + *size = usbd_otg->desc->dev_bot->wTotalLength; + *transmit_data = true; + return; + default: + *transmit_data = false; + *ep_stall = true; + return; + } +exit: + *transmit_data = false; + *ep_stall = true; + return; +} + +static int _usbd_handle_set_request(bool *ep_stall) +{ + int res = USB_RES_OK; + u8 bRequest = usbd_otg->control_setup.bRequest; + if (bRequest == USB_REQUEST_SET_ADDRESS) + { + res = _usbd_ep_ack(USB_EP_CTRL_IN); + + // Set USB address for device mode. + if (!res) + usbd_otg->regs->periodiclistbase = (usbd_otg->regs->periodiclistbase & 0x1FFFFFF) | ((usbd_otg->control_setup.wValue & 0xFF) << 25); + } + else if (bRequest == USB_REQUEST_SET_CONFIGURATION) + { + res = _usbd_ep_ack(USB_EP_CTRL_IN); + if (!res) + { + usbd_otg->config_num = usbd_otg->control_setup.wValue; + _usbd_initialize_ep_ctrl(USB_EP_BULK_OUT); + _usbd_initialize_ep_ctrl(USB_EP_BULK_IN); + usbd_otg->configuration_set = true; + } + } + else + *ep_stall = true; + + return res; +} + +static int _usbd_handle_ep0_control_transfer() +{ + int res = USB_RES_OK; + bool ep_stall = false; + bool transmit_data = false; + + u8 *descriptor = (u8 *)USB_DESCRIPTOR_ADDR; + int size = 0; + + u8 _bmRequestType = usbd_otg->control_setup.bmRequestType; + u8 _bRequest = usbd_otg->control_setup.bRequest; + u16 _wValue = usbd_otg->control_setup.wValue; + u16 _wIndex = usbd_otg->control_setup.wIndex; + u16 _wLength = usbd_otg->control_setup.wLength; + + //gfx_printf("%02X %02X %04X %04X %04X\n", _bmRequestType, _bRequest, _wValue, _wIndex, _wLength); + + switch (_bmRequestType) + { + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE): + res = _usbd_handle_set_request(&ep_stall); + break; + + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE): + res = _usbd_ep_ack(USB_EP_CTRL_IN); + if (!res) + usbd_otg->interface_num = _wValue; + break; + + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT): + switch (_bRequest) + { + case USB_REQUEST_CLEAR_FEATURE: + case USB_REQUEST_SET_FEATURE: + if ((_wValue & 0xFF) == USB_FEATURE_ENDPOINT_HALT) + { + int direction; + switch (_wIndex) // endpoint + { + case USB_EP_ADDR_CTRL_OUT: + direction = 2; + break; + case USB_EP_ADDR_CTRL_IN: + direction = 3; + break; + case USB_EP_ADDR_BULK_OUT: + direction = 0; + break; + case USB_EP_ADDR_BULK_IN: + direction = 1; + break; + default: + _usbd_stall_reset_ep1(3, USB_EP_CFG_STALL); + goto out; + } + + if (_bRequest == USB_REQUEST_CLEAR_FEATURE) + _usbd_stall_reset_ep1(direction, USB_EP_CFG_RESET); + else + _usbd_stall_reset_ep1(direction, USB_EP_CFG_STALL); + + res = _usbd_ep_ack(USB_EP_CTRL_IN); + } + else + _usbd_stall_reset_ep1(3, USB_EP_CFG_STALL); + + break; + default: + ep_stall = true; + break; + } + break; + + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE): + memset(descriptor, 0, _wLength); + _usbd_handle_get_class_request(&transmit_data, descriptor, &size, &ep_stall); + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE): + switch (_bRequest) + { + case USB_REQUEST_GET_STATUS: + descriptor[0] = USB_STATUS_DEV_SELF_POWERED; + descriptor[1] = 0; // No support for remove wake up. + transmit_data = true; + size = 2; + break; + case USB_REQUEST_GET_DESCRIPTOR: + _usbd_handle_get_descriptor(&transmit_data, (void **)&descriptor, &size, &ep_stall); + break; + case USB_REQUEST_GET_CONFIGURATION: + descriptor = (u8 *)&usbd_otg->config_num; + size = _wLength; + transmit_data = true; + break; + default: + ep_stall = true; + break; + } + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE): + if (_bRequest == USB_REQUEST_GET_INTERFACE) + { + memset(descriptor, 0, _wLength); + descriptor[0] = usbd_otg->interface_num; + size = _wLength; + } + else if (_bRequest == USB_REQUEST_GET_STATUS) + { + memset(descriptor, 0, _wLength); + size = _wLength; + } + else if (_bRequest == USB_REQUEST_GET_DESCRIPTOR && (_wValue >> 8) == USB_DESCRIPTOR_HID_REPORT && usbd_otg->gadget > USB_GADGET_UMS) + { + if (usbd_otg->gadget == USB_GADGET_HID_GAMEPAD) + { + descriptor = (u8 *)&hid_report_descriptor_jc; + size = hid_report_descriptor_jc_size; + } + else // USB_GADGET_HID_TOUCHPAD + { + descriptor = (u8 *)&hid_report_descriptor_touch; + size = hid_report_descriptor_touch_size; + } + + usbd_otg->hid_report_sent = true; + } + else + { + ep_stall = true; + break; + } + + if (_wLength < size) + size = _wLength; + transmit_data = true; + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT): + if (_bRequest == USB_REQUEST_GET_STATUS) + { + int ep_req; + switch (_wIndex) + { + case USB_EP_ADDR_CTRL_OUT: + ep_req = USB_EP_CTRL_OUT; + break; + case USB_EP_ADDR_BULK_OUT: + ep_req = USB_EP_BULK_OUT; + break; + case USB_EP_ADDR_CTRL_IN: + ep_req = USB_EP_CTRL_IN; + break; + case USB_EP_ADDR_BULK_IN: + ep_req = USB_EP_BULK_IN; + break; + default: + _usbd_stall_reset_ep1(3, USB_EP_CFG_STALL); + goto out; + } + + size = _wLength; + memset(descriptor, 0, size); + + if (_usbd_get_ep_status(ep_req) == USB_EP_STATUS_STALLED) + descriptor[0] = USB_STATUS_EP_HALTED; + else + descriptor[0] = USB_STATUS_EP_OK; + + transmit_data = true; + } + else + _usbd_stall_reset_ep1(3, USB_EP_CFG_STALL); + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE): + memset(descriptor, 0, _wLength); + _usbd_handle_get_class_request(&transmit_data, descriptor, &size, &ep_stall); + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_INTERFACE): + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_DEVICE): + if (_bRequest == USB_REQUEST_GET_MS_DESCRIPTOR) + { + switch (_wIndex) + { + case USB_DESCRIPTOR_MS_COMPAT_ID: + descriptor = (u8 *)usbd_otg->desc->ms_cid; + size = usbd_otg->desc->ms_cid->dLength; + transmit_data = true; + break; + case USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES: + descriptor = (u8 *)usbd_otg->desc->mx_ext; + size = usbd_otg->desc->mx_ext->dLength; + transmit_data = true; + break; + default: + ep_stall = true; + break; + } + } + else + ep_stall = true; + break; + + default: + ep_stall = true; + break; + } + + // Transmit data to HOST if any. + if (transmit_data) + { + memcpy(usb_ep0_ctrl_buf, descriptor, size); + + if (_wLength < size) + size = _wLength; + res = _usbd_ep_operation(USB_EP_CTRL_IN, usb_ep0_ctrl_buf, size, USB_XFER_SYNCED_ENUM); + if (!res) + res = _usbd_ep_ack(USB_EP_CTRL_OUT); + } + +out: + if (ep_stall) + _usbd_set_ep0_stall(); + + return res; +} + +static int _usbd_ep0_initialize() +{ + bool enter = false; + if (usbd_otg->configuration_set) + enter = true; + else + { + usbdaemon->qhs = (volatile dQH_t *)USB2_QH_USB2D_QH_EP_BASE; + + if (!_usbd_initialize_ep0()) + enter = true; + } + + if (enter) + { + usbd_otg->configuration_set = false; + usbd_otg->max_lun_set = false; + + // Timeout if cable or communication isn't started in 1.5 minutes. + u32 timer = get_tmr_ms() + 90000; + while (true) + { + u32 usb_status_irqs = usbd_otg->regs->usbsts; + + // Clear all interrupt statuses. + usbd_otg->regs->usbsts = usb_status_irqs; + + // Check if a reset was received. + if (usb_status_irqs & USB2D_USBSTS_URI) + { + //_disable_usb_wdt4(); + + // Clear all device addresses, enabled setup requests, transmit events and flush all endpoints. + usbd_otg->regs->periodiclistbase = 0; + usbd_otg->regs->endptsetupstat = usbd_otg->regs->endptsetupstat; + usbd_otg->regs->endptcomplete = usbd_otg->regs->endptcomplete; + usbd_flush_endpoint(USB_EP_ALL); + } + + // Check if port change happened. + if (usb_status_irqs & USB2D_USBSTS_PCI) + usbd_otg->port_speed = (usbd_otg->regs->hostpc1_devlc & USB2D_HOSTPC1_DEVLC_PSPD_MASK) >> 25; + + // Acknowledge setup request for EP0 and copy its configuration. + u32 ep0_setup_req = usbd_otg->regs->endptsetupstat; + if (ep0_setup_req & 1) + { + usbd_otg->regs->endptsetupstat = ep0_setup_req; + memcpy(&usbd_otg->control_setup, (void *)usbdaemon->qhs->setup, 8); + if (_usbd_handle_ep0_control_transfer()) + break; + } + if (usbd_otg->configuration_set) + return USB_RES_OK; + + if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + return USB_ERROR_USER_ABORT; + } + } + + return USB_ERROR_TIMEOUT; +} + +int usb_device_enumerate(usb_gadget_type gadget) +{ + switch (gadget) + { + case USB_GADGET_UMS: + usbd_otg->desc = &usb_gadget_ums_descriptors; + break; + case USB_GADGET_HID_GAMEPAD: + usbd_otg->desc = &usb_gadget_hid_jc_descriptors; + break; + case USB_GADGET_HID_TOUCHPAD: + usbd_otg->desc = &usb_gadget_hid_touch_descriptors; + break; + } + + usbd_otg->gadget = gadget; + + return _usbd_ep0_initialize(); +} + +int usbd_handle_ep0_ctrl_setup() +{ + // Acknowledge setup request for EP0 and copy its configuration. + u32 ep0_setup_req = usbd_otg->regs->endptsetupstat; + if (ep0_setup_req & 1) + { + usbd_otg->regs->endptsetupstat = ep0_setup_req; + memcpy(&usbd_otg->control_setup, (void *)usbdaemon->qhs->setup, 8); + _usbd_handle_ep0_control_transfer(); + memset(usb_ep0_ctrl_buf, 0, USB_TD_BUFFER_PAGE_SIZE); + } + + // Only return error if bulk reset was requested. + if (usbd_otg->bulk_reset_req) + { + usbd_otg->bulk_reset_req = false; + return USB_RES_BULK_RESET; + } + + return USB_RES_OK; +} + +static usb_ep_status_t _usbd_get_ep1_status(usb_dir_t dir) +{ + usb_ep_t ep; + if (dir == USB_DIR_OUT) + ep = USB_EP_BULK_OUT; + else + ep = USB_EP_BULK_IN; + return _usbd_get_ep_status(ep); +} + +int usb_device_ep1_out_read(u8 *buf, u32 len, u32 *bytes_read, u32 sync_timeout) +{ + if ((u32)buf % USB_EP_BUFFER_ALIGN) + return USB2_ERROR_XFER_NOT_ALIGNED; + + if (len > USB_EP_BUFFER_MAX_SIZE) + len = USB_EP_BUFFER_MAX_SIZE; + + int res = _usbd_ep_operation(USB_EP_BULK_OUT, buf, len, sync_timeout); + + if (sync_timeout && bytes_read) + *bytes_read = res ? 0 : len; + + return res; +} + +int usb_device_ep1_out_read_big(u8 *buf, u32 len, u32 *bytes_read) +{ + if ((u32)buf % USB_EP_BUFFER_ALIGN) + return USB2_ERROR_XFER_NOT_ALIGNED; + + if (len > USB_EP_BULK_OUT_MAX_XFER) + len = USB_EP_BULK_OUT_MAX_XFER; + + int res; + u32 bytes = 0; + *bytes_read = 0; + u8 *buf_curr = buf; + + while (len) + { + u32 len_ep = MIN(len, USB_EP_BUFFER_MAX_SIZE); + + res = usb_device_ep1_out_read(buf_curr, len_ep, &bytes, USB_XFER_SYNCED_DATA); + if (res) + return res; + + len -= len_ep; + buf_curr += len_ep; + *bytes_read = *bytes_read + bytes; + } + + return USB_RES_OK; +} + +static int _usbd_get_ep1_out_bytes_read() +{ + if (_usbd_get_ep_status(USB_EP_BULK_OUT) != USB_EP_STATUS_IDLE) + return 0; + else + return (usbdaemon->ep_bytes_requested[USB_EP_BULK_OUT] - (usbdaemon->qhs[USB_EP_BULK_OUT].token >> 16)); +} + +int usb_device_ep1_out_reading_finish(u32 *pending_bytes) +{ + usb_ep_status_t ep_status; + do + { + ep_status = _usbd_get_ep1_status(USB_DIR_OUT); + if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED)) + break; + + usbd_handle_ep0_ctrl_setup(); + } + while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED)); + + *pending_bytes = _usbd_get_ep1_out_bytes_read(); + + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + + if (ep_status == USB_EP_STATUS_IDLE) + return USB_RES_OK; + else if (ep_status == USB_EP_STATUS_DISABLED) + return USB2_ERROR_XFER_EP_DISABLED; + else + return USB_ERROR_XFER_ERROR; +} + +int usb_device_ep1_in_write(u8 *buf, u32 len, u32 *bytes_written, u32 sync_timeout) +{ + if ((u32)buf % USB_EP_BUFFER_ALIGN) + return USB2_ERROR_XFER_NOT_ALIGNED; + + if (len > USB_EP_BUFFER_MAX_SIZE) + len = USB_EP_BUFFER_MAX_SIZE; + + int res = _usbd_ep_operation(USB_EP_BULK_IN, buf, len, sync_timeout); + + if (sync_timeout && bytes_written) + *bytes_written = res ? 0 : len; + + return res; +} + +static int _usbd_get_ep1_in_bytes_written() +{ + if (_usbd_get_ep_status(USB_EP_BULK_IN) != USB_EP_STATUS_IDLE) + return 0; + else + return (usbdaemon->ep_bytes_requested[USB_EP_BULK_IN] - (usbdaemon->qhs[USB_EP_BULK_IN].token >> 16)); +} + +int usb_device_ep1_in_writing_finish(u32 *pending_bytes) +{ + usb_ep_status_t ep_status; + do + { + ep_status = _usbd_get_ep1_status(USB_DIR_IN); + if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED)) + break; + + usbd_handle_ep0_ctrl_setup(); + } + while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED)); + + *pending_bytes = _usbd_get_ep1_in_bytes_written(); + + if (ep_status == USB_EP_STATUS_IDLE) + return USB_RES_OK; + else if (ep_status == USB_EP_STATUS_DISABLED) + return USB2_ERROR_XFER_EP_DISABLED; + + usb_device_stall_ep1_bulk_out(); + return USB_ERROR_XFER_ERROR; +} + +bool usb_device_get_suspended() +{ + bool suspended = (usbd_otg->regs->portsc1 & USB2D_PORTSC1_SUSP) == USB2D_PORTSC1_SUSP; + return suspended; +} + +bool usb_device_get_port_in_sleep() +{ + // Windows heuristic: Forces port into suspend, sleep and J-State. + return (usbd_otg->regs->portsc1) == 0x885; +} + +int usb_device_class_send_max_lun(u8 max_lun) +{ + // Timeout if get MAX_LUN request doesn't happen in 10s. + u32 timer = get_tmr_ms() + 10000; + + usbd_otg->max_lun = max_lun; + + while (!usbd_otg->max_lun_set) + { + usbd_handle_ep0_ctrl_setup(); + if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + return USB_ERROR_USER_ABORT; + } + + return USB_RES_OK; +} + +int usb_device_class_send_hid_report() +{ + // Timeout if get GET_HID_REPORT request doesn't happen in 10s. + u32 timer = get_tmr_ms() + 10000; + + // Wait for request and transfer start. + while (!usbd_otg->hid_report_sent) + { + usbd_handle_ep0_ctrl_setup(); + if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + return USB_ERROR_USER_ABORT; + } + + return USB_RES_OK; +} + +void usb_device_get_ops(usb_ops_t *ops) +{ + ops->usbd_flush_endpoint = usbd_flush_endpoint; + ops->usbd_set_ep_stall = usbd_set_ep_stall; + ops->usbd_handle_ep0_ctrl_setup = usbd_handle_ep0_ctrl_setup; + ops->usbd_end = usbd_end; + ops->usb_device_init = usb_device_init; + ops->usb_device_enumerate = usb_device_enumerate; + ops->usb_device_class_send_max_lun = usb_device_class_send_max_lun; + ops->usb_device_class_send_hid_report = usb_device_class_send_hid_report; + ops->usb_device_get_suspended = usb_device_get_suspended; + ops->usb_device_get_port_in_sleep = usb_device_get_port_in_sleep; + + ops->usb_device_ep1_out_read = usb_device_ep1_out_read; + ops->usb_device_ep1_out_read_big = usb_device_ep1_out_read_big; + ops->usb_device_ep1_out_reading_finish = usb_device_ep1_out_reading_finish; + ops->usb_device_ep1_in_write = usb_device_ep1_in_write; + ops->usb_device_ep1_in_writing_finish = usb_device_ep1_in_writing_finish; +} + diff --git a/bdk/usb/usbd.h b/bdk/usb/usbd.h new file mode 100644 index 00000000..a0e4a63a --- /dev/null +++ b/bdk/usb/usbd.h @@ -0,0 +1,203 @@ +/* + * Enhanced & eXtensible USB Device (EDCI & XDCI) driver for Tegra X1 + * + * Copyright (c) 2019 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _USB_H_ +#define _USB_H_ + +#include + +#define USB_TD_BUFFER_PAGE_SIZE 0x1000 +#define USB_TD_BUFFER_MAX_SIZE (USB_TD_BUFFER_PAGE_SIZE * 4) +//#define USB_HW_BUFFER_5_PAGES 0x5000 +#define USB_EP_BUFFER_1_TD (USB_TD_BUFFER_MAX_SIZE) +#define USB_EP_BUFFER_2_TD (USB_TD_BUFFER_MAX_SIZE * 2) +#define USB_EP_BUFFER_4_TD (USB_TD_BUFFER_MAX_SIZE * 4) +#define USB_EP_BUFFER_MAX_SIZE (USB_EP_BUFFER_4_TD) +#define USB_EP_BUFFER_ALIGN (USB_TD_BUFFER_PAGE_SIZE) + +#define USB_XFER_START 0 +#define USB_XFER_SYNCED_ENUM 1000000 +#define USB_XFER_SYNCED_CMD 1000000 +#define USB_XFER_SYNCED_DATA 2000000 +#define USB_XFER_SYNCED_CLASS 5000000 +#define USB_XFER_SYNCED -1 + +typedef enum _usb_hid_type +{ + USB_HID_GAMEPAD, + USB_HID_TOUCHPAD +} usb_hid_type; + +typedef enum _usb_gadget_type +{ + USB_GADGET_UMS = 0, + USB_GADGET_HID_GAMEPAD = 1, + USB_GADGET_HID_TOUCHPAD = 2, +} usb_gadget_type; + +typedef enum { + USB_DIR_OUT = 0, + USB_DIR_IN = 1, +} usb_dir_t; + +typedef enum +{ + XUSB_EP_CTRL_IN = 0, // EP0. + XUSB_EP_CTRL_OUT = 1, // EP0. + + USB_EP_CTRL_OUT = 0, // EP0. + USB_EP_CTRL_IN = 1, // EP0. + + USB_EP_BULK_OUT = 2, // EP1. + USB_EP_BULK_IN = 3, // EP1. + USB_EP_ALL = 0xFFFFFFFF +} usb_ep_t; + +typedef enum +{ + USB_EP_ADDR_CTRL_OUT = 0x00, + USB_EP_ADDR_CTRL_IN = 0x80, + USB_EP_ADDR_BULK_OUT = 0x01, + USB_EP_ADDR_BULK_IN = 0x81, +} usb_ep_addr_t; + +typedef enum +{ + USB_EP_CFG_CLEAR = 0, + USB_EP_CFG_RESET = 0, + USB_EP_CFG_STALL = 1 +} usb_ep_cfg_t; + +typedef enum { + USB_STATUS_EP_OK = 0, + USB_STATUS_EP_HALTED = 1, + + USB_STATUS_DEV_SELF_POWERED = 1, + USB_STATUS_DEV_REMOTE_WAKE = 2, +} usb_set_clear_feature_req_t; + +typedef enum { + USB_SETUP_RECIPIENT_DEVICE = 0, + USB_SETUP_RECIPIENT_INTERFACE = 1, + USB_SETUP_RECIPIENT_ENDPOINT = 2, + USB_SETUP_RECIPIENT_OTHER = 3, + + USB_SETUP_TYPE_STANDARD = 0x00, + USB_SETUP_TYPE_CLASS = 0x20, + USB_SETUP_TYPE_VENDOR = 0x40, + USB_SETUP_TYPE_RESERVED = 0x60, + + USB_SETUP_HOST_TO_DEVICE = 0x00, + USB_SETUP_DEVICE_TO_HOST = 0x80, +} usb_setup_req_type_t; + +typedef enum { + USB_REQUEST_GET_STATUS = 0, + USB_REQUEST_CLEAR_FEATURE = 1, + USB_REQUEST_SET_FEATURE = 3, + USB_REQUEST_SET_ADDRESS = 5, + USB_REQUEST_GET_DESCRIPTOR = 6, + USB_REQUEST_SET_DESCRIPTOR = 7, + USB_REQUEST_GET_CONFIGURATION = 8, + USB_REQUEST_SET_CONFIGURATION = 9, + USB_REQUEST_GET_INTERFACE = 10, + USB_REQUEST_SET_INTERFACE = 11, + USB_REQUEST_SYNCH_FRAME = 12, + USB_REQUEST_SET_SEL = 13, + + USB_REQUEST_GET_MS_DESCRIPTOR = 0x99, + + USB_REQUEST_BULK_GET_MAX_LUN = 0xFE, + USB_REQUEST_BULK_RESET = 0xFF +} usb_standard_req_t; + +typedef enum { + USB_FEATURE_ENDPOINT_HALT = 0, + USB_FEATURE_DEVICE_REMOTE_WAKEUP = 1, + USB_FEATURE_TEST_MODE = 2, +} usb_get_status_req_t; + +typedef enum _usb_error_t +{ + USB_RES_OK = 0, + USB_RES_BULK_RESET = 1, + + USB_ERROR_USER_ABORT = 2, + USB_ERROR_TIMEOUT = 3, + USB_ERROR_INIT = 4, + USB_ERROR_XFER_ERROR = 5, + + USB2_ERROR_XFER_EP_DISABLED = 28, + USB2_ERROR_XFER_NOT_ALIGNED = 29, + + XUSB_ERROR_INVALID_EP = USB_ERROR_XFER_ERROR, // From 2. + XUSB_ERROR_XFER_BULK_IN_RESIDUE = 7, + XUSB_ERROR_INVALID_CYCLE = USB2_ERROR_XFER_EP_DISABLED, // From 8. + XUSB_ERROR_SEQ_NUM = 51, + XUSB_ERROR_XFER_DIR = 52, + XUSB_ERROR_PORT_CFG = 54 +} usb_error_t; + +typedef struct _usb_ctrl_setup_t +{ + u8 bmRequestType; + u8 bRequest; + u16 wValue; + u16 wIndex; + u16 wLength; +} usb_ctrl_setup_t; + +typedef struct _usb_ops_t +{ + int (*usbd_flush_endpoint)(u32); + int (*usbd_set_ep_stall)(u32, int); + int (*usbd_handle_ep0_ctrl_setup)(); + void (*usbd_end)(bool, bool); + int (*usb_device_init)(); + int (*usb_device_enumerate)(usb_gadget_type gadget); + int (*usb_device_class_send_max_lun)(u8); + int (*usb_device_class_send_hid_report)(); + + int (*usb_device_ep1_out_read)(u8 *, u32, u32 *, u32); + int (*usb_device_ep1_out_read_big)(u8 *, u32, u32 *); + int (*usb_device_ep1_out_reading_finish)(u32 *); + int (*usb_device_ep1_in_write)(u8 *, u32, u32 *, u32); + int (*usb_device_ep1_in_writing_finish)(u32 *); + bool (*usb_device_get_suspended)(); + bool (*usb_device_get_port_in_sleep)(); +} usb_ops_t; + +typedef struct _usb_ctxt_t +{ + u32 type; + u32 partition; + u32 offset; + u32 sectors; + u32 ro; + void (*system_maintenance)(bool); + void *label; + void (*set_text)(void *, const char *); +} usb_ctxt_t; + +void usb_device_get_ops(usb_ops_t *ops); +void xusb_device_get_ops(usb_ops_t *ops); + +int usb_device_gadget_ums(usb_ctxt_t *usbs); +int usb_device_gadget_hid(usb_ctxt_t *usbs); + +#endif \ No newline at end of file diff --git a/bdk/usb/xusbd.c b/bdk/usb/xusbd.c new file mode 100644 index 00000000..4beefcdb --- /dev/null +++ b/bdk/usb/xusbd.c @@ -0,0 +1,2026 @@ +/* + * eXtensible USB Device driver (XDCI) for Tegra X1 + * + * Copyright (c) 2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define XUSB_TRB_SLOTS 16 //! TODO: Consider upping it. +#define XUSB_LINK_TRB_IDX (XUSB_TRB_SLOTS - 1) +#define XUSB_LAST_TRB_IDX (XUSB_TRB_SLOTS - 1) + +#define EP_DONT_RING 0 +#define EP_RING_DOORBELL 1 + +typedef enum { + XUSB_FULL_SPEED = 1, + XUSB_HIGH_SPEED = 3, + XUSB_SUPER_SPEED = 4 +} xusb_speed_t; + +typedef enum { + EP_DISABLED = 0, + EP_RUNNING = 1, + EP_HALTED = 2, + EP_STOPPED = 3, + EP_ERROR = 4 +} xusb_ep_status_t; + +typedef enum { + EP_TYPE_ISOC_OUT = 1, + EP_TYPE_BULK_OUT = 2, + EP_TYPE_INTR_OUT = 3, + EP_TYPE_CNTRL = 4, + EP_TYPE_ISOC_IN = 5, + EP_TYPE_BULK_IN = 6, + EP_TYPE_INTR_IN = 7 +} xusb_ep_type_t; + +typedef enum { + XUSB_DEFAULT = 0, + XUSB_ADDRESSED_STS_WAIT = 1, + XUSB_ADDRESSED = 2, + XUSB_CONFIGURED_STS_WAIT = 3, + XUSB_CONFIGURED = 4, + + XUSB_LUN_CONFIGURED_STS_WAIT = 5, + XUSB_LUN_CONFIGURED = 6, + XUSB_HID_CONFIGURED_STS_WAIT = 7, + XUSB_HID_CONFIGURED = 8, + + // XUSB_CONNECTED = , + // XUSB_DISCONNECTED = , + // XUSB_RESET = , + // XUSB_SUSPENDED = , +} xusb_dev_state_t; + +typedef enum { + XUSB_TRB_NONE = 0, + XUSB_TRB_NORMAL = 1, + XUSB_TRB_DATA = 3, + XUSB_TRB_STATUS = 4, + XUSB_TRB_LINK = 6, + XUSB_TRB_TRANSFER = 32, + XUSB_TRB_PORT_CHANGE = 34, + XUSB_TRB_SETUP = 63, +} xusb_trb_type_t; + +typedef enum { + XUSB_COMP_INVALID = 0, + XUSB_COMP_SUCCESS = 1, + XUSB_COMP_DATA_BUFFER_ERROR = 2, + XUSB_COMP_BABBLE_DETECTED_ERROR = 3, + XUSB_COMP_USB_TRANSACTION_ERROR = 4, + XUSB_COMP_TRB_ERROR = 5, + XUSB_COMP_STALL_ERROR = 6, + XUSB_COMP_RESOURCE_ERROR = 7, + XUSB_COMP_BANDWIDTH_ERROR = 8, + XUSB_COMP_NO_SLOTS_AVAILABLE_ERROR = 9, + XUSB_COMP_INVALID_STREAM_TYPE_ERROR = 10, + XUSB_COMP_SLOT_NOT_ENABLED_ERROR = 11, + XUSB_COMP_EP_DISABLED_ERROR = 12, + XUSB_COMP_SHORT_PKT = 13, + XUSB_COMP_RING_UNDERRUN = 14, + XUSB_COMP_RING_OVERRUN = 15, + XUSB_COMP_VF_EVENT_RING_FULL_ERROR = 16, + XUSB_COMP_PARAMETER_ERROR = 17, + XUSB_COMP_BANDWIDTH_OVERRUN_ERROR = 18, + XUSB_COMP_CONTEXT_STATE_ERROR = 19, + XUSB_COMP_NO_PING_RESPONSE_ERROR = 20, + XUSB_COMP_EVENT_RING_FULL_ERROR = 21, + XUSB_COMP_INCOMPATIBLE_DEVICE_ERROR = 22, + XUSB_COMP_MISSED_SERVICE_ERROR = 23, + XUSB_COMP_COMMAND_RING_STOPPED = 24, + XUSB_COMP_COMMAND_ABORTED = 25, + XUSB_COMP_STOPPED = 26, + XUSB_COMP_STOPPED_LENGTH_INVALID = 27, + XUSB_COMP_STOPPED_SHORT_PACKET = 28, + XUSB_COMP_EXIT_LATENCY_LARGE_ERROR = 29, + XUSB_COMP_ISOCH_BUFFER_OVERRUN = 31, + XUSB_COMP_EVENT_LOST_ERROR = 32, + XUSB_COMP_UNDEFINED_ERROR = 33, + XUSB_COMP_INVALID_STREAM_ID_ERROR = 34, + XUSB_COMP_SECONDARY_BANDWIDTH_ERROR = 35, + XUSB_COMP_SPLIT_TRANSACTION_ERROR = 36, + + XUSB_COMP_CODE_STREAM_NUMP_ERROR = 219, + XUSB_COMP_PRIME_PIPE_RECEIVED = 220, + XUSB_COMP_HOST_REJECTED = 221, + XUSB_COMP_CTRL_DIR_ERROR = 222, + XUSB_COMP_CTRL_SEQ_NUM_ERROR = 223 +} xusb_comp_code_t; + +typedef struct _event_trb_t +{ + u32 rsvd0; + u32 rsvd1; + + u32 rsvd2:24; + u32 comp_code:8; + + u32 cycle:1; + u32 rsvd3:9; + u32 trb_type:6; + u32 ep_id:5; + u32 rsvd4:11; +} event_trb_t; + +typedef struct _transfer_event_trb_t { + u32 trb_pointer_lo; + u32 trb_pointer_hi; + + u32 trb_tx_len:24; + u32 comp_code:8; + + u32 cycle:1; + u32 rsvddw3_0:1; + u32 event_data:1; + u32 rsvddw3_1:7; + u32 trb_type:6; + u32 ep_id:5; + u32 rsvddw3_2:11; +} transfer_event_trb_t; + +typedef struct _setup_event_trb_t +{ + usb_ctrl_setup_t ctrl_setup_data; + + u32 ctrl_seq_num:16; + u32 rsvddw2_0:8; + u32 comp_code:8; + + u32 cycle:1; + u32 rsvddw3_0:9; + u32 trb_type:6; + u32 ep_id:5; + u32 rsvddw3_1:11; +} setup_event_trb_t; + +typedef struct _status_trb_t +{ + u32 rsvd0; + u32 rsvd1; + + u32 rsvd2:22; + u32 interrupt_target:10; + + u32 cycle:1; + u32 ent:1; + u32 rsvd3_0:2; + u32 chain:1; + u32 ioc:1; + u32 rsvd3_1:4; + u32 trb_type:6; + u32 dir:1; + u32 rsvd3_2:15; +} status_trb_t; + +typedef struct _normal_trb_t +{ + u32 databufptr_lo; + u32 databufptr_hi; + + u32 trb_tx_len:17; + u32 td_size:5; + u32 interrupt_target:10; + + u32 cycle:1; + u32 ent:1; + u32 isp:1; + u32 no_snoop:1; + u32 chain:1; + u32 ioc:1; + u32 idt:1; + u32 rsvd0_0:2; + u32 bei:1; + u32 trb_type:6; + u32 rsvd0_1:16; +} normal_trb_t; + +typedef struct _data_trb_t +{ + u32 databufptr_lo; + u32 databufptr_hi; + + u32 trb_tx_len:17; + u32 td_size:5; + u32 interrupt_target:10; + + u32 cycle:1; + u32 ent:1; + u32 isp:1; + u32 no_snoop:1; + u32 chain:1; + u32 ioc:1; + u32 rsvd0_0:4; + u32 trb_type:6; + u32 dir:1; + u32 rsvd0_1:15; +} data_trb_t; + +typedef struct _link_trb_t +{ + u32 rsvd0_0:4; + u32 ring_seg_ptrlo:28; + + u32 ring_seg_ptrhi; + + u32 rsvd1_0:22; + u32 interrupt_target:10; + + u32 cycle:1; + u32 toggle_cycle:1; + u32 rsvd3_0:2; + u32 chain:1; + u32 ioc:1; + u32 rsvd3_1:4; + u32 trb_type:6; + u32 rsvd3_2:16; +} link_trb_t; + +typedef struct _xusb_ep_ctx_t +{ + // Common context. + u32 ep_state:3; + u32 rsvddW0_0:5; + u32 mult:2; + u32 max_pstreams:5; + u32 lsa:1; + u32 interval:8; + u32 rsvddW0_1:8; + + u32 rsvddw1_0:1; + u32 cerr:2; + u32 ep_type:3; + u32 rsvddw1_1:1; + u32 hid:1; + u32 max_burst_size:8; + u32 max_packet_size:16; + + u32 dcs:1; + u32 rsvddw2_0:3; + u32 trd_dequeueptr_lo:28; + + u32 trd_dequeueptr_hi; + + u32 avg_trb_len:16; + u32 max_esit_payload:16; + + // Nvidia context. + u32 event_data_txlen_acc; + + u32 cprog:8; + u32 sbyte:7; + u32 tp:2; + u32 rec:1; + u32 cec:2; + u32 ced:1; + u32 hsp1:1; + u32 rty1:1; + u32 std:1; + u32 status:8; + + u32 data_offset; + + u32 scratch_pad0; + + u32 scratch_pad1; + + u32 cping:8; + u32 sping:8; + u32 toggle_cycle:2; + u32 no_snoop:1; + u32 ro:1; + u32 tlm:1; + u32 dlm:1; + u32 hsp2:1; + u32 rty2:1; + u32 stop_rec_req:8; + + u32 device_addr:8; + u32 hub_addr:8; + u32 root_port_num:8; + u32 slot_id:8; + + u32 routing_string:20; + u32 speed:4; + u32 lpu:1; + u32 mtt:1; + u32 hub:1; + u32 dci:5; + + u32 tthub_slot_id:8; + u32 ttport_num:8; + u32 ssf:4; + u32 sps:2; + u32 interrupt_target:10; + + u32 frz:1; + u32 end:1; + u32 elm:1; + u32 mrx:1; + u32 ep_linklo:28; + + u32 ep_linkhi; +} xusb_ep_ctx_t; + +typedef struct _xusbd_controller_t +{ + data_trb_t *cntrl_epenqueue_ptr; + data_trb_t *cntrl_epdequeue_ptr; + u32 cntrl_producer_cycle; + data_trb_t *bulkout_epenqueue_ptr; + data_trb_t *bulkout_epdequeue_ptr; + u32 bulkout_producer_cycle; + data_trb_t *bulkin_epenqueue_ptr; + data_trb_t *bulkin_epdequeue_ptr; + u32 bulkin_producer_cycle; + event_trb_t *event_enqueue_ptr; + event_trb_t *event_dequeue_ptr; + u32 event_ccs; + u32 device_state; + u32 bytes_remaining[2]; + u32 tx_count[2]; + u32 ctrl_seq_num; + u32 config_num; + u32 interface_num; + u32 wait_for_event_trb; + u32 port_speed; + + usb_desc_t *desc; + usb_gadget_type gadget; + + u8 max_lun; + bool max_lun_set; + bool bulk_reset_req; +} xusbd_controller_t; + +extern u32 hid_report_descriptor_jc_size; +extern u32 hid_report_descriptor_touch_size; +extern u8 hid_report_descriptor_jc[]; +extern u8 hid_report_descriptor_touch[]; +extern usb_desc_t usb_gadget_hid_jc_descriptors; +extern usb_desc_t usb_gadget_hid_touch_descriptors; +extern usb_desc_t usb_gadget_ums_descriptors; + +// All rings and EP context must be aligned to 0x10. +typedef struct _xusbd_event_queues_t +{ + event_trb_t xusb_event_ring_seg0[XUSB_TRB_SLOTS]; + event_trb_t xusb_event_ring_seg1[XUSB_TRB_SLOTS]; + data_trb_t xusb_cntrl_event_queue[XUSB_TRB_SLOTS]; + data_trb_t xusb_bulkin_event_queue[XUSB_TRB_SLOTS]; + data_trb_t xusb_bulkout_event_queue[XUSB_TRB_SLOTS]; + volatile xusb_ep_ctx_t xusb_ep_ctxt[4]; +} xusbd_event_queues_t; + +// Set event queues context to a 0x10 aligned address. +xusbd_event_queues_t *xusb_evtq = (xusbd_event_queues_t *)XUSB_RING_ADDR; + +xusbd_controller_t *usbd_xotg; +xusbd_controller_t usbd_xotg_controller_ctxt; + +static int _xusb_xhci_mask_wait(u32 reg, u32 mask, u32 val, u32 retries) +{ + do + { + if ((XUSB_DEV_XHCI(reg) & mask) == val) + return USB_RES_OK; + usleep(1); + --retries; + } + while (retries); + + return USB_ERROR_TIMEOUT; +} + +// Event rings aligned to 0x10 +static void _xusbd_ep_init_event_ring() +{ + memset(xusb_evtq->xusb_event_ring_seg0, 0, sizeof(xusb_evtq->xusb_event_ring_seg0)); + memset(xusb_evtq->xusb_event_ring_seg1, 0, sizeof(xusb_evtq->xusb_event_ring_seg1)); + + //! TODO USB3: enable pcie regulators. + + // Set Event Ring Segment 0 Base Address. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST0BALO) = (u32)xusb_evtq->xusb_event_ring_seg0; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST0BAHI) = 0; + + // Set Event Ring Segment 1 Base Address. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST1BALO) = (u32)xusb_evtq->xusb_event_ring_seg1; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERST1BAHI) = 0; + + // Set Event Ring Segment sizes. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERSTSZ) = (XUSB_TRB_SLOTS << 16) | XUSB_TRB_SLOTS; + + // Set Enqueue and Dequeue pointers. + usbd_xotg->event_enqueue_ptr = xusb_evtq->xusb_event_ring_seg0; + usbd_xotg->event_dequeue_ptr = xusb_evtq->xusb_event_ring_seg0; + usbd_xotg->event_ccs = 1; + + // Event Ring Enqueue Pointer. + u32 evt_ring_addr = (u32)xusb_evtq->xusb_event_ring_seg0 & 0xFFFFFFF0; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPLO) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPLO) & 0xE) | evt_ring_addr | XCHI_ECS; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPHI) = 0; + + // Set Event Ring Dequeue Pointer. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) & 0xF) | evt_ring_addr; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPHI) = 0; +} + +static void _xusb_ep_set_type_and_metrics(u32 ep_idx, volatile xusb_ep_ctx_t *ep_ctxt) +{ + usb_ep_descr_t *ep_desc = NULL; + usb_ep_descr_t *endpoints = usbd_xotg->desc->cfg->endpoint; + + switch (ep_idx) + { + case XUSB_EP_CTRL_IN: + // Set EP type. + ep_ctxt->ep_type = EP_TYPE_CNTRL; + + // Set max packet size based on port speed. + ep_ctxt->avg_trb_len = 8; + ep_ctxt->max_packet_size = 64; //! TODO USB3: max_packet_size = 512. + break; + + case USB_EP_BULK_OUT: + // Set default EP type. + ep_ctxt->ep_type = EP_TYPE_BULK_OUT; + + // Check configuration descriptor. + if (usbd_xotg->desc->cfg->interface.bInterfaceClass == 0x3) // HID Class. + endpoints = (usb_ep_descr_t *)((void *)endpoints + sizeof(usb_hid_descr_t)); + + for (u32 i = 0; i < usbd_xotg->desc->cfg->interface.bNumEndpoints; i++) + if (endpoints[i].bEndpointAddress == USB_EP_ADDR_BULK_OUT) + { + ep_desc = &endpoints[i]; + break; + } + + // Set actual EP type. + if (ep_desc) + { + switch (ep_desc->bmAttributes) + { + case USB_EP_TYPE_ISO: + ep_ctxt->ep_type = EP_TYPE_ISOC_OUT; + break; + case USB_EP_TYPE_BULK: + ep_ctxt->ep_type = EP_TYPE_BULK_OUT; + break; + case USB_EP_TYPE_INTR: + ep_ctxt->ep_type = EP_TYPE_INTR_OUT; + break; + } + } + + // Set average TRB length. + //TODO: Use ep type instead (we don't expect to calculate avg per gadget)? + switch (usbd_xotg->gadget) + { + case USB_GADGET_UMS: + ep_ctxt->avg_trb_len = 3072; + break; + case USB_GADGET_HID_GAMEPAD: + case USB_GADGET_HID_TOUCHPAD: + ep_ctxt->avg_trb_len = 1024; + break; + default: + switch (usbd_xotg->port_speed) + { + case XUSB_SUPER_SPEED: + ep_ctxt->avg_trb_len = 1024; + break; + case XUSB_HIGH_SPEED: + case XUSB_FULL_SPEED: + ep_ctxt->avg_trb_len = 512; + break; + } + break; + } + + // Set max burst rate. + ep_ctxt->max_burst_size = (ep_desc->wMaxPacketSize >> 11) & 3; + + // Set max packet size based on port speed. + if (usbd_xotg->port_speed == XUSB_SUPER_SPEED) + { + ep_ctxt->max_packet_size = 1024; + + //! TODO USB3: + // If ISO or INTR EP, set Max Esit Payload size. + // ep_ctxt->max_burst_size = bMaxBurst; + //if (ep_ctxt->ep_type == EP_TYPE_INTR_OUT || ep_ctxt->ep_type == EP_TYPE_ISOC_OUT) + // ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); + } + else if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) + { + ep_ctxt->max_packet_size = 512; + + // If ISO or INTR EP, set Max Esit Payload size. + if (ep_ctxt->ep_type == EP_TYPE_INTR_OUT || ep_ctxt->ep_type == EP_TYPE_ISOC_OUT) + ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); + } + else + { + ep_ctxt->max_packet_size = 64; + + // If ISO or INTR EP, set Max Esit Payload size. + if (ep_ctxt->ep_type == EP_TYPE_INTR_OUT || ep_ctxt->ep_type == EP_TYPE_ISOC_OUT) + ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size; + } + break; + + case USB_EP_BULK_IN: + // Set default EP type. + ep_ctxt->ep_type = EP_TYPE_BULK_IN; + + // Check configuration descriptor. + if (usbd_xotg->desc->cfg->interface.bInterfaceClass == 0x3) // HID Class. + endpoints = (usb_ep_descr_t *)((void *)endpoints + sizeof(usb_hid_descr_t)); + + for (u32 i = 0; i < usbd_xotg->desc->cfg->interface.bNumEndpoints; i++) + if (endpoints[i].bEndpointAddress == USB_EP_ADDR_BULK_IN) + { + ep_desc = &endpoints[i]; + break; + } + + // Set actual EP type. + if (ep_desc) + { + switch (ep_desc->bmAttributes) + { + case USB_EP_TYPE_ISO: + ep_ctxt->ep_type = EP_TYPE_ISOC_IN; + break; + case USB_EP_TYPE_BULK: + ep_ctxt->ep_type = EP_TYPE_BULK_IN; + break; + case USB_EP_TYPE_INTR: + ep_ctxt->ep_type = EP_TYPE_INTR_IN; + break; + } + } + + // Set average TRB length. + //TODO: Use ep type instead (we don't expect to calculate avg per gadget)? + switch (usbd_xotg->gadget) + { + case USB_GADGET_UMS: + ep_ctxt->avg_trb_len = 3072; + break; + case USB_GADGET_HID_GAMEPAD: + case USB_GADGET_HID_TOUCHPAD: + ep_ctxt->avg_trb_len = 16; // Normal interrupt avg is 1024KB. + break; + default: + switch (usbd_xotg->port_speed) + { + case XUSB_SUPER_SPEED: + ep_ctxt->avg_trb_len = 1024; + break; + case XUSB_HIGH_SPEED: + case XUSB_FULL_SPEED: + ep_ctxt->avg_trb_len = 512; + break; + } + break; + } + + // Set max burst rate. + ep_ctxt->max_burst_size = (ep_desc->wMaxPacketSize >> 11) & 3; + + // Set max packet size based on port speed. + if (usbd_xotg->port_speed == XUSB_SUPER_SPEED) + { + ep_ctxt->max_packet_size = 1024; + + //! TODO USB3: + // If ISO or INTR EP, set Max Esit Payload size. + // ep_ctxt->max_burst_size = bMaxBurst; + //if (ep_ctxt->ep_type == EP_TYPE_INTR_IN || ep_ctxt->ep_type == EP_TYPE_ISOC_IN) + // ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); + } + else if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) + { + ep_ctxt->max_packet_size = 512; + + // If ISO or INTR EP, set Max Esit Payload size. + if (ep_ctxt->ep_type == EP_TYPE_INTR_IN || ep_ctxt->ep_type == EP_TYPE_ISOC_IN) + ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size * (ep_ctxt->max_burst_size + 1); + } + else + { + ep_ctxt->max_packet_size = 64; + + // If ISO or INTR EP, set Max Esit Payload size. + if (ep_ctxt->ep_type == EP_TYPE_INTR_IN || ep_ctxt->ep_type == EP_TYPE_ISOC_IN) + ep_ctxt->max_esit_payload = ep_ctxt->max_packet_size; + } + break; + } +} + +static int _xusb_ep_init_context(u32 ep_idx) +{ + link_trb_t *link_trb; + + if (ep_idx > USB_EP_BULK_IN) + return USB_ERROR_INIT; + + if (ep_idx == XUSB_EP_CTRL_OUT) + ep_idx = XUSB_EP_CTRL_IN; + + volatile xusb_ep_ctx_t *ep_ctxt = &xusb_evtq->xusb_ep_ctxt[ep_idx]; + memset((void *)ep_ctxt, 0, sizeof(xusb_ep_ctx_t)); + + ep_ctxt->ep_state = EP_RUNNING; + ep_ctxt->dcs = 1; + ep_ctxt->cec = 3; + ep_ctxt->cerr = 3; + ep_ctxt->max_burst_size = 0; + + switch (ep_idx) + { + case XUSB_EP_CTRL_IN: + usbd_xotg->cntrl_producer_cycle = 1; + usbd_xotg->cntrl_epenqueue_ptr = xusb_evtq->xusb_cntrl_event_queue; + usbd_xotg->cntrl_epdequeue_ptr = xusb_evtq->xusb_cntrl_event_queue; + + _xusb_ep_set_type_and_metrics(ep_idx, ep_ctxt); + + ep_ctxt->trd_dequeueptr_lo = (u32)xusb_evtq->xusb_cntrl_event_queue >> 4; + ep_ctxt->trd_dequeueptr_hi = 0; + + link_trb = (link_trb_t *)&xusb_evtq->xusb_cntrl_event_queue[XUSB_LINK_TRB_IDX]; + link_trb->toggle_cycle = 1; + link_trb->ring_seg_ptrlo = (u32)xusb_evtq->xusb_cntrl_event_queue >> 4; + link_trb->ring_seg_ptrhi = 0; + link_trb->trb_type = XUSB_TRB_LINK; + break; + + case USB_EP_BULK_OUT: + usbd_xotg->bulkout_producer_cycle = 1; + usbd_xotg->bulkout_epenqueue_ptr = xusb_evtq->xusb_bulkout_event_queue; + usbd_xotg->bulkout_epdequeue_ptr = xusb_evtq->xusb_bulkout_event_queue; + + _xusb_ep_set_type_and_metrics(ep_idx, ep_ctxt); + + ep_ctxt->trd_dequeueptr_lo = (u32)xusb_evtq->xusb_bulkout_event_queue >> 4; + ep_ctxt->trd_dequeueptr_hi = 0; + + link_trb = (link_trb_t *)&xusb_evtq->xusb_bulkout_event_queue[XUSB_LINK_TRB_IDX]; + link_trb->toggle_cycle = 1; + link_trb->ring_seg_ptrlo = (u32)xusb_evtq->xusb_bulkout_event_queue >> 4; + link_trb->ring_seg_ptrhi = 0; + link_trb->trb_type = XUSB_TRB_LINK; + break; + + case USB_EP_BULK_IN: + usbd_xotg->bulkin_producer_cycle = 1; + usbd_xotg->bulkin_epenqueue_ptr = xusb_evtq->xusb_bulkin_event_queue; + usbd_xotg->bulkin_epdequeue_ptr = xusb_evtq->xusb_bulkin_event_queue; + + _xusb_ep_set_type_and_metrics(ep_idx, ep_ctxt); + + ep_ctxt->trd_dequeueptr_lo = (u32)xusb_evtq->xusb_bulkin_event_queue >> 4; + ep_ctxt->trd_dequeueptr_hi = 0; + + link_trb = (link_trb_t *)&xusb_evtq->xusb_bulkin_event_queue[XUSB_LINK_TRB_IDX]; + link_trb->toggle_cycle = 1; + link_trb->ring_seg_ptrlo = (u32)xusb_evtq->xusb_bulkin_event_queue >> 4; + link_trb->ring_seg_ptrhi = 0; + link_trb->trb_type = XUSB_TRB_LINK; + break; + } + + return USB_RES_OK; +} + +static int _xusbd_ep_initialize(u32 ep_idx) +{ + switch (ep_idx) + { + case XUSB_EP_CTRL_IN: + case XUSB_EP_CTRL_OUT: + return _xusb_ep_init_context(XUSB_EP_CTRL_IN); + case USB_EP_BULK_OUT: + case USB_EP_BULK_IN: + _xusb_ep_init_context(ep_idx); + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_RELOAD) = BIT(ep_idx); + int res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_RELOAD, BIT(ep_idx), 0, 1000); + if (!res) + { + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_PAUSE) &= ~BIT(ep_idx); + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) &= ~BIT(ep_idx); + } + return res; + default: + return USB_ERROR_INIT; + } +} + +static void _xusb_init_phy() +{ + // Configure and enable PLLU. + clock_enable_pllu(); + + // Enable IDDQ control by software and disable UTMIPLL IDDQ. + CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) = (CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) & 0xFFFFFFFC) | 1; + + // Set UTMIPLL dividers and config based on OSC and enable it to 960 MHz. + clock_enable_utmipll(); + + // Set UTMIP misc config. + CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) = (CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) & 0xFEFFFFE8) | 0x2000008 | 0x20 | 2; + usleep(2); + + // Set OTG PAD0 calibration. + u32 fuse_usb_calib = FUSE(FUSE_USB_CALIB); + // Set HS_CURR_LEVEL. + XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_0) = (XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_0) & 0xFFFFFFC0) | (fuse_usb_calib & 0x3F); + // Set TERM_RANGE_ADJ and RPD_CTRL. + XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1) = (XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1) & 0x83FFFF87) | ((fuse_usb_calib & 0x780) >> 4) | ((u32)(FUSE(FUSE_USB_CALIB_EXT) << 27) >> 1); + + // Set VREG_LEV to 1. + XUSB_PADCTL(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL1) = (XUSB_PADCTL(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL1) & 0xFFFFFE3F) | 0x80; + + // Disable power down on usb2 ports pads. + XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_0) &= 0xDBFFFFFF; // Clear pad power down. + XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1) &= 0xFFFFFFFB; // Clear pad dr power down. + XUSB_PADCTL(XUSB_PADCTL_USB2_BATTERY_CHRG_OTGPAD0_CTL0) &= 0xFFFFFFFE; // Clear charging power down. + XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_0) &= 0xFFFFF7FF; // Clear bias power down. + (void)XUSB_PADCTL(XUSB_PADCTL_USB2_OTG_PAD0_CTL_1); // Commit write. + + // Enable USB2 tracking clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_SET) = BIT(CLK_Y_USB2_TRK); + CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) & 0xFFFFFF00) | 6; // Set trank divisor to 4. + + // Set tracking parameters and trigger it. + XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x451E000; + XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x51E000; + usleep(100); + + // TRK cycle done. Force PDTRK input into power down. + XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x451E000; + usleep(3); + + // Re-trigger it. + XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) = 0x51E000; + usleep(100); + + // TRK cycle done. Force PDTRK input into power down. + XUSB_PADCTL(XUSB_PADCTL_USB2_BIAS_PAD_CTL_1) |= 0x4000000; + + // Disable USB2 tracking clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_CLR) = BIT(CLK_Y_USB2_TRK); + + // Wait for XUSB PHY to stabilize. + usleep(30); +} + +static void _xusbd_init_device_clocks() +{ + // Disable reset to PLLU_OUT1 + CLOCK(CLK_RST_CONTROLLER_PLLU_OUTA) |= 1; + usleep(2); + + // Enable XUSB device clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_U_SET) = BIT(CLK_U_XUSB_DEV); + + // Set XUSB device core clock source to PLLP for a 102MHz result. + CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_CORE_DEV) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_CORE_DEV) & 0x1FFFFF00) | (1 << 29) | 6; + usleep(2); + + // Set XUSB Full-Speed logic clock source to FO 48MHz. + CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_FS) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_FS) & 0x1FFFFFFF) | (2 << 29); + + // Enable XUSB Super-Speed logic clock. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_SET) = BIT(CLK_W_XUSB_SS); + + // Set XUSB Super-Speed logic clock source to HSIC 480MHz for 120MHz result and source FS logic clock from Super-Speed. + CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_SS) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_XUSB_SS) & 0x1FFFFF00) | (3 << 29) | 6; + + // Clear reset to XUSB device and Super-Speed logic. + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB_SS); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_U_CLR) = BIT(CLK_U_XUSB_DEV); + usleep(2); +} + +int xusb_device_init() +{ + ///////////////////////////////////////////////// + CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = BIT(CLK_L_USBD); + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = BIT(CLK_L_USBD); + ///////////////////////////////////////////////// + + + // Enable XUSB clock and clear Reset to XUSB Pad Control. + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_SET) = BIT(CLK_W_XUSB); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB); + usleep(2); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB_PADCTL); + usleep(2); + + // USB2 Pads to XUSB. + XUSB_PADCTL(XUSB_PADCTL_USB2_PAD_MUX) = + (XUSB_PADCTL(XUSB_PADCTL_USB2_PAD_MUX) & ~(PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_MASK | PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_MASK)) | + PADCTL_USB2_PAD_MUX_USB2_BIAS_PAD_XUSB | PADCTL_USB2_PAD_MUX_USB2_OTG_PAD_PORT0_XUSB; + + // Initialize XUSB controller PHY. + _xusb_init_phy(); + + // Set USB2.0 Port 0 to device mode. + XUSB_PADCTL(XUSB_PADCTL_USB2_PORT_CAP) = (XUSB_PADCTL(XUSB_PADCTL_USB2_PORT_CAP) & ~PADCTL_USB2_PORT_CAP_PORT_0_CAP_MASK) | PADCTL_USB2_PORT_CAP_PORT_0_CAP_DEV; + + //! TODO USB3 + // // Set USB3.0 Port 0 cap to device. + // XUSB_PADCTL(XUSB_PADCTL_SS_PORT_CAP) = (XUSB_PADCTL(XUSB_PADCTL_SS_PORT_CAP) & ~PADCTL_SS_PORT_CAP_0_PORT1_CAP_MASK) | PADCTL_SS_PORT_CAP_0_PORT1_CAP_DEVICE_ONLY; + + // Set Super Speed Port 0 to USB2 Port 0. + XUSB_PADCTL(XUSB_PADCTL_SS_PORT_MAP) &= ~PADCTL_SS_PORT_MAP_PORT0_MASK; // 0: USB2_PORT0 + + // Power Up ID Wake up and Vbus Wake Up for UTMIP + PMC(APBDEV_PMC_USB_AO) &= 0xFFFFFFF3; + usleep(1); + + // Initialize device clocks. + _xusbd_init_device_clocks(); + + // Enable AHB redirect for access to IRAM for Event/EP ring buffers. + mc_enable_ahb_redirect(); // Can be skipped if IRAM is not used. + + // Enable XUSB device IPFS. + XUSB_DEV_DEV(XUSB_DEV_CONFIGURATION) |= DEV_CONFIGURATION_EN_FPCI; + + // Configure PCI and BAR0 address space. + XUSB_DEV_PCI(XUSB_CFG_1) |= CFG_1_BUS_MASTER | CFG_1_MEMORY_SPACE | CFG_1_IO_SPACE; + usleep(1); + XUSB_DEV_PCI(XUSB_CFG_4) = XUSB_DEV_BASE | CFG_4_ADDRESS_TYPE_32_BIT; + + // Mask SATA interrupt to MCORE. + XUSB_DEV_DEV(XUSB_DEV_INTR_MASK) |= DEV_INTR_MASK_IP_INT_MASK; + + // AHB USB performance cfg. + AHB_GIZMO(AHB_GIZMO_AHB_MEM) |= AHB_MEM_DONT_SPLIT_AHB_WR | AHB_MEM_ENB_FAST_REARBITRATE; + AHB_GIZMO(AHB_GIZMO_USB3) |= AHB_GIZMO_IMMEDIATE; + AHB_GIZMO(AHB_ARBITRATION_PRIORITY_CTRL) = PRIORITY_CTRL_WEIGHT(7) | PRIORITY_SELECT_USB3; + AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) = + MEM_PREFETCH_ENABLE | MEM_PREFETCH_USB3_MST_ID | MEM_PREFETCH_ADDR_BNDRY(12) | 0x1000; // Addr boundary 64KB, Inactivity 4096 cycles. + + // Initialize context. + usbd_xotg = &usbd_xotg_controller_ctxt; + memset(usbd_xotg, 0, sizeof(xusbd_controller_t)); + + // Initialize event and EP rings. + _xusbd_ep_init_event_ring(); + memset(xusb_evtq->xusb_cntrl_event_queue, 0, sizeof(xusb_evtq->xusb_cntrl_event_queue)); + memset(xusb_evtq->xusb_bulkin_event_queue, 0, sizeof(xusb_evtq->xusb_bulkin_event_queue)); + memset(xusb_evtq->xusb_bulkout_event_queue, 0, sizeof(xusb_evtq->xusb_bulkout_event_queue)); + + // Initialize Control EP. + int res = _xusbd_ep_initialize(XUSB_EP_CTRL_IN); + if (res) + return USB_ERROR_INIT; + + // Enable events and interrupts. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) |= XHCI_CTRL_IE | XHCI_CTRL_LSE; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ECPLO) = (u32)xusb_evtq->xusb_ep_ctxt & 0xFFFFFFF0; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ECPHI) = 0; + + //! TODO USB3: + // XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) |= DEV_XHCI_PORTHALT_STCHG_INTR_EN; + + return USB_RES_OK; +} + +static int _xusb_queue_trb(int ep_idx, void *trb, bool ring_doorbell) +{ + int res = USB_RES_OK; + data_trb_t *next_trb; + link_trb_t *link_trb; + + // Copy TRB and advance Enqueue list. + switch (ep_idx) + { + case XUSB_EP_CTRL_IN: + memcpy(usbd_xotg->cntrl_epenqueue_ptr, trb, sizeof(data_trb_t)); + + // Advance queue and if Link TRB set index to 0 and toggle cycle bit. + next_trb = &usbd_xotg->cntrl_epenqueue_ptr[1]; + if (next_trb->trb_type == XUSB_TRB_LINK) + { + link_trb = (link_trb_t *)next_trb; + link_trb->cycle = usbd_xotg->cntrl_producer_cycle & 1; + link_trb->toggle_cycle = 1; + next_trb = (data_trb_t *)(link_trb->ring_seg_ptrlo << 4); + usbd_xotg->cntrl_producer_cycle ^= 1; + } + usbd_xotg->cntrl_epenqueue_ptr = next_trb; + break; + + case USB_EP_BULK_OUT: + memcpy(usbd_xotg->bulkout_epenqueue_ptr, trb, sizeof(data_trb_t)); + + // Advance queue and if Link TRB set index to 0 and toggle cycle bit. + next_trb = &usbd_xotg->bulkout_epenqueue_ptr[1]; + if (next_trb->trb_type == XUSB_TRB_LINK) + { + link_trb = (link_trb_t *)next_trb; + link_trb->cycle = usbd_xotg->bulkout_producer_cycle & 1; + link_trb->toggle_cycle = 1; + next_trb = (data_trb_t *)(link_trb->ring_seg_ptrlo << 4); + usbd_xotg->bulkout_producer_cycle ^= 1; + } + usbd_xotg->bulkout_epenqueue_ptr = next_trb; + break; + + case USB_EP_BULK_IN: + memcpy(usbd_xotg->bulkin_epenqueue_ptr, trb, sizeof(data_trb_t)); + + // Advance queue and if Link TRB set index to 0 and toggle cycle bit. + next_trb = &usbd_xotg->bulkin_epenqueue_ptr[1]; + if (next_trb->trb_type == XUSB_TRB_LINK) + { + link_trb = (link_trb_t *)next_trb; + link_trb->cycle = usbd_xotg->bulkin_producer_cycle & 1; + link_trb->toggle_cycle = 1; + next_trb = (data_trb_t *)(link_trb->ring_seg_ptrlo << 4); + usbd_xotg->bulkin_producer_cycle ^= 1; + } + usbd_xotg->bulkin_epenqueue_ptr = next_trb; + break; + + case XUSB_EP_CTRL_OUT: + default: + res = XUSB_ERROR_INVALID_EP; + break; + } + + // Ring doorbell. + if (ring_doorbell) + { + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + u32 target_id = (ep_idx << 8) & 0xFFFF; + if (ep_idx == XUSB_EP_CTRL_IN) + target_id |= usbd_xotg->ctrl_seq_num << 16; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_DB) = target_id; + } + + return res; +} + +static void _xusb_create_status_trb(status_trb_t *trb, usb_dir_t direction) +{ + trb->cycle = usbd_xotg->cntrl_producer_cycle & 1; + trb->ioc = 1; // Enable interrupt on completion. + trb->trb_type = XUSB_TRB_STATUS; + trb->dir = direction; +} + +static void _xusb_create_normal_trb(normal_trb_t *trb, u8 *buf, u32 len, usb_dir_t direction) +{ + u8 producer_cycle; + + trb->databufptr_lo = (u32)buf; + trb->databufptr_hi = 0; + + trb->trb_tx_len = len; + + // Single TRB transfer. + trb->td_size = 0; + trb->chain = 0; + + if (direction == USB_DIR_IN) + producer_cycle = usbd_xotg->bulkin_producer_cycle & 1; + else + producer_cycle = usbd_xotg->bulkout_producer_cycle & 1; + + trb->cycle = producer_cycle; + trb->isp = 1; // Enable interrupt on short packet. + trb->ioc = 1; // Enable interrupt on completion. + trb->trb_type = XUSB_TRB_NORMAL; +} + +static void _xusb_create_data_trb(data_trb_t *trb, u8 *buf, u32 len, usb_dir_t direction) +{ + trb->databufptr_lo = (u32)buf; + trb->databufptr_hi = 0; + + trb->trb_tx_len = len; + + // Single TRB transfer. + trb->td_size = 0; + trb->chain = 0; + + trb->cycle = usbd_xotg->cntrl_producer_cycle & 1; + trb->isp = 1; // Enable interrupt on short packet. + trb->ioc = 1; // Enable interrupt on completion. + trb->trb_type = XUSB_TRB_DATA; + trb->dir = direction; +} + +static int _xusb_issue_status_trb(usb_dir_t direction) +{ + int res = USB_RES_OK; + status_trb_t trb = {0}; + + if (usbd_xotg->cntrl_epenqueue_ptr == usbd_xotg->cntrl_epdequeue_ptr || direction == USB_DIR_OUT) + { + _xusb_create_status_trb(&trb, direction); + res = _xusb_queue_trb(XUSB_EP_CTRL_IN, &trb, EP_RING_DOORBELL); + usbd_xotg->wait_for_event_trb = XUSB_TRB_STATUS; + } + + return res; +} + +static int _xusb_issue_normal_trb(u8 *buf, u32 len, usb_dir_t direction) +{ + normal_trb_t trb = {0}; + + _xusb_create_normal_trb(&trb, buf, len, direction); + int ep_idx = USB_EP_BULK_IN; + if (direction == USB_DIR_OUT) + ep_idx = USB_EP_BULK_OUT; + int res = _xusb_queue_trb(ep_idx, &trb, EP_RING_DOORBELL); + if (!res) + usbd_xotg->wait_for_event_trb = XUSB_TRB_NORMAL; + + return res; +} + +static int _xusb_issue_data_trb(u8 *buf, u32 len, usb_dir_t direction) +{ + data_trb_t trb = {0}; + + int res = USB_RES_OK; + if (usbd_xotg->cntrl_epenqueue_ptr == usbd_xotg->cntrl_epdequeue_ptr) + { + _xusb_create_data_trb(&trb, buf, len, direction); + res = _xusb_queue_trb(XUSB_EP_CTRL_IN, &trb, EP_RING_DOORBELL); + if (!res) + usbd_xotg->wait_for_event_trb = XUSB_TRB_DATA; + } + return res; +} + +int xusb_set_ep_stall(u32 endpoint, int ep_stall) +{ + int ep_idx = BIT(endpoint); + if (ep_stall) + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) |= ep_idx; + else + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) &= ~ep_idx; + + // Wait for EP status to change. + int res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_STCHG, ep_idx, ep_idx, 1000); + if (res) + return res; + + // Clear status change. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_STCHG) = ep_idx; + + return USB_RES_OK; +} + +static int _xusb_handle_transfer_event(transfer_event_trb_t *trb) +{ + // Advance dequeue list. + data_trb_t *next_trb; + switch (trb->ep_id) + { + case XUSB_EP_CTRL_IN: + next_trb = &usbd_xotg->cntrl_epdequeue_ptr[1]; + if (next_trb->trb_type == XUSB_TRB_LINK) + next_trb = (data_trb_t *)(next_trb->databufptr_lo & 0xFFFFFFF0); + usbd_xotg->cntrl_epdequeue_ptr = next_trb; + break; + case USB_EP_BULK_OUT: + next_trb = &usbd_xotg->bulkout_epdequeue_ptr[1]; + if (next_trb->trb_type == XUSB_TRB_LINK) + next_trb = (data_trb_t *)(next_trb->databufptr_lo & 0xFFFFFFF0); + usbd_xotg->bulkout_epdequeue_ptr = next_trb; + break; + case USB_EP_BULK_IN: + next_trb = &usbd_xotg->bulkin_epdequeue_ptr[1]; + if (next_trb->trb_type == XUSB_TRB_LINK) + next_trb = (data_trb_t *)(next_trb->databufptr_lo & 0xFFFFFFF0); + usbd_xotg->bulkin_epdequeue_ptr = next_trb; + break; + default: + // Should never happen. + break; + } + + // Handle completion code. + switch (trb->comp_code) + { + case XUSB_COMP_SUCCESS: + case XUSB_COMP_SHORT_PKT: + switch (trb->ep_id) + { + case XUSB_EP_CTRL_IN: + if (usbd_xotg->wait_for_event_trb == XUSB_TRB_DATA) + return _xusb_issue_status_trb(USB_DIR_OUT); + else if (usbd_xotg->wait_for_event_trb == XUSB_TRB_STATUS) + { + if (usbd_xotg->device_state == XUSB_ADDRESSED_STS_WAIT) + usbd_xotg->device_state = XUSB_ADDRESSED; + else if (usbd_xotg->device_state == XUSB_CONFIGURED_STS_WAIT) + usbd_xotg->device_state = XUSB_CONFIGURED; + else if (usbd_xotg->device_state == XUSB_LUN_CONFIGURED_STS_WAIT) + usbd_xotg->device_state = XUSB_LUN_CONFIGURED; + else if (usbd_xotg->device_state == XUSB_HID_CONFIGURED_STS_WAIT) + usbd_xotg->device_state = XUSB_HID_CONFIGURED; + } + break; + + case USB_EP_BULK_IN: + usbd_xotg->bytes_remaining[USB_DIR_IN] -= trb->trb_tx_len; + if (usbd_xotg->tx_count[USB_DIR_IN])/////////// + usbd_xotg->tx_count[USB_DIR_IN]--; + + // If bytes remaining for a Bulk IN transfer, return error. + if (trb->trb_tx_len) + return XUSB_ERROR_XFER_BULK_IN_RESIDUE; + break; + + case USB_EP_BULK_OUT: + // If short packet and Bulk OUT, it's not an error because we prime EP for 4KB. + usbd_xotg->bytes_remaining[USB_DIR_OUT] -= trb->trb_tx_len; + if (usbd_xotg->tx_count[USB_DIR_OUT])/////////// + usbd_xotg->tx_count[USB_DIR_OUT]--; + break; + } + return USB_RES_OK; +/* + case XUSB_COMP_USB_TRANSACTION_ERROR: + case XUSB_COMP_TRB_ERROR: + case XUSB_COMP_RING_UNDERRUN: + case XUSB_COMP_RING_OVERRUN: + case XUSB_COMP_CTRL_DIR_ERROR: // Redefined. + xusb_set_ep_stall(trb->ep_id, USB_EP_CFG_STALL); + return USB_RES_OK; +*/ + case XUSB_COMP_CTRL_DIR_ERROR: + return XUSB_ERROR_XFER_DIR; + + case XUSB_COMP_CTRL_SEQ_NUM_ERROR: + return XUSB_ERROR_SEQ_NUM; //! TODO: Can mean a new setup packet was received. + + default: // Every other completion code. + return USB_ERROR_XFER_ERROR; + } +} + +/* + * Other XUSB impl: + * CBT: PR, PRC, WPR, WRC, CSC, REQ, PLC, CEC. + * LNX: REQ, PRC PR, PRC & !PR, WRC, CSC, PLC, CEC. + * BRO: CSC, PR | PRC, WPR | WRC, REQ, PLC, CEC. + */ + +static int _xusb_handle_port_change() +{ + u32 res = USB_RES_OK; + u32 status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC); + u32 halt = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT); + + // Connect status change (CSC). + if (status & XHCI_PORTSC_CSC) + { + //! TODO: Check CCS. + // CCS check seems to be + // XHCI_PORTSC_CCS 1: device_state = XUSB_CONNECTED + // XHCI_PORTSC_CCS 0: device_state = XUSB_DISCONNECTED + // Always do XHCI_PORTSC_CSC bit clear. + + // Set port speed. + usbd_xotg->port_speed = (status & XHCI_PORTSC_PS) >> 10; + + // In case host does not support Super Speed, revert the control EP packet size. + if (usbd_xotg->port_speed != XUSB_SUPER_SPEED) + { + volatile xusb_ep_ctx_t *ep_ctxt = &xusb_evtq->xusb_ep_ctxt[XUSB_EP_CTRL_IN]; + ep_ctxt->avg_trb_len = 8; + ep_ctxt->max_packet_size = 64; + } + + // Clear CSC bit. + status |= XHCI_PORTSC_CSC; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = status; + } + + // Port reset (PR), Port reset change (PRC). + if (status & XHCI_PORTSC_PR || status & XHCI_PORTSC_PRC) + { + //! TODO: + // XHCI_PORTSC_PR: device_state = XUSB_RESET + + //_disable_usb_wdt4(); + + //res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_PORTSC, XHCI_PORTSC_PRC, XHCI_PORTSC_PRC, 50000); // unpatched0 + // if (res) return res; + _xusb_xhci_mask_wait(XUSB_DEV_XHCI_PORTSC, XHCI_PORTSC_PRC, XHCI_PORTSC_PRC, 50000); // patched0 + + // Clear PRC bit. + status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) | XHCI_PORTSC_PRC; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) |= XHCI_PORTSC_PRC; + } + + // Warm Port Reset (WPR), Warm Port Reset Change (WRC). + if (status & XHCI_PORTSC_WPR || status & XHCI_PORTSC_WRC) + { + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) &= ~XHCI_PORTHALT_HALT_LTSSM; + (void)XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC); + res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_PORTSC, XHCI_PORTSC_WRC, XHCI_PORTSC_WRC, 1000); + + // Clear WRC bit. + status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) | XHCI_PORTSC_WRC; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) |= XHCI_PORTSC_WRC; + + //! TODO: WPR: device_state = XUSB_RESET + } + + // Handle Config Request (STCHG_REQ). + if (halt & XHCI_PORTHALT_STCHG_REQ) + { + // Clear Link Training Status. + status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) & ~XHCI_PORTHALT_HALT_LTSSM; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) &= ~XHCI_PORTHALT_HALT_LTSSM; + } + + // Port link state change (PLC). + if (status & XHCI_PORTSC_PLC) + { + //! WAR: Sometimes port speed changes without a CSC event. Set again. + usbd_xotg->port_speed = (status & XHCI_PORTSC_PS) >> 10; + + // check PLS + // if U3 + // device_state = XUSB_SUSPENDED + // else if U0 and XUSB_SUSPENDED + // val = XUSB_DEV_XHCI_EP_PAUSE + // XUSB_DEV_XHCI_EP_PAUSE = 0 + // XUSB_DEV_XHCI_EP_STCHG = val; + + // Clear PLC bit. + status = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) | XHCI_PORTSC_PLC; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) |= XHCI_PORTSC_PLC; + } + + // Port configuration link error (CEC). + if (status & XHCI_PORTSC_CEC) + { + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) |= XHCI_PORTSC_CEC; + res = XUSB_ERROR_PORT_CFG; + } + + return res; +} + +static int _xusb_handle_get_ep_status(usb_ctrl_setup_t *ctrl_setup) +{ + static u8 xusb_ep_status_descriptor[2] = {0}; + + // Get EP context pointer. + volatile xusb_ep_ctx_t *ep_ctxt = (volatile xusb_ep_ctx_t *)(XUSB_DEV_XHCI(XUSB_DEV_XHCI_ECPLO) & 0xFFFFFFF0); + ep_ctxt = &ep_ctxt[ctrl_setup->wIndex]; + + xusb_ep_status_descriptor[0] = (ep_ctxt->ep_state == EP_HALTED) ? USB_STATUS_EP_HALTED : USB_STATUS_EP_OK; + return _xusb_issue_data_trb(xusb_ep_status_descriptor, 2, USB_DIR_IN); +} + +static int _xusb_handle_get_class_request(usb_ctrl_setup_t *ctrl_setup) +{ + u8 _bRequest = ctrl_setup->bRequest; + u16 _wIndex = ctrl_setup->wIndex; + u16 _wValue = ctrl_setup->wValue; + u16 _wLength = ctrl_setup->wLength; + + bool valid_interface = _wIndex == usbd_xotg->interface_num; + bool valid_len = (_bRequest == USB_REQUEST_BULK_GET_MAX_LUN) ? 1 : 0; + + if (!valid_interface || _wValue != 0 || _wLength != valid_len) + goto stall; + + switch (_bRequest) + { + case USB_REQUEST_BULK_RESET: + usbd_xotg->bulk_reset_req = true; + return _xusb_issue_status_trb(USB_DIR_IN); // DELAYED_STATUS; + case USB_REQUEST_BULK_GET_MAX_LUN: + if (!usbd_xotg->max_lun_set) + goto stall; + usbd_xotg->device_state = XUSB_LUN_CONFIGURED_STS_WAIT; + return _xusb_issue_data_trb(&usbd_xotg->max_lun, 1, USB_DIR_IN); + } + +stall: + xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); + return USB_RES_OK; +} + +static int _xusb_handle_get_descriptor(usb_ctrl_setup_t *ctrl_setup) +{ + u32 size; + void *descriptor; + + u32 wLength = ctrl_setup->wLength; + + u8 descriptor_type = ctrl_setup->wValue >> 8; + u8 descriptor_subtype = ctrl_setup->wValue & 0xFF; + + switch (descriptor_type) + { + case USB_DESCRIPTOR_DEVICE: + //! TODO USB3: Provide a super speed descriptor. +/* + u32 soc_rev = APB_MISC(APB_MISC_GP_HIDREV); + usb_device_descriptor.idProduct = (soc_rev >> 8) & 0xFF; // chip_id. + usb_device_descriptor.idProduct |= ((soc_rev << 4) | (FUSE(FUSE_SKU_INFO) & 0xF)) << 8; // HIDFAM. + usb_device_descriptor.bcdDevice = (soc_rev >> 16) & 0xF; // MINORREV. + usb_device_descriptor.bcdDevice |= ((soc_rev >> 4) & 0xF) << 8; // MAJORREV. +*/ + descriptor = usbd_xotg->desc->dev; + size = usbd_xotg->desc->dev->bLength; + break; + case USB_DESCRIPTOR_CONFIGURATION: + //! TODO USB3: Provide a super speed descriptor. + if (usbd_xotg->gadget == USB_GADGET_UMS) + { + if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) // High speed. 512 bytes. + { + usbd_xotg->desc->cfg->endpoint[0].wMaxPacketSize = 0x200; // No burst. + usbd_xotg->desc->cfg->endpoint[1].wMaxPacketSize = 0x200; // No burst. + } + else // Full speed. 64 bytes. + { + usbd_xotg->desc->cfg->endpoint[0].wMaxPacketSize = 0x40; + usbd_xotg->desc->cfg->endpoint[1].wMaxPacketSize = 0x40; + } + } + else + { + usb_cfg_hid_descr_t *tmp = (usb_cfg_hid_descr_t *)usbd_xotg->desc->cfg; + if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) // High speed. 512 bytes. + { + tmp->endpoint[0].wMaxPacketSize = 0x200; + tmp->endpoint[1].wMaxPacketSize = 0x200; + tmp->endpoint[0].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms. + tmp->endpoint[1].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms. + } + else // Full speed. 64 bytes. + { + tmp->endpoint[0].wMaxPacketSize = 0x40; + tmp->endpoint[1].wMaxPacketSize = 0x40; + tmp->endpoint[0].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms. + tmp->endpoint[1].bInterval = usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms. + } + } + descriptor = usbd_xotg->desc->cfg; + size = usbd_xotg->desc->cfg->config.wTotalLength; + break; + case USB_DESCRIPTOR_STRING: + switch (descriptor_subtype) + { + case 1: + descriptor = usbd_xotg->desc->vendor; + size = usbd_xotg->desc->vendor[0]; + break; + case 2: + descriptor = usbd_xotg->desc->product; + size = usbd_xotg->desc->product[0]; + break; + case 3: + descriptor = usbd_xotg->desc->serial; + size = usbd_xotg->desc->serial[0]; + break; + case 0xEE: + descriptor = usbd_xotg->desc->ms_os; + size = usbd_xotg->desc->ms_os->bLength; + break; + default: + descriptor = usbd_xotg->desc->lang_id; + size = 4; + break; + } + break; + case USB_DESCRIPTOR_DEVICE_QUALIFIER: + if (!usbd_xotg->desc->dev_qual) + { + xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); + return USB_RES_OK; + } + usbd_xotg->desc->dev_qual->bNumOtherConfigs = 0; + descriptor = usbd_xotg->desc->dev_qual; + size = usbd_xotg->desc->dev_qual->bLength; + break; + case USB_DESCRIPTOR_OTHER_SPEED_CONFIGURATION: + if (!usbd_xotg->desc->cfg_other) + { + xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); + return USB_RES_OK; + } + if (usbd_xotg->port_speed == XUSB_HIGH_SPEED) + { + usbd_xotg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x40; + usbd_xotg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x40; + } + else + { + usbd_xotg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x200; + usbd_xotg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x200; + } + descriptor = usbd_xotg->desc->cfg_other; + size = usbd_xotg->desc->cfg_other->config.wTotalLength; + break; + case USB_DESCRIPTOR_DEVICE_BINARY_OBJECT: + descriptor = usbd_xotg->desc->dev_bot; + size = usbd_xotg->desc->dev_bot->wTotalLength; + break; + default: + xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); + return USB_RES_OK; + } + + if (wLength < size) + size = wLength; + + return _xusb_issue_data_trb(descriptor, size, USB_DIR_IN); +} + +static void _xusb_handle_set_request_dev_address(usb_ctrl_setup_t *ctrl_setup) +{ + u32 addr = ctrl_setup->wValue & 0xFF; + + XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) & 0x80FFFFFF) | (addr << 24); + xusb_evtq->xusb_ep_ctxt[XUSB_EP_CTRL_IN].device_addr = addr; + + _xusb_issue_status_trb(USB_DIR_IN); + + usbd_xotg->device_state = XUSB_ADDRESSED_STS_WAIT; +} + +static void _xusb_handle_set_request_configuration(usb_ctrl_setup_t *ctrl_setup) +{ + u32 config_num = ctrl_setup->wValue; + if (!config_num) //TODO! we can change device_state here. + return; + + // Initialize BULK EPs. + _xusbd_ep_initialize(USB_EP_BULK_OUT); + _xusbd_ep_initialize(USB_EP_BULK_IN); + + // Device mode start. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) |= XHCI_CTRL_RUN; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ST) |= XHCI_ST_RC; + + _xusb_issue_status_trb(USB_DIR_IN); + + usbd_xotg->config_num = config_num; + usbd_xotg->device_state = XUSB_CONFIGURED_STS_WAIT; +} + +static int _xusbd_handle_ep0_control_transfer(usb_ctrl_setup_t *ctrl_setup) +{ + u32 size; + u8 *desc; + bool ep_stall = false; + bool transmit_data = false; + + u8 _bmRequestType = ctrl_setup->bmRequestType; + u8 _bRequest = ctrl_setup->bRequest; + u16 _wValue = ctrl_setup->wValue; + u16 _wIndex = ctrl_setup->wIndex; + u16 _wLength = ctrl_setup->wLength; + + static u8 xusb_dev_status_descriptor[2] = {USB_STATUS_DEV_SELF_POWERED, 0}; + static u8 xusb_interface_descriptor[4] = {0}; + static u8 xusb_configuration_descriptor[2] = {0}; + static u8 xusb_status_descriptor[2] = {0}; + + //gfx_printf("ctrl: %02X %02X %04X %04X %04X\n", _bmRequestType, _bRequest, _wValue, _wIndex, _wLength); + + XUSB_DEV_XHCI(XUSB_DEV_XHCI_EP_HALT) &= ~XHCI_EP_HALT_DCI; + u32 res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_EP_HALT, XHCI_EP_HALT_DCI, 0, 1000); + if (res) + return res; + + switch (_bmRequestType) + { + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE): + if (_bRequest == USB_REQUEST_SET_ADDRESS) + _xusb_handle_set_request_dev_address(ctrl_setup); + else if (_bRequest == USB_REQUEST_SET_CONFIGURATION) + _xusb_handle_set_request_configuration(ctrl_setup); + return USB_RES_OK; // What about others. + + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE): + usbd_xotg->interface_num = _wValue; + return _xusb_issue_status_trb(USB_DIR_IN); + + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT): + if ((_wValue & 0xFF) == USB_FEATURE_ENDPOINT_HALT) + { + if (_bRequest == USB_REQUEST_CLEAR_FEATURE) + { + xusb_set_ep_stall(_wIndex, USB_EP_CFG_CLEAR); + return _xusb_issue_status_trb(USB_DIR_IN); + } + else if (_bRequest == USB_REQUEST_SET_FEATURE) + { + xusb_set_ep_stall(_wIndex, USB_EP_CFG_STALL); + return _xusb_issue_status_trb(USB_DIR_IN); + } + } + ep_stall = true; + break; + + case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE): + return _xusb_handle_get_class_request(ctrl_setup); + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE): + switch (_bRequest) + { + case USB_REQUEST_GET_STATUS: + desc = xusb_dev_status_descriptor; + size = sizeof(xusb_dev_status_descriptor); + transmit_data = true; + break; + case USB_REQUEST_GET_DESCRIPTOR: + return _xusb_handle_get_descriptor(ctrl_setup); + case USB_REQUEST_GET_CONFIGURATION: + xusb_configuration_descriptor[0] = usbd_xotg->config_num; + desc = xusb_configuration_descriptor; + size = sizeof(xusb_configuration_descriptor); + transmit_data = true; + break; + default: + ep_stall = true; + break; + } + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE): + if (_bRequest == USB_REQUEST_GET_INTERFACE) + { + desc = xusb_interface_descriptor; + size = sizeof(xusb_interface_descriptor); + xusb_interface_descriptor[0] = usbd_xotg->interface_num; + transmit_data = true; + } + else if (_bRequest == USB_REQUEST_GET_STATUS) + { + desc = xusb_status_descriptor; + size = sizeof(xusb_status_descriptor); + transmit_data = true; + } + else if (_bRequest == USB_REQUEST_GET_DESCRIPTOR && (_wValue >> 8) == USB_DESCRIPTOR_HID_REPORT && usbd_xotg->gadget > USB_GADGET_UMS) + { + if (usbd_xotg->gadget == USB_GADGET_HID_GAMEPAD) + { + desc = (u8 *)&hid_report_descriptor_jc; + size = hid_report_descriptor_jc_size; + } + else // USB_GADGET_HID_TOUCHPAD + { + desc = (u8 *)&hid_report_descriptor_touch; + size = hid_report_descriptor_touch_size; + } + transmit_data = true; + usbd_xotg->device_state = XUSB_HID_CONFIGURED_STS_WAIT; + } + else + ep_stall = true; + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT): + if (_bRequest == USB_REQUEST_GET_STATUS) + return _xusb_handle_get_ep_status(ctrl_setup); + + ep_stall = true; + break; + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE): + return _xusb_handle_get_class_request(ctrl_setup); + + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_INTERFACE): + case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_DEVICE): + if (_bRequest == USB_REQUEST_GET_MS_DESCRIPTOR) + { + switch (_wIndex) + { + case USB_DESCRIPTOR_MS_COMPAT_ID: + desc = (u8 *)usbd_xotg->desc->ms_cid; + size = usbd_xotg->desc->ms_cid->dLength; + transmit_data = true; + break; + case USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES: + desc = (u8 *)usbd_xotg->desc->mx_ext; + size = usbd_xotg->desc->mx_ext->dLength; + transmit_data = true; + break; + default: + ep_stall = true; + break; + } + } + else + ep_stall = true; + break; + + default: + ep_stall = true; + break; + } + + if (transmit_data) + { + memcpy((u8 *)USB_EP_CONTROL_BUF_ADDR, desc, size); + if (_wLength < size) + size = _wLength; + return _xusb_issue_data_trb((u8 *)USB_EP_CONTROL_BUF_ADDR, size, USB_DIR_IN); + } + + if (ep_stall) + xusb_set_ep_stall(XUSB_EP_CTRL_IN, USB_EP_CFG_STALL); + + return USB_RES_OK; +} + +static int _xusb_ep_operation(u32 tries) +{ + usb_ctrl_setup_t setup_event; + volatile event_trb_t *event_trb; + setup_event_trb_t *setup_event_trb; + + // Wait for an interrupt event. + int res = _xusb_xhci_mask_wait(XUSB_DEV_XHCI_ST, XHCI_ST_IP, XHCI_ST_IP, tries); + if (res) + return res; + + // Clear interrupt status. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ST) |= XHCI_ST_IP; + + usbd_xotg->event_enqueue_ptr = (event_trb_t *)(XUSB_DEV_XHCI(XUSB_DEV_XHCI_EREPLO) & 0xFFFFFFF0); + event_trb = usbd_xotg->event_dequeue_ptr; + + // Check if cycle matches. + if ((event_trb->cycle & 1) != usbd_xotg->event_ccs) + return XUSB_ERROR_INVALID_CYCLE; + + while ((event_trb->cycle & 1) == usbd_xotg->event_ccs) + { + switch (event_trb->trb_type) + { + case XUSB_TRB_TRANSFER: + res = _xusb_handle_transfer_event((transfer_event_trb_t *)event_trb); + break; + case XUSB_TRB_PORT_CHANGE: + res = _xusb_handle_port_change(); + break; + case XUSB_TRB_SETUP: + setup_event_trb = (setup_event_trb_t *)event_trb; + memcpy(&setup_event, &setup_event_trb->ctrl_setup_data, sizeof(usb_ctrl_setup_t)); + usbd_xotg->ctrl_seq_num = setup_event_trb->ctrl_seq_num; + res = _xusbd_handle_ep0_control_transfer(&setup_event); + break; + default: + // TRB not supported. + break; + } + + // Check if last event TRB and reset to first one. + if (usbd_xotg->event_dequeue_ptr == &xusb_evtq->xusb_event_ring_seg1[XUSB_LAST_TRB_IDX]) + { + usbd_xotg->event_dequeue_ptr = xusb_evtq->xusb_event_ring_seg0; + usbd_xotg->event_ccs ^= 1; + } + else // Advance dequeue to next event. + usbd_xotg->event_dequeue_ptr = &usbd_xotg->event_dequeue_ptr[1]; + + // Set next event. + event_trb = usbd_xotg->event_dequeue_ptr; + + // If events exceed the interrupt time, handle them next interrupt. + if (usbd_xotg->event_dequeue_ptr == usbd_xotg->event_enqueue_ptr) + break; + } + + // Clear Event Handler bit if enabled and set Dequeue pointer. + u32 erdp = XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) & 0xF; + if (erdp & XHCI_ERDPLO_EHB) + erdp |= XHCI_ERDPLO_EHB; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_ERDPLO) = ((u32)usbd_xotg->event_dequeue_ptr & 0xFFFFFFF0) | erdp; + + return res; +} + +int xusb_device_enumerate(usb_gadget_type gadget) +{ + switch (gadget) + { + case USB_GADGET_UMS: + usbd_xotg->desc = &usb_gadget_ums_descriptors; + break; + case USB_GADGET_HID_GAMEPAD: + usbd_xotg->desc = &usb_gadget_hid_jc_descriptors; + break; + case USB_GADGET_HID_TOUCHPAD: + usbd_xotg->desc = &usb_gadget_hid_touch_descriptors; + break; + } + + usbd_xotg->gadget = gadget; + + /* + * Set interrupt moderation to 0us. + * This is important because default value creates a 4.62ms latency. + * Effectively hurting transfers by having 15% to 96% performance loss. + */ + XUSB_DEV_XHCI(XUSB_DEV_XHCI_RT_IMOD) = 0; + + // Disable Wake events. + XUSB_PADCTL(XUSB_PADCTL_ELPG_PROGRAM_0) = 0; + XUSB_PADCTL(XUSB_PADCTL_ELPG_PROGRAM_1) = 0; + + // Enable overrides for VBUS and ID. + XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) = (XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) & ~(PADCTL_USB2_VBUS_ID_VBUS_OVR_MASK | PADCTL_USB2_VBUS_ID_SRC_MASK)) | + PADCTL_USB2_VBUS_ID_VBUS_OVR_EN | PADCTL_USB2_VBUS_ID_SRC_ID_OVR_EN; + + // Clear halt for LTSSM. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTHALT) &= ~XHCI_PORTHALT_HALT_LTSSM; + + // Enable device mode. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_CTRL) |= XHCI_CTRL_ENABLE; + + // Override access to High/Full Speed. + XUSB_DEV_XHCI(XUSB_DEV_XHCI_CFG_DEV_FE) = (XUSB_DEV_XHCI(XUSB_DEV_XHCI_CFG_DEV_FE) & ~XHCI_CFG_DEV_FE_PORTREGSEL_MASK) | XHCI_CFG_DEV_FE_PORTREGSEL_HSFS; + + XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) = + (XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) & ~XHCI_PORTSC_PLS_MASK) | XHCI_PORTSC_LWS | XHCI_PORTSC_PLS_RXDETECT; + XUSB_DEV_XHCI(XUSB_DEV_XHCI_CFG_DEV_FE) &= ~XHCI_CFG_DEV_FE_PORTREGSEL_MASK; + + // Enable VBUS and set ID to Float. + XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) = (XUSB_PADCTL(XUSB_PADCTL_USB2_VBUS_ID) & ~PADCTL_USB2_VBUS_ID_OVR_MASK) | + PADCTL_USB2_VBUS_ID_OVR_FLOAT | PADCTL_USB2_VBUS_ID_VBUS_ON; + + usbd_xotg->wait_for_event_trb = XUSB_TRB_SETUP; + usbd_xotg->device_state = XUSB_DEFAULT; + + // Timeout if cable or communication isn't started in 1.5 minutes. + u32 timer = get_tmr_ms() + 90000; + while (true) + { + int res = _xusb_ep_operation(USB_XFER_SYNCED_ENUM); // 2s timeout. + if (res && res != USB_ERROR_TIMEOUT) + return res; + + if (usbd_xotg->device_state == XUSB_CONFIGURED) + break; + + if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + return USB_ERROR_USER_ABORT; + } + + return USB_RES_OK; +} + +void xusb_end(bool reset_ep, bool only_controller) +{ + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB_SS); + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_CLR) = BIT(CLK_W_XUSB_SS); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_U_SET) = BIT(CLK_U_XUSB_DEV); + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_U_CLR) = BIT(CLK_U_XUSB_DEV); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB_PADCTL); + CLOCK(CLK_RST_CONTROLLER_CLK_ENB_W_CLR) = BIT(CLK_W_XUSB); + CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB); + mc_disable_ahb_redirect(); // Can be skipped if IRAM is not used. +} + +int xusb_handle_ep0_ctrl_setup() +{ + /* + * EP0 Control handling is done by normal ep operation in XUSB. + * Here we handle the bulk reset only. + */ + if (usbd_xotg->bulk_reset_req) + { + usbd_xotg->bulk_reset_req = false; + return USB_RES_BULK_RESET; + } + + return USB_RES_OK; +} + +int xusb_device_ep1_out_read(u8 *buf, u32 len, u32 *bytes_read, u32 sync_tries) +{ + if (len > USB_EP_BUFFER_MAX_SIZE) + len = USB_EP_BUFFER_MAX_SIZE; + + int res = USB_RES_OK; + usbd_xotg->tx_count[USB_DIR_OUT] = 0; + usbd_xotg->bytes_remaining[USB_DIR_OUT] = len; + _xusb_issue_normal_trb(buf, len, USB_DIR_OUT); + usbd_xotg->tx_count[USB_DIR_OUT]++; + + if (sync_tries) + { + while (!res && usbd_xotg->tx_count[USB_DIR_OUT]) + res = _xusb_ep_operation(sync_tries); + + if (bytes_read) + *bytes_read = res ? 0 : usbd_xotg->bytes_remaining[USB_DIR_OUT]; + + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + } + + return res; +} + +int xusb_device_ep1_out_read_big(u8 *buf, u32 len, u32 *bytes_read) +{ + if (len > USB_EP_BULK_OUT_MAX_XFER) + len = USB_EP_BULK_OUT_MAX_XFER; + + u32 bytes = 0; + *bytes_read = 0; + u8 *buf_curr = buf; + + while (len) + { + u32 len_ep = MIN(len, USB_EP_BUFFER_MAX_SIZE); + + int res = xusb_device_ep1_out_read(buf_curr, len_ep, &bytes, USB_XFER_SYNCED_DATA); + if (res) + return res; + + len -= len_ep; + buf_curr += len_ep; + *bytes_read = *bytes_read + bytes; + } + + return USB_RES_OK; +} + +int xusb_device_ep1_out_reading_finish(u32 *pending_bytes) +{ + int res = USB_RES_OK; + while (!res && usbd_xotg->tx_count[USB_DIR_OUT]) + res = _xusb_ep_operation(USB_XFER_SYNCED); // Infinite retries. + + if (pending_bytes) + *pending_bytes = res ? 0 : usbd_xotg->bytes_remaining[USB_DIR_OUT]; + + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + + return res; +} + +int xusb_device_ep1_in_write(u8 *buf, u32 len, u32 *bytes_written, u32 sync_tries) +{ + if (len > USB_EP_BUFFER_MAX_SIZE) + len = USB_EP_BUFFER_MAX_SIZE; + + bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false); + + int res = USB_RES_OK; + usbd_xotg->tx_count[USB_DIR_IN] = 0; + usbd_xotg->bytes_remaining[USB_DIR_IN] = len; + _xusb_issue_normal_trb(buf, len, USB_DIR_IN); + usbd_xotg->tx_count[USB_DIR_IN]++; + + if (sync_tries) + { + while (!res && usbd_xotg->tx_count[USB_DIR_IN]) + res = _xusb_ep_operation(sync_tries); + + if (bytes_written) + *bytes_written = res ? 0 : usbd_xotg->bytes_remaining[USB_DIR_IN]; + } + else + { + if ((usbd_xotg->port_speed == XUSB_FULL_SPEED && len == 64) || + (usbd_xotg->port_speed == XUSB_HIGH_SPEED && len == 512) || + (usbd_xotg->port_speed == XUSB_SUPER_SPEED && len == 1024)) + { + _xusb_issue_normal_trb(buf, 0, USB_DIR_IN); + usbd_xotg->tx_count[USB_DIR_IN]++; + } + } + + return res; +} + +int xusb_device_ep1_in_writing_finish(u32 *pending_bytes) +{ + int res = USB_RES_OK; + while (!res && usbd_xotg->tx_count[USB_DIR_IN]) + res = _xusb_ep_operation(USB_XFER_SYNCED); // Infinite retries. + + if (pending_bytes) + *pending_bytes = res ? 0 : usbd_xotg->bytes_remaining[USB_DIR_IN]; + + return res; +} + +bool xusb_device_get_port_in_sleep() +{ + // Ejection heuristic. + u32 link_mode = XUSB_DEV_XHCI(XUSB_DEV_XHCI_PORTSC) & XHCI_PORTSC_PLS_MASK; + return (link_mode == XHCI_PORTSC_PLS_U3); +} + +bool xusb_device_class_send_max_lun(u8 max_lun) +{ + // Timeout if get MAX_LUN request doesn't happen in 10s. + u32 timer = get_tmr_ms() + 10000; + + usbd_xotg->max_lun = max_lun; + usbd_xotg->max_lun_set = true; + + // Wait for request and transfer start. + while (usbd_xotg->device_state != XUSB_LUN_CONFIGURED) + { + _xusb_ep_operation(USB_XFER_SYNCED_CLASS); + if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + return true; + } + + usbd_xotg->device_state = XUSB_CONFIGURED; + + return false; +} + +bool xusb_device_class_send_hid_report() +{ + // Timeout if get GET_HID_REPORT request doesn't happen in 10s. + u32 timer = get_tmr_ms() + 10000; + + // Wait for request and transfer start. + while (usbd_xotg->device_state != XUSB_HID_CONFIGURED) + { + _xusb_ep_operation(USB_XFER_SYNCED_CLASS); + if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN)) + return true; + } + + usbd_xotg->device_state = XUSB_CONFIGURED; + + return false; +} + +void xusb_device_get_ops(usb_ops_t *ops) +{ + ops->usbd_flush_endpoint = NULL; + ops->usbd_set_ep_stall = xusb_set_ep_stall; + ops->usbd_handle_ep0_ctrl_setup = xusb_handle_ep0_ctrl_setup; + ops->usbd_end = xusb_end;////////////////// + ops->usb_device_init = xusb_device_init; + ops->usb_device_enumerate = xusb_device_enumerate; + ops->usb_device_class_send_max_lun = xusb_device_class_send_max_lun; + ops->usb_device_class_send_hid_report = xusb_device_class_send_hid_report; + ops->usb_device_get_suspended = xusb_device_get_port_in_sleep; + ops->usb_device_get_port_in_sleep = xusb_device_get_port_in_sleep; + + ops->usb_device_ep1_out_read = xusb_device_ep1_out_read; + ops->usb_device_ep1_out_read_big = xusb_device_ep1_out_read_big; + ops->usb_device_ep1_out_reading_finish = xusb_device_ep1_out_reading_finish; + ops->usb_device_ep1_in_write = xusb_device_ep1_in_write; + ops->usb_device_ep1_in_writing_finish = xusb_device_ep1_in_writing_finish; +} diff --git a/bdk/utils/aarch64_util.h b/bdk/utils/aarch64_util.h new file mode 100644 index 00000000..456bfc83 --- /dev/null +++ b/bdk/utils/aarch64_util.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2019 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _ARM64_H_ +#define _ARM64_H_ + +#include + +#define LSL0 0 +#define LSL16 16 +#define LSL32 32 + +#define _PAGEOFF(x) ((x) & 0xFFFFF000) + +#define _ADRP(r, o) (0x90000000 | ((((o) >> 12) & 0x3) << 29) | ((((o) >> 12) & 0x1FFFFC) << 3) | ((r) & 0x1F)) +#define _BL(a, o) (0x94000000 | ((((o) - (a)) >> 2) & 0x3FFFFFF)) +#define _B(a, o) (0x14000000 | ((((o) - (a)) >> 2) & 0x3FFFFFF)) +#define _MOVKX(r, i, s) (0xF2800000 | (((s) & 0x30) << 17) | (((i) & 0xFFFF) << 5) | ((r) & 0x1F)) +#define _MOVZX(r, i, s) (0xD2800000 | (((s) & 0x30) << 17) | (((i) & 0xFFFF) << 5) | ((r) & 0x1F)) +#define _MOVZW(r, i, s) (0x52800000 | (((s) & 0x30) << 17) | (((i) & 0xFFFF) << 5) | ((r) & 0x1F)) +#define _NOP() 0xD503201F + +#endif diff --git a/bdk/utils/btn.c b/bdk/utils/btn.c new file mode 100644 index 00000000..cc365734 --- /dev/null +++ b/bdk/utils/btn.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include "btn.h" +#include +#include +#include +#include +#include + +u8 btn_read() +{ + u8 res = 0; + if (!gpio_read(GPIO_PORT_X, GPIO_PIN_7)) + res |= BTN_VOL_DOWN; + if (!gpio_read(GPIO_PORT_X, GPIO_PIN_6)) + res |= BTN_VOL_UP; + if (i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_ONOFFSTAT) & MAX77620_ONOFFSTAT_EN0) + res |= BTN_POWER; + return res; +} + +u8 btn_read_vol() +{ + u8 res = 0; + if (!gpio_read(GPIO_PORT_X, GPIO_PIN_7)) + res |= BTN_VOL_DOWN; + if (!gpio_read(GPIO_PORT_X, GPIO_PIN_6)) + res |= BTN_VOL_UP; + return res; +} + +u8 btn_wait() +{ + u8 res = 0, btn = btn_read(); + bool pwr = false; + + //Power button down, raise a filter. + if (btn & BTN_POWER) + { + pwr = true; + btn &= ~BTN_POWER; + } + + do + { + res = btn_read(); + //Power button up, remove filter. + if (!(res & BTN_POWER) && pwr) + pwr = false; + else if (pwr) //Power button still down. + res &= ~BTN_POWER; + } while (btn == res); + + return res; +} + +u8 btn_wait_timeout(u32 time_ms, u8 mask) +{ + u32 timeout = get_tmr_ms() + time_ms; + u8 res = btn_read() & mask; + + while (get_tmr_ms() < timeout) + { + if (res == mask) + break; + else + res = btn_read() & mask; + }; + + return res; +} + +u8 btn_wait_timeout_single(u32 time_ms, u8 mask) +{ + u8 single_button = mask & BTN_SINGLE; + mask &= ~BTN_SINGLE; + + u32 timeout = get_tmr_ms() + time_ms; + u8 res = btn_read(); + + while (get_tmr_ms() < timeout) + { + if ((res & mask) == mask) + { + if (single_button && (res & ~mask)) // Undesired button detected. + res = btn_read(); + else + return (res & mask); + } + else + res = btn_read(); + }; + + // Timed out. + if (!single_button || !time_ms) + return (res & mask); + else + return 0; // Return no button press if single button requested. +} diff --git a/bdk/utils/btn.h b/bdk/utils/btn.h new file mode 100644 index 00000000..ac191fa9 --- /dev/null +++ b/bdk/utils/btn.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _BTN_H_ +#define _BTN_H_ + +#include + +#define BTN_POWER BIT(0) +#define BTN_VOL_DOWN BIT(1) +#define BTN_VOL_UP BIT(2) +#define BTN_SINGLE BIT(7) + +u8 btn_read(); +u8 btn_read_vol(); +u8 btn_wait(); +u8 btn_wait_timeout(u32 time_ms, u8 mask); +u8 btn_wait_timeout_single(u32 time_ms, u8 mask); + +#endif diff --git a/bdk/utils/dirlist.c b/bdk/utils/dirlist.c new file mode 100644 index 00000000..e05b2f18 --- /dev/null +++ b/bdk/utils/dirlist.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 CTCaer + * Copyright (c) 2020 Storm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include +#include + +#include +#include +#include + + //Dateien auflisten in Verzeichnis +char *dirlist(const char *directory, const char *pattern, bool includeHiddenFiles, bool parse_dirs) +{ + u8 max_entries = 61; + + int res = 0; + u32 i = 0, j = 0, k = 0; + DIR dir; + FILINFO fno; + + char *dir_entries = (char *)calloc(max_entries, 256); + char *temp = (char *)calloc(1, 256); + + if (!pattern && !f_opendir(&dir, directory)) + { + for (;;) + { + res = f_readdir(&dir, &fno); + if (res || !fno.fname[0]) + break; + + bool curr_parse = parse_dirs ? (fno.fattrib & AM_DIR) : !(fno.fattrib & AM_DIR); + + if (curr_parse) + { + if ((fno.fname[0] != '.') && (includeHiddenFiles || !(fno.fattrib & AM_HID))) + { + strcpy(dir_entries + (k * 256), fno.fname); + k++; + if (k > (max_entries - 1)) + break; + } + } + } + f_closedir(&dir); + } + else if (pattern && !f_findfirst(&dir, &fno, directory, pattern) && fno.fname[0]) + { + do + { + if (!(fno.fattrib & AM_DIR) && (fno.fname[0] != '.') && (includeHiddenFiles || !(fno.fattrib & AM_HID))) + { + strcpy(dir_entries + (k * 256), fno.fname); + k++; + if (k > (max_entries - 1)) + break; + } + res = f_findnext(&dir, &fno); + } while (fno.fname[0] && !res); + f_closedir(&dir); + } + + if (!k) + { + free(temp); + free(dir_entries); + + return NULL; + } + + // Reorder ini files by ASCII ordering. + for (i = 0; i < k - 1 ; i++) + { + for (j = i + 1; j < k; j++) + { + if (strcmp(&dir_entries[i * 256], &dir_entries[j * 256]) > 0) + { + strcpy(temp, &dir_entries[i * 256]); + strcpy(&dir_entries[i * 256], &dir_entries[j * 256]); + strcpy(&dir_entries[j * 256], temp); + } + } + } + + free(temp); + + return dir_entries; +} diff --git a/bdk/utils/dirlist.h b/bdk/utils/dirlist.h new file mode 100644 index 00000000..32197f33 --- /dev/null +++ b/bdk/utils/dirlist.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include + +char *dirlist(const char *directory, const char *pattern, bool includeHiddenFiles, bool parse_dirs); diff --git a/bdk/utils/ini.c b/bdk/utils/ini.c new file mode 100644 index 00000000..538435e1 --- /dev/null +++ b/bdk/utils/ini.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2018-2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 . + */ + +#include + +#include "ini.h" +#include +#include +#include + +static char *_strdup(char *str) +{ + if (!str) + return NULL; + + // Remove starting space. + if (str[0] == ' ' && strlen(str)) + str++; + + char *res = (char *)malloc(strlen(str) + 1); + strcpy(res, str); + + // Remove trailing space. + if (strlen(res) && res[strlen(res) - 1] == ' ') + res[strlen(res) - 1] = 0; + + return res; +} + +u32 _find_section_name(char *lbuf, u32 lblen, char schar) +{ + u32 i; + // Depends on 'FF_USE_STRFUNC 2' that removes \r. + for (i = 0; i < lblen && lbuf[i] != schar && lbuf[i] != '\n'; i++) + ; + lbuf[i] = 0; + + return i; +} + +ini_sec_t *_ini_create_section(link_t *dst, ini_sec_t *csec, char *name, u8 type) +{ + if (csec) + list_append(dst, &csec->link); + + csec = (ini_sec_t *)calloc(sizeof(ini_sec_t), 1); + csec->name = _strdup(name); + csec->type = type; + + return csec; +} + +int ini_parse(link_t *dst, char *ini_path, bool is_dir) +{ + u32 lblen; + u32 pathlen = strlen(ini_path); + u32 k = 0; + char lbuf[512]; + char *filelist = NULL; + FIL fp; + ini_sec_t *csec = NULL; + + char *filename = (char *)malloc(256); + + strcpy(filename, ini_path); + + // Get all ini filenames. + if (is_dir) + { + filelist = dirlist(filename, "*.ini", false, false); + if (!filelist) + { + free(filename); + return 0; + } + strcpy(filename + pathlen, "/"); + pathlen++; + } + + do + { + // Copy ini filename in path string. + if (is_dir) + { + if (filelist[k * 256]) + { + strcpy(filename + pathlen, &filelist[k * 256]); + k++; + } + else + break; + } + + // Open ini. + if (f_open(&fp, filename, FA_READ) != FR_OK) + { + free(filelist); + free(filename); + + return 0; + } + + do + { + // Fetch one line. + lbuf[0] = 0; + f_gets(lbuf, 512, &fp); + lblen = strlen(lbuf); + + // Remove trailing newline. Depends on 'FF_USE_STRFUNC 2' that removes \r. + if (lblen && lbuf[lblen - 1] == '\n') + lbuf[lblen - 1] = 0; + + if (lblen > 2 && lbuf[0] == '[') // Create new section. + { + _find_section_name(lbuf, lblen, ']'); + + csec = _ini_create_section(dst, csec, &lbuf[1], INI_CHOICE); + list_init(&csec->kvs); + } + else if (lblen > 1 && lbuf[0] == '{') // Create new caption. Support empty caption '{}'. + { + _find_section_name(lbuf, lblen, '}'); + + csec = _ini_create_section(dst, csec, &lbuf[1], INI_CAPTION); + csec->color = 0xFF0AB9E6; + } + else if (lblen > 2 && lbuf[0] == '#') // Create comment. + { + csec = _ini_create_section(dst, csec, &lbuf[1], INI_COMMENT); + } + else if (lblen < 2) // Create empty line. + { + csec = _ini_create_section(dst, csec, NULL, INI_NEWLINE); + } + else if (csec && csec->type == INI_CHOICE) // Extract key/value. + { + u32 i = _find_section_name(lbuf, lblen, '='); + + ini_kv_t *kv = (ini_kv_t *)calloc(sizeof(ini_kv_t), 1); + kv->key = _strdup(&lbuf[0]); + kv->val = _strdup(&lbuf[i + 1]); + list_append(&csec->kvs, &kv->link); + } + } while (!f_eof(&fp)); + + f_close(&fp); + + if (csec) + { + list_append(dst, &csec->link); + if (is_dir) + csec = NULL; + } + } while (is_dir); + + free(filename); + free(filelist); + + return 1; +} + +char *ini_check_payload_section(ini_sec_t *cfg) +{ + if (cfg == NULL) + return NULL; + + LIST_FOREACH_ENTRY(ini_kv_t, kv, &cfg->kvs, link) + { + if (!strcmp("payload", kv->key)) + return kv->val; + } + + return NULL; +} diff --git a/bdk/utils/ini.h b/bdk/utils/ini.h new file mode 100644 index 00000000..571d19d2 --- /dev/null +++ b/bdk/utils/ini.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _INI_H_ +#define _INI_H_ + +#include +#include + +#define INI_CHOICE 3 +#define INI_CAPTION 5 +#define INI_CHGLINE 6 +#define INI_NEWLINE 0xFE +#define INI_COMMENT 0xFF + +typedef struct _ini_kv_t +{ + char *key; + char *val; + link_t link; +} ini_kv_t; + +typedef struct _ini_sec_t +{ + char *name; + link_t kvs; + link_t link; + u32 type; + u32 color; +} ini_sec_t; + +int ini_parse(link_t *dst, char *ini_path, bool is_dir); +char *ini_check_payload_section(ini_sec_t *cfg); + +#endif + diff --git a/bdk/utils/list.h b/bdk/utils/list.h new file mode 100644 index 00000000..ece5402f --- /dev/null +++ b/bdk/utils/list.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2020 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _LIST_H_ +#define _LIST_H_ + +#include + +/*! Initialize list. */ +#define LIST_INIT(name) link_t name = {&name, &name} + +/*! Initialize static list. */ +#define LIST_INIT_STATIC(name) static link_t name = {&name, &name} + +/*! Iterate over all list links. */ +#define LIST_FOREACH(iter, list) \ + for(link_t *iter = (list)->next; iter != (list); iter = iter->next) + +/*! Safely iterate over all list links. */ +#define LIST_FOREACH_SAFE(iter, list) \ + for(link_t *iter = (list)->next, *safe = iter->next; iter != (list); iter = safe, safe = iter->next) + +/*! Iterate over all list members and make sure that the list has at least one entry. */ +#define LIST_FOREACH_ENTRY(etype, iter, list, mn) \ + if ((list)->next != (list)) \ + for(etype *iter = CONTAINER_OF((list)->next, etype, mn); &iter->mn != (list); iter = CONTAINER_OF(iter->mn.next, etype, mn)) + +typedef struct _link_t +{ + struct _link_t *prev; + struct _link_t *next; +} link_t; + +static inline void link_init(link_t *l) +{ + l->prev = NULL; + l->next = NULL; +} + +static inline int link_used(link_t *l) +{ + if(l->next == NULL) + return 1; + return 0; +} + +static inline void list_init(link_t *lh) +{ + lh->prev = lh; + lh->next = lh; +} + +static inline void list_prepend(link_t *lh, link_t *l) +{ + l->next = lh->next; + l->prev = lh; + lh->next->prev = l; + lh->next = l; +} + +static inline void list_append(link_t *lh, link_t *l) +{ + l->prev = lh->prev; + l->next = lh; + lh->prev->next = l; + lh->prev = l; +} + +static inline void list_remove(link_t *l) +{ + l->next->prev = l->prev; + l->prev->next = l->next; + link_init(l); +} + +static inline int list_empty(link_t *lh) +{ + if(lh->next == lh) + return 1; + return 0; +} + +#endif diff --git a/bdk/utils/sprintf.c b/bdk/utils/sprintf.c new file mode 100644 index 00000000..aa5b9523 --- /dev/null +++ b/bdk/utils/sprintf.c @@ -0,0 +1,142 @@ +/* +* Copyright (c) 2018 naehrwert +* Copyright (c) 2019-2020 CTCaer +* +* This program is free software; you can redistribute it and/or modify it +* under the terms and conditions of the GNU General Public License, +* version 2, as published by the Free Software Foundation. +* +* This program is distributed in the hope 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 . +*/ + +#include +#include + +#include + +char **sout_buf; + +static void _s_putc(char c) +{ + **sout_buf = c; + *sout_buf += 1; +} + +static void _s_puts(char *s) +{ + for (; *s; s++) + _s_putc(*s); +} + +static void _s_putn(u32 v, int base, char fill, int fcnt) +{ + char buf[65]; + static const char digits[] = "0123456789ABCDEFghijklmnopqrstuvwxyz"; + char *p; + int c = fcnt; + + if (base > 36) + return; + + p = buf + 64; + *p = 0; + do + { + c--; + *--p = digits[v % base]; + v /= base; + } while (v); + + if (fill != 0) + { + while (c > 0) + { + *--p = fill; + c--; + } + } + + _s_puts(p); +} + +void s_printf(char *out_buf, const char *fmt, ...) +{ + va_list ap; + int fill, fcnt; + + sout_buf = &out_buf; + + va_start(ap, fmt); + while(*fmt) + { + if(*fmt == '%') + { + fmt++; + fill = 0; + fcnt = 0; + if ((*fmt >= '0' && *fmt <= '9') || *fmt == ' ') + { + fcnt = *fmt; + fmt++; + if (*fmt >= '0' && *fmt <= '9') + { + fill = fcnt; + fcnt = *fmt - '0'; + fmt++; + } + else + { + fill = ' '; + fcnt -= '0'; + } + } + switch(*fmt) + { + case 'c': + _s_putc(va_arg(ap, u32)); + break; + case 's': + _s_puts(va_arg(ap, char *)); + break; + case 'd': + _s_putn(va_arg(ap, u32), 10, fill, fcnt); + break; + case 'p': + case 'P': + case 'x': + case 'X': + _s_putn(va_arg(ap, u32), 16, fill, fcnt); + break; + case 'k': + //gfx_con.fgcol = va_arg(ap, u32); + break; + case 'K': + //gfx_con.bgcol = va_arg(ap, u32); + //gfx_con.fillbg = 1; + break; + case '%': + _s_putc('%'); + break; + case '\0': + goto out; + default: + _s_putc('%'); + _s_putc(*fmt); + break; + } + } + else + _s_putc(*fmt); + fmt++; + } + +out: + **sout_buf = '\0'; + va_end(ap); +} \ No newline at end of file diff --git a/bdk/utils/sprintf.h b/bdk/utils/sprintf.h new file mode 100644 index 00000000..845f9b74 --- /dev/null +++ b/bdk/utils/sprintf.h @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2019 CTCaer +* +* This program is free software; you can redistribute it and/or modify it +* under the terms and conditions of the GNU General Public License, +* version 2, as published by the Free Software Foundation. +* +* This program is distributed in the hope 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 _SPRINTF_H_ +#define _SPRINTF_H_ + +#include + +void s_printf(char *out_buf, const char *fmt, ...); + +#endif \ No newline at end of file diff --git a/bdk/utils/types.h b/bdk/utils/types.h new file mode 100644 index 00000000..96615abb --- /dev/null +++ b/bdk/utils/types.h @@ -0,0 +1,128 @@ +/* +* Copyright (c) 2018 naehrwert +* Copyright (c) 2018-2020 CTCaer +* +* This program is free software; you can redistribute it and/or modify it +* under the terms and conditions of the GNU General Public License, +* version 2, as published by the Free Software Foundation. +* +* This program is distributed in the hope 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 _TYPES_H_ +#define _TYPES_H_ + +#define NULL ((void *)0) + +#define ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define ALIGN_DOWN(x, a) (((x) - ((a) - 1)) & ~((a) - 1)) +#define BIT(n) (1U << (n)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) + +#define OFFSET_OF(t, m) ((u32)&((t *)NULL)->m) +#define CONTAINER_OF(mp, t, mn) ((t *)((u32)mp - OFFSET_OF(t, mn))) + +typedef signed char s8; +typedef short s16; +typedef short SHORT; +typedef int s32; +typedef int INT; +typedef long LONG; +typedef long long int s64; +typedef unsigned char u8; +typedef unsigned char BYTE; +typedef unsigned short u16; +typedef unsigned short WORD; +typedef unsigned short WCHAR; +typedef unsigned int u32; +typedef unsigned int UINT; +typedef unsigned long DWORD; +typedef unsigned long long QWORD; +typedef unsigned long long int u64; +typedef volatile unsigned char vu8; +typedef volatile unsigned short vu16; +typedef volatile unsigned int vu32; + +#ifdef __aarch64__ +typedef u64 uptr; +#else /* __arm__ or __thumb__ */ +typedef u32 uptr; +#endif + +typedef int bool; +#define true 1 +#define false 0 + +#define DISABLE 0 +#define ENABLE 1 + +#define BOOT_CFG_AUTOBOOT_EN BIT(0) +#define BOOT_CFG_FROM_LAUNCH BIT(1) +#define BOOT_CFG_FROM_ID BIT(2) +#define BOOT_CFG_TO_EMUMMC BIT(3) +#define BOOT_CFG_SEPT_RUN BIT(7) + +#define EXTRA_CFG_KEYS BIT(0) +#define EXTRA_CFG_PAYLOAD BIT(1) +#define EXTRA_CFG_MODULE BIT(2) + +#define EXTRA_CFG_NYX_BIS BIT(4) +#define EXTRA_CFG_NYX_UMS BIT(5) +#define EXTRA_CFG_NYX_RELOAD BIT(6) +#define EXTRA_CFG_NYX_DUMP BIT(7) + +typedef enum _nyx_ums_type +{ + NYX_UMS_SD_CARD = 0, + NYX_UMS_EMMC_BOOT0, + NYX_UMS_EMMC_BOOT1, + NYX_UMS_EMMC_GPP, + NYX_UMS_EMUMMC_BOOT0, + NYX_UMS_EMUMMC_BOOT1, + NYX_UMS_EMUMMC_GPP +} nyx_ums_type; + +typedef struct __attribute__((__packed__)) _boot_cfg_t +{ + u8 boot_cfg; + u8 autoboot; + u8 autoboot_list; + u8 extra_cfg; + union + { + struct + { + char id[8]; // 7 char ASCII null teminated. + char emummc_path[0x78]; // emuMMC/XXX, ASCII null teminated. + }; + u8 ums; // nyx_ums_type. + u8 xt_str[0x80]; + }; +} boot_cfg_t; + +typedef struct __attribute__((__packed__)) _ipl_ver_meta_t +{ + u32 magic; + u32 version; + u16 rsvd0; + u16 rsvd1; +} ipl_ver_meta_t; + +typedef struct __attribute__((__packed__)) _reloc_meta_t +{ + u32 start; + u32 stack; + u32 end; + u32 ep; +} reloc_meta_t; + +#endif diff --git a/bdk/utils/util.c b/bdk/utils/util.c new file mode 100644 index 00000000..bb5ae2dd --- /dev/null +++ b/bdk/utils/util.c @@ -0,0 +1,242 @@ +/* +* Copyright (c) 2018 naehrwert +* Copyright (c) 2018-2020 CTCaer +* Copyright (c) 2020 Storm +* +* This program is free software; you can redistribute it and/or modify it +* under the terms and conditions of the GNU General Public License, +* version 2, as published by the Free Software Foundation. +* +* This program is distributed in the hope 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USE_RTC_TIMER + +extern volatile nyx_storage_t *nyx_str; + +u32 get_tmr_s() +{ + return RTC(APBDEV_RTC_SECONDS); +} + +u32 get_tmr_ms() +{ + // The registers must be read with the following order: + // RTC_MILLI_SECONDS (0x10) -> RTC_SHADOW_SECONDS (0xC) + return (RTC(APBDEV_RTC_MILLI_SECONDS) + (RTC(APBDEV_RTC_SHADOW_SECONDS) * 1000)); +} + +u32 get_tmr_us() +{ + return TMR(TIMERUS_CNTR_1US); +} + +void msleep(u32 ms) +{ +#ifdef USE_RTC_TIMER + u32 start = RTC(APBDEV_RTC_MILLI_SECONDS) + (RTC(APBDEV_RTC_SHADOW_SECONDS) * 1000); + // Casting to u32 is important! + while (((u32)(RTC(APBDEV_RTC_MILLI_SECONDS) + (RTC(APBDEV_RTC_SHADOW_SECONDS) * 1000)) - start) <= ms) + ; +#else + bpmp_msleep(ms); +#endif +} + +void usleep(u32 us) +{ +#ifdef USE_RTC_TIMER + u32 start = TMR(TIMERUS_CNTR_1US); + + // Check if timer is at upper limits and use BPMP sleep so it doesn't wake up immediately. + if ((start + us) < start) + bpmp_usleep(us); + else + while ((u32)(TMR(TIMERUS_CNTR_1US) - start) <= us) // Casting to u32 is important! + ; +#else + bpmp_usleep(us); +#endif +} + +void exec_cfg(u32 *base, const cfg_op_t *ops, u32 num_ops) +{ + for(u32 i = 0; i < num_ops; i++) + base[ops[i].off] = ops[i].val; +} + +u32 crc32_calc(u32 crc, const u8 *buf, u32 len) +{ + const u8 *p, *q; + static u32 *table = NULL; + + // Calculate CRC table. + if (!table) + { + table = calloc(256, sizeof(u32)); + for (u32 i = 0; i < 256; i++) + { + u32 rem = i; + for (u32 j = 0; j < 8; j++) + { + if (rem & 1) + { + rem >>= 1; + rem ^= 0xedb88320; + } + else + rem >>= 1; + } + table[i] = rem; + } + } + + crc = ~crc; + q = buf + len; + for (p = buf; p < q; p++) + { + u8 oct = *p; + crc = (crc >> 8) ^ table[(crc & 0xff) ^ oct]; + } + + return ~crc; +} + +void panic(u32 val) +{ + // Set panic code. + PMC(APBDEV_PMC_SCRATCH200) = val; + //PMC(APBDEV_PMC_CRYPTO_OP) = PMC_CRYPTO_OP_SE_DISABLE; + TMR(TIMER_WDT4_UNLOCK_PATTERN) = TIMER_MAGIC_PTRN; + TMR(TIMER_TMR9_TMR_PTV) = TIMER_EN | TIMER_PER_EN; + TMR(TIMER_WDT4_CONFIG) = TIMER_SRC(9) | TIMER_PER(1) | TIMER_PMCRESET_EN; + TMR(TIMER_WDT4_COMMAND) = TIMER_START_CNT; + + while (true) + usleep(1); +} + +void power_set_state(power_state_t state) +{ + u8 reg; + + // Unmount and power down sd card. + sd_end(); + + // De-initialize and power down various hardware. + hw_reinit_workaround(false, 0); + + // Stop the alarm, in case we injected and powered off too fast. + max77620_rtc_stop_alarm(); + + // Set power state. + switch (state) + { + case REBOOT_RCM: + PMC(APBDEV_PMC_SCRATCH0) = PMC_SCRATCH0_MODE_RCM; // Enable RCM path. + PMC(APBDEV_PMC_CNTRL) |= PMC_CNTRL_MAIN_RST; // PMC reset. + break; + + case REBOOT_BYPASS_FUSES: + panic(0x21); // Bypass fuse programming in package1. + break; + + case POWER_OFF: + // Initiate power down sequence and do not generate a reset (regulators retain state). + i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_ONOFFCNFG1, MAX77620_ONOFFCNFG1_PWR_OFF); + break; + + case POWER_OFF_RESET: + case POWER_OFF_REBOOT: + default: + // Enable/Disable soft reset wake event. + reg = i2c_recv_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_ONOFFCNFG2); + if (state == POWER_OFF_RESET) // Do not wake up after power off. + reg &= ~(MAX77620_ONOFFCNFG2_SFT_RST_WK | MAX77620_ONOFFCNFG2_WK_ALARM1 | MAX77620_ONOFFCNFG2_WK_ALARM2); + else // POWER_OFF_REBOOT. Wake up after power off. + reg |= MAX77620_ONOFFCNFG2_SFT_RST_WK; + i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_ONOFFCNFG2, reg); + + // Initiate power down sequence and generate a reset (regulators' state resets). + i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_ONOFFCNFG1, MAX77620_ONOFFCNFG1_SFT_RST); + break; + } + + while (true) + bpmp_halt(); +} + +void power_set_state_ex(void *param) +{ + power_state_t *state = (power_state_t *)param; + power_set_state(*state); +} + +//String Replace +#include + +char* str_replace(char* orig, char* rep, char* with) +{ + char* result; // the return string + char* ins; // the next insert point + char* tmp; // varies + int len_rep; // length of rep (the string to remove) + int len_with; // length of with (the string to replace rep with) + int len_front; // distance between rep and end of last rep + int count; // number of replacements + + // sanity checks and initialization + if (!orig || !rep) + return NULL; + len_rep = strlen(rep); + if (len_rep == 0) + return NULL; // empty rep causes infinite loop during count + if (!with) + with = ""; + len_with = strlen(with); + + // count the number of replacements needed + ins = orig; + for (count = 0; (tmp = strstr(ins, rep)); ++count) + { + ins = tmp + len_rep; + } + + tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1); + + if (!result) + return NULL; + + // first time through the loop, all the variable are set correctly + // from here on, + // tmp points to the end of the result string + // ins points to the next occurrence of rep in orig + // orig points to the remainder of orig after "end of rep" + while (count--) + { + ins = strstr(orig, rep); + len_front = ins - orig; + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; // move to next "end of rep" + } + strcpy(tmp, orig); + return result; +} \ No newline at end of file diff --git a/bdk/utils/util.h b/bdk/utils/util.h new file mode 100644 index 00000000..8c2d7f69 --- /dev/null +++ b/bdk/utils/util.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018 naehrwert + * Copyright (c) 2018-2020 CTCaer + * Copyright (c) 2020 Storm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 _UTIL_H_ +#define _UTIL_H_ + +#include +#include + +#define NYX_NEW_INFO 0x3058594E + +typedef enum +{ + REBOOT_RCM, // PMC reset. Enter RCM mode. + REBOOT_BYPASS_FUSES, // PMC reset via watchdog. Enter Normal mode. Bypass fuse programming in package1. + + POWER_OFF, // Power off PMIC. Do not reset regulators. + POWER_OFF_RESET, // Power off PMIC. Reset regulators. + POWER_OFF_REBOOT, // Power off PMIC. Reset regulators. Power on. +} power_state_t; + +typedef enum +{ + NYX_CFG_BIS = BIT(5), + NYX_CFG_UMS = BIT(6), + NYX_CFG_DUMP = BIT(7), +} nyx_cfg_t; + +typedef enum +{ + ERR_LIBSYS_LP0 = BIT(0), + ERR_SYSOLD_NYX = BIT(1), + ERR_LIBSYS_MTC = BIT(2), + ERR_SD_BOOT_EN = BIT(3), + ERR_L4T_KERNEL = BIT(24), + ERR_EXCEPTION = BIT(31), +} hekate_errors_t; + +#define byte_swap_32(num) ((((num) >> 24) & 0xff) | (((num) << 8) & 0xff0000) | \ + (((num) >> 8 )& 0xff00) | (((num) << 24) & 0xff000000)) + +typedef struct _cfg_op_t +{ + u32 off; + u32 val; +} cfg_op_t; + +typedef struct _nyx_info_t +{ + u32 magic; + u32 sd_init; + u32 sd_errors[3]; + u8 rsvd[0x1000]; + u32 disp_id; + u32 errors; +} nyx_info_t; + +typedef struct _nyx_storage_t +{ + u32 version; + u32 cfg; + u8 irama[0x8000]; + u8 hekate[0x30000]; + u8 rsvd[0x800000 - sizeof(nyx_info_t)]; + nyx_info_t info; + mtc_config_t mtc_cfg; + emc_table_t mtc_table[10]; +} nyx_storage_t; + +void exec_cfg(u32 *base, const cfg_op_t *ops, u32 num_ops); +u32 crc32_calc(u32 crc, const u8 *buf, u32 len); + +u32 get_tmr_us(); +u32 get_tmr_ms(); +u32 get_tmr_s(); +void usleep(u32 us); +void msleep(u32 ms); + +void panic(u32 val); +void power_set_state(power_state_t state); +void power_set_state_ex(void *param); + + +//String Replace Replace a pattern of string for another string + +char* str_replace(char* orig, char* rep, char* with); + +#endif