Merge remote-tracking branch 'jokker/troy/inifile'

This commit is contained in:
Eric Helgeson 2023-04-01 11:13:05 -05:00
commit 142117c83a
6 changed files with 1304 additions and 148 deletions

View File

@ -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
View 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
View 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, &quotes); /* 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, &quotes); /* 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
View 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 */

View File

@ -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(" ");
}

View File

@ -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[] = {