mirror of
https://github.com/erichelgeson/BlueSCSI.git
synced 2024-12-22 12:30:12 +00:00
Merge remote-tracking branch 'jokker/troy/inifile'
This commit is contained in:
commit
142117c83a
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
@ -27,8 +27,5 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade platformio
|
||||
|
||||
- name: Install library dependencies
|
||||
run: pio lib -g install 1
|
||||
|
||||
- name: Run PlatformIO
|
||||
run: pio ci --project-conf platformio.ini .
|
||||
run: pio run -e STM32F1 -e STM32F1-USB -e STM32F1-XCVR -e STM32F1-USB-96MHz -e STM32F1-USB-128MHz
|
||||
|
15
lib/minIni/minGlue.h
Normal file
15
lib/minIni/minGlue.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* Glue functions for the minIni library to SdFat library */
|
||||
|
||||
#include <SdFat.h>
|
||||
|
||||
extern SdFs SD;
|
||||
|
||||
#define INI_READONLY 1
|
||||
#define INI_FILETYPE FsFile
|
||||
#define ini_openread(filename,file) ((file)->open(SD.vol(), filename, O_RDONLY))
|
||||
#define ini_close(file) ((file)->close())
|
||||
#define ini_read(buffer,size,file) ((file)->fgets((buffer),(size)) > 0)
|
||||
|
||||
#define INI_FILEPOS fspos_t
|
||||
#define ini_tell(file,pos) ((file)->fgetpos(pos))
|
||||
#define ini_seek(file,pos) ((file)->fsetpos(pos))
|
952
lib/minIni/minIni.cpp
Normal file
952
lib/minIni/minIni.cpp
Normal file
@ -0,0 +1,952 @@
|
||||
/* minIni - Multi-Platform INI file parser, suitable for embedded systems
|
||||
*
|
||||
* These routines are in part based on the article "Multiplatform .INI Files"
|
||||
* by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal.
|
||||
*
|
||||
* Copyright (c) CompuPhase, 2008-2021
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $
|
||||
*/
|
||||
|
||||
#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
|
||||
# if !defined UNICODE /* for Windows */
|
||||
# define UNICODE
|
||||
# endif
|
||||
# if !defined _UNICODE /* for C library */
|
||||
# define _UNICODE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define MININI_IMPLEMENTATION
|
||||
#include "minIni.h"
|
||||
#if defined NDEBUG
|
||||
#define assert(e)
|
||||
#else
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
#if !defined __T || defined INI_ANSIONLY
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#define TCHAR char
|
||||
#define __T(s) s
|
||||
#define _tcscat strcat
|
||||
#define _tcschr strchr
|
||||
#define _tcscmp strcmp
|
||||
#define _tcscpy strcpy
|
||||
#define _tcsicmp stricmp
|
||||
#define _tcslen strlen
|
||||
#define _tcsncmp strncmp
|
||||
#define _tcsnicmp strnicmp
|
||||
#define _tcsrchr strrchr
|
||||
#define _tcstol strtol
|
||||
#define _tcstod strtod
|
||||
#define _totupper toupper
|
||||
#define _stprintf sprintf
|
||||
#define _tfgets fgets
|
||||
#define _tfputs fputs
|
||||
#define _tfopen fopen
|
||||
#define _tremove remove
|
||||
#define _trename rename
|
||||
#endif
|
||||
|
||||
#if defined __linux || defined __linux__
|
||||
#define __LINUX__
|
||||
#elif defined FREEBSD && !defined __FreeBSD__
|
||||
#define __FreeBSD__
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */
|
||||
#endif
|
||||
#if !defined strnicmp && !defined PORTABLE_STRNICMP
|
||||
#if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__ || defined __GNUC__
|
||||
#define strnicmp strncasecmp
|
||||
#endif
|
||||
#endif
|
||||
#if !defined _totupper
|
||||
#define _totupper toupper
|
||||
#endif
|
||||
|
||||
#if !defined INI_LINETERM
|
||||
#if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__
|
||||
#define INI_LINETERM __T("\n")
|
||||
#else
|
||||
#define INI_LINETERM __T("\r\n")
|
||||
#endif
|
||||
#endif
|
||||
#if !defined INI_FILETYPE
|
||||
#error Missing definition for INI_FILETYPE.
|
||||
#endif
|
||||
|
||||
#if !defined sizearray
|
||||
#define sizearray(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#endif
|
||||
|
||||
enum quote_option {
|
||||
QUOTE_NONE,
|
||||
QUOTE_ENQUOTE,
|
||||
QUOTE_DEQUOTE,
|
||||
};
|
||||
|
||||
#if defined PORTABLE_STRNICMP
|
||||
int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n)
|
||||
{
|
||||
while (n-- != 0 && (*s1 || *s2)) {
|
||||
register int c1, c2;
|
||||
c1 = *s1++;
|
||||
if ('a' <= c1 && c1 <= 'z')
|
||||
c1 += ('A' - 'a');
|
||||
c2 = *s2++;
|
||||
if ('a' <= c2 && c2 <= 'z')
|
||||
c2 += ('A' - 'a');
|
||||
if (c1 != c2)
|
||||
return c1 - c2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* PORTABLE_STRNICMP */
|
||||
|
||||
static TCHAR *skipleading(const TCHAR *str)
|
||||
{
|
||||
assert(str != NULL);
|
||||
while ('\0' < *str && *str <= ' ')
|
||||
str++;
|
||||
return (TCHAR *)str;
|
||||
}
|
||||
|
||||
static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base)
|
||||
{
|
||||
assert(str != NULL);
|
||||
assert(base != NULL);
|
||||
while (str > base && '\0' < *(str-1) && *(str-1) <= ' ')
|
||||
str--;
|
||||
return (TCHAR *)str;
|
||||
}
|
||||
|
||||
static TCHAR *striptrailing(TCHAR *str)
|
||||
{
|
||||
TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str);
|
||||
assert(ptr != NULL);
|
||||
*ptr = '\0';
|
||||
return str;
|
||||
}
|
||||
|
||||
static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option)
|
||||
{
|
||||
size_t d, s;
|
||||
|
||||
assert(maxlen>0);
|
||||
assert(source != NULL && dest != NULL);
|
||||
assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source));
|
||||
if (option == QUOTE_ENQUOTE && maxlen < 3)
|
||||
option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */
|
||||
|
||||
switch (option) {
|
||||
case QUOTE_NONE:
|
||||
for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++)
|
||||
dest[d] = source[d];
|
||||
assert(d < maxlen);
|
||||
dest[d] = '\0';
|
||||
break;
|
||||
case QUOTE_ENQUOTE:
|
||||
d = 0;
|
||||
dest[d++] = '"';
|
||||
for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) {
|
||||
if (source[s] == '"') {
|
||||
if (d >= maxlen - 3)
|
||||
break; /* no space to store the escape character plus the one that follows it */
|
||||
dest[d++] = '\\';
|
||||
}
|
||||
dest[d] = source[s];
|
||||
}
|
||||
dest[d++] = '"';
|
||||
dest[d] = '\0';
|
||||
break;
|
||||
case QUOTE_DEQUOTE:
|
||||
for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) {
|
||||
if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"')
|
||||
s++;
|
||||
dest[d] = source[s];
|
||||
}
|
||||
dest[d] = '\0';
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes)
|
||||
{
|
||||
int isstring;
|
||||
TCHAR *ep;
|
||||
|
||||
assert(string != NULL);
|
||||
assert(quotes != NULL);
|
||||
|
||||
/* Remove a trailing comment */
|
||||
isstring = 0;
|
||||
for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) {
|
||||
if (*ep == '"') {
|
||||
if (*(ep + 1) == '"')
|
||||
ep++; /* skip "" (both quotes) */
|
||||
else
|
||||
isstring = !isstring; /* single quote, toggle isstring */
|
||||
} else if (*ep == '\\' && *(ep + 1) == '"') {
|
||||
ep++; /* skip \" (both quotes */
|
||||
}
|
||||
}
|
||||
assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#'));
|
||||
*ep = '\0'; /* terminate at a comment */
|
||||
striptrailing(string);
|
||||
/* Remove double quotes surrounding a value */
|
||||
*quotes = QUOTE_NONE;
|
||||
if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') {
|
||||
string++;
|
||||
*--ep = '\0';
|
||||
*quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key,
|
||||
int idxSection, int idxKey, TCHAR *Buffer, int BufferSize,
|
||||
INI_FILEPOS *mark)
|
||||
{
|
||||
TCHAR *sp, *ep;
|
||||
int len, idx;
|
||||
enum quote_option quotes;
|
||||
TCHAR LocalBuffer[INI_BUFFERSIZE];
|
||||
|
||||
assert(fp != NULL);
|
||||
/* Move through file 1 line at a time until a section is matched or EOF. If
|
||||
* parameter Section is NULL, only look at keys above the first section. If
|
||||
* idxSection is positive, copy the relevant section name.
|
||||
*/
|
||||
len = (Section != NULL) ? (int)_tcslen(Section) : 0;
|
||||
if (len > 0 || idxSection >= 0) {
|
||||
assert(idxSection >= 0 || Section != NULL);
|
||||
idx = -1;
|
||||
do {
|
||||
do {
|
||||
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp))
|
||||
return 0;
|
||||
sp = skipleading(LocalBuffer);
|
||||
ep = _tcsrchr(sp, ']');
|
||||
} while (*sp != '[' || ep == NULL);
|
||||
/* When arrived here, a section was found; now optionally skip leading and
|
||||
* trailing whitespace.
|
||||
*/
|
||||
assert(sp != NULL && *sp == '[');
|
||||
sp = skipleading(sp + 1);
|
||||
assert(ep != NULL && *ep == ']');
|
||||
ep = skiptrailing(ep, sp);
|
||||
} while ((((int)(ep-sp) != len || Section == NULL || _tcsnicmp(sp, Section, len) != 0) && ++idx != idxSection));
|
||||
if (idxSection >= 0) {
|
||||
if (idx == idxSection) {
|
||||
assert(ep != NULL);
|
||||
*ep = '\0'; /* the end of the section name was found earlier */
|
||||
ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
|
||||
return 1;
|
||||
}
|
||||
return 0; /* no more section found */
|
||||
}
|
||||
}
|
||||
|
||||
/* Now that the section has been found, find the entry.
|
||||
* Stop searching upon leaving the section's area.
|
||||
*/
|
||||
assert(Key != NULL || idxKey >= 0);
|
||||
len = (Key != NULL) ? (int)_tcslen(Key) : 0;
|
||||
idx = -1;
|
||||
do {
|
||||
if (mark != NULL)
|
||||
ini_tell(fp, mark); /* optionally keep the mark to the start of the line */
|
||||
if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[')
|
||||
return 0;
|
||||
sp = skipleading(LocalBuffer);
|
||||
ep = _tcschr(sp, '='); /* Parse out the equal sign */
|
||||
if (ep == NULL)
|
||||
ep = _tcschr(sp, ':');
|
||||
} while (*sp == ';' || *sp == '#' || ep == NULL
|
||||
|| ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey));
|
||||
if (idxKey >= 0) {
|
||||
if (idx == idxKey) {
|
||||
assert(ep != NULL);
|
||||
assert(*ep == '=' || *ep == ':');
|
||||
*ep = '\0';
|
||||
striptrailing(sp);
|
||||
ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
|
||||
return 1;
|
||||
}
|
||||
return 0; /* no more key found (in this section) */
|
||||
}
|
||||
|
||||
/* Copy up to BufferSize chars to buffer */
|
||||
assert(ep != NULL);
|
||||
assert(*ep == '=' || *ep == ':');
|
||||
sp = skipleading(ep + 1);
|
||||
sp = cleanstring(sp, "es); /* Remove a trailing comment */
|
||||
ini_strncpy(Buffer, sp, BufferSize, quotes);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** ini_gets()
|
||||
* \param Section the name of the section to search for
|
||||
* \param Key the name of the entry to find the value of
|
||||
* \param DefValue default string in the event of a failed read
|
||||
* \param Buffer a pointer to the buffer to copy into
|
||||
* \param BufferSize the maximum number of characters to copy
|
||||
* \param Filename the name and full path of the .ini file to read from
|
||||
*
|
||||
* \return the number of characters copied into the supplied buffer
|
||||
*/
|
||||
int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue,
|
||||
TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
|
||||
{
|
||||
INI_FILETYPE fp;
|
||||
int ok = 0;
|
||||
|
||||
if (Buffer == NULL || BufferSize <= 0 || Key == NULL)
|
||||
return 0;
|
||||
if (ini_openread(Filename, &fp)) {
|
||||
ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL);
|
||||
(void)ini_close(&fp);
|
||||
}
|
||||
if (!ok)
|
||||
ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE);
|
||||
return (int)_tcslen(Buffer);
|
||||
}
|
||||
|
||||
/** ini_getl()
|
||||
* \param Section the name of the section to search for
|
||||
* \param Key the name of the entry to find the value of
|
||||
* \param DefValue the default value in the event of a failed read
|
||||
* \param Filename the name of the .ini file to read from
|
||||
*
|
||||
* \return the value located at Key
|
||||
*/
|
||||
long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[64];
|
||||
int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
|
||||
return (len == 0) ? DefValue
|
||||
: ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16)
|
||||
: _tcstol(LocalBuffer, NULL, 10));
|
||||
}
|
||||
|
||||
#if defined INI_REAL
|
||||
/** ini_getf()
|
||||
* \param Section the name of the section to search for
|
||||
* \param Key the name of the entry to find the value of
|
||||
* \param DefValue the default value in the event of a failed read
|
||||
* \param Filename the name of the .ini file to read from
|
||||
*
|
||||
* \return the value located at Key
|
||||
*/
|
||||
INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[64];
|
||||
int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
|
||||
return (len == 0) ? DefValue : ini_atof(LocalBuffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** ini_getbool()
|
||||
* \param Section the name of the section to search for
|
||||
* \param Key the name of the entry to find the value of
|
||||
* \param DefValue default value in the event of a failed read; it should
|
||||
* zero (0) or one (1).
|
||||
* \param Filename the name and full path of the .ini file to read from
|
||||
*
|
||||
* A true boolean is found if one of the following is matched:
|
||||
* - A string starting with 'y' or 'Y'
|
||||
* - A string starting with 't' or 'T'
|
||||
* - A string starting with '1'
|
||||
*
|
||||
* A false boolean is found if one of the following is matched:
|
||||
* - A string starting with 'n' or 'N'
|
||||
* - A string starting with 'f' or 'F'
|
||||
* - A string starting with '0'
|
||||
*
|
||||
* \return the true/false flag as interpreted at Key
|
||||
*/
|
||||
int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[2] = __T("");
|
||||
int ret;
|
||||
|
||||
ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
|
||||
LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]);
|
||||
if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T')
|
||||
ret = 1;
|
||||
else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F')
|
||||
ret = 0;
|
||||
else
|
||||
ret = DefValue;
|
||||
|
||||
return(ret);
|
||||
}
|
||||
|
||||
/** ini_getsection()
|
||||
* \param idx the zero-based sequence number of the section to return
|
||||
* \param Buffer a pointer to the buffer to copy into
|
||||
* \param BufferSize the maximum number of characters to copy
|
||||
* \param Filename the name and full path of the .ini file to read from
|
||||
*
|
||||
* \return the number of characters copied into the supplied buffer
|
||||
*/
|
||||
int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
|
||||
{
|
||||
INI_FILETYPE fp;
|
||||
int ok = 0;
|
||||
|
||||
if (Buffer == NULL || BufferSize <= 0 || idx < 0)
|
||||
return 0;
|
||||
if (ini_openread(Filename, &fp)) {
|
||||
ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL);
|
||||
(void)ini_close(&fp);
|
||||
}
|
||||
if (!ok)
|
||||
*Buffer = '\0';
|
||||
return (int)_tcslen(Buffer);
|
||||
}
|
||||
|
||||
/** ini_getkey()
|
||||
* \param Section the name of the section to browse through, or NULL to
|
||||
* browse through the keys outside any section
|
||||
* \param idx the zero-based sequence number of the key to return
|
||||
* \param Buffer a pointer to the buffer to copy into
|
||||
* \param BufferSize the maximum number of characters to copy
|
||||
* \param Filename the name and full path of the .ini file to read from
|
||||
*
|
||||
* \return the number of characters copied into the supplied buffer
|
||||
*/
|
||||
int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
|
||||
{
|
||||
INI_FILETYPE fp;
|
||||
int ok = 0;
|
||||
|
||||
if (Buffer == NULL || BufferSize <= 0 || idx < 0)
|
||||
return 0;
|
||||
if (ini_openread(Filename, &fp)) {
|
||||
ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL);
|
||||
(void)ini_close(&fp);
|
||||
}
|
||||
if (!ok)
|
||||
*Buffer = '\0';
|
||||
return (int)_tcslen(Buffer);
|
||||
}
|
||||
|
||||
/** ini_hassection()
|
||||
* \param Section the name of the section to search for
|
||||
* \param Filename the name of the .ini file to read from
|
||||
*
|
||||
* \return 1 if the section is found, 0 if not found
|
||||
*/
|
||||
int ini_hassection(const mTCHAR *Section, const mTCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[32]; /* dummy buffer */
|
||||
INI_FILETYPE fp;
|
||||
int ok = 0;
|
||||
|
||||
if (ini_openread(Filename, &fp)) {
|
||||
ok = getkeystring(&fp, Section, NULL, -1, 0, LocalBuffer, sizearray(LocalBuffer), NULL);
|
||||
(void)ini_close(&fp);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/** ini_haskey()
|
||||
* \param Section the name of the section to search for
|
||||
* \param Key the name of the entry to find the value of
|
||||
* \param Filename the name of the .ini file to read from
|
||||
*
|
||||
* \return 1 if the key is found, 0 if not found
|
||||
*/
|
||||
int ini_haskey(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[32]; /* dummy buffer */
|
||||
INI_FILETYPE fp;
|
||||
int ok = 0;
|
||||
|
||||
if (ini_openread(Filename, &fp)) {
|
||||
ok = getkeystring(&fp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL);
|
||||
(void)ini_close(&fp);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
#if !defined INI_NOBROWSE
|
||||
/** ini_browse()
|
||||
* \param Callback a pointer to a function that will be called for every
|
||||
* setting in the INI file.
|
||||
* \param UserData arbitrary data, which the function passes on the
|
||||
* \c Callback function
|
||||
* \param Filename the name and full path of the .ini file to read from
|
||||
*
|
||||
* \return 1 on success, 0 on failure (INI file not found)
|
||||
*
|
||||
* \note The \c Callback function must return 1 to continue
|
||||
* browsing through the INI file, or 0 to stop. Even when the
|
||||
* callback stops the browsing, this function will return 1
|
||||
* (for success).
|
||||
*/
|
||||
int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[INI_BUFFERSIZE];
|
||||
int lenSec, lenKey;
|
||||
enum quote_option quotes;
|
||||
INI_FILETYPE fp;
|
||||
|
||||
if (Callback == NULL)
|
||||
return 0;
|
||||
if (!ini_openread(Filename, &fp))
|
||||
return 0;
|
||||
|
||||
LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */
|
||||
lenSec = (int)_tcslen(LocalBuffer) + 1;
|
||||
for ( ;; ) {
|
||||
TCHAR *sp, *ep;
|
||||
if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp))
|
||||
break;
|
||||
sp = skipleading(LocalBuffer + lenSec);
|
||||
/* ignore empty strings and comments */
|
||||
if (*sp == '\0' || *sp == ';' || *sp == '#')
|
||||
continue;
|
||||
/* see whether we reached a new section */
|
||||
ep = _tcsrchr(sp, ']');
|
||||
if (*sp == '[' && ep != NULL) {
|
||||
sp = skipleading(sp + 1);
|
||||
ep = skiptrailing(ep, sp);
|
||||
*ep = '\0';
|
||||
ini_strncpy(LocalBuffer, sp, INI_BUFFERSIZE, QUOTE_NONE);
|
||||
lenSec = (int)_tcslen(LocalBuffer) + 1;
|
||||
continue;
|
||||
}
|
||||
/* not a new section, test for a key/value pair */
|
||||
ep = _tcschr(sp, '='); /* test for the equal sign or colon */
|
||||
if (ep == NULL)
|
||||
ep = _tcschr(sp, ':');
|
||||
if (ep == NULL)
|
||||
continue; /* invalid line, ignore */
|
||||
*ep++ = '\0'; /* split the key from the value */
|
||||
striptrailing(sp);
|
||||
ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE);
|
||||
lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1;
|
||||
/* clean up the value */
|
||||
sp = skipleading(ep);
|
||||
sp = cleanstring(sp, "es); /* Remove a trailing comment */
|
||||
ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes);
|
||||
/* call the callback */
|
||||
if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData))
|
||||
break;
|
||||
}
|
||||
|
||||
(void)ini_close(&fp);
|
||||
return 1;
|
||||
}
|
||||
#endif /* INI_NOBROWSE */
|
||||
|
||||
#if ! defined INI_READONLY
|
||||
static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength)
|
||||
{
|
||||
TCHAR *p;
|
||||
|
||||
ini_strncpy(dest, source, maxlength, QUOTE_NONE);
|
||||
p = _tcschr(dest, '\0');
|
||||
assert(p != NULL);
|
||||
*(p - 1) = '~';
|
||||
}
|
||||
|
||||
static enum quote_option check_enquote(const TCHAR *Value)
|
||||
{
|
||||
const TCHAR *p;
|
||||
|
||||
/* run through the value, if it has trailing spaces, or '"', ';' or '#'
|
||||
* characters, enquote it
|
||||
*/
|
||||
assert(Value != NULL);
|
||||
for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++)
|
||||
/* nothing */;
|
||||
return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE;
|
||||
}
|
||||
|
||||
static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp)
|
||||
{
|
||||
if (Section != NULL && _tcslen(Section) > 0) {
|
||||
TCHAR *p;
|
||||
LocalBuffer[0] = '[';
|
||||
ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */
|
||||
p = _tcschr(LocalBuffer, '\0');
|
||||
assert(p != NULL);
|
||||
*p++ = ']';
|
||||
_tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
|
||||
if (fp != NULL)
|
||||
(void)ini_write(LocalBuffer, fp);
|
||||
}
|
||||
}
|
||||
|
||||
static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp)
|
||||
{
|
||||
TCHAR *p;
|
||||
enum quote_option option = check_enquote(Value);
|
||||
ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */
|
||||
p = _tcschr(LocalBuffer, '\0');
|
||||
assert(p != NULL);
|
||||
*p++ = '=';
|
||||
ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */
|
||||
p = _tcschr(LocalBuffer, '\0');
|
||||
assert(p != NULL);
|
||||
_tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
|
||||
if (fp != NULL)
|
||||
(void)ini_write(LocalBuffer, fp);
|
||||
}
|
||||
|
||||
static int cache_accum(const TCHAR *string, int *size, int max)
|
||||
{
|
||||
int len = (int)_tcslen(string);
|
||||
if (*size + len >= max)
|
||||
return 0;
|
||||
*size += len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int cache_flush(TCHAR *buffer, int *size,
|
||||
INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark)
|
||||
{
|
||||
int terminator_len = (int)_tcslen(INI_LINETERM);
|
||||
int pos = 0, pos_prev = -1;
|
||||
|
||||
(void)ini_seek(rfp, mark);
|
||||
assert(buffer != NULL);
|
||||
buffer[0] = '\0';
|
||||
assert(size != NULL);
|
||||
assert(*size <= INI_BUFFERSIZE);
|
||||
while (pos < *size && pos != pos_prev) {
|
||||
pos_prev = pos; /* to guard against zero bytes in the INI file */
|
||||
(void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp);
|
||||
while (pos < *size && buffer[pos] != '\0')
|
||||
pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */
|
||||
}
|
||||
if (buffer[0] != '\0') {
|
||||
assert(pos > 0 && pos <= INI_BUFFERSIZE);
|
||||
if (pos == INI_BUFFERSIZE)
|
||||
pos--;
|
||||
buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */
|
||||
(void)ini_write(buffer, wfp);
|
||||
}
|
||||
ini_tell(rfp, mark); /* update mark */
|
||||
*size = 0;
|
||||
/* return whether the buffer ended with a line termination */
|
||||
return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0);
|
||||
}
|
||||
|
||||
static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer)
|
||||
{
|
||||
(void)ini_close(rfp);
|
||||
(void)ini_close(wfp);
|
||||
(void)ini_tempname(buffer, filename, INI_BUFFERSIZE);
|
||||
#if defined ini_remove || defined INI_REMOVE
|
||||
(void)ini_remove(filename);
|
||||
#endif
|
||||
(void)ini_rename(buffer, filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** ini_puts()
|
||||
* \param Section the name of the section to write the string in
|
||||
* \param Key the name of the entry to write, or NULL to erase all keys in the section
|
||||
* \param Value a pointer to the buffer the string, or NULL to erase the key
|
||||
* \param Filename the name and full path of the .ini file to write to
|
||||
*
|
||||
* \return 1 if successful, otherwise 0
|
||||
*/
|
||||
int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename)
|
||||
{
|
||||
INI_FILETYPE rfp;
|
||||
INI_FILETYPE wfp;
|
||||
INI_FILEPOS mark;
|
||||
INI_FILEPOS head, tail;
|
||||
TCHAR *sp, *ep;
|
||||
TCHAR LocalBuffer[INI_BUFFERSIZE];
|
||||
int len, match, flag, cachelen;
|
||||
|
||||
assert(Filename != NULL);
|
||||
if (!ini_openread(Filename, &rfp)) {
|
||||
/* If the .ini file doesn't exist, make a new file */
|
||||
if (Key != NULL && Value != NULL) {
|
||||
if (!ini_openwrite(Filename, &wfp))
|
||||
return 0;
|
||||
writesection(LocalBuffer, Section, &wfp);
|
||||
writekey(LocalBuffer, Key, Value, &wfp);
|
||||
(void)ini_close(&wfp);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* If parameters Key and Value are valid (so this is not an "erase" request)
|
||||
* and the setting already exists, there are two short-cuts to avoid rewriting
|
||||
* the INI file.
|
||||
*/
|
||||
if (Key != NULL && Value != NULL) {
|
||||
match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head);
|
||||
if (match) {
|
||||
/* if the current setting is identical to the one to write, there is
|
||||
* nothing to do.
|
||||
*/
|
||||
if (_tcscmp(LocalBuffer,Value) == 0) {
|
||||
(void)ini_close(&rfp);
|
||||
return 1;
|
||||
}
|
||||
/* if the new setting has the same length as the current setting, and the
|
||||
* glue file permits file read/write access, we can modify in place.
|
||||
*/
|
||||
#if defined ini_openrewrite || defined INI_OPENREWRITE
|
||||
/* we already have the start of the (raw) line, get the end too */
|
||||
ini_tell(&rfp, &tail);
|
||||
/* create new buffer (without writing it to file) */
|
||||
writekey(LocalBuffer, Key, Value, NULL);
|
||||
if (_tcslen(LocalBuffer) == (size_t)(tail - head)) {
|
||||
/* length matches, close the file & re-open for read/write, then
|
||||
* write at the correct position
|
||||
*/
|
||||
(void)ini_close(&rfp);
|
||||
if (!ini_openrewrite(Filename, &wfp))
|
||||
return 0;
|
||||
(void)ini_seek(&wfp, &head);
|
||||
(void)ini_write(LocalBuffer, &wfp);
|
||||
(void)ini_close(&wfp);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/* key not found, or different value & length -> proceed */
|
||||
} else if (Key != NULL && Value == NULL) {
|
||||
/* Conversely, for a request to delete a setting; if that setting isn't
|
||||
present, just return */
|
||||
match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL);
|
||||
if (!match) {
|
||||
(void)ini_close(&rfp);
|
||||
return 1;
|
||||
}
|
||||
/* key found -> proceed to delete it */
|
||||
}
|
||||
|
||||
/* Get a temporary file name to copy to. Use the existing name, but with
|
||||
* the last character set to a '~'.
|
||||
*/
|
||||
(void)ini_close(&rfp);
|
||||
ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE);
|
||||
if (!ini_openwrite(LocalBuffer, &wfp))
|
||||
return 0;
|
||||
/* In the case of (advisory) file locks, ini_openwrite() may have been blocked
|
||||
* on the open, and after the block is lifted, the original file may have been
|
||||
* renamed, which is why the original file was closed and is now reopened */
|
||||
if (!ini_openread(Filename, &rfp)) {
|
||||
/* If the .ini file doesn't exist any more, make a new file */
|
||||
assert(Key != NULL && Value != NULL);
|
||||
writesection(LocalBuffer, Section, &wfp);
|
||||
writekey(LocalBuffer, Key, Value, &wfp);
|
||||
(void)ini_close(&wfp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
(void)ini_tell(&rfp, &mark);
|
||||
cachelen = 0;
|
||||
|
||||
/* Move through the file one line at a time until a section is
|
||||
* matched or until EOF. Copy to temp file as it is read.
|
||||
*/
|
||||
len = (Section != NULL) ? (int)_tcslen(Section) : 0;
|
||||
if (len > 0) {
|
||||
do {
|
||||
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
|
||||
/* Failed to find section, so add one to the end */
|
||||
flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
if (Key!=NULL && Value!=NULL) {
|
||||
if (!flag)
|
||||
(void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
|
||||
writesection(LocalBuffer, Section, &wfp);
|
||||
writekey(LocalBuffer, Key, Value, &wfp);
|
||||
}
|
||||
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
|
||||
}
|
||||
/* Check whether this line is a section */
|
||||
sp = skipleading(LocalBuffer);
|
||||
ep = _tcsrchr(sp, ']');
|
||||
match = (*sp == '[' && ep != NULL);
|
||||
if (match) {
|
||||
/* A section was found, skip leading and trailing whitespace */
|
||||
assert(sp != NULL && *sp == '[');
|
||||
sp = skipleading(sp + 1);
|
||||
assert(ep != NULL && *ep == ']');
|
||||
ep = skiptrailing(ep, sp);
|
||||
match = ((int)(ep-sp) == len && _tcsnicmp(sp, Section, len) == 0);
|
||||
}
|
||||
/* Copy the line from source to dest, but not if this is the section that
|
||||
* we are looking for and this section must be removed
|
||||
*/
|
||||
if (!match || Key != NULL) {
|
||||
if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
|
||||
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
|
||||
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
|
||||
}
|
||||
}
|
||||
} while (!match);
|
||||
}
|
||||
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
/* when deleting a section, the section head that was just found has not been
|
||||
* copied to the output file, but because this line was not "accumulated" in
|
||||
* the cache, the position in the input file was reset to the point just
|
||||
* before the section; this must now be skipped (again)
|
||||
*/
|
||||
if (Key == NULL) {
|
||||
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
|
||||
(void)ini_tell(&rfp, &mark);
|
||||
}
|
||||
|
||||
/* Now that the section has been found, find the entry. Stop searching
|
||||
* upon leaving the section's area. Copy the file as it is read
|
||||
* and create an entry if one is not found.
|
||||
*/
|
||||
len = (Key != NULL) ? (int)_tcslen(Key) : 0;
|
||||
for( ;; ) {
|
||||
if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
|
||||
/* EOF without an entry so make one */
|
||||
flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
if (Key!=NULL && Value!=NULL) {
|
||||
if (!flag)
|
||||
(void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
|
||||
writekey(LocalBuffer, Key, Value, &wfp);
|
||||
}
|
||||
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
|
||||
}
|
||||
sp = skipleading(LocalBuffer);
|
||||
ep = _tcschr(sp, '='); /* Parse out the equal sign */
|
||||
if (ep == NULL)
|
||||
ep = _tcschr(sp, ':');
|
||||
match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0);
|
||||
if ((Key != NULL && match) || *sp == '[')
|
||||
break; /* found the key, or found a new section */
|
||||
/* copy other keys in the section */
|
||||
if (Key == NULL) {
|
||||
(void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */
|
||||
} else {
|
||||
if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
|
||||
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
|
||||
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* the key was found, or we just dropped on the next section (meaning that it
|
||||
* wasn't found); in both cases we need to write the key, but in the latter
|
||||
* case, we also need to write the line starting the new section after writing
|
||||
* the key
|
||||
*/
|
||||
flag = (*sp == '[');
|
||||
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
if (Key != NULL && Value != NULL)
|
||||
writekey(LocalBuffer, Key, Value, &wfp);
|
||||
/* cache_flush() reset the "read pointer" to the start of the line with the
|
||||
* previous key or the new section; read it again (because writekey() destroyed
|
||||
* the buffer)
|
||||
*/
|
||||
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
|
||||
if (flag) {
|
||||
/* the new section heading needs to be copied to the output file */
|
||||
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
|
||||
} else {
|
||||
/* forget the old key line */
|
||||
(void)ini_tell(&rfp, &mark);
|
||||
}
|
||||
/* Copy the rest of the INI file */
|
||||
while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
|
||||
if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
|
||||
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
(void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
|
||||
cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
|
||||
}
|
||||
}
|
||||
cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
|
||||
return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
|
||||
}
|
||||
|
||||
/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */
|
||||
#define ABS(v) ((v) < 0 ? -(v) : (v))
|
||||
|
||||
static void strreverse(TCHAR *str)
|
||||
{
|
||||
int i, j;
|
||||
for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) {
|
||||
TCHAR t = str[i];
|
||||
str[i] = str[j];
|
||||
str[j] = t;
|
||||
}
|
||||
}
|
||||
|
||||
static void long2str(long value, TCHAR *str)
|
||||
{
|
||||
int i = 0;
|
||||
long sign = value;
|
||||
|
||||
/* generate digits in reverse order */
|
||||
do {
|
||||
int n = (int)(value % 10); /* get next lowest digit */
|
||||
str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */
|
||||
} while (value /= 10); /* delete the lowest digit */
|
||||
if (sign < 0)
|
||||
str[i++] = '-';
|
||||
str[i] = '\0';
|
||||
|
||||
strreverse(str);
|
||||
}
|
||||
|
||||
/** ini_putl()
|
||||
* \param Section the name of the section to write the value in
|
||||
* \param Key the name of the entry to write
|
||||
* \param Value the value to write
|
||||
* \param Filename the name and full path of the .ini file to write to
|
||||
*
|
||||
* \return 1 if successful, otherwise 0
|
||||
*/
|
||||
int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[32];
|
||||
long2str(Value, LocalBuffer);
|
||||
return ini_puts(Section, Key, LocalBuffer, Filename);
|
||||
}
|
||||
|
||||
#if defined INI_REAL
|
||||
/** ini_putf()
|
||||
* \param Section the name of the section to write the value in
|
||||
* \param Key the name of the entry to write
|
||||
* \param Value the value to write
|
||||
* \param Filename the name and full path of the .ini file to write to
|
||||
*
|
||||
* \return 1 if successful, otherwise 0
|
||||
*/
|
||||
int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename)
|
||||
{
|
||||
TCHAR LocalBuffer[64];
|
||||
ini_ftoa(LocalBuffer, Value);
|
||||
return ini_puts(Section, Key, LocalBuffer, Filename);
|
||||
}
|
||||
#endif /* INI_REAL */
|
||||
#endif /* !INI_READONLY */
|
166
lib/minIni/minIni.h
Normal file
166
lib/minIni/minIni.h
Normal file
@ -0,0 +1,166 @@
|
||||
/* minIni - Multi-Platform INI file parser, suitable for embedded systems
|
||||
*
|
||||
* Copyright (c) CompuPhase, 2008-2021
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy
|
||||
* of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $
|
||||
*/
|
||||
#ifndef MININI_H
|
||||
#define MININI_H
|
||||
|
||||
#include "minGlue.h"
|
||||
|
||||
#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
|
||||
#include <tchar.h>
|
||||
#define mTCHAR TCHAR
|
||||
#else
|
||||
/* force TCHAR to be "char", but only for minIni */
|
||||
#define mTCHAR char
|
||||
#endif
|
||||
|
||||
#if !defined INI_BUFFERSIZE
|
||||
#define INI_BUFFERSIZE 512
|
||||
#endif
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int ini_getbool(const mTCHAR *Section, const mTCHAR *Key, int DefValue, const mTCHAR *Filename);
|
||||
long ini_getl(const mTCHAR *Section, const mTCHAR *Key, long DefValue, const mTCHAR *Filename);
|
||||
int ini_gets(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *DefValue, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
|
||||
int ini_getsection(int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
|
||||
int ini_getkey(const mTCHAR *Section, int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename);
|
||||
|
||||
int ini_hassection(const mTCHAR *Section, const mTCHAR *Filename);
|
||||
int ini_haskey(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Filename);
|
||||
|
||||
#if defined INI_REAL
|
||||
INI_REAL ini_getf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL DefValue, const mTCHAR *Filename);
|
||||
#endif
|
||||
|
||||
#if !defined INI_READONLY
|
||||
int ini_putl(const mTCHAR *Section, const mTCHAR *Key, long Value, const mTCHAR *Filename);
|
||||
int ini_puts(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, const mTCHAR *Filename);
|
||||
#if defined INI_REAL
|
||||
int ini_putf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL Value, const mTCHAR *Filename);
|
||||
#endif
|
||||
#endif /* INI_READONLY */
|
||||
|
||||
#if !defined INI_NOBROWSE
|
||||
typedef int (*INI_CALLBACK)(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData);
|
||||
int ini_browse(INI_CALLBACK Callback, void *UserData, const mTCHAR *Filename);
|
||||
#endif /* INI_NOBROWSE */
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined __cplusplus
|
||||
|
||||
#if defined __WXWINDOWS__
|
||||
#include "wxMinIni.h"
|
||||
#else
|
||||
#include <string>
|
||||
|
||||
/* The C++ class in minIni.h was contributed by Steven Van Ingelgem. */
|
||||
class minIni
|
||||
{
|
||||
public:
|
||||
minIni(const std::string& filename) : iniFilename(filename)
|
||||
{ }
|
||||
|
||||
bool getbool(const std::string& Section, const std::string& Key, bool DefValue=false) const
|
||||
{ return ini_getbool(Section.c_str(), Key.c_str(), int(DefValue), iniFilename.c_str()) != 0; }
|
||||
|
||||
long getl(const std::string& Section, const std::string& Key, long DefValue=0) const
|
||||
{ return ini_getl(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); }
|
||||
|
||||
int geti(const std::string& Section, const std::string& Key, int DefValue=0) const
|
||||
{ return static_cast<int>(this->getl(Section, Key, long(DefValue))); }
|
||||
|
||||
std::string gets(const std::string& Section, const std::string& Key, const std::string& DefValue="") const
|
||||
{
|
||||
char buffer[INI_BUFFERSIZE];
|
||||
ini_gets(Section.c_str(), Key.c_str(), DefValue.c_str(), buffer, INI_BUFFERSIZE, iniFilename.c_str());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string getsection(int idx) const
|
||||
{
|
||||
char buffer[INI_BUFFERSIZE];
|
||||
ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.c_str());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string getkey(const std::string& Section, int idx) const
|
||||
{
|
||||
char buffer[INI_BUFFERSIZE];
|
||||
ini_getkey(Section.c_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.c_str());
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool hassection(const std::string& Section) const
|
||||
{ return ini_hassection(Section.c_str(), iniFilename.c_str()) != 0; }
|
||||
|
||||
bool haskey(const std::string& Section, const std::string& Key) const
|
||||
{ return ini_haskey(Section.c_str(), Key.c_str(), iniFilename.c_str()) != 0; }
|
||||
|
||||
#if defined INI_REAL
|
||||
INI_REAL getf(const std::string& Section, const std::string& Key, INI_REAL DefValue=0) const
|
||||
{ return ini_getf(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); }
|
||||
#endif
|
||||
|
||||
#if ! defined INI_READONLY
|
||||
bool put(const std::string& Section, const std::string& Key, long Value)
|
||||
{ return ini_putl(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; }
|
||||
|
||||
bool put(const std::string& Section, const std::string& Key, int Value)
|
||||
{ return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; }
|
||||
|
||||
bool put(const std::string& Section, const std::string& Key, bool Value)
|
||||
{ return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; }
|
||||
|
||||
bool put(const std::string& Section, const std::string& Key, const std::string& Value)
|
||||
{ return ini_puts(Section.c_str(), Key.c_str(), Value.c_str(), iniFilename.c_str()) != 0; }
|
||||
|
||||
bool put(const std::string& Section, const std::string& Key, const char* Value)
|
||||
{ return ini_puts(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; }
|
||||
|
||||
#if defined INI_REAL
|
||||
bool put(const std::string& Section, const std::string& Key, INI_REAL Value)
|
||||
{ return ini_putf(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; }
|
||||
#endif
|
||||
|
||||
bool del(const std::string& Section, const std::string& Key)
|
||||
{ return ini_puts(Section.c_str(), Key.c_str(), 0, iniFilename.c_str()) != 0; }
|
||||
|
||||
bool del(const std::string& Section)
|
||||
{ return ini_puts(Section.c_str(), 0, 0, iniFilename.c_str()) != 0; }
|
||||
#endif
|
||||
|
||||
#if !defined INI_NOBROWSE
|
||||
bool browse(INI_CALLBACK Callback, void *UserData) const
|
||||
{ return ini_browse(Callback, UserData, iniFilename.c_str()) != 0; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::string iniFilename;
|
||||
};
|
||||
|
||||
#endif /* __WXWINDOWS__ */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MININI_H */
|
281
src/BlueSCSI.cpp
281
src/BlueSCSI.cpp
@ -52,6 +52,7 @@
|
||||
#include "scsi_sense.h"
|
||||
#include "scsi_status.h"
|
||||
#include "scsi_mode.h"
|
||||
#include "minIni.h"
|
||||
|
||||
#ifdef USE_STM32_DMA
|
||||
#warning "warning USE_STM32_DMA"
|
||||
@ -64,22 +65,16 @@ FsFile LOG_FILE;
|
||||
volatile bool m_isBusReset = false; // Bus reset
|
||||
volatile bool m_resetJmp = false; // Call longjmp on reset
|
||||
jmp_buf m_resetJmpBuf;
|
||||
|
||||
byte scsi_id_mask; // Mask list of responding SCSI IDs
|
||||
byte m_id; // Currently responding SCSI-ID
|
||||
byte m_lun; // Logical unit number currently responding
|
||||
byte m_sts; // Status byte
|
||||
byte m_msg; // Message bytes
|
||||
byte m_buf[MAX_BLOCKSIZE]; // General purpose buffer
|
||||
byte m_scsi_buf[SCSI_BUF_SIZE]; // Buffer for SCSI READ/WRITE Buffer
|
||||
unsigned m_scsi_buf_size = 0;
|
||||
byte m_msb[256]; // Command storage bytes
|
||||
|
||||
SCSI_DEVICE scsi_device_list[NUM_SCSIID][NUM_SCSILUN]; // Maximum number
|
||||
SCSI_INQUIRY_DATA default_hdd, default_optical;
|
||||
|
||||
// Enables SCSI IDs to be representing as LUNs on SCSI ID 0
|
||||
// This supports a specific case for the Atari MegaSTE internal SCSI adapter
|
||||
bool megaste_mode = false;
|
||||
bool ids_as_luns = false;
|
||||
|
||||
// function table
|
||||
byte (*scsi_command_table[MAX_SCSI_COMMAND])(SCSI_DEVICE *dev, const byte *cdb);
|
||||
@ -112,7 +107,7 @@ static void LBAtoMSF(const uint32_t lba, byte *msf);
|
||||
|
||||
static void flashError(const unsigned error);
|
||||
void onBusReset(void);
|
||||
void initFileLog(int);
|
||||
void initFileLog(void);
|
||||
void finalizeFileLog(void);
|
||||
void findDriveImages(FsFile root);
|
||||
|
||||
@ -132,67 +127,106 @@ inline byte readIO(void)
|
||||
return bret;
|
||||
}
|
||||
|
||||
// If config file exists, read the first three lines and copy the contents.
|
||||
// File must be well formed or you will get junk in the SCSI Vendor fields.
|
||||
void readSCSIDeviceConfig(SCSI_DEVICE *dev) {
|
||||
FsFile config_file = SD.open("scsi-config.txt", O_RDONLY);
|
||||
if (!config_file.isOpen()) {
|
||||
// Read config file for per device settings
|
||||
void readSCSIDeviceConfig(uint8_t scsi_id, SCSI_DEVICE *dev) {
|
||||
SCSI_INQUIRY_DATA *iq = &dev->inquiry_block;
|
||||
char section[6] = {'S', 'C', 'S', 'I', 0, 0};
|
||||
FsFile config_file;
|
||||
char *buf = (char *)&m_scsi_buf;
|
||||
|
||||
// check for bluescsi.ini
|
||||
if(!SD.exists(BLUESCSI_INI)) {
|
||||
return;
|
||||
}
|
||||
SCSI_INQUIRY_DATA *iq = dev->inquiry_block;
|
||||
|
||||
char vendor[9];
|
||||
memset(vendor, 0, sizeof(vendor));
|
||||
config_file.readBytes(vendor, sizeof(vendor));
|
||||
LOG_FILE.print("SCSI VENDOR: ");
|
||||
LOG_FILE.println(vendor);
|
||||
memcpy(&iq->vendor, vendor, 8);
|
||||
// create section name from id
|
||||
section[4] = INT_TO_CHAR(scsi_id);
|
||||
|
||||
char product[17];
|
||||
memset(product, 0, sizeof(product));
|
||||
config_file.readBytes(product, sizeof(product));
|
||||
LOG_FILE.print("SCSI PRODUCT: ");
|
||||
LOG_FILE.println(product);
|
||||
memcpy(&iq->product, product, 16);
|
||||
switch(ini_getl(section, "type", 99, BLUESCSI_INI))
|
||||
{
|
||||
case 0:
|
||||
dev->m_type = SCSI_DEVICE_HDD;
|
||||
memcpy(iq, &default_hdd, sizeof(default_hdd));
|
||||
LOG_FILE.println("Forced HDD");
|
||||
break;
|
||||
|
||||
char version[5];
|
||||
memset(version, 0, sizeof(version));
|
||||
config_file.readBytes(version, sizeof(version));
|
||||
LOG_FILE.print("SCSI VERSION: ");
|
||||
LOG_FILE.println(version);
|
||||
memcpy(&iq->revision, version, 4);
|
||||
config_file.close();
|
||||
case 2:
|
||||
dev->m_type = SCSI_DEVICE_OPTICAL;
|
||||
memcpy(iq, &default_optical, sizeof(default_optical));
|
||||
LOG_FILE.println("Forced Optical");
|
||||
break;
|
||||
|
||||
case 99:
|
||||
// default, do nothing at all
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_FILE.println("Unsupported override type");
|
||||
}
|
||||
|
||||
if(ini_gets(section, "vendor", NULL, buf, SCSI_BUF_SIZE, BLUESCSI_INI)) {
|
||||
memcpy(iq->vendor, buf, SCSI_VENDOR_LENGTH);
|
||||
LOG_FILE.print("vendor:");
|
||||
LOG_FILE.println(buf);
|
||||
}
|
||||
|
||||
if(ini_gets(section, "product", NULL, buf, SCSI_BUF_SIZE, BLUESCSI_INI)) {
|
||||
memcpy(iq->product, buf, SCSI_PRODUCT_LENGTH);
|
||||
LOG_FILE.print("product:");
|
||||
LOG_FILE.println(buf);
|
||||
}
|
||||
|
||||
if(ini_gets(section, "revision", NULL, buf, SCSI_BUF_SIZE, BLUESCSI_INI)) {
|
||||
memcpy(iq->revision, buf, SCSI_REVISION_LENGTH);
|
||||
LOG_FILE.print("revision:");
|
||||
LOG_FILE.println(buf);
|
||||
}
|
||||
}
|
||||
|
||||
// read SD information and print to logfile
|
||||
void readSDCardInfo()
|
||||
void readSDCardInfo(int success_mhz)
|
||||
{
|
||||
cid_t sd_cid;
|
||||
|
||||
LOG_FILE.println("SDCard Info:");
|
||||
LOG_FILE.print(" Format:");
|
||||
switch(SD.vol()->fatType()) {
|
||||
case FAT_TYPE_EXFAT:
|
||||
LOG_FILE.println("exFAT");
|
||||
break;
|
||||
default:
|
||||
LOG_FILE.print("FAT32/16/12 - exFAT may improve performance");
|
||||
}
|
||||
LOG_FILE.print("SPI speed: ");
|
||||
LOG_FILE.print(success_mhz);
|
||||
LOG_FILE.println("Mhz");
|
||||
LOG_FILE.print(" Max Filename Length:");
|
||||
LOG_FILE.println(MAX_FILE_PATH);
|
||||
|
||||
if(SD.card()->readCID(&sd_cid))
|
||||
{
|
||||
LOG_FILE.print("Sd MID:");
|
||||
LOG_FILE.print(" MID:");
|
||||
LOG_FILE.print(sd_cid.mid, 16);
|
||||
LOG_FILE.print(" OID:");
|
||||
LOG_FILE.print(sd_cid.oid[0]);
|
||||
LOG_FILE.println(sd_cid.oid[1]);
|
||||
|
||||
LOG_FILE.print("Sd Name:");
|
||||
LOG_FILE.print(" Name:");
|
||||
LOG_FILE.print(sd_cid.pnm[0]);
|
||||
LOG_FILE.print(sd_cid.pnm[1]);
|
||||
LOG_FILE.print(sd_cid.pnm[2]);
|
||||
LOG_FILE.print(sd_cid.pnm[3]);
|
||||
LOG_FILE.println(sd_cid.pnm[4]);
|
||||
LOG_FILE.print(sd_cid.pnm[4]);
|
||||
|
||||
LOG_FILE.print("Sd Date:");
|
||||
LOG_FILE.print(" Date:");
|
||||
LOG_FILE.print(sd_cid.mdtMonth());
|
||||
LOG_FILE.print("/");
|
||||
LOG_FILE.println(sd_cid.mdtYear());
|
||||
|
||||
LOG_FILE.print("Sd Serial:");
|
||||
LOG_FILE.print(" Serial:");
|
||||
LOG_FILE.println(sd_cid.psn());
|
||||
LOG_FILE.sync();
|
||||
}
|
||||
LOG_FILE.sync();
|
||||
}
|
||||
|
||||
bool VerifyISOPVD(SCSI_DEVICE *dev, unsigned sector_size, bool mode2)
|
||||
@ -202,13 +236,13 @@ bool VerifyISOPVD(SCSI_DEVICE *dev, unsigned sector_size, bool mode2)
|
||||
if(mode2) seek += 8;
|
||||
bool ret = false;
|
||||
|
||||
dev->m_file->seekSet(seek);
|
||||
dev->m_file->read(m_buf, 2048);
|
||||
dev->m_file.seekSet(seek);
|
||||
dev->m_file.read(m_buf, 2048);
|
||||
|
||||
ret = ((m_buf[0] == 1 && !strncmp((char *)&m_buf[1], "CD001", 5) && m_buf[6] == 1) ||
|
||||
(m_buf[8] == 1 && !strncmp((char *)&m_buf[9], "CDROM", 5) && m_buf[14] == 1));
|
||||
|
||||
dev->m_file->rewind();
|
||||
dev->m_file.rewind();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -220,18 +254,19 @@ bool hddimageOpen(SCSI_DEVICE *dev, FsFile *file,int id,int lun,int blocksize)
|
||||
{
|
||||
dev->m_fileSize= 0;
|
||||
dev->m_sector_offset = 0;
|
||||
dev->flags = 0;
|
||||
dev->m_blocksize = blocksize;
|
||||
dev->m_rawblocksize = blocksize;
|
||||
dev->m_file = file;
|
||||
if(!dev->m_file->isOpen()) { goto failed; }
|
||||
dev->m_file = *file;
|
||||
if(!dev->m_file.isOpen()) { goto failed; }
|
||||
|
||||
dev->m_fileSize = dev->m_file->size();
|
||||
dev->m_fileSize = dev->m_file.size();
|
||||
|
||||
if(dev->m_fileSize < 1) {
|
||||
LOG_FILE.println(" - file is 0 bytes, can not use.");
|
||||
goto failed;
|
||||
}
|
||||
if(!dev->m_file->isContiguous())
|
||||
if(!dev->m_file.isContiguous())
|
||||
{
|
||||
LOG_FILE.println(" - file is fragmented, see https://github.com/erichelgeson/BlueSCSI/wiki/Image-File-Fragmentation");
|
||||
}
|
||||
@ -243,19 +278,18 @@ bool hddimageOpen(SCSI_DEVICE *dev, FsFile *file,int id,int lun,int blocksize)
|
||||
// Borrowed from PCEM
|
||||
if(VerifyISOPVD(dev, CDROM_COMMON_SECTORSIZE, false)) {
|
||||
dev->m_rawblocksize = CDROM_COMMON_SECTORSIZE;
|
||||
dev->m_mode2 = false;
|
||||
SET_DEVICE_FLAG(dev->flags, SCSI_DEVICE_FLAG_OPTICAL_MODE2);
|
||||
} else if(VerifyISOPVD(dev, CDROM_RAW_SECTORSIZE, false)) {
|
||||
dev->m_rawblocksize = CDROM_RAW_SECTORSIZE;
|
||||
dev->m_mode2 = false;
|
||||
dev->m_raw = true;
|
||||
SET_DEVICE_FLAG(dev->flags, SCSI_DEVICE_FLAG_OPTICAL_RAW);
|
||||
dev->m_sector_offset = 16;
|
||||
} else if(VerifyISOPVD(dev, 2336, true)) {
|
||||
dev->m_rawblocksize = 2336;
|
||||
dev->m_mode2 = true;
|
||||
SET_DEVICE_FLAG(dev->flags, SCSI_DEVICE_FLAG_OPTICAL_MODE2);
|
||||
} else if(VerifyISOPVD(dev, CDROM_RAW_SECTORSIZE, true)) {
|
||||
dev->m_rawblocksize = CDROM_RAW_SECTORSIZE;
|
||||
dev->m_mode2 = true;
|
||||
dev->m_raw = true;
|
||||
SET_DEVICE_FLAG(dev->flags, SCSI_DEVICE_FLAG_OPTICAL_MODE2);
|
||||
SET_DEVICE_FLAG(dev->flags, SCSI_DEVICE_FLAG_OPTICAL_RAW);
|
||||
dev->m_sector_offset = 24;
|
||||
} else {
|
||||
// Last ditch effort
|
||||
@ -264,7 +298,7 @@ bool hddimageOpen(SCSI_DEVICE *dev, FsFile *file,int id,int lun,int blocksize)
|
||||
goto failed;
|
||||
}
|
||||
|
||||
dev->m_raw = true;
|
||||
SET_DEVICE_FLAG(dev->flags, SCSI_DEVICE_FLAG_OPTICAL_RAW);
|
||||
|
||||
if(!(dev->m_fileSize % CDROM_COMMON_SECTORSIZE)) {
|
||||
// try a multiple of 2048
|
||||
@ -291,17 +325,17 @@ bool hddimageOpen(SCSI_DEVICE *dev, FsFile *file,int id,int lun,int blocksize)
|
||||
LOG_FILE.println("MiB");
|
||||
|
||||
if(dev->m_type == SCSI_DEVICE_OPTICAL) {
|
||||
LOG_FILE.print(" MODE2:");LOG_FILE.print(dev->m_mode2);
|
||||
LOG_FILE.print(" BlockSize:");LOG_FILE.println(dev->m_rawblocksize);
|
||||
LOG_FILE.print(" MODE2:");LOG_FILE.print(IS_MODE2(dev->flags));
|
||||
LOG_FILE.print(" BlockSize:");LOG_FILE.println(IS_RAW(dev->flags));
|
||||
}
|
||||
return true; // File opened
|
||||
|
||||
failed:
|
||||
|
||||
dev->m_file->close();
|
||||
dev->m_file.close();
|
||||
dev->m_fileSize = dev->m_blocksize = 0; // no file
|
||||
delete dev->m_file;
|
||||
dev->m_file = NULL;
|
||||
//delete dev->m_file;
|
||||
//dev->m_file = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -326,15 +360,6 @@ void setup()
|
||||
scsi_command_table[i] = onUnimplemented;
|
||||
}
|
||||
|
||||
// zero all SCSI device structs
|
||||
for(unsigned id = 0; id < NUM_SCSIID; id++)
|
||||
{
|
||||
for(unsigned lun = 0; lun < NUM_SCSILUN; lun++)
|
||||
{
|
||||
memset(&scsi_device_list[id][lun], 0, sizeof(SCSI_DEVICE));
|
||||
}
|
||||
}
|
||||
|
||||
// SCSI commands that just need to return ok
|
||||
scsi_command_table[SCSI_FORMAT_UNIT4] = onNOP;
|
||||
scsi_command_table[SCSI_FORMAT_UNIT6] = onNOP;
|
||||
@ -483,20 +508,24 @@ void setup()
|
||||
|
||||
if(!sd_ready) {
|
||||
#if DEBUG > 0
|
||||
Serial.println("SD initialization failed!");
|
||||
Serial.println("SD Init failed!");
|
||||
#endif
|
||||
flashError(ERROR_NO_SDCARD);
|
||||
}
|
||||
initFileLog(mhz);
|
||||
readSDCardInfo();
|
||||
initFileLog();
|
||||
readSDCardInfo(mhz);
|
||||
|
||||
//HD image file open
|
||||
scsi_id_mask = 0x00;
|
||||
|
||||
// Look for this file to enable MSTE_MODE
|
||||
if(SD.exists("MSTE_MODE")) {
|
||||
LOG_FILE.println("MSTE_MODE - IDs treated as LUNs");
|
||||
megaste_mode = true;
|
||||
if(ini_getl("SCSI", "MapLunsToIDs", 0, BLUESCSI_INI))
|
||||
{
|
||||
LOG_FILE.println("IDs treated as LUNs for ID0");
|
||||
ids_as_luns = true;
|
||||
}
|
||||
|
||||
if(SD.exists("scsi-config.txt")) {
|
||||
LOG_FILE.println("scsi-config.txt is deprecated, use bluescsi.ini");
|
||||
}
|
||||
|
||||
// Iterate over the root path in the SD card looking for candidate image files.
|
||||
@ -540,7 +569,7 @@ void setup()
|
||||
|
||||
void findDriveImages(FsFile root) {
|
||||
bool image_ready;
|
||||
FsFile *file = NULL;
|
||||
FsFile file;
|
||||
char path_name[MAX_FILE_PATH+1];
|
||||
root.getName(path_name, sizeof(path_name));
|
||||
SD.chdir(path_name);
|
||||
@ -564,14 +593,11 @@ void findDriveImages(FsFile root) {
|
||||
}
|
||||
|
||||
// Valid file, open for reading/writing.
|
||||
file = new FsFile(SD.open(name, O_RDWR));
|
||||
if(file && file->isFile()) {
|
||||
file = SD.open(name, O_RDWR);
|
||||
if(file && file.isFile()) {
|
||||
SCSI_DEVICE_TYPE device_type;
|
||||
if(tolower(name[1]) != 'd') {
|
||||
file->close();
|
||||
delete file;
|
||||
LOG_FILE.print("Not an image: ");
|
||||
LOG_FILE.println(name);
|
||||
file.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -581,10 +607,7 @@ void findDriveImages(FsFile root) {
|
||||
case 'c': device_type = SCSI_DEVICE_OPTICAL;
|
||||
break;
|
||||
default:
|
||||
file->close();
|
||||
delete file;
|
||||
LOG_FILE.print("Not an image: ");
|
||||
LOG_FILE.println(name);
|
||||
file.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -597,7 +620,7 @@ void findDriveImages(FsFile root) {
|
||||
// We only require the minimum and read in the next if provided.
|
||||
int file_name_length = strlen(name);
|
||||
if(file_name_length > 2) { // HD[N]
|
||||
int tmp_id = name[HDIMG_ID_POS] - '0';
|
||||
int tmp_id = CHAR_TO_INT(name[HDIMG_ID_POS]);
|
||||
|
||||
// If valid id, set it, else use default
|
||||
if(tmp_id > -1 && tmp_id < 8) {
|
||||
@ -609,7 +632,7 @@ void findDriveImages(FsFile root) {
|
||||
}
|
||||
|
||||
if(file_name_length > 3) { // HDN[N]
|
||||
int tmp_lun = name[HDIMG_LUN_POS] - '0';
|
||||
int tmp_lun = CHAR_TO_INT(name[HDIMG_LUN_POS]);
|
||||
|
||||
// If valid lun, set it, else use default
|
||||
if(tmp_lun == 0 || tmp_lun == 1) {
|
||||
@ -622,11 +645,11 @@ void findDriveImages(FsFile root) {
|
||||
|
||||
int blk1 = 0, blk2, blk3, blk4 = 0;
|
||||
if(file_name_length > 8) { // HD00_[111]
|
||||
blk1 = name[HDIMG_BLK_POS] - '0';
|
||||
blk2 = name[HDIMG_BLK_POS+1] - '0';
|
||||
blk3 = name[HDIMG_BLK_POS+2] - '0';
|
||||
blk1 = CHAR_TO_INT(name[HDIMG_BLK_POS]);
|
||||
blk2 = CHAR_TO_INT(name[HDIMG_BLK_POS+1]);
|
||||
blk3 = CHAR_TO_INT(name[HDIMG_BLK_POS+2]);
|
||||
if(file_name_length > 9) // HD00_NNN[1]
|
||||
blk4 = name[HDIMG_BLK_POS+3] - '0';
|
||||
blk4 = CHAR_TO_INT(name[HDIMG_BLK_POS+3]);
|
||||
}
|
||||
if(blk1 == 2 && blk2 == 5 && blk3 == 6) {
|
||||
blk = 256;
|
||||
@ -641,7 +664,7 @@ void findDriveImages(FsFile root) {
|
||||
LOG_FILE.print(" - ");
|
||||
LOG_FILE.print(name);
|
||||
dev->m_type = device_type;
|
||||
image_ready = hddimageOpen(dev, file, id, lun, blk);
|
||||
image_ready = hddimageOpen(dev, &file, id, lun, blk);
|
||||
if(image_ready) { // Marked as a responsive ID
|
||||
scsi_id_mask |= 1<<id;
|
||||
|
||||
@ -649,16 +672,16 @@ void findDriveImages(FsFile root) {
|
||||
{
|
||||
case SCSI_DEVICE_HDD:
|
||||
// default SCSI HDD
|
||||
dev->inquiry_block = &default_hdd;
|
||||
dev->inquiry_block = default_hdd;
|
||||
break;
|
||||
|
||||
case SCSI_DEVICE_OPTICAL:
|
||||
// default SCSI CDROM
|
||||
dev->inquiry_block = &default_optical;
|
||||
dev->inquiry_block = default_optical;
|
||||
break;
|
||||
}
|
||||
|
||||
readSCSIDeviceConfig(dev);
|
||||
readSCSIDeviceConfig(id, dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -671,32 +694,14 @@ void findDriveImages(FsFile root) {
|
||||
/*
|
||||
* Setup initialization logfile
|
||||
*/
|
||||
void initFileLog(int success_mhz) {
|
||||
void initFileLog() {
|
||||
LOG_FILE = SD.open(LOG_FILENAME, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
LOG_FILE.println("BlueSCSI <-> SD - https://github.com/erichelgeson/BlueSCSI");
|
||||
LOG_FILE.println("BlueSCSI https://github.com/erichelgeson/BlueSCSI");
|
||||
LOG_FILE.print("VER: ");
|
||||
LOG_FILE.print(VERSION);
|
||||
LOG_FILE.println(BUILD_TAGS);
|
||||
LOG_FILE.print("DEBUG:");
|
||||
LOG_FILE.print(DEBUG);
|
||||
LOG_FILE.print(" SDFAT_FILE_TYPE:");
|
||||
LOG_FILE.println(SDFAT_FILE_TYPE);
|
||||
LOG_FILE.print("SdFat version: ");
|
||||
LOG_FILE.println(SD_FAT_VERSION_STR);
|
||||
LOG_FILE.print("Sd Format: ");
|
||||
switch(SD.vol()->fatType()) {
|
||||
case FAT_TYPE_EXFAT:
|
||||
LOG_FILE.println("exFAT");
|
||||
break;
|
||||
default:
|
||||
LOG_FILE.print("FAT 32/16/12 - Consider formatting the SD Card with exFAT for improved performance.");
|
||||
}
|
||||
LOG_FILE.print("SPI speed: ");
|
||||
LOG_FILE.print(success_mhz);
|
||||
LOG_FILE.println("Mhz");
|
||||
LOG_FILE.print("SdFat Max FileName Length: ");
|
||||
LOG_FILE.println(MAX_FILE_PATH);
|
||||
LOG_FILE.println("Initialized SD Card - let's go!");
|
||||
LOG_FILE.println(DEBUG);
|
||||
LOG_FILE.sync();
|
||||
}
|
||||
|
||||
@ -730,7 +735,7 @@ void finalizeFileLog() {
|
||||
}
|
||||
LOG_FILE.println(":");
|
||||
}
|
||||
LOG_FILE.println("Finished initialization of SCSI Devices - Entering main loop.");
|
||||
LOG_FILE.println("Finished configuration - Starting BlueSCSI");
|
||||
LOG_FILE.sync();
|
||||
#if DEBUG < 2
|
||||
LOG_FILE.close();
|
||||
@ -948,7 +953,7 @@ void writeDataPhaseSD(SCSI_DEVICE *dev, uint32_t adds, uint32_t len)
|
||||
SCSI_PHASE_CHANGE(SCSI_PHASE_DATAIN);
|
||||
//Bus settle delay 400ns, file.seek() measured at over 1000ns.
|
||||
uint64_t pos = (uint64_t)adds * dev->m_rawblocksize;
|
||||
dev->m_file->seekSet(pos);
|
||||
dev->m_file.seekSet(pos);
|
||||
#ifdef XCVR
|
||||
TRANSCEIVER_IO_SET(vTR_DBP,TR_OUTPUT)
|
||||
#endif
|
||||
@ -957,7 +962,7 @@ void writeDataPhaseSD(SCSI_DEVICE *dev, uint32_t adds, uint32_t len)
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
// Asynchronous reads will make it faster ...
|
||||
m_resetJmp = false;
|
||||
dev->m_file->read(m_buf, dev->m_rawblocksize);
|
||||
dev->m_file.read(m_buf, dev->m_rawblocksize);
|
||||
enableResetJmp();
|
||||
|
||||
writeDataLoop(dev->m_blocksize, &m_buf[dev->m_sector_offset]);
|
||||
@ -1029,18 +1034,18 @@ void readDataPhaseSD(SCSI_DEVICE *dev, uint32_t adds, uint32_t len)
|
||||
//Bus settle delay 400ns, file.seek() measured at over 1000ns.
|
||||
|
||||
uint64_t pos = (uint64_t)adds * dev->m_blocksize;
|
||||
dev->m_file->seekSet(pos);
|
||||
dev->m_file.seekSet(pos);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
m_resetJmp = true;
|
||||
readDataLoop(dev->m_blocksize, m_buf);
|
||||
m_resetJmp = false;
|
||||
dev->m_file->write(m_buf, dev->m_blocksize);
|
||||
dev->m_file.write(m_buf, dev->m_blocksize);
|
||||
// If a reset happened while writing, break and let the flush happen before it is handled.
|
||||
if (m_isBusReset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
dev->m_file->flush();
|
||||
dev->m_file.flush();
|
||||
enableResetJmp();
|
||||
}
|
||||
|
||||
@ -1055,7 +1060,7 @@ void verifyDataPhaseSD(SCSI_DEVICE *dev, uint32_t adds, uint32_t len)
|
||||
//Bus settle delay 400ns, file.seek() measured at over 1000ns.
|
||||
|
||||
uint64_t pos = (uint64_t)adds * dev->m_blocksize;
|
||||
dev->m_file->seekSet(pos);
|
||||
dev->m_file.seekSet(pos);
|
||||
for(uint32_t i = 0; i < len; i++) {
|
||||
readDataLoop(dev->m_blocksize, m_buf);
|
||||
// This has just gone through the transfer to make things work, a compare would go here.
|
||||
@ -1079,6 +1084,12 @@ void MsgIn2(int msg)
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
byte m_id = 0; // Currently responding SCSI-ID
|
||||
byte m_lun = 0xff; // Logical unit number currently responding
|
||||
byte m_sts = 0; // Status byte
|
||||
byte m_msg = 0; // Message bytes
|
||||
byte m_msb[256]; // Command storage bytes
|
||||
|
||||
#ifdef XCVR
|
||||
// Reset all DB and Target pins, switch transceivers to input
|
||||
// Precaution against bugs or jumps which don't clean up properly
|
||||
@ -1088,9 +1099,6 @@ void loop()
|
||||
TRANSCEIVER_IO_SET(vTR_INITIATOR,TR_INPUT)
|
||||
#endif
|
||||
|
||||
//int msg = 0;
|
||||
m_msg = 0;
|
||||
m_lun = 0xff;
|
||||
SCSI_DEVICE *dev = (SCSI_DEVICE *)0; // HDD image for current SCSI-ID, LUN
|
||||
|
||||
do {} while( SCSI_IN(vBSY) || !SCSI_IN(vSEL) || SCSI_IN(vRST));
|
||||
@ -1221,7 +1229,7 @@ void loop()
|
||||
if(m_lun > MAX_SCSILUN)
|
||||
{
|
||||
m_lun = (cmd[1] & 0xe0) >> 5;
|
||||
if(megaste_mode)
|
||||
if(ids_as_luns)
|
||||
{
|
||||
// if this mode is enabled we are going to substitute all SCSI IDs as LUNs on ID0
|
||||
// this is because the MegaSTE internal adapter only supports ID0. This lets multiple
|
||||
@ -1255,16 +1263,16 @@ void loop()
|
||||
LOGN("onInquiry - InvalidLUN");
|
||||
dev = &(scsi_device_list[m_id][0]);
|
||||
|
||||
byte temp = dev->inquiry_block->raw[0];
|
||||
byte temp = dev->inquiry_block.raw[0];
|
||||
|
||||
// If the LUN is invalid byte 0 of inquiry block needs to be 7fh
|
||||
dev->inquiry_block->raw[0] = 0x7f;
|
||||
dev->inquiry_block.raw[0] = 0x7f;
|
||||
|
||||
// only write back what was asked for
|
||||
writeDataPhase(cmd[4], dev->inquiry_block->raw);
|
||||
writeDataPhase(cmd[4], dev->inquiry_block.raw);
|
||||
|
||||
// return it back to normal if it was altered
|
||||
dev->inquiry_block->raw[0] = temp;
|
||||
dev->inquiry_block.raw[0] = temp;
|
||||
}
|
||||
else if(cmd[0] == SCSI_REQUEST_SENSE)
|
||||
{
|
||||
@ -1290,7 +1298,7 @@ void loop()
|
||||
}
|
||||
|
||||
dev = &(scsi_device_list[m_id][m_lun]);
|
||||
if(!dev->m_file)
|
||||
if(!dev->m_file.isOpen())
|
||||
{
|
||||
dev->m_senseKey = SCSI_SENSE_ILLEGAL_REQUEST;
|
||||
dev->m_additional_sense_code = SCSI_ASC_LOGICAL_UNIT_NOT_SUPPORTED;
|
||||
@ -1359,7 +1367,7 @@ static byte onNOP(SCSI_DEVICE *dev, const byte *cdb)
|
||||
*/
|
||||
byte onInquiry(SCSI_DEVICE *dev, const byte *cdb)
|
||||
{
|
||||
writeDataPhase(cdb[4] < 47 ? cdb[4] : 47, dev->inquiry_block->raw);
|
||||
writeDataPhase(cdb[4] < 47 ? cdb[4] : 47, dev->inquiry_block.raw);
|
||||
return SCSI_STATUS_GOOD;
|
||||
}
|
||||
|
||||
@ -1891,7 +1899,7 @@ byte onWriteBuffer(SCSI_DEVICE *dev, const byte *cdb)
|
||||
byte onReadBuffer(SCSI_DEVICE *dev, const byte *cdb)
|
||||
{
|
||||
byte mode = cdb[1] & 7;
|
||||
uint32_t allocLength = ((uint32_t)cdb[6] << 16) | ((uint32_t)cdb[7] << 8) | cdb[8];
|
||||
unsigned m_scsi_buf_size = 0;
|
||||
|
||||
LOGN("-ReadBuffer");
|
||||
LOGHEXN(mode);
|
||||
@ -1911,6 +1919,7 @@ byte onReadBuffer(SCSI_DEVICE *dev, const byte *cdb)
|
||||
writeDataPhase(4 + m_scsi_buf_size, m_buf);
|
||||
|
||||
#if DEBUG > 0
|
||||
uint32_t allocLength = ((uint32_t)cdb[6] << 16) | ((uint32_t)cdb[7] << 8) | cdb[8];
|
||||
for (unsigned i = 0; i < allocLength; i++) {
|
||||
LOGHEX(m_scsi_buf[i]);LOG(" ");
|
||||
}
|
||||
|
@ -15,6 +15,11 @@
|
||||
#define SCSI_BUF_SIZE 512 // Size of the SCSI Buffer
|
||||
#define HDD_BLOCK_SIZE 512
|
||||
#define OPTICAL_BLOCK_SIZE 2048
|
||||
#define BLUESCSI_INI "bluescsi.ini"
|
||||
|
||||
#define SCSI_VENDOR_LENGTH 8
|
||||
#define SCSI_PRODUCT_LENGTH 16
|
||||
#define SCSI_REVISION_LENGTH 4
|
||||
|
||||
// HDD format
|
||||
#define MAX_BLOCKSIZE 4096 // Maximum BLOCK size
|
||||
@ -32,6 +37,18 @@ enum SCSI_DEVICE_TYPE
|
||||
SCSI_DEVICE_OPTICAL,
|
||||
};
|
||||
|
||||
#define SCSI_DEVICE_FLAG_OPTICAL_MODE2 0x1
|
||||
#define SCSI_DEVICE_FLAG_OPTICAL_RAW 0x2
|
||||
|
||||
#define SET_DEVICE_FLAG(var, flag) (var |= flag)
|
||||
#define UNSET_DEVICE_FLAG(var, flag) (var &= ~flag)
|
||||
#define IS_DEVICE_FLAG_SET(var, flag) ((var & flag) == flag)
|
||||
#define IS_RAW(var) IS_DEVICE_FLAG_SET(var, SCSI_DEVICE_FLAG_OPTICAL_RAW)
|
||||
#define IS_MODE2(var) IS_DEVICE_FLAG_SET(var, SCSI_DEVICE_FLAG_OPTICAL_MODE2)
|
||||
|
||||
#define INT_TO_CHAR(var) var+'0'
|
||||
#define CHAR_TO_INT(var) var-'0'
|
||||
|
||||
#define CDROM_RAW_SECTORSIZE 2352
|
||||
#define CDROM_COMMON_SECTORSIZE 2048
|
||||
|
||||
@ -124,7 +141,7 @@ enum SCSI_DEVICE_TYPE
|
||||
// SCSI output pin control: opendrain active LOW (direct pin drive)
|
||||
#define SCSI_OUT(VPIN,ACTIVE) { GPIOREG(VPIN)->BSRR = BITMASK(VPIN)<<((ACTIVE)?16:0); }
|
||||
|
||||
// SCSI input pin check (inactive=0,avtive=1)
|
||||
// SCSI input pin check (inactive=0,active=1)
|
||||
#define SCSI_IN(VPIN) ((~GPIOREG(VPIN)->IDR>>(VPIN&15))&1)
|
||||
|
||||
#define NOP(x) for(unsigned _nopcount = x; _nopcount; _nopcount--) { asm("NOP"); }
|
||||
@ -231,7 +248,8 @@ enum SCSI_DEVICE_TYPE
|
||||
*/
|
||||
|
||||
// Parity bit generation
|
||||
#define PTY(V) (1^((V)^((V)>>1)^((V)>>2)^((V)>>3)^((V)>>4)^((V)>>5)^((V)>>6)^((V)>>7))&1)
|
||||
//#define PTY(V) (1^((V)^((V)>>1)^((V)>>2)^((V)>>3)^((V)>>4)^((V)>>5)^((V)>>6)^((V)>>7))&1)
|
||||
#define PTY(n) ((1 ^ (n) ^ ((n)>>1) ^ ((n)>>2) ^ ((n)>>3) ^ ((n)>>4) ^ ((n)>>5) ^ ((n)>>6) ^ ((n)>>7)) & 1)
|
||||
|
||||
// Data byte to BSRR register setting value conversion table
|
||||
// BSRR[31:24] = DB[7:0]
|
||||
@ -263,7 +281,7 @@ uint32_t db_bsrr[256];
|
||||
|
||||
|
||||
|
||||
struct SCSI_INQUIRY_DATA
|
||||
typedef struct _SCSI_INQUIRY_DATA
|
||||
{
|
||||
union
|
||||
{
|
||||
@ -306,23 +324,22 @@ struct SCSI_INQUIRY_DATA
|
||||
// raw bytes
|
||||
byte raw[64];
|
||||
};
|
||||
};
|
||||
} SCSI_INQUIRY_DATA;
|
||||
|
||||
// HDD image
|
||||
typedef __attribute__((aligned(4))) struct _SCSI_DEVICE
|
||||
{
|
||||
FsFile *m_file; // File object
|
||||
FsFile m_file; // File object
|
||||
uint64_t m_fileSize; // File size
|
||||
uint16_t m_blocksize; // SCSI BLOCK size
|
||||
uint16_t m_rawblocksize; // OPTICAL raw sector size
|
||||
uint8_t m_type; // SCSI device type
|
||||
uint32_t m_blockcount; // blockcount
|
||||
bool m_raw; // Raw disk
|
||||
SCSI_INQUIRY_DATA *inquiry_block; // SCSI information
|
||||
SCSI_INQUIRY_DATA inquiry_block; // SCSI information
|
||||
uint8_t m_senseKey; // Sense key
|
||||
uint16_t m_additional_sense_code; // ASC/ASCQ
|
||||
bool m_mode2; // MODE2 CDROM
|
||||
uint8_t m_sector_offset; // optical sector offset for missing sync header
|
||||
uint8_t flags; // various device flags
|
||||
} SCSI_DEVICE;
|
||||
|
||||
static byte cdb_len_lookup[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user