diff --git a/README.md b/README.md index 1f97464b..e16f74e4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,65 @@ +### Warning: This version has major internal changes. + +SdFat version 2.3.0 has major changes to implement RP2040/RP2350 SDIO. + +In addition there are number of bug fixes. + +Begin by running the Rp2040SdioSetup example to try RP2040/RP2350 SDIO. + +This example requires a SDIO Card socket with the following six lines. + +CLK - A clock signal sent to the card by the MCU. +CMD - A bidirectional line for for commands and responses. +DAT[0:3] - Four bidirectional lines for data transfer. + +CLK and CMD can be connected to any GPIO pins. DAT[0:3] can be connected +to any four consecutive GPIO pins in the order DAT0, DAT1, DAT2, DAT3. + +Here is an example of SDIO for Pico using an Adafruit socket, PiCowbell +Proto and PiCowbell Proto Doubler. + +![Alt text](images/SdioSpi.jpg) + +This Socket supports SDIO with: +``` +#define RP_CLK_GPIO 10 +#define RP_CMD_GPIO 11 +#define RP_DAT0_GPIO 12 // DAT1: GPIO13 DAT2: GPIO14, DAT3: GPIO15. +``` +It also can be used on SPI1 with: +``` +const uint8_t SD_CS_PIN = 15; +#define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK, &SPI1) + + // In setup + SPI1.setSCK(10); + SPI1.setTX(11); + SPI1.setRX(12); +``` + +This setup gets the following result in the bench example using SDIO. + +
+FILE_SIZE_MB = 5
+BUF_SIZE = 512 bytes
+Starting write test, please wait.
+
+write speed and latency
+speed,max,min,avg
+KB/Sec,usec,usec,usec
+15014.05,1165,32,32
+15289.54,1249,32,32
+
+Starting read test, please wait.
+
+read speed and latency
+speed,max,min,avg
+KB/Sec,usec,usec,usec
+15624.00,58,32,32
+15624.00,51,32,32
+
+ + File copy constructors and file assignment operators have been made private by default in 2.2.3 to prevent call by value and multiple copies of file instances. diff --git a/doc/Doxyfile b/doc/Doxyfile index 54e8ce86..78b5b418 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.9.6 +# Doxyfile 1.10.0 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -63,6 +63,12 @@ PROJECT_BRIEF = PROJECT_LOGO = +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If @@ -363,6 +369,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -487,6 +504,14 @@ LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -872,7 +897,14 @@ WARN_IF_UNDOC_ENUM_VAL = NO # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -926,7 +958,9 @@ INPUT = ../src \ ../src/SpiDriver \ mainpage.h \ ../src/FsLib \ - ../src/FsLib + ../src/FsLib \ + ../src/SdCard/TeensySdio \ + ../src/SdCard/Rp2040Sdio # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -959,12 +993,12 @@ INPUT_FILE_ENCODING = # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, -# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C -# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1016,7 +1050,9 @@ EXCLUDE = ../src/common/FsStructs.h \ ../src/common/PrintBasic.h \ ../src/common/PrintBasic.cpp \ ../src/SpiDriver/SdSpiBareUnoDriver.h \ - ../src/iostream/StreamBaseClass.cpp + ../src/iostream/StreamBaseClass.cpp \ + ../src/SdCard/Rp2040Sdio/DbgLog.h \ + ../src/common/FsStructs.h # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1039,9 +1075,6 @@ EXCLUDE_PATTERNS = # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = @@ -1155,7 +1188,8 @@ FORTRAN_COMMENT_AFTER = 72 SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. +# multi-line macros, enums or list initialized variables directly into the +# documentation. # The default value is: NO. INLINE_SOURCES = NO @@ -1424,15 +1458,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1452,6 +1477,33 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1582,6 +1634,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -2070,9 +2132,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2093,14 +2162,6 @@ LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2266,7 +2327,7 @@ DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. @@ -2277,6 +2338,28 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to Sqlite3 output #--------------------------------------------------------------------------- +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2423,15 +2506,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2445,16 +2528,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2463,7 +2539,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2516,13 +2592,19 @@ DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a -# graph for each documented class showing the direct and indirect inheritance -# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, -# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set -# to TEXT the direct and indirect inheritance relations will be shown as texts / -# links. -# Possible values are: NO, YES, TEXT and GRAPH. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. Explicit enabling an inheritance +# graph or choosing a different representation for an inheritance graph of a +# specific class, can be accomplished by means of the command \inheritancegraph. +# Disabling an inheritance graph can be accomplished by means of the command +# \hideinheritancegraph. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES @@ -2530,15 +2612,21 @@ CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. See also the chapter Grouping -# in the manual. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2580,8 +2668,8 @@ DOT_UML_DETAILS = NO # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. +# significantly it will be wrapped across multiple lines. Some heuristics are +# applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2598,7 +2686,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2607,7 +2697,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2647,7 +2740,10 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2663,7 +2759,7 @@ DIR_GRAPH_MAX_DEPTH = 1 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2700,11 +2796,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2781,3 +2878,19 @@ GENERATE_LEGEND = YES # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/doc/SdErrorCodes.txt b/doc/SdErrorCodes.txt index 91802597..29cc64ee 100644 --- a/doc/SdErrorCodes.txt +++ b/doc/SdErrorCodes.txt @@ -1,4 +1,4 @@ -2022-07-01 +2025-01-01 Run the SdErrorCode example to produce an updated list. @@ -12,7 +12,7 @@ Code,Symbol - failed operation 0X06,SD_CARD_ERROR_CMD8 - Send and check interface settings 0X07,SD_CARD_ERROR_CMD9 - Read CSD data 0X08,SD_CARD_ERROR_CMD10 - Read CID data -0X09,SD_CARD_ERROR_CMD12 - Stop multiple block read +0X09,SD_CARD_ERROR_CMD12 - Stop multiple block transmission 0X0A,SD_CARD_ERROR_CMD13 - Read card status 0X0B,SD_CARD_ERROR_CMD17 - Read single block 0X0C,SD_CARD_ERROR_CMD18 - Read multiple blocks diff --git a/doc/html.zip b/doc/html.zip index efbd3841..ee7cbc21 100644 Binary files a/doc/html.zip and b/doc/html.zip differ diff --git a/examples/AvrAdcLogger/AvrAdcLogger.ino b/examples/AvrAdcLogger/AvrAdcLogger.ino index 6c70af8b..03200658 100644 --- a/examples/AvrAdcLogger/AvrAdcLogger.ino +++ b/examples/AvrAdcLogger/AvrAdcLogger.ino @@ -20,11 +20,10 @@ */ #ifdef __AVR__ #include - +#include "SdFat.h" #include "AvrAdcLogger.h" #include "BufferedPrint.h" #include "FreeStack.h" -#include "SdFat.h" // Save SRAM if 328. #ifdef __AVR_ATmega328P__ diff --git a/examples/BufferedPrint/BufferedPrint.ino b/examples/BufferedPrint/BufferedPrint.ino index 5a924eb6..a87ca799 100644 --- a/examples/BufferedPrint/BufferedPrint.ino +++ b/examples/BufferedPrint/BufferedPrint.ino @@ -1,9 +1,9 @@ // Test and benchmark of the fast bufferedPrint class. // // Mainly for AVR but may improve print performance with other CPUs. -#include "BufferedPrint.h" +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" - +#include "BufferedPrint.h" // SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, // 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. #define SD_FAT_TYPE 3 @@ -28,13 +28,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; diff --git a/examples/DirectoryFunctions/DirectoryFunctions.ino b/examples/DirectoryFunctions/DirectoryFunctions.ino index 498ce513..9ab4db70 100644 --- a/examples/DirectoryFunctions/DirectoryFunctions.ino +++ b/examples/DirectoryFunctions/DirectoryFunctions.ino @@ -1,6 +1,7 @@ /* * Example use of chdir(), ls(), mkdir(), and rmdir(). */ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" #include "sdios.h" @@ -28,13 +29,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO //------------------------------------------------------------------------------ #if SD_FAT_TYPE == 0 diff --git a/examples/ExFatLogger/ExFatLogger.ino b/examples/ExFatLogger/ExFatLogger.ino index f3a6c55d..9062df60 100644 --- a/examples/ExFatLogger/ExFatLogger.ino +++ b/examples/ExFatLogger/ExFatLogger.ino @@ -3,9 +3,10 @@ // // The maximum data rate will depend on the quality of your SD, // the size of the FIFO, and using dedicated SPI. +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. +#include "SdFat.h" #include "ExFatLogger.h" #include "FreeStack.h" -#include "SdFat.h" //------------------------------------------------------------------------------ // This example was designed for exFAT but will support FAT16/FAT32. // Note: Uno will not support SD_FAT_TYPE = 3. @@ -69,13 +70,16 @@ const uint32_t PREALLOCATE_SIZE_MiB = 1024UL; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO // Save SRAM if 328. #ifdef __AVR_ATmega328P__ @@ -92,7 +96,7 @@ void logRecord(data_t* data, uint16_t overrun) { data->adc[0] = 0X8000 | overrun; } else { for (size_t i = 0; i < ADC_COUNT; i++) { - data->adc[i] = analogRead(i); + data->adc[i] = analogRead(A0 + i); } } } diff --git a/examples/OpenNext/OpenNext.ino b/examples/OpenNext/OpenNext.ino index 2e58110d..a2ff1242 100644 --- a/examples/OpenNext/OpenNext.ino +++ b/examples/OpenNext/OpenNext.ino @@ -1,6 +1,7 @@ /* * Print size, modify date/time, and name for all files in root. */ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" // SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, @@ -27,13 +28,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; diff --git a/examples/ReadCsvFile/ReadCsvFile.ino b/examples/ReadCsvFile/ReadCsvFile.ino index 7fb61cad..b6a85c1a 100644 --- a/examples/ReadCsvFile/ReadCsvFile.ino +++ b/examples/ReadCsvFile/ReadCsvFile.ino @@ -1,3 +1,4 @@ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" // SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, @@ -24,13 +25,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; @@ -126,7 +130,7 @@ void setup() { if (!file.open("ReadCsvDemo.csv", FILE_WRITE)) { error("open failed"); } - // Write test data. + // Write test data. Test missing CRLF on last line. file.print( F("abc,123,456,7.89\r\n" "def,-321,654,-9.87\r\n" @@ -143,6 +147,10 @@ void setup() { if (line[n - 1] != '\n' && n == (sizeof(line) - 1)) { error("line too long"); } + if (line[n - 1] == '\n') { + // Remove new line. + line[n -1] = 0; + } if (!parseLine(line)) { error("parseLine failed"); } diff --git a/examples/Rp2040SdioSetup/Rp2040SdioSetup.ino b/examples/Rp2040SdioSetup/Rp2040SdioSetup.ino new file mode 100644 index 00000000..468af71d --- /dev/null +++ b/examples/Rp2040SdioSetup/Rp2040SdioSetup.ino @@ -0,0 +1,93 @@ +// RP2040 PIO SDIO setup and test. +/* +This example requires a SDIO Card socket with the following six lines. + +CLK - A clock signal sent to the card by the MCU. +CMD - A bidirectional line for for commands and responses. +DAT[0:3] - Four bidirectional lines for data transfer. + +CLK and CMD can be connected to any GPIO pins. DAT[0:3] can be connected +to any four consecutive GPIO pins in the order DAT0, DAT1, DAT2, DAT3. + +For testing, I use several RP2040/RP3350 boards. +The Adafruit Metro RP2040 which has a builtin SDIO socket. + +https://learn.adafruit.com/adafruit-metro-rp2040 + +I use this SD socket breakout board for other boards. + +https://learn.adafruit.com/adafruit-microsd-spi-sdio + +Wires should be short since signals can be as faster than 50 MHz. +*/ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. +#include "SdFat.h" +//------------------------------------------------------------------------------ +// Example GPIO definitions I use for debug. Edit for your setup. +// Run this example as is to print the symbol for your variant. +// +#if defined(ARDUINO_ADAFRUIT_METRO_RP2040) +#define RP_CLK_GPIO 18 +#define RP_CMD_GPIO 19 +#define RP_DAT0_GPIO 20 // DAT1: GPIO21, DAT2: GPIO22, DAT3: GPIO23. +#elif defined(ARDUINO_RASPBERRY_PI_PICO) || defined(ARDUINO_RASPBERRY_PI_PICO_2) +#define RP_CLK_GPIO 16 +#define RP_CMD_GPIO 17 +#define RP_DAT0_GPIO 18 // DAT1: GPIO19, DAT2: GPIO20, DAT3: GPIO21. +#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) +#define RP_CLK_GPIO 11 +#define RP_CMD_GPIO 10 +#define RP_DAT0_GPIO 22 // DAT1: GPIO23, DAT2: GPIO24, DAT3: GPIO25. +#endif // defined(ARDUINO_ADAFRUIT_METRO_RP2040)) + +#if defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) +#else // defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +#warning "Undefined SD_CONFIG. Run this program for the Variant Symbol." +#endif // defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +//------------------------------------------------------------------------------ +// Class File is not defined by SdFat since the RP2040 system defines it. +// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. +#define SD_FAT_TYPE 3 + +#if SD_FAT_TYPE == 1 +SdFat32 sd; +File32 file; +#elif SD_FAT_TYPE == 2 +SdExFat sd; +ExFile file; +#elif SD_FAT_TYPE == 3 +SdFs sd; +FsFile file; +#else // SD_FAT_TYPE +#error Invalid SD_FAT_TYPE +#endif // SD_FAT_TYPE + +void setup() { + Serial.begin(9600); + while (!Serial) { + yield(); + } + Serial.println("Type any character to start\n"); + while (!Serial.available()) { + yield(); + } + Serial.print("Variant Symbol: "); + Serial.print("ARDUINO_"); + Serial.println(BOARD_NAME); + Serial.println(); +#if defined(SD_CONFIG) + if (!sd.begin(SD_CONFIG)) { + sd.initErrorHalt(&Serial); + } + Serial.println("Card successfully initialized."); + Serial.println("\nls:"); + sd.ls(LS_A | LS_DATE | LS_SIZE); // Add LS_R for recursive list. + Serial.println("\nDone! Try the bench example next."); +#else // #if defined(SD_CONFIG) + Serial.println("Error: SD_CONFIG undefined for your board."); + Serial.println("Define RP_CLK_GPIO, RP_CMD_GPIO, and RP_DAT0_GPIO above."); +#endif +} + +void loop() {} diff --git a/examples/RtcTimestampTest/RtcTimestampTest.ino b/examples/RtcTimestampTest/RtcTimestampTest.ino index 3e5e5a74..c9796d2d 100644 --- a/examples/RtcTimestampTest/RtcTimestampTest.ino +++ b/examples/RtcTimestampTest/RtcTimestampTest.ino @@ -35,13 +35,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; diff --git a/examples/SdFormatter/SdFormatter.ino b/examples/SdFormatter/SdFormatter.ino index 6cfd37be..1bc5a605 100644 --- a/examples/SdFormatter/SdFormatter.ino +++ b/examples/SdFormatter/SdFormatter.ino @@ -10,6 +10,7 @@ * For very small cards this program uses FAT16 * and the above SDFormatter uses FAT12. */ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" #include "sdios.h" @@ -40,13 +41,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO //============================================================================== // Serial output stream ArduinoOutStream cout(Serial); diff --git a/examples/SdInfo/SdInfo.ino b/examples/SdInfo/SdInfo.ino index a2c31623..5d04570d 100644 --- a/examples/SdInfo/SdInfo.ino +++ b/examples/SdInfo/SdInfo.ino @@ -6,6 +6,7 @@ * https://gurumeditation.org/1342/sd-memory-card-register-decoder/ * https://archive.goughlui.com/static/multicid.htm */ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" #include "sdios.h" /* @@ -30,13 +31,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #endif // SDCARD_SS_PIN // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16)) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(16)) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO //------------------------------------------------------------------------------ SdFs sd; diff --git a/examples/TeensyDmaAdcLogger/TeensyDmaAdcLogger.ino b/examples/TeensyDmaAdcLogger/TeensyDmaAdcLogger.ino index ad491f4a..7bda5309 100644 --- a/examples/TeensyDmaAdcLogger/TeensyDmaAdcLogger.ino +++ b/examples/TeensyDmaAdcLogger/TeensyDmaAdcLogger.ino @@ -6,9 +6,9 @@ // #include "ADC.h" #include "DMAChannel.h" +#include "SdFat.h" #include "FreeStack.h" #include "RingBuf.h" -#include "SdFat.h" // Pin must be on first ADC. #define ADC_PIN A0 diff --git a/examples/TeensyRtcTimestamp/TeensyRtcTimestamp.ino b/examples/TeensyRtcTimestamp/TeensyRtcTimestamp.ino index 496c1d02..babbf1af 100644 --- a/examples/TeensyRtcTimestamp/TeensyRtcTimestamp.ino +++ b/examples/TeensyRtcTimestamp/TeensyRtcTimestamp.ino @@ -29,13 +29,13 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; diff --git a/examples/TeensySdioLogger/TeensySdioLogger.ino b/examples/TeensySdioLogger/TeensySdioLogger.ino index 36a51b93..912ea01f 100644 --- a/examples/TeensySdioLogger/TeensySdioLogger.ino +++ b/examples/TeensySdioLogger/TeensySdioLogger.ino @@ -5,9 +5,8 @@ // puts the controller in write mode and takes about 11 usec on a // Teensy 4.1. About 5 usec is required to write a sector when the // controller is in write mode. - -#include "RingBuf.h" #include "SdFat.h" +#include "RingBuf.h" // Use Teensy SDIO #define SD_CONFIG SdioConfig(FIFO_SDIO) diff --git a/examples/UnicodeFilenames/UnicodeFilenames.ino b/examples/UnicodeFilenames/UnicodeFilenames.ino index a1524070..6f1ee90e 100644 --- a/examples/UnicodeFilenames/UnicodeFilenames.ino +++ b/examples/UnicodeFilenames/UnicodeFilenames.ino @@ -1,7 +1,16 @@ // Simple test of Unicode filename. // Unicode is supported as UTF-8 encoded strings. +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" +// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, +// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. +#if defined __has_include +#if __has_include() +#define SD_FAT_TYPE 3 // Can't use SdFat/File +#endif // __has_include() +#endif // defined __has_include + // USE_UTF8_LONG_NAMES must be non-zero in SdFat/src/SdFatCongfig.h #if USE_UTF8_LONG_NAMES @@ -11,10 +20,6 @@ const char* names[] = {u8"россиянин", u8"très élégant", u8"狗.txt", // Remove files if non-zero. #define REMOVE_UTF8_FILES 1 -// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, -// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. -#define SD_FAT_TYPE 0 - // SDCARD_SS_PIN is defined for the built-in SD on some boards. #ifndef SDCARD_SS_PIN const uint8_t SD_CS_PIN = SS; @@ -24,13 +29,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #endif // SDCARD_SS_PIN // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16)) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SD_SCK_MHZ(16)) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; diff --git a/examples/bench/bench.ino b/examples/bench/bench.ino index 36a577c5..1c646f67 100644 --- a/examples/bench/bench.ino +++ b/examples/bench/bench.ino @@ -1,13 +1,22 @@ /* * This program is a simple binary write/read benchmark. */ +#define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" #include "FreeStack.h" #include "sdios.h" // SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, // 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. -#define SD_FAT_TYPE 3 +#if defined __has_include +#if __has_include() +#define SD_FAT_TYPE 3 // Can't use SdFat/File +#endif // __has_include() +#endif // defined __has_include + +#ifndef SD_FAT_TYPE +#define SD_FAT_TYPE 0 // Use SdFat/File +#endif // SD_FAT_TYPE /* Change the value of SD_CS_PIN if you are using SPI and your hardware does not use the default value, SS. @@ -27,14 +36,24 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; // Try max SPI clock for an SD. Reduce SPI_CLOCK if errors occur. #define SPI_CLOCK SD_SCK_MHZ(50) +// Example SDIO definition for RP2040/RP2350. See the Rp2040SdioSetup example. +#if defined(ARDUINO_ADAFRUIT_METRO_RP2040) && !defined(RP_CLK_GPIO) +#define RP_CLK_GPIO 18 +#define RP_CMD_GPIO 19 +#define RP_DAT0_GPIO 20 // DAT1: GPIO21, DAT2: GPIO22, DAT3: GPIO23. +#endif // defined(ARDUINO_ADAFRUIT_METRO_RP2040) + // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO // Set PRE_ALLOCATE true to pre-allocate file clusters. const bool PRE_ALLOCATE = true; @@ -128,6 +147,11 @@ void setup() { "\nSet ENABLE_DEDICATED_SPI nonzero in\n" "SdFatConfig.h for best SPI performance.\n"); } + if (!SD_HAS_CUSTOM_SPI && !USE_SPI_ARRAY_TRANSFER && isSpi(SD_CONFIG)) { + cout << F( + "\nSetting USE_SPI_ARRAY_TRANSFER nonzero in\n" + "SdFatConfig.h may improve SPI performance.\n"); + } // use uppercase in hex and use 0X base prefix cout << uppercase << showbase << endl; } diff --git a/examples/rename/rename.ino b/examples/rename/rename.ino index 3995fce5..aa9fedcc 100644 --- a/examples/rename/rename.ino +++ b/examples/rename/rename.ino @@ -1,6 +1,7 @@ /* * This program demonstrates use of rename(). */ + #define DISABLE_FS_H_WARNING // Disable warning for type File not defined. #include "SdFat.h" #include "sdios.h" @@ -29,13 +30,16 @@ const uint8_t SD_CS_PIN = SDCARD_SS_PIN; #define SPI_CLOCK SD_SCK_MHZ(50) // Try to select the best SD card configuration. -#if HAS_SDIO_CLASS +#if defined(HAS_TEENSY_SDIO) #define SD_CONFIG SdioConfig(FIFO_SDIO) +#elif defined(RP_CLK_GPIO) && defined(RP_CMD_GPIO) && defined(RP_DAT0_GPIO) +// See the Rp2040SdioSetup example for RP2040/RP2350 boards. +#define SD_CONFIG SdioConfig(RP_CLK_GPIO, RP_CMD_GPIO, RP_DAT0_GPIO) #elif ENABLE_DEDICATED_SPI #define SD_CONFIG SdSpiConfig(SD_CS_PIN, DEDICATED_SPI, SPI_CLOCK) -#else // HAS_SDIO_CLASS +#else // HAS_TEENSY_SDIO #define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI, SPI_CLOCK) -#endif // HAS_SDIO_CLASS +#endif // HAS_TEENSY_SDIO #if SD_FAT_TYPE == 0 SdFat sd; diff --git a/extras/cpplint.bat b/extras/cpplint.bat index f874f45f..3246259e 100644 --- a/extras/cpplint.bat +++ b/extras/cpplint.bat @@ -1,2 +1,2 @@ -sh cpplint.sh +bash cpplint.sh pause \ No newline at end of file diff --git a/extras/cpplint.py b/extras/cpplint.py index 704618f5..9d4ff5af 100644 --- a/extras/cpplint.py +++ b/extras/cpplint.py @@ -42,30 +42,43 @@ """ import codecs +import collections import copy import getopt +import glob +import itertools import math # for log import os import re -import sre_compile import string import sys -import unicodedata import sysconfig +import unicodedata +import xml.etree.ElementTree -try: - xrange # Python 2 -except NameError: - xrange = range # Python 3 +# if empty, use defaults +_valid_extensions = set([]) +__VERSION__ = '1.7' _USAGE = """ -Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] +Syntax: cpplint.py [--verbose=#] [--output=emacs|eclipse|vs7|junit|sed|gsed] + [--filter=-x,+y,...] [--counting=total|toplevel|detailed] [--root=subdir] + [--repository=path] [--linelength=digits] [--headers=x,y,...] + [--recursive] + [--exclude=path] + [--extensions=hpp,cpp,...] + [--includeorder=default|standardcfirst] + [--config=filename] [--quiet] + [--version] [file] ... + Style checker for C/C++ source files. + This is a fork of the Google style checker with minor extensions. + The style guidelines this tries to follow are those in https://google.github.io/styleguide/cppguide.html @@ -73,22 +86,37 @@ certain of the problem, and 1 meaning it could be a legitimate construct. This will miss some errors, and is not a substitute for a code review. - To suppress false-positive errors of a certain category, add a - 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) - suppresses errors of all categories on that line. + To suppress false-positive errors of certain categories, add a + 'NOLINT(category[, category...])' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. To suppress categories + on the next line use NOLINTNEXTLINE instead of NOLINT. To suppress errors in + a block of code 'NOLINTBEGIN(category[, category...])' comment to a line at + the start of the block and to end the block add a comment with 'NOLINTEND'. + NOLINT blocks are inclusive so any statements on the same line as a BEGIN + or END will have the error suppression applied. The files passed in will be linted; at least one file must be provided. - Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the - extensions with the --extensions flag. + Default linted extensions are %s. + Other file types will be ignored. + Change the extensions with the --extensions flag. Flags: - output=vs7 + output=emacs|eclipse|vs7|junit|sed|gsed By default, the output is formatted to ease emacs parsing. Visual Studio - compatible output (vs7) may also be used. Other formats are unsupported. + compatible output (vs7) may also be used. Further support exists for + eclipse (eclipse), and JUnit (junit). XML parsers such as those used + in Jenkins and Bamboo may also be used. + The sed format outputs sed commands that should fix some of the errors. + Note that this requires gnu sed. If that is installed as gsed on your + system (common e.g. on macOS with homebrew) you can use the gsed output + format. Sed commands are written to stdout, not stderr, so you should be + able to pipe output straight to a shell to run the fixes. verbose=# Specify a number 0-5 to restrict errors to certain verbosity levels. + Errors with lower verbosity levels have lower confidence and are more + likely to be false positives. quiet Don't print anything if no errors are found. @@ -98,16 +126,24 @@ error messages whose category names pass the filters will be printed. (Category names are printed with the message and look like "[whitespace/indent]".) Filters are evaluated left to right. - "-FOO" and "FOO" means "do not print categories that start with FOO". + "-FOO" means "do not print categories that start with FOO". "+FOO" means "do print categories that start with FOO". Examples: --filter=-whitespace,+whitespace/braces - --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-whitespace,-runtime/printf,+runtime/printf_format --filter=-,+build/include_what_you_use To see a list of all the categories used in cpplint, pass no arg: --filter= + Filters can directly be limited to files and also line numbers. The + syntax is category:file:line , where line is optional. The filter limitation + works for both + and - and can be combined with ordinary filters: + + Examples: --filter=-whitespace:foo.h,+whitespace/braces:foo.h + --filter=-whitespace,-runtime/printf:foo.h:14,+runtime/printf_format:foo.h + --filter=-,+build/include_what_you_use:foo.h:321 + counting=total|toplevel|detailed The total number of errors found is always printed. If 'toplevel' is provided, then the count of errors in each of @@ -115,17 +151,41 @@ also be printed. If 'detailed' is provided, then a count is provided for each category like 'build/class'. + repository=path + The top level directory of the repository, used to derive the header + guard CPP variable. By default, this is determined by searching for a + path that contains .git, .hg, or .svn. When this flag is specified, the + given path is used instead. This option allows the header guard CPP + variable to remain consistent even if members of a team have different + repository root directories (such as when checking out a subdirectory + with SVN). In addition, users of non-mainstream version control systems + can use this flag to ensure readable header guard CPP variables. + + Examples: + Assuming that Alice checks out ProjectName and Bob checks out + ProjectName/trunk and trunk contains src/chrome/ui/browser.h, then + with no --repository flag, the header guard CPP variable will be: + + Alice => TRUNK_SRC_CHROME_BROWSER_UI_BROWSER_H_ + Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ + + If Alice uses the --repository=trunk flag and Bob omits the flag or + uses --repository=. then the header guard CPP variable will be: + + Alice => SRC_CHROME_BROWSER_UI_BROWSER_H_ + Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ + root=subdir The root directory used for deriving header guard CPP variable. - By default, the header guard CPP variable is calculated as the relative - path to the directory that contains .git, .hg, or .svn. When this flag - is specified, the relative path is calculated from the specified - directory. If the specified directory does not exist, this flag is - ignored. + This directory is relative to the top level directory of the repository + which by default is determined by searching for a directory that contains + .git, .hg, or .svn but can also be controlled with the --repository flag. + If the specified directory does not exist, this flag is ignored. Examples: - Assuming that top/src/.git exists (and cwd=top/src), the header guard - CPP variables for top/src/chrome/browser/ui/browser.h are: + Assuming that src is the top level directory of the repository (and + cwd=top/src), the header guard CPP variables for + src/chrome/browser/ui/browser.h are: No flag => CHROME_BROWSER_UI_BROWSER_H_ --root=chrome => BROWSER_UI_BROWSER_H_ @@ -139,17 +199,48 @@ Examples: --linelength=120 + recursive + Search for files to lint recursively. Each directory given in the list + of files to be linted is replaced by all files that descend from that + directory. Files with extensions not in the valid extensions list are + excluded. + + exclude=path + Exclude the given path from the list of files to be linted. Relative + paths are evaluated relative to the current directory and shell globbing + is performed. This flag can be provided multiple times to exclude + multiple files. + + Examples: + --exclude=one.cc + --exclude=src/*.cc + --exclude=src/*.cc --exclude=test/*.cc + extensions=extension,extension,... The allowed file extensions that cpplint will check Examples: - --extensions=hpp,cpp + --extensions=%s + + includeorder=default|standardcfirst + For the build/include_order rule, the default is to blindly assume angle + bracket includes with file extension are c-system-headers (default), + even knowing this will have false classifications. + The default is established at google. + standardcfirst means to instead use an allow-list of known c headers and + treat all others as separate group of "other system headers". The C headers + included are those of the C-standard lib and closely related ones. + + config=filename + Search for config files with the specified name instead of CPPLINT.cfg headers=x,y,... The header extensions that cpplint will treat as .h in checks. Values are automatically added to --extensions list. + (by default, only files with extensions %s will be assumed to be headers) Examples: + --headers=%s --headers=hpp,hxx --headers=hpp @@ -174,7 +265,7 @@ "exclude_files" allows to specify a regular expression to be matched against a file name. If the expression matches, the file is skipped and not run - through liner. + through the linter. "linelength" allows to specify the allowed line length for the project. @@ -189,7 +280,7 @@ Example file: filter=-build/include_order,+build/include_alpha - exclude_files=.*\.cc + exclude_files=.*\\.cc The above example disables build/include_order warning and enables build/include_alpha as well as excludes all .cc from being @@ -204,17 +295,19 @@ _ERROR_CATEGORIES = [ 'build/class', 'build/c++11', - 'build/c++14', - 'build/c++tr1', + 'build/c++17', 'build/deprecated', 'build/endif_comment', 'build/explicit_make_pair', 'build/forward_decl', 'build/header_guard', 'build/include', + 'build/include_subdir', 'build/include_alpha', 'build/include_order', 'build/include_what_you_use', + 'build/namespaces_headers', + 'build/namespaces_literals', 'build/namespaces', 'build/printf_format', 'build/storage_class', @@ -242,7 +335,6 @@ 'runtime/invalid_increment', 'runtime/member_string_references', 'runtime/memset', - 'runtime/indentation_namespace', 'runtime/operator', 'runtime/printf', 'runtime/printf_format', @@ -261,6 +353,7 @@ 'whitespace/ending_newline', 'whitespace/forcolon', 'whitespace/indent', + 'whitespace/indent_namespace', 'whitespace/line_length', 'whitespace/newline', 'whitespace/operators', @@ -270,6 +363,13 @@ 'whitespace/todo', ] +# keywords to use with --outputs which generate stdout for machine processing +_MACHINE_OUTPUTS = [ + 'junit', + 'sed', + 'gsed' +] + # These error categories are no longer enforced by cpplint, but for backwards- # compatibility they may still appear in NOLINT comments. _LEGACY_ERROR_CATEGORIES = [ @@ -277,6 +377,36 @@ 'readability/function', ] +# These prefixes for categories should be ignored since they relate to other +# tools which also use the NOLINT syntax, e.g. clang-tidy. +_OTHER_NOLINT_CATEGORY_PREFIXES = [ + 'clang-analyzer-', + 'abseil-', + 'altera-', + 'android-', + 'boost-', + 'bugprone-', + 'cert-', + 'concurrency-', + 'cppcoreguidelines-', + 'darwin-', + 'fuchsia-', + 'google-', + 'hicpp-', + 'linuxkernel-', + 'llvm-', + 'llvmlibc-', + 'misc-', + 'modernize-', + 'mpi-', + 'objc-', + 'openmp-', + 'performance-', + 'portability-', + 'readability-', + 'zircon-', + ] + # The default state of the category filter. This is overridden by the --filter= # flag. By default all errors are on, so only add here categories that should be # off by default (i.e., categories that must be enabled by the --filter= flags). @@ -305,7 +435,7 @@ 'alloc.h', 'builtinbuf.h', 'bvector.h', - 'complex.h', + # 'complex.h', collides with System C header "complex.h" since C11 'defalloc.h', 'deque.h', 'editbuf.h', @@ -351,7 +481,7 @@ 'tree.h', 'type_traits.h', 'vector.h', - # 17.6.1.2 C++ library headers + # C++ library headers 'algorithm', 'array', 'atomic', @@ -405,7 +535,45 @@ 'utility', 'valarray', 'vector', - # 17.6.1.2 C++ headers for C library facilities + # C++14 headers + 'shared_mutex', + # C++17 headers + 'any', + 'charconv', + 'codecvt', + 'execution', + 'filesystem', + 'memory_resource', + 'optional', + 'string_view', + 'variant', + # C++20 headers + 'barrier', + 'bit', + 'compare', + 'concepts', + 'coroutine', + 'format', + 'latch' + 'numbers', + 'ranges', + 'semaphore', + 'source_location', + 'span', + 'stop_token', + 'syncstream', + 'version', + # C++23 headers + 'expected', + 'flat_map', + 'flat_set', + 'generator', + 'mdspan', + 'print', + 'spanstream', + 'stacktrace', + 'stdfloat', + # C++ headers for C library facilities 'cassert', 'ccomplex', 'cctype', @@ -434,6 +602,189 @@ 'cwctype', ]) +# C headers +_C_HEADERS = frozenset([ + # System C headers + 'assert.h', + 'complex.h', + 'ctype.h', + 'errno.h', + 'fenv.h', + 'float.h', + 'inttypes.h', + 'iso646.h', + 'limits.h', + 'locale.h', + 'math.h', + 'setjmp.h', + 'signal.h', + 'stdalign.h', + 'stdarg.h', + 'stdatomic.h', + 'stdbool.h', + 'stddef.h', + 'stdint.h', + 'stdio.h', + 'stdlib.h', + 'stdnoreturn.h', + 'string.h', + 'tgmath.h', + 'threads.h', + 'time.h', + 'uchar.h', + 'wchar.h', + 'wctype.h', + # C23 headers + 'stdbit.h', + 'stdckdint.h', + # additional POSIX C headers + 'aio.h', + 'arpa/inet.h', + 'cpio.h', + 'dirent.h', + 'dlfcn.h', + 'fcntl.h', + 'fmtmsg.h', + 'fnmatch.h', + 'ftw.h', + 'glob.h', + 'grp.h', + 'iconv.h', + 'langinfo.h', + 'libgen.h', + 'monetary.h', + 'mqueue.h', + 'ndbm.h', + 'net/if.h', + 'netdb.h', + 'netinet/in.h', + 'netinet/tcp.h', + 'nl_types.h', + 'poll.h', + 'pthread.h', + 'pwd.h', + 'regex.h', + 'sched.h', + 'search.h', + 'semaphore.h', + 'setjmp.h', + 'signal.h', + 'spawn.h', + 'strings.h', + 'stropts.h', + 'syslog.h', + 'tar.h', + 'termios.h', + 'trace.h', + 'ulimit.h', + 'unistd.h', + 'utime.h', + 'utmpx.h', + 'wordexp.h', + # additional GNUlib headers + 'a.out.h', + 'aliases.h', + 'alloca.h', + 'ar.h', + 'argp.h', + 'argz.h', + 'byteswap.h', + 'crypt.h', + 'endian.h', + 'envz.h', + 'err.h', + 'error.h', + 'execinfo.h', + 'fpu_control.h', + 'fstab.h', + 'fts.h', + 'getopt.h', + 'gshadow.h', + 'ieee754.h', + 'ifaddrs.h', + 'libintl.h', + 'mcheck.h', + 'mntent.h', + 'obstack.h', + 'paths.h', + 'printf.h', + 'pty.h', + 'resolv.h', + 'shadow.h', + 'sysexits.h', + 'ttyent.h', + # Additional linux glibc headers + 'dlfcn.h', + 'elf.h', + 'features.h', + 'gconv.h', + 'gnu-versions.h', + 'lastlog.h', + 'libio.h', + 'link.h', + 'malloc.h', + 'memory.h', + 'netash/ash.h', + 'netatalk/at.h', + 'netax25/ax25.h', + 'neteconet/ec.h', + 'netipx/ipx.h', + 'netiucv/iucv.h', + 'netpacket/packet.h', + 'netrom/netrom.h', + 'netrose/rose.h', + 'nfs/nfs.h', + 'nl_types.h', + 'nss.h', + 're_comp.h', + 'regexp.h', + 'sched.h', + 'sgtty.h', + 'stab.h', + 'stdc-predef.h', + 'stdio_ext.h', + 'syscall.h', + 'termio.h', + 'thread_db.h', + 'ucontext.h', + 'ustat.h', + 'utmp.h', + 'values.h', + 'wait.h', + 'xlocale.h', + # Hardware specific headers + 'arm_neon.h', + 'emmintrin.h', + 'xmmintin.h', + ]) + +# Folders of C libraries so commonly used in C++, +# that they have parity with standard C libraries. +C_STANDARD_HEADER_FOLDERS = frozenset([ + # standard C library + "sys", + # glibc for linux + "arpa", + "asm-generic", + "bits", + "gnu", + "net", + "netinet", + "protocols", + "rpc", + "rpcsvc", + "scsi", + # linux kernel header + "drm", + "linux", + "misc", + "mtd", + "rdma", + "sound", + "video", + "xen", + ]) + # Type names _TYPES = re.compile( r'^(?:' @@ -457,7 +808,8 @@ r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') # Pattern for matching FileInfo.BaseName() against test file name -_TEST_FILE_SUFFIX = r'(_test|_unittest|_regtest)$' +_test_suffixes = ['_test', '_regtest', '_unittest'] +_TEST_FILE_SUFFIX = '(' + '|'.join(_test_suffixes) + r')$' # Pattern that matches only complete whitespace, possibly across multiple lines. _EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL) @@ -471,21 +823,21 @@ ] # Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE -_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) +_CHECK_REPLACEMENT = dict([(macro_var, {}) for macro_var in _CHECK_MACROS]) for op, replacement in [('==', 'EQ'), ('!=', 'NE'), ('>=', 'GE'), ('>', 'GT'), ('<=', 'LE'), ('<', 'LT')]: - _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement - _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement - _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement - _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + _CHECK_REPLACEMENT['DCHECK'][op] = f'DCHECK_{replacement}' + _CHECK_REPLACEMENT['CHECK'][op] = f'CHECK_{replacement}' + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = f'EXPECT_{replacement}' + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = f'ASSERT_{replacement}' for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), ('>=', 'LT'), ('>', 'LE'), ('<=', 'GT'), ('<', 'GE')]: - _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement - _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = f'EXPECT_{inv_replacement}' + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = f'ASSERT_{inv_replacement}' # Alternative tokens and their replacements. For full list, see section 2.5 # Alternative tokens [lex.digraph] in the C++ standard. @@ -512,16 +864,17 @@ # False positives include C-style multi-line comments and multi-line strings # but those have always been troublesome for cpplint. _ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( - r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + r'([ =()])(' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')([ (]|$)') # These constants define types of headers for use with # _IncludeState.CheckNextIncludeOrder(). _C_SYS_HEADER = 1 _CPP_SYS_HEADER = 2 -_LIKELY_MY_HEADER = 3 -_POSSIBLE_MY_HEADER = 4 -_OTHER_HEADER = 5 +_OTHER_SYS_HEADER = 3 +_LIKELY_MY_HEADER = 4 +_POSSIBLE_MY_HEADER = 5 +_OTHER_HEADER = 6 # These constants define the current inline assembly state _NO_ASM = 0 # Outside of inline assembly block @@ -541,7 +894,21 @@ # Match string that indicates we're working on a Linux Kernel file. _SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') -_regexp_compile_cache = {} +# Commands for sed to fix the problem +_SED_FIXUPS = { + 'Remove spaces around =': r's/ = /=/', + 'Remove spaces around !=': r's/ != /!=/', + 'Remove space before ( in if (': r's/if (/if(/', + 'Remove space before ( in for (': r's/for (/for(/', + 'Remove space before ( in while (': r's/while (/while(/', + 'Remove space before ( in switch (': r's/switch (/switch(/', + 'Should have a space between // and comment': r's/\/\//\/\/ /', + 'Missing space before {': r's/\([^ ]\){/\1 {/', + 'Tab found, replace by spaces': r's/\t/ /g', + 'Line ends in whitespace. Consider deleting these extra spaces.': r's/\s*$//', + 'You don\'t need a ; after a }': r's/};/}/', + 'Missing space after ,': r's/,\([^ ]\)/, \1/g', +} # {str, set(int)}: a map from error categories to sets of linenumbers # on which those errors are expected and should be suppressed. @@ -552,33 +919,142 @@ _root = None _root_debug = False +# The top level repository directory. If set, _root is calculated relative to +# this directory instead of the directory containing version control artifacts. +# This is set by the --repository flag. +_repository = None + +# Files to exclude from linting. This is set by the --exclude flag. +_excludes = None + +# Whether to suppress all PrintInfo messages, UNRELATED to --quiet flag +_quiet = False + # The allowed line length of files. # This is set by --linelength flag. _line_length = 80 -# The allowed extensions for file names -# This is set by --extensions flag. -_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) +# This allows to use different include order rule than default +_include_order = "default" + +# This allows different config files to be used +_config_filename = "CPPLINT.cfg" # Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc. # This is set by --headers flag. -_hpp_headers = set(['h']) +_hpp_headers = set([]) + +class ErrorSuppressions: + """Class to track all error suppressions for cpplint""" + + class LineRange: + """Class to represent a range of line numbers for which an error is suppressed""" + def __init__(self, begin, end): + self.begin = begin + self.end = end + + def __str__(self): + return f'[{self.begin}-{self.end}]' + + def __contains__(self, obj): + return self.begin <= obj <= self.end + + def ContainsRange(self, other): + return self.begin <= other.begin and self.end >= other.end -# {str, bool}: a map from error categories to booleans which indicate if the -# category should be suppressed for every line. -_global_error_suppressions = {} + def __init__(self): + self._suppressions = collections.defaultdict(list) + self._open_block_suppression = None + + def _AddSuppression(self, category, line_range): + suppressed = self._suppressions[category] + if not (suppressed and suppressed[-1].ContainsRange(line_range)): + suppressed.append(line_range) + + def GetOpenBlockStart(self): + """:return: The start of the current open block or `-1` if there is not an open block""" + return self._open_block_suppression.begin if self._open_block_suppression else -1 + + def AddGlobalSuppression(self, category): + """Add a suppression for `category` which is suppressed for the whole file""" + self._AddSuppression(category, self.LineRange(0, math.inf)) + + def AddLineSuppression(self, category, linenum): + """Add a suppression for `category` which is suppressed only on `linenum`""" + self._AddSuppression(category, self.LineRange(linenum, linenum)) + + def StartBlockSuppression(self, category, linenum): + """Start a suppression block for `category` on `linenum`. inclusive""" + if self._open_block_suppression is None: + self._open_block_suppression = self.LineRange(linenum, math.inf) + self._AddSuppression(category, self._open_block_suppression) + + def EndBlockSuppression(self, linenum): + """End the current block suppression on `linenum`. inclusive""" + if self._open_block_suppression: + self._open_block_suppression.end = linenum + self._open_block_suppression = None + + def IsSuppressed(self, category, linenum): + """:return: `True` if `category` is suppressed for `linenum`""" + suppressed = self._suppressions[category] + self._suppressions[None] + return any(linenum in lr for lr in suppressed) + + def HasOpenBlock(self): + """:return: `True` if a block suppression was started but not ended""" + return self._open_block_suppression is not None + + def Clear(self): + """Clear all current error suppressions""" + self._suppressions.clear() + self._open_block_suppression = None + +_error_suppressions = ErrorSuppressions() def ProcessHppHeadersOption(val): global _hpp_headers try: - _hpp_headers = set(val.split(',')) - # Automatically append to extensions list so it does not have to be set 2 times - _valid_extensions.update(_hpp_headers) + _hpp_headers = {ext.strip() for ext in val.split(',')} except ValueError: PrintUsage('Header extensions must be comma separated list.') +def ProcessIncludeOrderOption(val): + if val is None or val == "default": + pass + elif val == "standardcfirst": + global _include_order + _include_order = val + else: + PrintUsage('Invalid includeorder value %s. Expected default|standardcfirst') + def IsHeaderExtension(file_extension): - return file_extension in _hpp_headers + return file_extension in GetHeaderExtensions() + +def GetHeaderExtensions(): + if _hpp_headers: + return _hpp_headers + if _valid_extensions: + return {h for h in _valid_extensions if 'h' in h} + return set(['h', 'hh', 'hpp', 'hxx', 'h++', 'cuh']) + +# The allowed extensions for file names +# This is set by --extensions flag +def GetAllExtensions(): + return GetHeaderExtensions().union(_valid_extensions or set( + ['c', 'cc', 'cpp', 'cxx', 'c++', 'cu'])) + +def ProcessExtensionsOption(val): + global _valid_extensions + try: + extensions = [ext.strip() for ext in val.split(',')] + _valid_extensions = set(extensions) + except ValueError: + PrintUsage('Extensions should be a comma-separated list of values;' + 'for example: extensions=hpp,cpp\n' + f'This could not be parsed: "{val}"') + +def GetNonHeaderExtensions(): + return GetAllExtensions().difference(GetHeaderExtensions()) def ParseNolintSuppressions(filename, raw_line, linenum, error): """Updates the global list of line error-suppressions. @@ -593,26 +1069,50 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): linenum: int, the number of the current line. error: function, an error handler. """ - matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + matched = re.search(r'\bNOLINT(NEXTLINE|BEGIN|END)?\b(\([^)]+\))?', raw_line) if matched: - if matched.group(1): - suppressed_line = linenum + 1 - else: - suppressed_line = linenum - category = matched.group(2) - if category in (None, '(*)'): # => "suppress all" - _error_suppressions.setdefault(None, set()).add(suppressed_line) + no_lint_type = matched.group(1) + if no_lint_type == 'NEXTLINE': + def ProcessCategory(category): + _error_suppressions.AddLineSuppression(category, linenum + 1) + elif no_lint_type == 'BEGIN': + if _error_suppressions.HasOpenBlock(): + error(filename, linenum, 'readability/nolint', 5, + f'NONLINT block already defined on line {_error_suppressions.GetOpenBlockStart()}') + + def ProcessCategory(category): + _error_suppressions.StartBlockSuppression(category, linenum) + elif no_lint_type == 'END': + if not _error_suppressions.HasOpenBlock(): + error(filename, linenum, 'readability/nolint', 5, 'Not in a NOLINT block') + + def ProcessCategory(category): + if category is not None: + error(filename, linenum, 'readability/nolint', 5, + f'NOLINT categories not supported in block END: {category}') + _error_suppressions.EndBlockSuppression(linenum) else: - if category.startswith('(') and category.endswith(')'): - category = category[1:-1] + def ProcessCategory(category): + _error_suppressions.AddLineSuppression(category, linenum) + categories = matched.group(2) + if categories in (None, '(*)'): # => "suppress all" + ProcessCategory(None) + elif categories.startswith('(') and categories.endswith(')'): + for category in set(map(lambda c: c.strip(), categories[1:-1].split(','))): if category in _ERROR_CATEGORIES: - _error_suppressions.setdefault(category, set()).add(suppressed_line) + ProcessCategory(category) + elif any(c for c in _OTHER_NOLINT_CATEGORY_PREFIXES if category.startswith(c)): + # Ignore any categories from other tools. + pass elif category not in _LEGACY_ERROR_CATEGORIES: error(filename, linenum, 'readability/nolint', 5, - 'Unknown NOLINT error category: %s' % category) - + f'Unknown NOLINT error category: {category}') def ProcessGlobalSuppresions(lines): + """Deprecated; use ProcessGlobalSuppressions.""" + ProcessGlobalSuppressions(lines) + +def ProcessGlobalSuppressions(lines): """Updates the list of global error suppressions. Parses any lint directives in the file that have global effect. @@ -624,74 +1124,36 @@ def ProcessGlobalSuppresions(lines): for line in lines: if _SEARCH_C_FILE.search(line): for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: - _global_error_suppressions[category] = True + _error_suppressions.AddGlobalSuppression(category) if _SEARCH_KERNEL_FILE.search(line): for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: - _global_error_suppressions[category] = True + _error_suppressions.AddGlobalSuppression(category) def ResetNolintSuppressions(): """Resets the set of NOLINT suppressions to empty.""" - _error_suppressions.clear() - _global_error_suppressions.clear() + _error_suppressions.Clear() def IsErrorSuppressedByNolint(category, linenum): """Returns true if the specified error category is suppressed on this line. Consults the global error_suppressions map populated by - ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions. + ParseNolintSuppressions/ProcessGlobalSuppressions/ResetNolintSuppressions. Args: category: str, the category of the error. linenum: int, the current line number. Returns: - bool, True iff the error should be suppressed due to a NOLINT comment or - global suppression. - """ - return (_global_error_suppressions.get(category, False) or - linenum in _error_suppressions.get(category, set()) or - linenum in _error_suppressions.get(None, set())) - - -def Match(pattern, s): - """Matches the string with the pattern, caching the compiled regexp.""" - # The regexp compilation caching is inlined in both Match and Search for - # performance reasons; factoring it out into a separate function turns out - # to be noticeably expensive. - if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].match(s) - - -def ReplaceAll(pattern, rep, s): - """Replaces instances of pattern in a string with a replacement. - - The compiled regex is kept in a cache shared by Match and Search. - - Args: - pattern: regex pattern - rep: replacement text - s: search string - - Returns: - string with replacements made (or original string if no replacements) + bool, True iff the error should be suppressed due to a NOLINT comment, + block suppression or global suppression. """ - if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].sub(rep, s) - - -def Search(pattern, s): - """Searches the string for the pattern, caching the compiled regexp.""" - if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].search(s) + return _error_suppressions.IsSuppressed(category, linenum) def _IsSourceExtension(s): """File extension (excluding dot) matches a source file extension.""" - return s in ('c', 'cc', 'cpp', 'cxx') + return s in GetNonHeaderExtensions() class _IncludeState(object): @@ -712,11 +1174,13 @@ class _IncludeState(object): _MY_H_SECTION = 1 _C_SECTION = 2 _CPP_SECTION = 3 - _OTHER_H_SECTION = 4 + _OTHER_SYS_SECTION = 4 + _OTHER_H_SECTION = 5 _TYPE_NAMES = { _C_SYS_HEADER: 'C system header', _CPP_SYS_HEADER: 'C++ system header', + _OTHER_SYS_HEADER: 'other system header', _LIKELY_MY_HEADER: 'header this file implements', _POSSIBLE_MY_HEADER: 'header this file may implement', _OTHER_HEADER: 'other header', @@ -726,11 +1190,14 @@ class _IncludeState(object): _MY_H_SECTION: 'a header this file implements', _C_SECTION: 'C system header', _CPP_SECTION: 'C++ system header', + _OTHER_SYS_SECTION: 'other system header', _OTHER_H_SECTION: 'other header', } def __init__(self): self.include_list = [[]] + self._section = None + self._last_header = None self.ResetSection('') def FindHeader(self, header): @@ -801,7 +1268,7 @@ def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): # If previous line was a blank line, assume that the headers are # intentionally sorted the way they are. if (self._last_header > header_path and - Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): + re.match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): return False return True @@ -819,9 +1286,8 @@ def CheckNextIncludeOrder(self, header_type): error message describing what's wrong. """ - error_message = ('Found %s after %s' % - (self._TYPE_NAMES[header_type], - self._SECTION_NAMES[self._section])) + error_message = (f'Found {self._TYPE_NAMES[header_type]}' + f' after {self._SECTION_NAMES[self._section]}') last_section = self._section @@ -837,6 +1303,12 @@ def CheckNextIncludeOrder(self, header_type): else: self._last_header = '' return error_message + elif header_type == _OTHER_SYS_HEADER: + if self._section <= self._OTHER_SYS_SECTION: + self._section = self._OTHER_SYS_SECTION + else: + self._last_header = '' + return error_message elif header_type == _LIKELY_MY_HEADER: if self._section <= self._MY_H_SECTION: self._section = self._MY_H_SECTION @@ -875,9 +1347,18 @@ def __init__(self): # output format: # "emacs" - format that emacs can parse (default) + # "eclipse" - format that eclipse can parse # "vs7" - format that Microsoft Visual Studio 7 can parse + # "junit" - format that Jenkins, Bamboo, etc can parse + # "sed" - returns a gnu sed command to fix the problem + # "gsed" - like sed, but names the command gsed, e.g. for macOS homebrew users self.output_format = 'emacs' + # For JUnit output, save errors and failures until the end so that they + # can be written into the XML + self._junit_errors = [] + self._junit_failures = [] + def SetOutputFormat(self, output_format): """Sets the output format for errors.""" self.output_format = output_format @@ -925,7 +1406,7 @@ def AddFilters(self, filters): for filt in self.filters: if not (filt.startswith('+') or filt.startswith('-')): raise ValueError('Every filter in --filters must start with + or -' - ' (%s does not)' % filt) + f' ({filt} does not)') def BackupFilters(self): """ Saves the current filter list to backup storage.""" @@ -952,10 +1433,70 @@ def IncrementErrorCount(self, category): def PrintErrorCounts(self): """Print a summary of errors by category, and the total.""" - for category, count in self.errors_by_category.iteritems(): - sys.stderr.write('Category \'%s\' errors found: %d\n' % - (category, count)) - sys.stdout.write('Total errors found: %d\n' % self.error_count) + for category, count in sorted(dict.items(self.errors_by_category)): + self.PrintInfo(f'Category \'{category}\' errors found: {count}\n') + if self.error_count > 0: + self.PrintInfo(f'Total errors found: {self.error_count}\n') + + def PrintInfo(self, message): + # _quiet does not represent --quiet flag. + # Hide infos from stdout to keep stdout pure for machine consumption + if not _quiet and self.output_format not in _MACHINE_OUTPUTS: + sys.stdout.write(message) + + def PrintError(self, message): + if self.output_format == 'junit': + self._junit_errors.append(message) + else: + sys.stderr.write(message) + + def AddJUnitFailure(self, filename, linenum, message, category, confidence): + self._junit_failures.append((filename, linenum, message, category, + confidence)) + + def FormatJUnitXML(self): + num_errors = len(self._junit_errors) + num_failures = len(self._junit_failures) + + testsuite = xml.etree.ElementTree.Element('testsuite') + testsuite.attrib['errors'] = str(num_errors) + testsuite.attrib['failures'] = str(num_failures) + testsuite.attrib['name'] = 'cpplint' + + if num_errors == 0 and num_failures == 0: + testsuite.attrib['tests'] = str(1) + xml.etree.ElementTree.SubElement(testsuite, 'testcase', name='passed') + + else: + testsuite.attrib['tests'] = str(num_errors + num_failures) + if num_errors > 0: + testcase = xml.etree.ElementTree.SubElement(testsuite, 'testcase') + testcase.attrib['name'] = 'errors' + error = xml.etree.ElementTree.SubElement(testcase, 'error') + error.text = '\n'.join(self._junit_errors) + if num_failures > 0: + # Group failures by file + failed_file_order = [] + failures_by_file = {} + for failure in self._junit_failures: + failed_file = failure[0] + if failed_file not in failed_file_order: + failed_file_order.append(failed_file) + failures_by_file[failed_file] = [] + failures_by_file[failed_file].append(failure) + # Create a testcase for each file + for failed_file in failed_file_order: + failures = failures_by_file[failed_file] + testcase = xml.etree.ElementTree.SubElement(testsuite, 'testcase') + testcase.attrib['name'] = failed_file + failure = xml.etree.ElementTree.SubElement(testcase, 'failure') + template = '{0}: {1} [{2}] [{3}]' + texts = [template.format(f[1], f[2], f[3], f[4]) for f in failures] + failure.text = '\n'.join(texts) + + xml_decl = '\n' + return xml_decl + xml.etree.ElementTree.tostring(testsuite, 'utf-8').decode('utf-8') + _cpplint_state = _CppLintState() @@ -1067,7 +1608,7 @@ def Check(self, error, filename, linenum): if not self.in_a_function: return - if Match(r'T(EST|est)', self.current_function): + if re.match(r'T(EST|est)', self.current_function): base_trigger = self._TEST_TRIGGER else: base_trigger = self._NORMAL_TRIGGER @@ -1080,9 +1621,8 @@ def Check(self, error, filename, linenum): error_level = 5 error(filename, linenum, 'readability/fn_size', error_level, 'Small and focused functions are preferred:' - ' %s has %d non-comment lines' - ' (error triggered by exceeding %d lines).' % ( - self.current_function, self.lines_in_function, trigger)) + f' {self.current_function} has {self.lines_in_function} non-comment lines' + f' (error triggered by exceeding {trigger} lines).') def End(self): """Stop analyzing function body.""" @@ -1109,12 +1649,12 @@ def FullName(self): return os.path.abspath(self._filename).replace('\\', '/') def RepositoryName(self): - """FullName after removing the local path to the repository. + r"""FullName after removing the local path to the repository. If we have a real absolute path name here we can try to do something smart: detecting the root of the checkout and truncating /path/to/checkout from the name so that we get header guards that don't include things like - "C:\Documents and Settings\..." or "/home/username/..." in them and thus + "C:\\Documents and Settings\\..." or "/home/username/..." in them and thus people on different computers who have checked the source out to different locations won't see bogus errors. """ @@ -1123,6 +1663,20 @@ def RepositoryName(self): if os.path.exists(fullname): project_dir = os.path.dirname(fullname) + # If the user specified a repository path, it exists, and the file is + # contained in it, use the specified repository path + if _repository: + repo = FileInfo(_repository).FullName() + root_dir = project_dir + while os.path.exists(root_dir): + # allow case insensitive compare on Windows + if os.path.normcase(root_dir) == os.path.normcase(repo): + return os.path.relpath(fullname, root_dir).replace('\\', '/') + one_up_dir = os.path.dirname(root_dir) + if one_up_dir == root_dir: + break + root_dir = one_up_dir + if os.path.exists(os.path.join(project_dir, ".svn")): # If there's a .svn file in the current directory, we recursively look # up the directory tree for the top of the SVN checkout @@ -1143,6 +1697,7 @@ def RepositoryName(self): os.path.exists(os.path.join(current_dir, ".hg")) or os.path.exists(os.path.join(current_dir, ".svn"))): root_dir = current_dir + break current_dir = os.path.dirname(current_dir) if (os.path.exists(os.path.join(root_dir, ".git")) or @@ -1173,7 +1728,7 @@ def BaseName(self): return self.Split()[1] def Extension(self): - """File extension - text following the final period.""" + """File extension - text following the final period, includes that period.""" return self.Split()[2] def NoExtension(self): @@ -1185,7 +1740,7 @@ def IsSource(self): return _IsSourceExtension(self.Extension()[1:]) -def _ShouldPrintError(category, confidence, linenum): +def _ShouldPrintError(category, confidence, filename, linenum): """If confidence >= verbose, category passes filter and is not suppressed.""" # There are three ways we might decide not to print an error message: @@ -1199,11 +1754,16 @@ def _ShouldPrintError(category, confidence, linenum): is_filtered = False for one_filter in _Filters(): + filter_cat, filter_file, filter_line = _ParseFilterSelector(one_filter[1:]) + category_match = category.startswith(filter_cat) + file_match = filter_file == "" or filter_file == filename + line_match = filter_line == linenum or filter_line == -1 + if one_filter.startswith('-'): - if category.startswith(one_filter[1:]): + if category_match and file_match and line_match: is_filtered = True elif one_filter.startswith('+'): - if category.startswith(one_filter[1:]): + if category_match and file_match and line_match: is_filtered = False else: assert False # should have been checked for in SetFilter. @@ -1220,9 +1780,9 @@ def Error(filename, linenum, category, confidence, message): that is, how certain we are this is a legitimate style regression, and not a misidentification or a use that's sometimes justified. - False positives can be suppressed by the use of - "cpplint(category)" comments on the offending line. These are - parsed into _error_suppressions. + False positives can be suppressed by the use of "NOLINT(category)" + comments, NOLINTNEXTLINE or in blocks started by NOLINTBEGIN. These + are parsed into _error_suppressions. Args: filename: The name of the file containing the error. @@ -1235,17 +1795,28 @@ def Error(filename, linenum, category, confidence, message): and 1 meaning that it could be a legitimate construct. message: The error message. """ - if _ShouldPrintError(category, confidence, linenum): + if _ShouldPrintError(category, confidence, filename, linenum): _cpplint_state.IncrementErrorCount(category) if _cpplint_state.output_format == 'vs7': - sys.stderr.write('%s(%s): error cpplint: [%s] %s [%d]\n' % ( - filename, linenum, category, message, confidence)) + _cpplint_state.PrintError(f'{filename}({linenum}): error cpplint:' + f' [{category}] {message} [{confidence}]\n') elif _cpplint_state.output_format == 'eclipse': - sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( - filename, linenum, message, category, confidence)) + sys.stderr.write(f'{filename}:{linenum}: warning:' + f' {message} [{category}] [{confidence}]\n') + elif _cpplint_state.output_format == 'junit': + _cpplint_state.AddJUnitFailure(filename, linenum, message, category, confidence) + elif _cpplint_state.output_format in ['sed', 'gsed']: + if message in _SED_FIXUPS: + sys.stdout.write(f"{_cpplint_state.output_format} -i" + f" '{linenum}{_SED_FIXUPS[message]}' {filename}" + f" # {message} [{category}] [{confidence}]\n") + else: + sys.stderr.write(f'# {filename}:{linenum}: ' + f' "{message}" [{category}] [{confidence}]\n') else: - sys.stderr.write('%s:%s: %s [%s] [%d]\n' % ( - filename, linenum, message, category, confidence)) + final_message = (f'{filename}:{linenum}: ' + f' {message} [{category}] [{confidence}]\n') + sys.stderr.write(final_message) # Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. @@ -1315,7 +1886,7 @@ def CleanseRawStrings(raw_lines): # Found the end of the string, match leading space for this # line and resume copying the original lines, and also insert # a "" on the last line. - leading_space = Match(r'^(\s*)\S', line) + leading_space = re.match(r'^(\s*)\S', line) line = leading_space.group(1) + '""' + line[end + len(delimiter):] delimiter = None else: @@ -1336,9 +1907,9 @@ def CleanseRawStrings(raw_lines): # before removing raw strings. This is because there are some # cpplint checks that requires the comments to be preserved, but # we don't want to check comments that are inside raw strings. - matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + matched = re.match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) if (matched and - not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', + not re.match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', matched.group(1))): delimiter = ')' + matched.group(2) + '"' @@ -1421,6 +1992,28 @@ def CleanseComments(line): return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) +def ReplaceAlternateTokens(line): + """Replace any alternate token by its original counterpart. + + In order to comply with the google rule stating that unary operators should + never be followed by a space, an exception is made for the 'not' and 'compl' + alternate tokens. For these, any trailing space is removed during the + conversion. + + Args: + line: The line being processed. + + Returns: + The line with alternate tokens replaced. + """ + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + token = _ALT_TOKEN_REPLACEMENT[match.group(2)] + tail = '' if match.group(2) in ['not', 'compl'] and match.group(3) == ' ' \ + else r'\3' + line = re.sub(match.re, rf'\1{token}{tail}', line, count=1) + return line + + class CleansedLines(object): """Holds 4 copies of all lines with different preprocessing applied to them. @@ -1433,15 +2026,17 @@ class CleansedLines(object): """ def __init__(self, lines): + if '-readability/alt_tokens' in _cpplint_state.filters: + for i, line in enumerate(lines): + lines[i] = ReplaceAlternateTokens(line) self.elided = [] self.lines = [] self.raw_lines = lines self.num_lines = len(lines) self.lines_without_raw_strings = CleanseRawStrings(lines) - for linenum in range(len(self.lines_without_raw_strings)): - self.lines.append(CleanseComments( - self.lines_without_raw_strings[linenum])) - elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + for line in self.lines_without_raw_strings: + self.lines.append(CleanseComments(line)) + elided = self._CollapseStrings(line) self.elided.append(CleanseComments(elided)) def NumLines(self): @@ -1474,7 +2069,7 @@ def _CollapseStrings(elided): collapsed = '' while True: # Find the first quote character - match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) + match = re.match(r'^([^\'"]*)([\'"])(.*)$', elided) if not match: collapsed += elided break @@ -1499,8 +2094,8 @@ def _CollapseStrings(elided): # correctly as long as there are digits on both sides of the # separator. So we are fine as long as we don't see something # like "0.'3" (gcc 4.9.0 will not allow this literal). - if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): - match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) + if re.search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): + match_literal = re.match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) collapsed += head + match_literal.group(1).replace("'", '') elided = match_literal.group(2) else: @@ -1529,7 +2124,7 @@ def FindEndOfExpressionInLine(line, startpos, stack): On finding an unclosed expression: (-1, None) Otherwise: (-1, new stack at end of this line) """ - for i in xrange(startpos, len(line)): + for i in range(startpos, len(line)): char = line[i] if char in '([{': # Found start of parenthesized expression, push to expression stack @@ -1542,7 +2137,7 @@ def FindEndOfExpressionInLine(line, startpos, stack): stack.pop() if not stack: return (-1, None) - elif i > 0 and Search(r'\boperator\s*$', line[0:i]): + elif i > 0 and re.search(r'\boperator\s*$', line[0:i]): # operator<, don't add to stack continue else: @@ -1571,7 +2166,7 @@ def FindEndOfExpressionInLine(line, startpos, stack): # Ignore "->" and operator functions if (i > 0 and - (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): + (line[i - 1] == '-' or re.search(r'\boperator\s*$', line[0:i - 1]))): continue # Pop the stack if there is a matching '<'. Otherwise, ignore @@ -1618,7 +2213,7 @@ def CloseExpression(clean_lines, linenum, pos): """ line = clean_lines.elided[linenum] - if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): + if (line[pos] not in '({[<') or re.match(r'<[<=]', line[pos:]): return (line, clean_lines.NumLines(), -1) # Check first line @@ -1666,8 +2261,8 @@ def FindStartOfExpressionInLine(line, endpos, stack): # Ignore it if it's a "->" or ">=" or "operator>" if (i > 0 and (line[i - 1] == '-' or - Match(r'\s>=\s', line[i - 1:]) or - Search(r'\boperator\s*$', line[0:i]))): + re.match(r'\s>=\s', line[i - 1:]) or + re.search(r'\boperator\s*$', line[0:i]))): i -= 1 else: stack.append('>') @@ -1758,7 +2353,7 @@ def CheckForCopyright(filename, lines, error): # We'll say it should occur by line 10. Don't forget there's a # placeholder line at the front. - for line in xrange(1, min(len(lines), 11)): + for line in range(1, min(len(lines), 11)): if re.search(r'Copyright', lines[line], re.I): break else: # means no copyright line was found error(filename, 0, 'legal/copyright', 5, @@ -1775,7 +2370,7 @@ def GetIndentLevel(line): Returns: An integer count of leading spaces, possibly zero. """ - indent = Match(r'^( *)\S', line) + indent = re.match(r'^( *)\S', line) if indent: return len(indent.group(1)) else: @@ -1793,10 +2388,10 @@ def PathSplitToList(path): lst = [] while True: (head, tail) = os.path.split(path) - if head == path: # absolute paths end + if head == path: # absolute paths end lst.append(head) break - if tail == path: # relative paths end + if tail == path: # relative paths end lst.append(tail) break @@ -1830,8 +2425,8 @@ def GetHeaderGuardCPPVariable(filename): def FixupPathFromRoot(): if _root_debug: - sys.stderr.write("\n_root fixup, _root = '%s', repository name = '%s'\n" - %(_root, fileinfo.RepositoryName())) + sys.stderr.write(f"\n_root fixup, _root = '{_root}'," + f" repository name = '{fileinfo.RepositoryName()}'\n") # Process the file path with the --root flag if it was set. if not _root: @@ -1853,27 +2448,28 @@ def StripListPrefix(lst, prefix): if _root_debug: sys.stderr.write(("_root lstrip (maybe_path=%s, file_path_from_root=%s," + - " _root=%s)\n") %(maybe_path, file_path_from_root, _root)) + " _root=%s)\n") % (maybe_path, file_path_from_root, _root)) if maybe_path: return os.path.join(*maybe_path) # --root=.. , will prepend the outer directory to the header guard full_path = fileinfo.FullName() - root_abspath = os.path.abspath(_root) + # adapt slashes for windows + root_abspath = os.path.abspath(_root).replace('\\', '/') maybe_path = StripListPrefix(PathSplitToList(full_path), PathSplitToList(root_abspath)) if _root_debug: sys.stderr.write(("_root prepend (maybe_path=%s, full_path=%s, " + - "root_abspath=%s)\n") %(maybe_path, full_path, root_abspath)) + "root_abspath=%s)\n") % (maybe_path, full_path, root_abspath)) if maybe_path: return os.path.join(*maybe_path) if _root_debug: - sys.stderr.write("_root ignore, returning %s\n" %(file_path_from_root)) + sys.stderr.write(f"_root ignore, returning {file_path_from_root}\n") # --root=FAKE_DIR is ignored return file_path_from_root @@ -1902,7 +2498,12 @@ def CheckForHeaderGuard(filename, clean_lines, error): # and not the general NOLINT or NOLINT(*) syntax. raw_lines = clean_lines.lines_without_raw_strings for i in raw_lines: - if Search(r'//\s*NOLINT\(build/header_guard\)', i): + if re.search(r'//\s*NOLINT\(build/header_guard\)', i): + return + + # Allow pragma once instead of header guards + for i in raw_lines: + if re.search(r'^\s*#pragma\s+once', i): return cppvar = GetHeaderGuardCPPVariable(filename) @@ -1929,8 +2530,7 @@ def CheckForHeaderGuard(filename, clean_lines, error): if not ifndef or not define or ifndef != define: error(filename, 0, 'build/header_guard', 5, - 'No #ifndef header guard found, suggested CPP variable is: %s' % - cppvar) + f'No #ifndef header guard found, suggested CPP variable is: {cppvar}') return # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ @@ -1943,66 +2543,75 @@ def CheckForHeaderGuard(filename, clean_lines, error): ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, error) error(filename, ifndef_linenum, 'build/header_guard', error_level, - '#ifndef header guard has wrong style, please use: %s' % cppvar) + f'#ifndef header guard has wrong style, please use: {cppvar}') # Check for "//" comments on endif line. ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, error) - match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) + match = re.match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) if match: if match.group(1) == '_': # Issue low severity warning for deprecated double trailing underscore error(filename, endif_linenum, 'build/header_guard', 0, - '#endif line should be "#endif // %s"' % cppvar) + f'#endif line should be "#endif // {cppvar}"') return # Didn't find the corresponding "//" comment. If this file does not # contain any "//" comments at all, it could be that the compiler # only wants "/**/" comments, look for those instead. no_single_line_comments = True - for i in xrange(1, len(raw_lines) - 1): + for i in range(1, len(raw_lines) - 1): line = raw_lines[i] - if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): + if re.match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): no_single_line_comments = False break if no_single_line_comments: - match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) + match = re.match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) if match: if match.group(1) == '_': # Low severity warning for double trailing underscore error(filename, endif_linenum, 'build/header_guard', 0, - '#endif line should be "#endif /* %s */"' % cppvar) + f'#endif line should be "#endif /* {cppvar} */"') return # Didn't find anything error(filename, endif_linenum, 'build/header_guard', 5, - '#endif line should be "#endif // %s"' % cppvar) + f'#endif line should be "#endif // {cppvar}"') def CheckHeaderFileIncluded(filename, include_state, error): - """Logs an error if a .cc file does not include its header.""" + """Logs an error if a source file does not include its header.""" # Do not check test files fileinfo = FileInfo(filename) - if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): + if re.search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): return - headerfile = filename[0:len(filename) - len(fileinfo.Extension())] + '.h' - if not os.path.exists(headerfile): - return - headername = FileInfo(headerfile).RepositoryName() - first_include = 0 - for section_list in include_state.include_list: - for f in section_list: - if headername in f[0] or f[0] in headername: - return - if not first_include: - first_include = f[1] + first_include = message = None + basefilename = filename[0:len(filename) - len(fileinfo.Extension())] + for ext in GetHeaderExtensions(): + headerfile = basefilename + '.' + ext + if not os.path.exists(headerfile): + continue + headername = FileInfo(headerfile).RepositoryName() + include_uses_unix_dir_aliases = False + for section_list in include_state.include_list: + for f in section_list: + include_text = f[0] + if "./" in include_text: + include_uses_unix_dir_aliases = True + if headername in include_text or include_text in headername: + return + if not first_include: + first_include = f[1] + + message = f'{fileinfo.RepositoryName()} should include its header file {headername}' + if include_uses_unix_dir_aliases: + message += ". Relative paths like . and .. are not allowed." - error(filename, first_include, 'build/include', 5, - '%s should include its header file %s' % (fileinfo.RepositoryName(), - headername)) + if message: + error(filename, first_include, 'build/include', 5, message) def CheckForBadCharacters(filename, lines, error): @@ -2023,7 +2632,7 @@ def CheckForBadCharacters(filename, lines, error): error: The function to call with any errors found. """ for linenum, line in enumerate(lines): - if u'\ufffd' in line: + if '\ufffd' in line: error(filename, linenum, 'readability/utf8', 5, 'Line contains invalid UTF-8 (or Unicode replacement character).') if '\0' in line: @@ -2135,7 +2744,7 @@ def CheckPosixThreading(filename, clean_lines, linenum, error): for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: # Additional pattern matching check to confirm that this is the # function we are looking for - if Search(pattern, line): + if re.search(pattern, line): error(filename, linenum, 'runtime/threadsafe_fn', 2, 'Consider using ' + multithread_safe_func + '...) instead of ' + single_thread_func + @@ -2155,7 +2764,7 @@ def CheckVlogArguments(filename, clean_lines, linenum, error): error: The function to call with any errors found. """ line = clean_lines.elided[linenum] - if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + if re.search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): error(filename, linenum, 'runtime/vlog', 5, 'VLOG() should be used with numeric verbosity level. ' 'Use LOG() if you want symbolic severity levels.') @@ -2189,17 +2798,17 @@ def CheckInvalidIncrement(filename, clean_lines, linenum, error): def IsMacroDefinition(clean_lines, linenum): - if Search(r'^#define', clean_lines[linenum]): + if re.search(r'^#define', clean_lines[linenum]): return True - if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): + if linenum > 0 and re.search(r'\\$', clean_lines[linenum - 1]): return True return False def IsForwardClassDeclaration(clean_lines, linenum): - return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) + return re.match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) class _BlockInfo(object): @@ -2294,15 +2903,15 @@ def __init__(self, name, class_or_struct, clean_lines, linenum): def CheckBegin(self, filename, clean_lines, linenum, error): # Look for a bare ':' - if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + if re.search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): self.is_derived = True def CheckEnd(self, filename, clean_lines, linenum, error): # If there is a DISALLOW macro, it should appear near the end of # the class. seen_last_thing_in_class = False - for i in xrange(linenum - 1, self.starting_linenum, -1): - match = Search( + for i in range(linenum - 1, self.starting_linenum, -1): + match = re.search( r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + self.name + r'\)', clean_lines.elided[i]) @@ -2312,20 +2921,20 @@ def CheckEnd(self, filename, clean_lines, linenum, error): match.group(1) + ' should be the last thing in the class') break - if not Match(r'^\s*$', clean_lines.elided[i]): + if not re.match(r'^\s*$', clean_lines.elided[i]): seen_last_thing_in_class = True # Check that closing brace is aligned with beginning of the class. # Only do this if the closing brace is indented by only whitespaces. # This means we will not check single-line class definitions. - indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + indent = re.match(r'^( *)\}', clean_lines.elided[linenum]) if indent and len(indent.group(1)) != self.class_indent: if self.is_struct: parent = 'struct ' + self.name else: parent = 'class ' + self.name error(filename, linenum, 'whitespace/indent', 3, - 'Closing brace should be aligned with beginning of %s' % parent) + f'Closing brace should be aligned with beginning of {parent}') class _NamespaceInfo(_BlockInfo): @@ -2352,7 +2961,7 @@ def CheckEnd(self, filename, clean_lines, linenum, error): # deciding what these nontrivial things are, so this check is # triggered by namespace size only, which works most of the time. if (linenum - self.starting_linenum < 10 - and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): + and not re.match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): return # Look for matching comment at end of namespace. @@ -2369,18 +2978,17 @@ def CheckEnd(self, filename, clean_lines, linenum, error): # expected namespace. if self.name: # Named namespace - if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + + if not re.match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) + r'[\*/\.\\\s]*$'), line): error(filename, linenum, 'readability/namespace', 5, - 'Namespace should be terminated with "// namespace %s"' % - self.name) + f'Namespace should be terminated with "// namespace {self.name}"') else: # Anonymous namespace - if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + if not re.match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): # If "// namespace anonymous" or "// anonymous namespace (more text)", # mention "// anonymous namespace" as an acceptable form - if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): + if re.match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): error(filename, linenum, 'readability/namespace', 5, 'Anonymous namespace should be terminated with "// namespace"' ' or "// anonymous namespace"') @@ -2483,7 +3091,7 @@ def InTemplateArgumentList(self, clean_lines, linenum, pos): while linenum < clean_lines.NumLines(): # Find the earliest character that might indicate a template argument line = clean_lines.elided[linenum] - match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) + match = re.match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) if not match: linenum += 1 pos = 0 @@ -2543,11 +3151,11 @@ def UpdatePreprocessor(self, line): Args: line: current line to check. """ - if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + if re.match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): # Beginning of #if block, save the nesting stack here. The saved # stack will allow us to restore the parsing state in the #else case. self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) - elif Match(r'^\s*#\s*(else|elif)\b', line): + elif re.match(r'^\s*#\s*(else|elif)\b', line): # Beginning of #else block if self.pp_stack: if not self.pp_stack[-1].seen_else: @@ -2562,7 +3170,7 @@ def UpdatePreprocessor(self, line): else: # TODO(unknown): unexpected #else, issue warning? pass - elif Match(r'^\s*#\s*endif\b', line): + elif re.match(r'^\s*#\s*endif\b', line): # End of #if or #else blocks. if self.pp_stack: # If we saw an #else, we will need to restore the nesting @@ -2634,7 +3242,7 @@ def Update(self, filename, clean_lines, linenum, error): # declarations even if it weren't followed by a whitespace, this # is so that we don't confuse our namespace checker. The # missing spaces will be flagged by CheckSpacing. - namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + namespace_decl_match = re.match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) if not namespace_decl_match: break @@ -2651,9 +3259,9 @@ def Update(self, filename, clean_lines, linenum, error): # such as in: # class LOCKABLE API Object { # }; - class_decl_match = Match( - r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?' - r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))' + class_decl_match = re.match( + r'^(\s*(?:template\s*<[\w\s<>,:=]*>\s*)?' + r'(class|struct)\s+(?:[a-zA-Z0-9_]+\s+)*(\w+(?:::\w+)*))' r'(.*)$', line) if (class_decl_match and (not self.stack or self.stack[-1].open_parentheses == 0)): @@ -2681,7 +3289,7 @@ def Update(self, filename, clean_lines, linenum, error): # Update access control if we are inside a class/struct if self.stack and isinstance(self.stack[-1], _ClassInfo): classinfo = self.stack[-1] - access_match = Match( + access_match = re.match( r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' r':(?:[^:]|$)', line) @@ -2692,7 +3300,7 @@ def Update(self, filename, clean_lines, linenum, error): # check if the keywords are not preceded by whitespaces. indent = access_match.group(1) if (len(indent) != classinfo.class_indent + 1 and - Match(r'^\s*$', indent)): + re.match(r'^\s*$', indent)): if classinfo.is_struct: parent = 'struct ' + classinfo.name else: @@ -2701,13 +3309,13 @@ def Update(self, filename, clean_lines, linenum, error): if access_match.group(3): slots = access_match.group(3) error(filename, linenum, 'whitespace/indent', 3, - '%s%s: should be indented +1 space inside %s' % ( - access_match.group(2), slots, parent)) + f'{access_match.group(2)}{slots}:' + f' should be indented +1 space inside {parent}') # Consume braces or semicolons from what's left of the line while True: # Match first brace, semicolon, or closed parenthesis. - matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + matched = re.match(r'^[^{;)}]*([{;)}])(.*)$', line) if not matched: break @@ -2718,7 +3326,7 @@ def Update(self, filename, clean_lines, linenum, error): # stack otherwise. if not self.SeenOpenBrace(): self.stack[-1].seen_open_brace = True - elif Match(r'^extern\s*"[^"]*"\s*\{', line): + elif re.match(r'^extern\s*"[^"]*"\s*\{', line): self.stack.append(_ExternCInfo(linenum)) else: self.stack.append(_BlockInfo(linenum, True)) @@ -2769,12 +3377,10 @@ def CheckCompletedBlocks(self, filename, error): for obj in self.stack: if isinstance(obj, _ClassInfo): error(filename, obj.starting_linenum, 'build/class', 5, - 'Failed to find complete declaration of class %s' % - obj.name) + f'Failed to find complete declaration of class {obj.name}') elif isinstance(obj, _NamespaceInfo): error(filename, obj.starting_linenum, 'build/namespaces', 5, - 'Failed to find complete declaration of namespace %s' % - obj.name) + f'Failed to find complete declaration of namespace {obj.name}') def CheckForNonStandardConstructs(filename, clean_lines, linenum, @@ -2809,25 +3415,25 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, # Remove comments from the line, but leave in strings for now. line = clean_lines.lines[linenum] - if Search(r'printf\s*\(.*".*%[-+ ]?\d*q', line): + if re.search(r'printf\s*\(.*".*%[-+ ]?\d*q', line): error(filename, linenum, 'runtime/printf_format', 3, '%q in format strings is deprecated. Use %ll instead.') - if Search(r'printf\s*\(.*".*%\d+\$', line): + if re.search(r'printf\s*\(.*".*%\d+\$', line): error(filename, linenum, 'runtime/printf_format', 2, '%N$ formats are unconventional. Try rewriting to avoid them.') # Remove escaped backslashes before looking for undefined escapes. line = line.replace('\\\\', '') - if Search(r'("|\').*\\(%|\[|\(|{)', line): + if re.search(r'("|\').*\\(%|\[|\(|{)', line): error(filename, linenum, 'build/printf_format', 3, '%, [, (, and { are undefined character escapes. Unescape them.') # For the rest, work with both comments and strings removed. line = clean_lines.elided[linenum] - if Search(r'\b(const|volatile|void|char|short|int|long' + if re.search(r'\b(const|volatile|void|char|short|int|long' r'|float|double|signed|unsigned' r'|schar|u?int8|u?int16|u?int32|u?int64)' r'\s+(register|static|extern|typedef)\b', @@ -2836,20 +3442,20 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, 'Storage-class specifier (static, extern, typedef, etc) should be ' 'at the beginning of the declaration.') - if Match(r'\s*#\s*endif\s*[^/\s]+', line): + if re.match(r'\s*#\s*endif\s*[^/\s]+', line): error(filename, linenum, 'build/endif_comment', 5, 'Uncommented text after #endif is non-standard. Use a comment.') - if Match(r'\s*class\s+(\w+\s*::\s*)+\w+\s*;', line): + if re.match(r'\s*class\s+(\w+\s*::\s*)+\w+\s*;', line): error(filename, linenum, 'build/forward_decl', 5, 'Inner-style forward declarations are invalid. Remove this line.') - if Search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + if re.search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', line): error(filename, linenum, 'build/deprecated', 3, '>? and = 1 and not noarg_constructor and - len(defaulted_args) >= len(constructor_args) - 1)) + len(defaulted_args) >= len(constructor_args) - 1) or + # variadic arguments with zero or one argument + (len(constructor_args) <= 2 and + len(variadic_args) >= 1)) initializer_list_constructor = bool( onearg_constructor and - Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) + re.search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) copy_constructor = bool( onearg_constructor and - Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' - % re.escape(base_classname), constructor_args[0].strip())) + re.match(r'((const\s+(volatile\s+)?)?|(volatile\s+(const\s+)?))?' + rf'{re.escape(base_classname)}(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&', + constructor_args[0].strip()) + ) if (not is_marked_explicit and onearg_constructor and not initializer_list_constructor and not copy_constructor): - if defaulted_args: - error(filename, linenum, 'runtime/explicit', 5, + if defaulted_args or variadic_args: + error(filename, linenum, 'runtime/explicit', 4, 'Constructors callable with one argument ' 'should be marked explicit.') else: - error(filename, linenum, 'runtime/explicit', 5, + error(filename, linenum, 'runtime/explicit', 4, 'Single-parameter constructors should be marked explicit.') - elif is_marked_explicit and not onearg_constructor: - if noarg_constructor: - error(filename, linenum, 'runtime/explicit', 5, - 'Zero-parameter constructors should not be marked explicit.') def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): @@ -2957,7 +3563,7 @@ def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): r'\bfor\s*\((.*)\)\s*{', r'\bwhile\s*\((.*)\)\s*[{;]', r'\bswitch\s*\((.*)\)\s*{'): - match = Search(pattern, line) + match = re.search(pattern, line) if match: fncall = match.group(1) # look inside the parens for function calls break @@ -2976,26 +3582,26 @@ def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): # Note that we assume the contents of [] to be short enough that # they'll never need to wrap. if ( # Ignore control structures. - not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + not re.search(r'\b(if|elif|for|while|switch|return|new|delete|catch|sizeof)\b', fncall) and # Ignore pointers/references to functions. - not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + not re.search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and # Ignore pointers/references to arrays. - not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): - if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + not re.search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if re.search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call error(filename, linenum, 'whitespace/parens', 4, 'Extra space after ( in function call') - elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + elif re.search(r'\(\s+(?!(\s*\\)|\()', fncall): error(filename, linenum, 'whitespace/parens', 2, 'Extra space after (') - if (Search(r'\w\s+\(', fncall) and - not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and - not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and - not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and - not Search(r'\bcase\s+\(', fncall)): + if (re.search(r'\w\s+\(', fncall) and + not re.search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and + not re.search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and + not re.search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and + not re.search(r'\bcase\s+\(', fncall)): # TODO(unknown): Space after an operator function seem to be a common # error, silence those for now by restricting them to highest verbosity. - if Search(r'\boperator_*\b', line): + if re.search(r'\boperator_*\b', line): error(filename, linenum, 'whitespace/parens', 0, 'Extra space before ( in function call') else: @@ -3003,10 +3609,10 @@ def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): 'Extra space before ( in function call') # If the ) is followed only by a newline or a { + newline, assume it's # part of a control statement (if/while/etc), and don't complain - if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + if re.search(r'[^)]\s+\)\s*[^{\s]', fncall): # If the closing parenthesis is preceded by only whitespaces, # try to give a more descriptive error message. - if Search(r'^\s+\)', fncall): + if re.search(r'^\s+\)', fncall): error(filename, linenum, 'whitespace/parens', 2, 'Closing ) should be moved to the previous line') else: @@ -3032,10 +3638,10 @@ def IsBlankLine(line): def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, error): is_namespace_indent_item = ( - len(nesting_state.stack) > 1 and - nesting_state.stack[-1].check_namespace_indentation and - isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and - nesting_state.previous_stack_top == nesting_state.stack[-2]) + len(nesting_state.stack) >= 1 and + (isinstance(nesting_state.stack[-1], _NamespaceInfo) or + (isinstance(nesting_state.previous_stack_top, _NamespaceInfo))) + ) if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, clean_lines.elided, line): @@ -3072,28 +3678,28 @@ def CheckForFunctionLengths(filename, clean_lines, linenum, starting_func = False regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... - match_result = Match(regexp, line) + match_result = re.match(regexp, line) if match_result: # If the name is all caps and underscores, figure it's a macro and # ignore it, unless it's TEST or TEST_F. function_name = match_result.group(1).split()[-1] if function_name == 'TEST' or function_name == 'TEST_F' or ( - not Match(r'[A-Z_]+$', function_name)): + not re.match(r'[A-Z_]+$', function_name)): starting_func = True if starting_func: body_found = False - for start_linenum in xrange(linenum, clean_lines.NumLines()): + for start_linenum in range(linenum, clean_lines.NumLines()): start_line = lines[start_linenum] joined_line += ' ' + start_line.lstrip() - if Search(r'(;|})', start_line): # Declarations and trivial functions + if re.search(r'(;|})', start_line): # Declarations and trivial functions body_found = True break # ... ignore - elif Search(r'{', start_line): + if re.search(r'{', start_line): body_found = True - function = Search(r'((\w|:)*)\(', line).group(1) - if Match(r'TEST', function): # Handle TEST... macros - parameter_regexp = Search(r'(\(.*\))', joined_line) + function = re.search(r'((\w|:)*)\(', line).group(1) + if re.match(r'TEST', function): # Handle TEST... macros + parameter_regexp = re.search(r'(\(.*\))', joined_line) if parameter_regexp: # Ignore bad syntax function += parameter_regexp.group(1) else: @@ -3104,10 +3710,10 @@ def CheckForFunctionLengths(filename, clean_lines, linenum, # No body for the function (or evidence of a non-function) was found. error(filename, linenum, 'readability/fn_size', 5, 'Lint failed to find start of function body.') - elif Match(r'^\}\s*$', line): # function end + elif re.match(r'^\}\s*$', line): # function end function_state.Check(error, filename, linenum) function_state.End() - elif not Match(r'^\s*$', line): + elif not re.match(r'^\s*$', line): function_state.Count() # Count non-blank/non-comment lines. @@ -3129,7 +3735,7 @@ def CheckComment(line, filename, linenum, next_line_start, error): # Check if the // may be in quotes. If so, ignore it if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0: # Allow one space for new scopes, two spaces otherwise: - if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and + if (not (re.match(r'^.*{ *//', line) and next_line_start == commentpos) and ((commentpos >= 1 and line[commentpos-1] not in string.whitespace) or (commentpos >= 2 and @@ -3154,7 +3760,8 @@ def CheckComment(line, filename, linenum, next_line_start, error): '"// TODO(my_username): Stuff."') middle_whitespace = match.group(3) - # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + # Comparisons made explicit for correctness + # -- pylint: disable=g-explicit-bool-comparison if middle_whitespace != ' ' and middle_whitespace != '': error(filename, linenum, 'whitespace/todo', 2, 'TODO(my_username) should be followed by a space') @@ -3162,8 +3769,8 @@ def CheckComment(line, filename, linenum, next_line_start, error): # If the comment contains an alphanumeric character, there # should be a space somewhere between it and the // unless # it's a /// or //! Doxygen comment. - if (Match(r'//[^ ]*\w', comment) and - not Match(r'(///|//\!)(\s+|$)', comment)): + if (re.match(r'//[^ ]*\w', comment) and + not re.match(r'(///|//\!)(\s+|$)', comment)): error(filename, linenum, 'whitespace/comments', 4, 'Should have a space between // and comment') @@ -3226,12 +3833,12 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): # the previous line is indented 6 spaces, which may happen when the # initializers of a constructor do not fit into a 80 column line. exception = False - if Match(r' {6}\w', prev_line): # Initializer list? + if re.match(r' {6}\w', prev_line): # Initializer list? # We are looking for the opening column of initializer list, which # should be indented 4 spaces to cause 6 space indentation afterwards. search_position = linenum-2 while (search_position >= 0 - and Match(r' {6}\w', elided[search_position])): + and re.match(r' {6}\w', elided[search_position])): search_position -= 1 exception = (search_position >= 0 and elided[search_position][:5] == ' :') @@ -3242,9 +3849,9 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): # or colon (for initializer lists) we assume that it is the last line of # a function header. If we have a colon indented 4 spaces, it is an # initializer list. - exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + exception = (re.match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', prev_line) - or Match(r' {4}:', prev_line)) + or re.match(r' {4}:', prev_line)) if not exception: error(filename, linenum, 'whitespace/blank_line', 2, @@ -3261,16 +3868,16 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): if linenum + 1 < clean_lines.NumLines(): next_line = raw[linenum + 1] if (next_line - and Match(r'\s*}', next_line) + and re.match(r'\s*}', next_line) and next_line.find('} else ') == -1): error(filename, linenum, 'whitespace/blank_line', 3, 'Redundant blank line at the end of a code block ' 'should be deleted.') - matched = Match(r'\s*(public|protected|private):', prev_line) + matched = re.match(r'\s*(public|protected|private):', prev_line) if matched: error(filename, linenum, 'whitespace/blank_line', 3, - 'Do not leave a blank line after "%s:"' % matched.group(1)) + f'Do not leave a blank line after "{matched.group(1)}:"') # Next, check comments next_line_start = 0 @@ -3282,16 +3889,17 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): # get rid of comments and strings line = clean_lines.elided[linenum] - # You shouldn't have spaces before your brackets, except maybe after - # 'delete []', 'return []() {};', or 'auto [abc, ...] = ...;'. - if Search(r'\w\s+\[', line) and not Search(r'(?:auto&?|delete|return)\s+\[', line): + # You shouldn't have spaces before your brackets, except for C++11 attributes + # or maybe after 'delete []', 'return []() {};', or 'auto [abc, ...] = ...;'. + if (re.search(r'\w\s+\[(?!\[)', line) and + not re.search(r'(?:auto&?|delete|return)\s+\[', line)): error(filename, linenum, 'whitespace/braces', 5, 'Extra space before [') # In range-based for, we wanted spaces before and after the colon, but # not around "::" tokens that might appear. - if (Search(r'for *\(.*[^:]:[^: ]', line) or - Search(r'for *\(.*[^: ]:[^:]', line)): + if (re.search(r'for *\(.*[^:]:[^: ]', line) or + re.search(r'for *\(.*[^: ]:[^:]', line)): error(filename, linenum, 'whitespace/forcolon', 2, 'Missing space around colon in range-based for loop') @@ -3314,7 +3922,7 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): # The replacement is done repeatedly to avoid false positives from # operators that call operators. while True: - match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) + match = re.match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) if match: line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) else: @@ -3324,12 +3932,12 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): # Otherwise not. Note we only check for non-spaces on *both* sides; # sometimes people put non-spaces on one side when aligning ='s among # many lines (not that this is behavior that I approve of...) - if ((Search(r'[\w.]=', line) or - Search(r'=[\w.]', line)) - and not Search(r'\b(if|while|for) ', line) + if ((re.search(r'[\w.]=', line) or + re.search(r'=[\w.]', line)) + and not re.search(r'\b(if|while|for) ', line) # Operators taken from [lex.operators] in C++11 standard. - and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) - and not Search(r'operator=', line)): + and not re.search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) + and not re.search(r'operator=', line)): error(filename, linenum, 'whitespace/operators', 4, 'Missing spaces around =') @@ -3348,16 +3956,17 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): # # Note that && is not included here. This is because there are too # many false positives due to RValue references. - match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) + match = re.search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) if match: + # TODO: support alternate operators error(filename, linenum, 'whitespace/operators', 3, - 'Missing spaces around %s' % match.group(1)) - elif not Match(r'#.*include', line): + f'Missing spaces around {match.group(1)}') + elif not re.match(r'#.*include', line): # Look for < that is not surrounded by spaces. This is only # triggered if both sides are missing spaces, even though # technically should should flag if at least one side is missing a # space. This is done to avoid some false positives with shifts. - match = Match(r'^(.*[^\s<])<[^\s=<,]', line) + match = re.match(r'^(.*[^\s<])<[^\s=<,]', line) if match: (_, _, end_pos) = CloseExpression( clean_lines, linenum, len(match.group(1))) @@ -3368,7 +3977,7 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): # Look for > that is not surrounded by spaces. Similar to the # above, we only trigger if both sides are missing spaces to avoid # false positives with shifts. - match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) + match = re.match(r'^(.*[^-\s>])>[^\s=>,]', line) if match: (_, _, start_pos) = ReverseCloseExpression( clean_lines, linenum, len(match.group(1))) @@ -3381,7 +3990,7 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): # # We also allow operators following an opening parenthesis, since # those tend to be macros that deal with operators. - match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line) + match = re.search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line) if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and not (match.group(1) == 'operator' and match.group(2) == ';')): error(filename, linenum, 'whitespace/operators', 3, @@ -3399,16 +4008,16 @@ def CheckOperatorSpacing(filename, clean_lines, linenum, error): # follows would be part of an identifier, and there should still be # a space separating the template type and the identifier. # type> alpha - match = Search(r'>>[a-zA-Z_]', line) + match = re.search(r'>>[a-zA-Z_]', line) if match: error(filename, linenum, 'whitespace/operators', 3, 'Missing spaces around >>') # There shouldn't be space around unary operators - match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + match = re.search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) if match: error(filename, linenum, 'whitespace/operators', 4, - 'Extra space for operator %s' % match.group(1)) + f'Extra space for operator {match.group(1)}') def CheckParenthesisSpacing(filename, clean_lines, linenum, error): @@ -3423,30 +4032,29 @@ def CheckParenthesisSpacing(filename, clean_lines, linenum, error): line = clean_lines.elided[linenum] # No spaces after an if, while, switch, or for - match = Search(r' (if\(|for\(|while\(|switch\()', line) + match = re.search(r' (if\(|for\(|while\(|switch\()', line) if match: error(filename, linenum, 'whitespace/parens', 5, - 'Missing space before ( in %s' % match.group(1)) + f'Missing space before ( in {match.group(1)}') # For if/for/while/switch, the left and right parens should be # consistent about how many spaces are inside the parens, and # there should either be zero or one spaces inside the parens. # We don't want: "if ( foo)" or "if ( foo )". # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. - match = Search(r'\b(if|for|while|switch)\s*' + match = re.search(r'\b(if|for|while|switch)\s*' r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', line) if match: if len(match.group(2)) != len(match.group(4)): if not (match.group(3) == ';' and len(match.group(2)) == 1 + len(match.group(4)) or - not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + not match.group(2) and re.search(r'\bfor\s*\(.*; \)', line)): error(filename, linenum, 'whitespace/parens', 5, - 'Mismatching spaces inside () in %s' % match.group(1)) + f'Mismatching spaces inside () in {match.group(1)}') if len(match.group(2)) not in [0, 1]: error(filename, linenum, 'whitespace/parens', 5, - 'Should have zero or one spaces inside ( and ) in %s' % - match.group(1)) + f'Should have zero or one spaces inside ( and ) in {match.group(1)}') def CheckCommaSpacing(filename, clean_lines, linenum, error): @@ -3471,8 +4079,9 @@ def CheckCommaSpacing(filename, clean_lines, linenum, error): # verify that lines contain missing whitespaces, second pass on raw # lines to confirm that those missing whitespaces are not due to # elided comments. - if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and - Search(r',[^,\s]', raw[linenum])): + match = re.search(r',[^,\s]', re.sub(r'\b__VA_OPT__\s*\(,\)', '', + re.sub(r'\boperator\s*,\s*\(', 'F(', line))) + if (match and re.search(r',[^,\s]', raw[linenum])): error(filename, linenum, 'whitespace/comma', 3, 'Missing space after ,') @@ -3480,7 +4089,7 @@ def CheckCommaSpacing(filename, clean_lines, linenum, error): # except for few corner cases # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more # space after ; - if Search(r';[^\s};\\)/]', line): + if re.search(r';[^\s};\\)/]', line): error(filename, linenum, 'whitespace/semicolon', 3, 'Missing space after ;') @@ -3497,7 +4106,7 @@ def _IsType(clean_lines, nesting_state, expr): True, if token looks like a type. """ # Keep only the last token in the expression - last_word = Match(r'^.*(\b\S+)$', expr) + last_word = re.match(r'^.*(\b\S+)$', expr) if last_word: token = last_word.group(1) else: @@ -3540,8 +4149,8 @@ def _IsType(clean_lines, nesting_state, expr): continue # Look for typename in the specified range - for i in xrange(first_line, last_line + 1, 1): - if Search(typename_pattern, clean_lines.elided[i]): + for i in range(first_line, last_line + 1, 1): + if re.search(typename_pattern, clean_lines.elided[i]): return True block_index -= 1 @@ -3567,7 +4176,7 @@ def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): # And since you should never have braces at the beginning of a line, # this is an easy test. Except that braces used for initialization don't # follow the same rule; we often don't want spaces before those. - match = Match(r'^(.*[^ ({>]){', line) + match = re.match(r'^(.*[^ ({>]){', line) if match: # Try a bit harder to check for brace initialization. This @@ -3604,34 +4213,34 @@ def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): trailing_text = '' if endpos > -1: trailing_text = endline[endpos:] - for offset in xrange(endlinenum + 1, + for offset in range(endlinenum + 1, min(endlinenum + 3, clean_lines.NumLines() - 1)): trailing_text += clean_lines.elided[offset] # We also suppress warnings for `uint64_t{expression}` etc., as the style # guide recommends brace initialization for integral types to avoid # overflow/truncation. - if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) + if (not re.match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) and not _IsType(clean_lines, nesting_state, leading_text)): error(filename, linenum, 'whitespace/braces', 5, 'Missing space before {') # Make sure '} else {' has spaces. - if Search(r'}else', line): + if re.search(r'}else', line): error(filename, linenum, 'whitespace/braces', 5, 'Missing space before else') # You shouldn't have a space before a semicolon at the end of the line. # There's a special case for "for" since the style guide allows space before # the semicolon there. - if Search(r':\s*;\s*$', line): + if re.search(r':\s*;\s*$', line): error(filename, linenum, 'whitespace/semicolon', 5, 'Semicolon defining empty statement. Use {} instead.') - elif Search(r'^\s*;\s*$', line): + elif re.search(r'^\s*;\s*$', line): error(filename, linenum, 'whitespace/semicolon', 5, 'Line contains only semicolon. If this should be an empty statement, ' 'use {} instead.') - elif (Search(r'\s+;\s*$', line) and - not Search(r'\bfor\b', line)): + elif (re.search(r'\s+;\s*$', line) and + not re.search(r'\bfor\b', line)): error(filename, linenum, 'whitespace/semicolon', 5, 'Extra space before last semicolon. If this should be an empty ' 'statement, use {} instead.') @@ -3650,11 +4259,10 @@ def IsDecltype(clean_lines, linenum, column): (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) if start_col < 0: return False - if Search(r'\bdecltype\s*$', text[0:start_col]): + if re.search(r'\bdecltype\s*$', text[0:start_col]): return True return False - def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): """Checks for additional blank line issues related to sections. @@ -3682,7 +4290,7 @@ def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): linenum <= class_info.starting_linenum): return - matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + matched = re.match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) if matched: # Issue warning if the line before public/protected/private was # not a blank line, but don't do this if the previous line contains @@ -3694,20 +4302,20 @@ def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): # common when defining classes in C macros. prev_line = clean_lines.lines[linenum - 1] if (not IsBlankLine(prev_line) and - not Search(r'\b(class|struct)\b', prev_line) and - not Search(r'\\$', prev_line)): + not re.search(r'\b(class|struct)\b', prev_line) and + not re.search(r'\\$', prev_line)): # Try a bit harder to find the beginning of the class. This is to # account for multi-line base-specifier lists, e.g.: # class Derived # : public Base { end_class_head = class_info.starting_linenum for i in range(class_info.starting_linenum, linenum): - if Search(r'\{\s*$', clean_lines.lines[i]): + if re.search(r'\{\s*$', clean_lines.lines[i]): end_class_head = i break if end_class_head < linenum - 1: error(filename, linenum, 'whitespace/blank_line', 3, - '"%s:" should be preceded by a blank line' % matched.group(1)) + f'"{matched.group(1)}:" should be preceded by a blank line') def GetPreviousNonBlankLine(clean_lines, linenum): @@ -3745,7 +4353,7 @@ def CheckBraces(filename, clean_lines, linenum, error): line = clean_lines.elided[linenum] # get rid of comments and strings - if Match(r'\s*{\s*$', line): + if re.match(r'\s*{\s*$', line): # We allow an open brace to start a line in the case where someone is using # braces in a block to explicitly create a new scope, which is commonly used # to control the lifetime of stack-allocated variables. Braces are also @@ -3756,23 +4364,23 @@ def CheckBraces(filename, clean_lines, linenum, error): # following line if it is part of an array initialization and would not fit # within the 80 character limit of the preceding line. prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if (not Search(r'[,;:}{(]\s*$', prevline) and - not Match(r'\s*#', prevline) and + if (not re.search(r'[,;:}{(]\s*$', prevline) and + not re.match(r'\s*#', prevline) and not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)): error(filename, linenum, 'whitespace/braces', 4, '{ should almost always be at the end of the previous line') # An else clause should be on the same line as the preceding closing brace. - if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): + if re.match(r'\s*else\b\s*(?:if\b|\{|$)', line): prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if Match(r'\s*}\s*$', prevline): + if re.match(r'\s*}\s*$', prevline): error(filename, linenum, 'whitespace/newline', 4, 'An else should appear on the same line as the preceding }') # If braces come on one side of an else, they should be on both. # However, we have to worry about "else if" that spans multiple lines! - if Search(r'else if\s*\(', line): # could be multi-line if - brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) + if re.search(r'else if\s*\(', line): # could be multi-line if + brace_on_left = bool(re.search(r'}\s*else if\s*\(', line)) # find the ( after the if pos = line.find('else if') pos = line.find('(', pos) @@ -3782,17 +4390,17 @@ def CheckBraces(filename, clean_lines, linenum, error): if brace_on_left != brace_on_right: # must be brace after if error(filename, linenum, 'readability/braces', 5, 'If an else has a brace on one side, it should have it on both') - elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + elif re.search(r'}\s*else[^{]*$', line) or re.match(r'[^}]*else\s*{', line): error(filename, linenum, 'readability/braces', 5, 'If an else has a brace on one side, it should have it on both') # Likewise, an else should never have the else clause on the same line - if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + if re.search(r'\belse [^\s{]', line) and not re.search(r'\belse if\b', line): error(filename, linenum, 'whitespace/newline', 4, 'Else clause should never be on same line as else (use 2 lines)') # In the same way, a do/while should never be on one line - if Match(r'\s*do [^\s{]', line): + if re.match(r'\s*do [^\s{]', line): error(filename, linenum, 'whitespace/newline', 4, 'do/while clauses should not be on a single line') @@ -3803,21 +4411,21 @@ def CheckBraces(filename, clean_lines, linenum, error): # its line, and the line after that should have an indent level equal to or # lower than the if. We also check for ambiguous if/else nesting without # braces. - if_else_match = Search(r'\b(if\s*\(|else\b)', line) - if if_else_match and not Match(r'\s*#', line): + if_else_match = re.search(r'\b(if\s*(|constexpr)\s*\(|else\b)', line) + if if_else_match and not re.match(r'\s*#', line): if_indent = GetIndentLevel(line) endline, endlinenum, endpos = line, linenum, if_else_match.end() - if_match = Search(r'\bif\s*\(', line) + if_match = re.search(r'\bif\s*(|constexpr)\s*\(', line) if if_match: # This could be a multiline if condition, so find the end first. pos = if_match.end() - 1 (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) # Check for an opening brace, either directly after the if or on the next # line. If found, this isn't a single-statement conditional. - if (not Match(r'\s*{', endline[endpos:]) - and not (Match(r'\s*$', endline[endpos:]) + if (not re.match(r'\s*{', endline[endpos:]) + and not (re.match(r'\s*$', endline[endpos:]) and endlinenum < (len(clean_lines.elided) - 1) - and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): + and re.match(r'\s*{', clean_lines.elided[endlinenum + 1]))): while (endlinenum < len(clean_lines.elided) and ';' not in clean_lines.elided[endlinenum][endpos:]): endlinenum += 1 @@ -3827,11 +4435,11 @@ def CheckBraces(filename, clean_lines, linenum, error): # We allow a mix of whitespace and closing braces (e.g. for one-liner # methods) and a single \ after the semicolon (for macros) endpos = endline.find(';') - if not Match(r';[\s}]*(\\?)$', endline[endpos:]): + if not re.match(r';[\s}]*(\\?)$', endline[endpos:]): # Semicolon isn't the last character, there's something trailing. # Output a warning if the semicolon is not contained inside # a lambda expression. - if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', + if not re.match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', endline): error(filename, linenum, 'readability/braces', 4, 'If/else bodies with multiple statements require braces') @@ -3842,7 +4450,7 @@ def CheckBraces(filename, clean_lines, linenum, error): # With ambiguous nested if statements, this will error out on the # if that *doesn't* match the else, regardless of whether it's the # inner one or outer one. - if (if_match and Match(r'\s*else\b', next_line) + if (if_match and re.match(r'\s*else\b', next_line) and next_indent != if_indent): error(filename, linenum, 'readability/braces', 4, 'Else clause should be indented at the same level as if. ' @@ -3908,7 +4516,7 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): # to namespaces. For now we do not warn for this case. # # Try matching case 1 first. - match = Match(r'^(.*\)\s*)\{', line) + match = re.match(r'^(.*\)\s*)\{', line) if match: # Matched closing parenthesis (case 1). Check the token before the # matching opening parenthesis, and don't warn if it looks like a @@ -3941,27 +4549,27 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): clean_lines, linenum, closing_brace_pos) if opening_parenthesis[2] > -1: line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] - macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) - func = Match(r'^(.*\])\s*$', line_prefix) + macro = re.search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) + func = re.match(r'^(.*\])\s*$', line_prefix) if ((macro and macro.group(1) not in ( 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or - (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or - Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or - Search(r'\bdecltype$', line_prefix) or - Search(r'\s+=\s*$', line_prefix)): + (func and not re.search(r'\boperator\s*\[\s*\]', func.group(1))) or + re.search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or + re.search(r'\bdecltype$', line_prefix) or + re.search(r'\s+=\s*$', line_prefix)): match = None if (match and opening_parenthesis[1] > 1 and - Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): + re.search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): # Multi-line lambda-expression match = None else: # Try matching cases 2-3. - match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + match = re.match(r'^(.*(?:else|\)\s*const)\s*)\{', line) if not match: # Try matching cases 4-6. These are always matched on separate lines. # @@ -3972,14 +4580,14 @@ def CheckTrailingSemicolon(filename, clean_lines, linenum, error): # // blank line # } prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if prevline and Search(r'[;{}]\s*$', prevline): - match = Match(r'^(\s*)\{', line) + if prevline and re.search(r'[;{}]\s*$', prevline): + match = re.match(r'^(\s*)\{', line) # Check matching closing brace if match: (endline, endlinenum, endpos) = CloseExpression( clean_lines, linenum, len(match.group(1))) - if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + if endpos > -1 and re.match(r'^\s*;', endline[endpos:]): # Current {} pair is eligible for semicolon check, and we have found # the redundant semicolon, output warning here. # @@ -4016,7 +4624,7 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): # We also check "if" blocks here, since an empty conditional block # is likely an error. line = clean_lines.elided[linenum] - matched = Match(r'\s*(for|while|if)\s*\(', line) + matched = re.match(r'\s*(for|while|if)\s*\(', line) if matched: # Find the end of the conditional expression. (end_line, end_linenum, end_pos) = CloseExpression( @@ -4025,7 +4633,7 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): # Output warning if what follows the condition expression is a semicolon. # No warning for all other cases, including whitespace or newline, since we # have a separate check for semicolons preceded by whitespace. - if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if end_pos >= 0 and re.match(r';', end_line[end_pos:]): if matched.group(1) == 'if': error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, 'Empty conditional bodies should use {}') @@ -4041,8 +4649,8 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): opening_linenum = end_linenum opening_line_fragment = end_line[end_pos:] # Loop until EOF or find anything that's not whitespace or opening {. - while not Search(r'^\s*\{', opening_line_fragment): - if Search(r'^(?!\s*$)', opening_line_fragment): + while not re.search(r'^\s*\{', opening_line_fragment): + if re.search(r'^(?!\s*$)', opening_line_fragment): # Conditional has no brackets. return opening_linenum += 1 @@ -4072,12 +4680,12 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): return if closing_linenum > opening_linenum: # Opening line after the {. Ignore comments here since we checked above. - body = list(opening_line[opening_pos+1:]) + bodylist = list(opening_line[opening_pos+1:]) # All lines until closing line, excluding closing line, with comments. - body.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) + bodylist.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) # Closing line before the }. Won't (and can't) have comments. - body.append(clean_lines.elided[closing_linenum][:closing_pos-1]) - body = '\n'.join(body) + bodylist.append(clean_lines.elided[closing_linenum][:closing_pos-1]) + body = '\n'.join(bodylist) else: # If statement has brackets and fits on a single line. body = opening_line[opening_pos+1:closing_pos-1] @@ -4089,8 +4697,8 @@ def CheckEmptyBlockBody(filename, clean_lines, linenum, error): current_linenum = closing_linenum current_line_fragment = closing_line[closing_pos:] # Loop until EOF or find anything that's not whitespace or else clause. - while Search(r'^\s*$|^(?=\s*else)', current_line_fragment): - if Search(r'^(?=\s*else)', current_line_fragment): + while re.search(r'^\s*$|^(?=\s*else)', current_line_fragment): + if re.search(r'^(?=\s*else)', current_line_fragment): # Found an else clause, so don't log an error. return current_linenum += 1 @@ -4119,7 +4727,7 @@ def FindCheckMacro(line): # to make sure that we are matching the expected CHECK macro, as # opposed to some other macro that happens to contain the CHECK # substring. - matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) + matched = re.match(r'^(.*\b' + macro + r'\s*)\(', line) if not matched: continue return (macro, len(matched.group(1))) @@ -4151,14 +4759,14 @@ def CheckCheck(filename, clean_lines, linenum, error): # If the check macro is followed by something other than a # semicolon, assume users will log their own custom error messages # and don't suggest any replacements. - if not Match(r'\s*;', last_line[end_pos:]): + if not re.match(r'\s*;', last_line[end_pos:]): return if linenum == end_line: expression = lines[linenum][start_pos + 1:end_pos - 1] else: expression = lines[linenum][start_pos + 1:] - for i in xrange(linenum + 1, end_line): + for i in range(linenum + 1, end_line): expression += lines[i] expression += last_line[0:end_pos - 1] @@ -4169,7 +4777,7 @@ def CheckCheck(filename, clean_lines, linenum, error): rhs = '' operator = None while expression: - matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + matched = re.match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' r'==|!=|>=|>|<=|<|\()(.*)$', expression) if matched: token = matched.group(1) @@ -4203,9 +4811,9 @@ def CheckCheck(filename, clean_lines, linenum, error): # characters at once if possible. Trivial benchmark shows that this # is more efficient when the operands are longer than a single # character, which is generally the case. - matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + matched = re.match(r'^([^-=!<>()&|]+)(.*)$', expression) if not matched: - matched = Match(r'^(\s*\S)(.*)$', expression) + matched = re.match(r'^(\s*\S)(.*)$', expression) if not matched: break lhs += matched.group(1) @@ -4229,7 +4837,7 @@ def CheckCheck(filename, clean_lines, linenum, error): lhs = lhs.strip() rhs = rhs.strip() match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' - if Match(match_constant, lhs) or Match(match_constant, rhs): + if re.match(match_constant, lhs) or re.match(match_constant, rhs): # Note: since we know both lhs and rhs, we can provide a more # descriptive error message like: # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) @@ -4239,9 +4847,8 @@ def CheckCheck(filename, clean_lines, linenum, error): # We are still keeping the less descriptive message because if lhs # or rhs gets long, the error message might become unreadable. error(filename, linenum, 'readability/check', 2, - 'Consider using %s instead of %s(a %s b)' % ( - _CHECK_REPLACEMENT[check_macro][operator], - check_macro, operator)) + f'Consider using {_CHECK_REPLACEMENT[check_macro][operator]}' + f' instead of {check_macro}(a {operator} b)') def CheckAltTokens(filename, clean_lines, linenum, error): @@ -4256,7 +4863,7 @@ def CheckAltTokens(filename, clean_lines, linenum, error): line = clean_lines.elided[linenum] # Avoid preprocessor lines - if Match(r'^\s*#', line): + if re.match(r'^\s*#', line): return # Last ditch effort to avoid multi-line comments. This will not help @@ -4272,8 +4879,8 @@ def CheckAltTokens(filename, clean_lines, linenum, error): for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): error(filename, linenum, 'readability/alt_tokens', 2, - 'Use operator %s instead of %s' % ( - _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + f'Use operator {_ALT_TOKEN_REPLACEMENT[match.group(2)]}' + f' instead of {match.group(2)}') def GetLineWidth(line): @@ -4286,7 +4893,7 @@ def GetLineWidth(line): The width of the line in column positions, accounting for Unicode combining characters and wide characters. """ - if isinstance(line, unicode): + if isinstance(line, str): width = 0 for uc in unicodedata.normalize('NFC', line): if unicodedata.east_asian_width(uc) in ('W', 'F'): @@ -4301,7 +4908,7 @@ def GetLineWidth(line): is_low_surrogate = 0xDC00 <= ord(uc) <= 0xDFFF if not is_wide_build and is_low_surrogate: width -= 1 - + width += 1 return width else: @@ -4349,7 +4956,7 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, # if(match($0, " <<")) complain = 0; # if(match(prev, " +for \\(")) complain = 0; # if(prevodd && match(prevprev, " +for \\(")) complain = 0; - scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$' + scope_or_label_pattern = r'\s*(?:public|private|protected|signals)(?:\s+(?:slots\s*)?)?:\s*\\?$' classinfo = nesting_state.InnermostClass() initial_spaces = 0 cleansed_line = clean_lines.elided[linenum] @@ -4360,11 +4967,11 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, # We also don't check for lines that look like continuation lines # (of lines ending in double quotes, commas, equals, or angle brackets) # because the rules for how to indent those are non-trivial. - if (not Search(r'[",=><] *$', prev) and + if (not re.search(r'[",=><] *$', prev) and (initial_spaces == 1 or initial_spaces == 3) and - not Match(scope_or_label_pattern, cleansed_line) and + not re.match(scope_or_label_pattern, cleansed_line) and not (clean_lines.raw_lines[linenum] != line and - Match(r'^\s*""', line))): + re.match(r'^\s*""', line))): error(filename, linenum, 'whitespace/indent', 3, 'Weird number of spaces at line-start. ' 'Are you using a 2-space indent?') @@ -4377,9 +4984,9 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, is_header_guard = False if IsHeaderExtension(file_extension): cppvar = GetHeaderGuardCPPVariable(filename) - if (line.startswith('#ifndef %s' % cppvar) or - line.startswith('#define %s' % cppvar) or - line.startswith('#endif // %s' % cppvar)): + if (line.startswith(f'#ifndef {cppvar}') or + line.startswith(f'#define {cppvar}') or + line.startswith(f'#endif // {cppvar}')): is_header_guard = True # #include lines and header guards can be long, since there's no clean way to # split them. @@ -4389,16 +4996,23 @@ def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, # # The "$Id:...$" comment may also get very long without it being the # developers fault. + # + # Doxygen documentation copying can get pretty long when using an overloaded + # function declaration if (not line.startswith('#include') and not is_header_guard and - not Match(r'^\s*//.*http(s?)://\S*$', line) and - not Match(r'^\s*//\s*[^\s]*$', line) and - not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): + not re.match(r'^\s*//.*http(s?)://\S*$', line) and + not re.match(r'^\s*//\s*[^\s]*$', line) and + not re.match(r'^// \$Id:.*#[0-9]+ \$$', line) and + not re.match(r'^\s*/// [@\\](copydoc|copydetails|copybrief) .*$', line)): line_width = GetLineWidth(line) if line_width > _line_length: error(filename, linenum, 'whitespace/line_length', 2, - 'Lines should be <= %i characters long' % _line_length) + f'Lines should be <= {_line_length} characters long') if (cleansed_line.count(';') > 1 and + # allow simple single line lambdas + not re.match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}\n\r]*\}', + line) and # for loops are allowed two ;'s (and may run over two lines). cleansed_line.find('for') == -1 and (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or @@ -4455,21 +5069,25 @@ def _DropCommonSuffixes(filename): Returns: The filename with the common suffix removed. """ - for suffix in ('test.cc', 'regtest.cc', 'unittest.cc', - 'inl.h', 'impl.h', 'internal.h'): + for suffix in itertools.chain( + (f"{test_suffix.lstrip('_')}.{ext}" + for test_suffix, ext in itertools.product(_test_suffixes, GetNonHeaderExtensions())), + (f'{suffix}.{ext}' + for suffix, ext in itertools.product(['inl', 'imp', 'internal'], GetHeaderExtensions()))): if (filename.endswith(suffix) and len(filename) > len(suffix) and filename[-len(suffix) - 1] in ('-', '_')): return filename[:-len(suffix) - 1] return os.path.splitext(filename)[0] -def _ClassifyInclude(fileinfo, include, is_system): +def _ClassifyInclude(fileinfo, include, used_angle_brackets, include_order="default"): """Figures out what kind of header 'include' is. Args: fileinfo: The current file cpplint is running over. A FileInfo instance. include: The path to a #included file. - is_system: True if the #include used <> rather than "". + used_angle_brackets: True if the #include used <> rather than "". + include_order: "default" or other value allowed in program arguments Returns: One of the _XXX_HEADER constants. @@ -4479,6 +5097,8 @@ def _ClassifyInclude(fileinfo, include, is_system): _C_SYS_HEADER >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', True, "standardcfirst") + _OTHER_SYS_HEADER >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) _LIKELY_MY_HEADER >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), @@ -4489,13 +5109,24 @@ def _ClassifyInclude(fileinfo, include, is_system): """ # This is a list of all standard c++ header files, except # those already checked for above. - is_cpp_h = include in _CPP_HEADERS + is_cpp_header = include in _CPP_HEADERS + + # Mark include as C header if in list or in a known folder for standard-ish C headers. + is_std_c_header = (include_order == "default") or (include in _C_HEADERS + # additional linux glibc header folders + or re.search(rf'(?:{"|".join(C_STANDARD_HEADER_FOLDERS)})\/.*\.h', include)) + + # Headers with C++ extensions shouldn't be considered C system headers + include_ext = os.path.splitext(include)[1] + is_system = used_angle_brackets and include_ext not in ['.hh', '.hpp', '.hxx', '.h++'] if is_system: - if is_cpp_h: + if is_cpp_header: return _CPP_SYS_HEADER - else: + if is_std_c_header: return _C_SYS_HEADER + else: + return _OTHER_SYS_HEADER # If the target file and the include we're checking share a # basename when we drop common extensions, and the include @@ -4503,9 +5134,11 @@ def _ClassifyInclude(fileinfo, include, is_system): target_dir, target_base = ( os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + target_dir_pub = os.path.normpath(target_dir + '/../public') + target_dir_pub = target_dir_pub.replace('\\', '/') if target_base == include_base and ( include_dir == target_dir or - include_dir == os.path.normpath(target_dir + '/../public')): + include_dir == target_dir_pub): return _LIKELY_MY_HEADER # If the target and include share some initial basename @@ -4547,10 +5180,12 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): # # We also make an exception for Lua headers, which follow google # naming convention but not the include convention. - match = Match(r'#include\s*"([^/]+\.h)"', line) - if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): - error(filename, linenum, 'build/include', 4, - 'Include the directory when naming .h files') + match = re.match(r'#include\s*"([^/]+\.(.*))"', line) + if match: + if (IsHeaderExtension(match.group(2)) and + not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1))): + error(filename, linenum, 'build/include_subdir', 4, + 'Include the directory when naming header files') # we shouldn't include a file more than once. actually, there are a # handful of instances where doing so is okay, but in general it's @@ -4558,17 +5193,33 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): match = _RE_PATTERN_INCLUDE.search(line) if match: include = match.group(2) - is_system = (match.group(1) == '<') + used_angle_brackets = match.group(1) == '<' duplicate_line = include_state.FindHeader(include) if duplicate_line >= 0: error(filename, linenum, 'build/include', 4, - '"%s" already included at %s:%s' % - (include, filename, duplicate_line)) - elif (include.endswith('.cc') and + f'"{include}" already included at {filename}:{duplicate_line}') + return + + for extension in GetNonHeaderExtensions(): + if (include.endswith('.' + extension) and os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): - error(filename, linenum, 'build/include', 4, - 'Do not include .cc files from other packages') - elif not _THIRD_PARTY_HEADERS_PATTERN.match(include): + error(filename, linenum, 'build/include', 4, + 'Do not include .' + extension + ' files from other packages') + return + + # We DO want to include a 3rd party looking header if it matches the + # filename. Otherwise we get an erroneous error "...should include its + # header" error later. + third_src_header = False + for ext in GetHeaderExtensions(): + basefilename = filename[0:len(filename) - len(fileinfo.Extension())] + headerfile = basefilename + '.' + ext + headername = FileInfo(headerfile).RepositoryName() + if headername in include or include in headername: + third_src_header = True + break + + if third_src_header or not _THIRD_PARTY_HEADERS_PATTERN.match(include): include_state.include_list[-1].append((include, linenum)) # We want to ensure that headers appear in the right order: @@ -4583,16 +5234,16 @@ def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): # track of the highest type seen, and complains if we see a # lower type after that. error_message = include_state.CheckNextIncludeOrder( - _ClassifyInclude(fileinfo, include, is_system)) + _ClassifyInclude(fileinfo, include, used_angle_brackets, _include_order)) if error_message: error(filename, linenum, 'build/include_order', 4, - '%s. Should be: %s.h, c system, c++ system, other.' % - (error_message, fileinfo.BaseName())) + f'{error_message}. Should be: {fileinfo.BaseName()}.h, c system,' + ' c++ system, other.') canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) if not include_state.IsInAlphabeticalOrder( clean_lines, linenum, canonical_include): error(filename, linenum, 'build/include_alpha', 4, - 'Include "%s" not in alphabetical order' % include) + f'Include "{include}" not in alphabetical order') include_state.SetLastHeader(canonical_include) @@ -4622,7 +5273,7 @@ def _GetTextInside(text, start_pattern): # Give opening punctuations to get the matching close-punctuations. matching_punctuation = {'(': ')', '{': '}', '[': ']'} - closing_punctuation = set(matching_punctuation.itervalues()) + closing_punctuation = set(dict.values(matching_punctuation)) # Find the position to start extracting text. match = re.search(start_pattern, text, re.M) @@ -4712,12 +5363,10 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, # Reset include state across preprocessor directives. This is meant # to silence warnings for conditional includes. - match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) + match = re.match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) if match: include_state.ResetSection(match.group(1)) - # Make Windows paths like Unix. - fullname = os.path.abspath(filename).replace('\\', '/') # Perform other checks now that we are sure that this is not an include line CheckCasts(filename, clean_lines, linenum, error) @@ -4734,15 +5383,15 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, # Check if people are using the verboten C basic types. The only exception # we regularly allow is "unsigned short port" for port. - if Search(r'\bshort port\b', line): - if not Search(r'\bunsigned short port\b', line): + if re.search(r'\bshort port\b', line): + if not re.search(r'\bunsigned short port\b', line): error(filename, linenum, 'runtime/int', 4, 'Use "unsigned short" for ports, not "short"') else: - match = Search(r'\b(short|long(?! +double)|long long)\b', line) + match = re.search(r'\b(short|long(?! +double)|long long)\b', line) if match: error(filename, linenum, 'runtime/int', 4, - 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + f'Use int16/int64/etc, rather than the C type {match.group(1)}') # Check if some verboten operator overloading is going on # TODO(unknown): catch out-of-line unary operator&: @@ -4750,13 +5399,13 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, # int operator&(const X& x) { return 42; } // unary operator& # The trick is it's hard to tell apart from binary operator&: # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& - if Search(r'\boperator\s*&\s*\(\s*\)', line): + if re.search(r'\boperator\s*&\s*\(\s*\)', line): error(filename, linenum, 'runtime/operator', 4, 'Unary operator& is dangerous. Do not use it.') # Check for suspicious usage of "if" like # } if (a == b) { - if Search(r'\}\s*if\s*\(', line): + if re.search(r'\}\s*if\s*\(', line): error(filename, linenum, 'readability/braces', 4, 'Did you mean "else if"? If not, start a new line for "if".') @@ -4769,28 +5418,32 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') if printf_args: - match = Match(r'([\w.\->()]+)$', printf_args) + match = re.match(r'([\w.\->()]+)$', printf_args) if match and match.group(1) != '__VA_ARGS__': function_name = re.search(r'\b((?:string)?printf)\s*\(', line, re.I).group(1) error(filename, linenum, 'runtime/printf', 4, - 'Potential format string bug. Do %s("%%s", %s) instead.' - % (function_name, match.group(1))) + 'Potential format string bug. Do' + f' {function_name}("%s", {match.group(1)}) instead.') # Check for potential memset bugs like memset(buf, sizeof(buf), 0). - match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) - if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + match = re.search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not re.match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): error(filename, linenum, 'runtime/memset', 4, - 'Did you mean "memset(%s, 0, %s)"?' - % (match.group(1), match.group(2))) + f'Did you mean "memset({match.group(1)}, 0, {match.group(2)})"?') - if Search(r'\busing namespace\b', line): - error(filename, linenum, 'build/namespaces', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') + if re.search(r'\busing namespace\b', line): + if re.search(r'\bliterals\b', line): + error(filename, linenum, 'build/namespaces_literals', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + else: + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') # Detect variable-length arrays. - match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + match = re.match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) if (match and match.group(2) != 'return' and match.group(2) != 'delete' and match.group(3).find(']') == -1): # Split the size using space and arithmetic operators as delimiters. @@ -4804,17 +5457,17 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, skip_next = False continue - if Search(r'sizeof\(.+\)', tok): continue - if Search(r'arraysize\(\w+\)', tok): continue + if re.search(r'sizeof\(.+\)', tok): continue + if re.search(r'arraysize\(\w+\)', tok): continue tok = tok.lstrip('(') tok = tok.rstrip(')') if not tok: continue - if Match(r'\d+', tok): continue - if Match(r'0[xX][0-9a-fA-F]+', tok): continue - if Match(r'k[A-Z0-9]\w*', tok): continue - if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue - if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + if re.match(r'\d+', tok): continue + if re.match(r'0[xX][0-9a-fA-F]+', tok): continue + if re.match(r'k[A-Z0-9]\w*', tok): continue + if re.match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if re.match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue # A catch all for tricky sizeof cases, including 'sizeof expression', # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' # requires skipping the next token because we split on ' ' and '*'. @@ -4832,9 +5485,9 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, # macros are typically OK, so we allow use of "namespace {" on lines # that end with backslashes. if (IsHeaderExtension(file_extension) - and Search(r'\bnamespace\s*{', line) + and re.search(r'\bnamespace\s*{', line) and line[-1] != '\\'): - error(filename, linenum, 'build/namespaces', 4, + error(filename, linenum, 'build/namespaces_headers', 4, 'Do not use unnamed namespaces in header files. See ' 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' ' for more information.') @@ -4852,7 +5505,7 @@ def CheckGlobalStatic(filename, clean_lines, linenum, error): line = clean_lines.elided[linenum] # Match two lines at a time to support multiline declarations - if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): + if linenum + 1 < clean_lines.NumLines() and not re.search(r'[;({]', line): line += clean_lines.elided[linenum + 1].strip() # Check for people declaring static/global STL strings at the top level. @@ -4861,7 +5514,7 @@ def CheckGlobalStatic(filename, clean_lines, linenum, error): # also because globals can be destroyed when some threads are still running. # TODO(unknown): Generalize this to also find static unique_ptr instances. # TODO(unknown): File bugs for clang-tidy to find these. - match = Match( + match = re.match( r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +' r'([a-zA-Z0-9_:]+)\b(.*)', line) @@ -4883,20 +5536,19 @@ def CheckGlobalStatic(filename, clean_lines, linenum, error): # matching identifiers. # string Class::operator*() if (match and - not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and - not Search(r'\boperator\W', line) and - not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): - if Search(r'\bconst\b', line): + not re.search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and + not re.search(r'\boperator\W', line) and + not re.match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): + if re.search(r'\bconst\b', line): error(filename, linenum, 'runtime/string', 4, - 'For a static/global string constant, use a C style string ' - 'instead: "%schar%s %s[]".' % - (match.group(1), match.group(2) or '', match.group(3))) + 'For a static/global string constant, use a C style string instead:' + f' "{match.group(1)}char{match.group(2) or ""} {match.group(3)}[]".') else: error(filename, linenum, 'runtime/string', 4, 'Static/global string variables are not permitted.') - if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or - Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): + if (re.search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or + re.search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): error(filename, linenum, 'runtime/init', 4, 'You seem to be initializing a member variable with itself.') @@ -4913,21 +5565,21 @@ def CheckPrintf(filename, clean_lines, linenum, error): line = clean_lines.elided[linenum] # When snprintf is used, the second argument shouldn't be a literal. - match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + match = re.search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) if match and match.group(2) != '0': # If 2nd arg is zero, snprintf is used to calculate size. - error(filename, linenum, 'runtime/printf', 3, - 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' - 'to snprintf.' % (match.group(1), match.group(2))) + error(filename, linenum, 'runtime/printf', 3, 'If you can, use' + f' sizeof({match.group(1)}) instead of {match.group(2)}' + ' as the 2nd arg to snprintf.') # Check if some verboten C functions are being used. - if Search(r'\bsprintf\s*\(', line): + if re.search(r'\bsprintf\s*\(', line): error(filename, linenum, 'runtime/printf', 5, 'Never use sprintf. Use snprintf instead.') - match = Search(r'\b(strcpy|strcat)\s*\(', line) + match = re.search(r'\b(strcpy|strcat)\s*\(', line) if match: error(filename, linenum, 'runtime/printf', 4, - 'Almost always, snprintf is better than %s' % match.group(1)) + f'Almost always, snprintf is better than {match.group(1)}') def IsDerivedFunction(clean_lines, linenum): @@ -4941,14 +5593,14 @@ def IsDerivedFunction(clean_lines, linenum): virt-specifier. """ # Scan back a few lines for start of current function - for i in xrange(linenum, max(-1, linenum - 10), -1): - match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) + for i in range(linenum, max(-1, linenum - 10), -1): + match = re.match(r'^([^()]*\w+)\(', clean_lines.elided[i]) if match: # Look for "override" after the matching closing parenthesis line, _, closing_paren = CloseExpression( clean_lines, i, len(match.group(1))) return (closing_paren >= 0 and - Search(r'\boverride\b', line[closing_paren:])) + re.search(r'\boverride\b', line[closing_paren:])) return False @@ -4962,9 +5614,9 @@ def IsOutOfLineMethodDefinition(clean_lines, linenum): True if current line contains an out-of-line method definition. """ # Scan back a few lines for start of current function - for i in xrange(linenum, max(-1, linenum - 10), -1): - if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): - return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None + for i in range(linenum, max(-1, linenum - 10), -1): + if re.match(r'^([^()]*\w+)\(', clean_lines.elided[i]): + return re.match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None return False @@ -4978,24 +5630,24 @@ def IsInitializerList(clean_lines, linenum): True if current line appears to be inside constructor initializer list, False otherwise. """ - for i in xrange(linenum, 1, -1): + for i in range(linenum, 1, -1): line = clean_lines.elided[i] if i == linenum: - remove_function_body = Match(r'^(.*)\{\s*$', line) + remove_function_body = re.match(r'^(.*)\{\s*$', line) if remove_function_body: line = remove_function_body.group(1) - if Search(r'\s:\s*\w+[({]', line): + if re.search(r'\s:\s*\w+[({]', line): # A lone colon tend to indicate the start of a constructor # initializer list. It could also be a ternary operator, which # also tend to appear in constructor initializer lists as # opposed to parameter lists. return True - if Search(r'\}\s*,\s*$', line): + if re.search(r'\}\s*,\s*$', line): # A closing brace followed by a comma is probably the end of a # brace-initialized member in constructor initializer list. return True - if Search(r'[{};]\s*$', line): + if re.search(r'[{};]\s*$', line): # Found one of the following: # - A closing brace or semicolon, probably the end of the previous # function. @@ -5059,13 +5711,13 @@ def CheckForNonConstReference(filename, clean_lines, linenum, # that spans more than 2 lines, please use a typedef. if linenum > 1: previous = None - if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + if re.match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): # previous_line\n + ::current_line - previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + previous = re.search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', clean_lines.elided[linenum - 1]) - elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + elif re.match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): # previous_line::\n + current_line - previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + previous = re.search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', clean_lines.elided[linenum - 1]) if previous: line = previous.group(1) + line.lstrip() @@ -5079,7 +5731,7 @@ def CheckForNonConstReference(filename, clean_lines, linenum, # Found the matching < on an earlier line, collect all # pieces up to current line. line = '' - for i in xrange(startline, linenum + 1): + for i in range(startline, linenum + 1): line += clean_lines.elided[i].strip() # Check for non-const references in function parameters. A single '&' may @@ -5103,15 +5755,15 @@ def CheckForNonConstReference(filename, clean_lines, linenum, # appear inside the second set of parentheses on the current line as # opposed to the first set. if linenum > 0: - for i in xrange(linenum - 1, max(0, linenum - 10), -1): + for i in range(linenum - 1, max(0, linenum - 10), -1): previous_line = clean_lines.elided[i] - if not Search(r'[),]\s*$', previous_line): + if not re.search(r'[),]\s*$', previous_line): break - if Match(r'^\s*:\s+\S', previous_line): + if re.match(r'^\s*:\s+\S', previous_line): return # Avoid preprocessors - if Search(r'\\\s*$', line): + if re.search(r'\\\s*$', line): return # Avoid constructor initializer lists @@ -5128,25 +5780,25 @@ def CheckForNonConstReference(filename, clean_lines, linenum, r'operator\s*[<>][<>]|' r'static_assert|COMPILE_ASSERT' r')\s*\(') - if Search(allowed_functions, line): + if re.search(allowed_functions, line): return - elif not Search(r'\S+\([^)]*$', line): + elif not re.search(r'\S+\([^)]*$', line): # Don't see an allowed function on this line. Actually we # didn't see any function name on this line, so this is likely a # multi-line parameter list. Try a bit harder to catch this case. - for i in xrange(2): + for i in range(2): if (linenum > i and - Search(allowed_functions, clean_lines.elided[linenum - i - 1])): + re.search(allowed_functions, clean_lines.elided[linenum - i - 1])): return - decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + decls = re.sub(r'{[^}]*}', ' ', line) # exclude function body for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): - if (not Match(_RE_PATTERN_CONST_REF_PARAM, parameter) and - not Match(_RE_PATTERN_REF_STREAM_PARAM, parameter)): + if (not re.match(_RE_PATTERN_CONST_REF_PARAM, parameter) and + not re.match(_RE_PATTERN_REF_STREAM_PARAM, parameter)): error(filename, linenum, 'runtime/references', 2, 'Is this a non-const reference? ' 'If so, make const or use a pointer: ' + - ReplaceAll(' *<', '<', parameter)) + re.sub(' *<', '<', parameter)) def CheckCasts(filename, clean_lines, linenum, error): @@ -5164,7 +5816,7 @@ def CheckCasts(filename, clean_lines, linenum, error): # I just try to capture the most common basic types, though there are more. # Parameterless conversion functions, such as bool(), are allowed as they are # probably a member operator declaration or default constructor. - match = Search( + match = re.search( r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b' r'(int|float|double|bool|char|int32|uint32|int64|uint64)' r'(\([^)].*)', line) @@ -5188,7 +5840,7 @@ def CheckCasts(filename, clean_lines, linenum, error): # Avoid arrays by looking for brackets that come after the closing # parenthesis. - if Match(r'\([^()]+\)\s*\[', match.group(3)): + if re.match(r'\([^()]+\)\s*\[', match.group(3)): return # Other things to ignore: @@ -5199,19 +5851,18 @@ def CheckCasts(filename, clean_lines, linenum, error): matched_funcptr = match.group(3) if (matched_new_or_template is None and not (matched_funcptr and - (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + (re.match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', matched_funcptr) or matched_funcptr.startswith('(*)'))) and - not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and - not Search(r'new\(\S+\)\s*' + matched_type, line)): + not re.match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and + not re.search(r'new\(\S+\)\s*' + matched_type, line)): error(filename, linenum, 'readability/casting', 4, 'Using deprecated casting style. ' - 'Use static_cast<%s>(...) instead' % - matched_type) + f'Use static_cast<{matched_type}>(...) instead') if not expecting_function: CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', - r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + r'\((int|float|double|bool|char|u?int(16|32|64)|size_t)\)', error) # This doesn't catch all cases. Consider (const char * const)"hello". # @@ -5236,7 +5887,7 @@ def CheckCasts(filename, clean_lines, linenum, error): # # This is not a cast: # reference_type&(int* function_param); - match = Search( + match = re.search( r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) if match: @@ -5244,7 +5895,7 @@ def CheckCasts(filename, clean_lines, linenum, error): # dereferenced by the casted pointer, as opposed to the casted # pointer itself. parenthesis_error = False - match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) + match = re.match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) if match: _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) if x1 >= 0 and clean_lines.elided[y1][x1] == '(': @@ -5253,7 +5904,7 @@ def CheckCasts(filename, clean_lines, linenum, error): extended_line = clean_lines.elided[y2][x2:] if y2 < clean_lines.NumLines() - 1: extended_line += clean_lines.elided[y2 + 1] - if Match(r'\s*(?:->|\[)', extended_line): + if re.match(r'\s*(?:->|\[)', extended_line): parenthesis_error = True if parenthesis_error: @@ -5285,38 +5936,38 @@ def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): False otherwise. """ line = clean_lines.elided[linenum] - match = Search(pattern, line) + match = re.search(pattern, line) if not match: return False # Exclude lines with keywords that tend to look like casts context = line[0:match.start(1) - 1] - if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): + if re.match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): return False # Try expanding current context to see if we one level of # parentheses inside a macro. if linenum > 0: - for i in xrange(linenum - 1, max(0, linenum - 5), -1): + for i in range(linenum - 1, max(0, linenum - 5), -1): context = clean_lines.elided[i] + context - if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): + if re.match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): return False # operator++(int) and operator--(int) - if context.endswith(' operator++') or context.endswith(' operator--'): + if (context.endswith(' operator++') or context.endswith(' operator--') or + context.endswith('::operator++') or context.endswith('::operator--')): return False # A single unnamed argument for a function tends to look like old style cast. # If we see those, don't issue warnings for deprecated casts. remainder = line[match.end(0):] - if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', + if re.match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', remainder): return False # At this point, all that should be left is actual casts. error(filename, linenum, 'readability/casting', 4, - 'Using C-style cast. Use %s<%s>(...) instead' % - (cast_type, match.group(1))) + f'Using C-style cast. Use {cast_type}<{match.group(1)}>(...) instead') return True @@ -5333,13 +5984,13 @@ def ExpectingFunctionArgs(clean_lines, linenum): of function types. """ line = clean_lines.elided[linenum] - return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + return (re.match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or (linenum >= 2 and - (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + (re.match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', clean_lines.elided[linenum - 1]) or - Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + re.match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', clean_lines.elided[linenum - 2]) or - Search(r'\bstd::m?function\s*\<\s*$', + re.search(r'\bstd::m?function\s*\<\s*$', clean_lines.elided[linenum - 1])))) @@ -5364,11 +6015,11 @@ def ExpectingFunctionArgs(clean_lines, linenum): )), ('', ('numeric_limits',)), ('', ('list',)), - ('', ('map', 'multimap',)), + ('', ('multimap',)), ('', ('allocator', 'make_shared', 'make_unique', 'shared_ptr', 'unique_ptr', 'weak_ptr')), ('', ('queue', 'priority_queue',)), - ('', ('set', 'multiset',)), + ('', ('multiset',)), ('', ('stack',)), ('', ('char_traits', 'basic_string',)), ('', ('tuple',)), @@ -5391,17 +6042,46 @@ def ExpectingFunctionArgs(clean_lines, linenum): ('', ('forward', 'make_pair', 'move', 'swap')), ) -_RE_PATTERN_STRING = re.compile(r'\bstring\b') +# Non templated types or global objects +_HEADERS_TYPES_OR_OBJS = ( + # String and others are special -- it is a non-templatized type in STL. + ('', ('string',)), + ('', ('cin', 'cout', 'cerr', 'clog', 'wcin', 'wcout', + 'wcerr', 'wclog')), + ('', ('FILE', 'fpos_t'))) + +# Non templated functions +_HEADERS_FUNCTIONS = ( + ('', ('fopen', 'freopen', + 'fclose', 'fflush', 'setbuf', 'setvbuf', 'fread', + 'fwrite', 'fgetc', 'getc', 'fgets', 'fputc', 'putc', + 'fputs', 'getchar', 'gets', 'putchar', 'puts', 'ungetc', + 'scanf', 'fscanf', 'sscanf', 'vscanf', 'vfscanf', + 'vsscanf', 'printf', 'fprintf', 'sprintf', 'snprintf', + 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf', + 'ftell', 'fgetpos', 'fseek', 'fsetpos', + 'clearerr', 'feof', 'ferror', 'perror', + 'tmpfile', 'tmpnam'),),) _re_pattern_headers_maybe_templates = [] for _header, _templates in _HEADERS_MAYBE_TEMPLATES: for _template in _templates: # Match max(..., ...), max(..., ...), but not foo->max, foo.max or - # type::max(). + # 'type::max()'. _re_pattern_headers_maybe_templates.append( - (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + (re.compile(r'((\bstd::)|[^>.:])\b' + _template + r'(<.*?>)?\([^\)]'), _template, _header)) +# Match set, but not foo->set, foo.set +_re_pattern_headers_maybe_templates.append( + (re.compile(r'[^>.]\bset\s*\<'), + 'set<>', + '')) +# Match 'map var' and 'std::map(...)', but not 'map(...)'' +_re_pattern_headers_maybe_templates.append( + (re.compile(r'(std\b::\bmap\s*\<)|(^(std\b::\b)map\b\(\s*\<)'), + 'map<>', + '')) # Other scripts may reach in and modify this pattern. _re_pattern_templates = [] @@ -5412,6 +6092,23 @@ def ExpectingFunctionArgs(clean_lines, linenum): _template + '<>', _header)) +_re_pattern_types_or_objs = [] +for _header, _types_or_objs in _HEADERS_TYPES_OR_OBJS: + for _type_or_obj in _types_or_objs: + _re_pattern_types_or_objs.append( + (re.compile(r'\b' + _type_or_obj + r'\b'), + _type_or_obj, + _header)) + +_re_pattern_functions = [] +for _header, _functions in _HEADERS_FUNCTIONS: + for _function in _functions: + # Match printf(..., ...), but not foo->printf, foo.printf or + # 'type::printf()'. + _re_pattern_functions.append( + (re.compile(r'([^>.]|^)\b' + _function + r'\([^\)]'), + _function, + _header)) def FilesBelongToSameModule(filename_cc, filename_h): """Check if these two filenames belong to the same module. @@ -5434,7 +6131,7 @@ def FilesBelongToSameModule(filename_cc, filename_h): some false positives. This should be sufficiently rare in practice. Args: - filename_cc: is the path for the .cc file + filename_cc: is the path for the source (e.g. .cc) file filename_h: is the path for the header path Returns: @@ -5442,20 +6139,23 @@ def FilesBelongToSameModule(filename_cc, filename_h): bool: True if filename_cc and filename_h belong to the same module. string: the additional prefix needed to open the header file. """ + fileinfo_cc = FileInfo(filename_cc) + if fileinfo_cc.Extension().lstrip('.') not in GetNonHeaderExtensions(): + return (False, '') - fileinfo = FileInfo(filename_cc) - if not fileinfo.IsSource(): + fileinfo_h = FileInfo(filename_h) + if not IsHeaderExtension(fileinfo_h.Extension().lstrip('.')): return (False, '') - filename_cc = filename_cc[:-len(fileinfo.Extension())] - matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()) + + filename_cc = filename_cc[:-(len(fileinfo_cc.Extension()))] + matched_test_suffix = re.search(_TEST_FILE_SUFFIX, fileinfo_cc.BaseName()) if matched_test_suffix: filename_cc = filename_cc[:-len(matched_test_suffix.group(1))] + filename_cc = filename_cc.replace('/public/', '/') filename_cc = filename_cc.replace('/internal/', '/') - if not filename_h.endswith('.h'): - return (False, '') - filename_h = filename_h[:-len('.h')] + filename_h = filename_h[:-(len(fileinfo_h.Extension()))] if filename_h.endswith('-inl'): filename_h = filename_h[:-len('-inl')] filename_h = filename_h.replace('/public/', '/') @@ -5468,33 +6168,6 @@ def FilesBelongToSameModule(filename_cc, filename_h): return files_belong_to_same_module, common_path -def UpdateIncludeState(filename, include_dict, io=codecs): - """Fill up the include_dict with new includes found from the file. - - Args: - filename: the name of the header to read. - include_dict: a dictionary in which the headers are inserted. - io: The io factory to use to read the file. Provided for testability. - - Returns: - True if a header was successfully added. False otherwise. - """ - headerfile = None - try: - headerfile = io.open(filename, 'r', 'utf8', 'replace') - except IOError: - return False - linenum = 0 - for line in headerfile: - linenum += 1 - clean_line = CleanseComments(line) - match = _RE_PATTERN_INCLUDE.search(clean_line) - if match: - include = match.group(2) - include_dict.setdefault(include, linenum) - return True - - def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, io=codecs): """Reports for missing stl includes. @@ -5516,26 +6189,29 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, required = {} # A map of header name to linenumber and the template entity. # Example of required: { '': (1219, 'less<>') } - for linenum in xrange(clean_lines.NumLines()): + for linenum in range(clean_lines.NumLines()): line = clean_lines.elided[linenum] if not line or line[0] == '#': continue - # String is special -- it is a non-templatized type in STL. - matched = _RE_PATTERN_STRING.search(line) - if matched: - # Don't warn about strings in non-STL namespaces: - # (We check only the first match per line; good enough.) - prefix = line[:matched.start()] - if prefix.endswith('std::') or not prefix.endswith('::'): - required[''] = (linenum, 'string') + _re_patterns = [] + _re_patterns.extend(_re_pattern_types_or_objs) + _re_patterns.extend(_re_pattern_functions) + for pattern, item, header in _re_patterns: + matched = pattern.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[header] = (linenum, item) for pattern, template, header in _re_pattern_headers_maybe_templates: if pattern.search(line): required[header] = (linenum, template) # The following function is just a speed up, no semantics are changed. - if not '<' in line: # Reduces the cpu time usage by skipping lines. + if '<' not in line: # Reduces the cpu time usage by skipping lines. continue for pattern, template, header in _re_pattern_templates: @@ -5547,46 +6223,12 @@ def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, if prefix.endswith('std::') or not prefix.endswith('::'): required[header] = (linenum, template) - # The policy is that if you #include something in foo.h you don't need to - # include it again in foo.cc. Here, we will look at possible includes. # Let's flatten the include_state include_list and copy it into a dictionary. include_dict = dict([item for sublist in include_state.include_list for item in sublist]) - # Did we find the header for this file (if any) and successfully load it? - header_found = False - - # Use the absolute path so that matching works properly. - abs_filename = FileInfo(filename).FullName() - - # For Emacs's flymake. - # If cpplint is invoked from Emacs's flymake, a temporary file is generated - # by flymake and that file name might end with '_flymake.cc'. In that case, - # restore original file name here so that the corresponding header file can be - # found. - # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' - # instead of 'foo_flymake.h' - abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) - - # include_dict is modified during iteration, so we iterate over a copy of - # the keys. - header_keys = include_dict.keys() - for header in header_keys: - (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) - fullpath = common_path + header - if same_module and UpdateIncludeState(fullpath, include_dict, io): - header_found = True - - # If we can't find the header file for a .cc, assume it's because we don't - # know where to look. In that case we'll give up as we're not sure they - # didn't include it in the .h file. - # TODO(unknown): Do a better job of finding .h files so we are confident that - # not having the .h file means there isn't one. - if filename.endswith('.cc') and not header_found: - return - # All the lines have been processed, report the errors found. - for required_header_unstripped in required: + for required_header_unstripped in sorted(required, key=required.__getitem__): template = required[required_header_unstripped][1] if required_header_unstripped.strip('<>"') not in include_dict: error(filename, required[required_header_unstripped][0], @@ -5629,20 +6271,20 @@ def CheckRedundantVirtual(filename, clean_lines, linenum, error): """ # Look for "virtual" on current line. line = clean_lines.elided[linenum] - virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) + virtual = re.match(r'^(.*)(\bvirtual\b)(.*)$', line) if not virtual: return # Ignore "virtual" keywords that are near access-specifiers. These # are only used in class base-specifier and do not apply to member # functions. - if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or - Match(r'^\s+(public|protected|private)\b', virtual.group(3))): + if (re.search(r'\b(public|protected|private)\s+$', virtual.group(1)) or + re.match(r'^\s+(public|protected|private)\b', virtual.group(3))): return # Ignore the "virtual" keyword from virtual base classes. Usually # there is a column on the same line in these cases (virtual base # classes are rare in google3 because multiple inheritance is rare). - if Match(r'^.*[^:]:[^:].*$', line): return + if re.match(r'^.*[^:]:[^:].*$', line): return # Look for the next opening parenthesis. This is the start of the # parameter list (possibly on the next line shortly after virtual). @@ -5652,9 +6294,9 @@ def CheckRedundantVirtual(filename, clean_lines, linenum, error): end_col = -1 end_line = -1 start_col = len(virtual.group(2)) - for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): + for start_line in range(linenum, min(linenum + 3, clean_lines.NumLines())): line = clean_lines.elided[start_line][start_col:] - parameter_list = Match(r'^([^(]*)\(', line) + parameter_list = re.match(r'^([^(]*)\(', line) if parameter_list: # Match parentheses to find the end of the parameter list (_, end_line, end_col) = CloseExpression( @@ -5667,18 +6309,18 @@ def CheckRedundantVirtual(filename, clean_lines, linenum, error): # Look for "override" or "final" after the parameter list # (possibly on the next few lines). - for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): + for i in range(end_line, min(end_line + 3, clean_lines.NumLines())): line = clean_lines.elided[i][end_col:] - match = Search(r'\b(override|final)\b', line) + match = re.search(r'\b(override|final)\b', line) if match: error(filename, linenum, 'readability/inheritance', 4, ('"virtual" is redundant since function is ' - 'already declared as "%s"' % match.group(1))) + f'already declared as "{match.group(1)}"')) # Set end_col to check whole lines after we are done with the # first line. end_col = 0 - if Search(r'[^\w]\s*$', line): + if re.search(r'[^\w]\s*$', line): break @@ -5705,7 +6347,7 @@ def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): return # Check that at most one of "override" or "final" is present, not both - if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): + if re.search(r'\boverride\b', fragment) and re.search(r'\bfinal\b', fragment): error(filename, linenum, 'readability/inheritance', 4, ('"override" is redundant since function is ' 'already declared as "final"')) @@ -5725,15 +6367,17 @@ def IsBlockInNameSpace(nesting_state, is_forward_declaration): Whether or not the new block is directly in a namespace. """ if is_forward_declaration: - if len(nesting_state.stack) >= 1 and ( - isinstance(nesting_state.stack[-1], _NamespaceInfo)): - return True - else: - return False + return len(nesting_state.stack) >= 1 and ( + isinstance(nesting_state.stack[-1], _NamespaceInfo)) - return (len(nesting_state.stack) > 1 and - nesting_state.stack[-1].check_namespace_indentation and - isinstance(nesting_state.stack[-2], _NamespaceInfo)) + if len(nesting_state.stack) >= 1: + if isinstance(nesting_state.stack[-1], _NamespaceInfo): + return True + elif (len(nesting_state.stack) > 1 and + isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and + isinstance(nesting_state.stack[-2], _NamespaceInfo)): + return True + return False def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, @@ -5772,14 +6416,14 @@ def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, error): line = raw_lines_no_comments[linenum] - if Match(r'^\s+', line): - error(filename, linenum, 'runtime/indentation_namespace', 4, - 'Do not indent within a namespace') + if re.match(r'^\s+', line): + error(filename, linenum, 'whitespace/indent_namespace', 4, + 'Do not indent within a namespace.') def ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, - extra_check_functions=[]): + extra_check_functions=None): """Processes a single line in the file. Args: @@ -5818,11 +6462,13 @@ def ProcessLine(filename, file_extension, clean_lines, line, CheckMakePairUsesDeduction(filename, clean_lines, line, error) CheckRedundantVirtual(filename, clean_lines, line, error) CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) - for check_fn in extra_check_functions: - check_fn(filename, clean_lines, line, error) + if extra_check_functions: + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + -def FlagCxx11Features(filename, clean_lines, linenum, error): - """Flag those c++11 features that we only allow in certain places. +def FlagCxxHeaders(filename, clean_lines, linenum, error): + """Flag C++ headers that the styleguide restricts. Args: filename: The name of the current file. @@ -5832,68 +6478,24 @@ def FlagCxx11Features(filename, clean_lines, linenum, error): """ line = clean_lines.elided[linenum] - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++ TR1 headers. - if include and include.group(1).startswith('tr1/'): - error(filename, linenum, 'build/c++tr1', 5, - ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) + include = re.match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) # Flag unapproved C++11 headers. if include and include.group(1) in ('cfenv', - 'condition_variable', 'fenv.h', - 'future', - 'mutex', - 'thread', - 'chrono', 'ratio', - 'regex', - 'system_error', ): error(filename, linenum, 'build/c++11', 5, - ('<%s> is an unapproved C++11 header.') % include.group(1)) - - # The only place where we need to worry about C++11 keywords and library - # features in preprocessor directives is in macro definitions. - if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return - - # These are classes and free functions. The classes are always - # mentioned as std::*, but we only catch the free functions if - # they're not found by ADL. They're alphabetical by header. - for top_name in ( - # type_traits - 'alignment_of', - 'aligned_union', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++11', 5, - ('std::%s is an unapproved C++11 class or function. Send c-style ' - 'an example of where it would make your code more readable, and ' - 'they may let you use it.') % top_name) - - -def FlagCxx14Features(filename, clean_lines, linenum, error): - """Flag those C++14 features that we restrict. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] + f"<{include.group(1)}> is an unapproved C++11 header.") - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++14 headers. - if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): - error(filename, linenum, 'build/c++14', 5, - ('<%s> is an unapproved C++14 header.') % include.group(1)) + # filesystem is the only unapproved C++17 header + if include and include.group(1) == 'filesystem': + error(filename, linenum, 'build/c++17', 5, + " is an unapproved C++17 header.") def ProcessFileData(filename, file_extension, lines, error, - extra_check_functions=[]): + extra_check_functions=None): """Performs lint checks and reports any errors to the given error function. Args: @@ -5917,18 +6519,21 @@ def ProcessFileData(filename, file_extension, lines, error, ResetNolintSuppressions() CheckForCopyright(filename, lines, error) - ProcessGlobalSuppresions(lines) + ProcessGlobalSuppressions(lines) RemoveMultiLineComments(filename, lines, error) clean_lines = CleansedLines(lines) if IsHeaderExtension(file_extension): CheckForHeaderGuard(filename, clean_lines, error) - for line in xrange(clean_lines.NumLines()): + for line in range(clean_lines.NumLines()): ProcessLine(filename, file_extension, clean_lines, line, include_state, function_state, nesting_state, error, extra_check_functions) - FlagCxx11Features(filename, clean_lines, line, error) + FlagCxxHeaders(filename, clean_lines, line, error) + if _error_suppressions.HasOpenBlock(): + error(filename, _error_suppressions.GetOpenBlockStart(), 'readability/nolint', 5, + 'NONLINT block never ended') nesting_state.CheckCompletedBlocks(filename, error) CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) @@ -5961,13 +6566,13 @@ def ProcessConfigOverrides(filename): if not base_name: break # Reached the root directory. - cfg_file = os.path.join(abs_path, "CPPLINT.cfg") + cfg_file = os.path.join(abs_path, _config_filename) abs_filename = abs_path if not os.path.isfile(cfg_file): continue try: - with open(cfg_file) as file_handle: + with codecs.open(cfg_file, 'r', 'utf8', 'replace') as file_handle: for line in file_handle: line, _, _ = line.partition('#') # Remove comments. if not line.strip(): @@ -5993,42 +6598,45 @@ def ProcessConfigOverrides(filename): if _cpplint_state.quiet: # Suppress "Ignoring file" warning when using --quiet. return False - sys.stderr.write('Ignoring "%s": file excluded by "%s". ' + _cpplint_state.PrintInfo(f'Ignoring "{filename}": file excluded by "{cfg_file}". ' 'File path component "%s" matches ' 'pattern "%s"\n' % - (filename, cfg_file, base_name, val)) + (base_name, val)) return False elif name == 'linelength': global _line_length try: - _line_length = int(val) + _line_length = int(val) except ValueError: - sys.stderr.write('Line length must be numeric.') + _cpplint_state.PrintError('Line length must be numeric.') + elif name == 'extensions': + ProcessExtensionsOption(val) elif name == 'root': global _root # root directories are specified relative to CPPLINT.cfg dir. _root = os.path.join(os.path.dirname(cfg_file), val) elif name == 'headers': ProcessHppHeadersOption(val) + elif name == 'includeorder': + ProcessIncludeOrderOption(val) else: - sys.stderr.write( - 'Invalid configuration option (%s) in file %s\n' % - (name, cfg_file)) + _cpplint_state.PrintError( + f'Invalid configuration option ({name}) in file {cfg_file}\n') except IOError: - sys.stderr.write( - "Skipping config file '%s': Can't open for reading\n" % cfg_file) + _cpplint_state.PrintError( + f"Skipping config file '{cfg_file}': Can't open for reading\n") keep_looking = False # Apply all the accumulated filters in reverse order (top-level directory # config options having the least priority). - for filter in reversed(cfg_filters): - _AddFilters(filter) + for cfg_filter in reversed(cfg_filters): + _AddFilters(cfg_filter) return True -def ProcessFile(filename, vlevel, extra_check_functions=[]): +def ProcessFile(filename, vlevel, extra_check_functions=None): """Does google-lint on a single file. Args: @@ -6066,7 +6674,8 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]): codecs.getwriter('utf8'), 'replace').read().split('\n') else: - lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + with codecs.open(filename, 'r', 'utf8', 'replace') as target_file: + lines = target_file.read().split('\n') # Remove trailing '\r'. # The -1 accounts for the extra trailing blank line we get from split() @@ -6078,8 +6687,9 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]): lf_lines.append(linenum + 1) except IOError: - sys.stderr.write( - "Skipping input '%s': Can't open for reading\n" % filename) + # TODO: Maybe make this have an exit code of 2 after all is done + _cpplint_state.PrintError( + f"Skipping input '{filename}': Can't open for reading\n") _RestoreFilters() return @@ -6088,9 +6698,9 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]): # When reading from stdin, the extension is unknown, so no cpplint tests # should rely on the extension. - if filename != '-' and file_extension not in _valid_extensions: - sys.stderr.write('Ignoring %s; not a valid file name ' - '(%s)\n' % (filename, ', '.join(_valid_extensions))) + if filename != '-' and file_extension not in GetAllExtensions(): + _cpplint_state.PrintError(f'Ignoring {filename}; not a valid file name' + f' ({(", ".join(GetAllExtensions()))})\n') else: ProcessFileData(filename, file_extension, lines, Error, extra_check_functions) @@ -6116,7 +6726,7 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]): # Suppress printing anything if --quiet was passed unless the error # count has increased after processing this file. if not _cpplint_state.quiet or old_errors != _cpplint_state.error_count: - sys.stdout.write('Done processing %s\n' % filename) + _cpplint_state.PrintInfo(f'Done processing {filename}\n') _RestoreFilters() @@ -6126,19 +6736,28 @@ def PrintUsage(message): Args: message: The optional error message. """ - sys.stderr.write(_USAGE) + sys.stderr.write(_USAGE % (sorted(list(GetAllExtensions())), + ','.join(sorted(list(GetAllExtensions()))), + sorted(GetHeaderExtensions()), + ','.join(sorted(GetHeaderExtensions())))) + if message: sys.exit('\nFATAL ERROR: ' + message) else: - sys.exit(1) + sys.exit(0) +def PrintVersion(): + sys.stdout.write('Cpplint fork (https://github.com/cpplint/cpplint)\n') + sys.stdout.write('cpplint ' + __VERSION__ + '\n') + sys.stdout.write('Python ' + sys.version + '\n') + sys.exit(0) def PrintCategories(): """Prints a list of all the error-categories used by error messages. These are the categories used to filter messages via --filter. """ - sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.stderr.write(''.join(f' {cat}\n' for cat in _ERROR_CATEGORIES)) sys.exit(0) @@ -6155,12 +6774,19 @@ def ParseArguments(args): """ try: (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'v=', + 'version', 'counting=', 'filter=', 'root=', + 'repository=', 'linelength=', 'extensions=', + 'exclude=', + 'recursive', 'headers=', + 'includeorder=', + 'config=', 'quiet']) except getopt.GetoptError: PrintUsage('Invalid arguments.') @@ -6170,17 +6796,21 @@ def ParseArguments(args): filters = '' quiet = _Quiet() counting_style = '' + recursive = False for (opt, val) in opts: if opt == '--help': PrintUsage(None) + if opt == '--version': + PrintVersion() elif opt == '--output': - if val not in ('emacs', 'vs7', 'eclipse'): - PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.') + if val not in ('emacs', 'vs7', 'eclipse', 'junit', 'sed', 'gsed'): + PrintUsage('The only allowed output formats are emacs, vs7, eclipse ' + 'sed, gsed and junit.') output_format = val elif opt == '--quiet': quiet = True - elif opt == '--verbose': + elif opt == '--verbose' or opt == '--v': verbosity = int(val) elif opt == '--filter': filters = val @@ -6193,49 +6823,157 @@ def ParseArguments(args): elif opt == '--root': global _root _root = val + elif opt == '--repository': + global _repository + _repository = val elif opt == '--linelength': global _line_length try: - _line_length = int(val) + _line_length = int(val) except ValueError: - PrintUsage('Line length must be digits.') + PrintUsage('Line length must be digits.') + elif opt == '--exclude': + global _excludes + if not _excludes: + _excludes = set() + _excludes.update(glob.glob(val)) elif opt == '--extensions': - global _valid_extensions - try: - _valid_extensions = set(val.split(',')) - except ValueError: - PrintUsage('Extensions must be comma separated list.') + ProcessExtensionsOption(val) elif opt == '--headers': ProcessHppHeadersOption(val) + elif opt == '--recursive': + recursive = True + elif opt == '--includeorder': + ProcessIncludeOrderOption(val) + elif opt == '--config': + global _config_filename + _config_filename = val + if os.path.basename(_config_filename) != _config_filename: + PrintUsage('Config file name must not include directory components.') if not filenames: PrintUsage('No files were specified.') + if recursive: + filenames = _ExpandDirectories(filenames) + + if _excludes: + filenames = _FilterExcludedFiles(filenames) + _SetOutputFormat(output_format) _SetQuiet(quiet) _SetVerboseLevel(verbosity) _SetFilters(filters) _SetCountingStyle(counting_style) + filenames.sort() return filenames +def _ParseFilterSelector(parameter): + """Parses the given command line parameter for file- and line-specific + exclusions. + readability/casting:file.cpp + readability/casting:file.cpp:43 -def main(): - filenames = ParseArguments(sys.argv[1:]) + Args: + parameter: The parameter value of --filter + + Returns: + [category, filename, line]. + Category is always given. + Filename is either a filename or empty if all files are meant. + Line is either a line in filename or -1 if all lines are meant. + """ + colon_pos = parameter.find(":") + if colon_pos == -1: + return parameter, "", -1 + category = parameter[:colon_pos] + second_colon_pos = parameter.find(":", colon_pos + 1) + if second_colon_pos == -1: + return category, parameter[colon_pos + 1:], -1 + else: + return category, parameter[colon_pos + 1: second_colon_pos], \ + int(parameter[second_colon_pos + 1:]) + +def _ExpandDirectories(filenames): + """Searches a list of filenames and replaces directories in the list with + all files descending from those directories. Files with extensions not in + the valid extensions list are excluded. - # Change stderr to write with replacement characters so we don't die - # if we try to print something containing non-ASCII characters. - sys.stderr = codecs.StreamReaderWriter(sys.stderr, - codecs.getreader('utf8'), - codecs.getwriter('utf8'), - 'replace') + Args: + filenames: A list of files or directories - _cpplint_state.ResetErrorCounts() + Returns: + A list of all files that are members of filenames or descended from a + directory in filenames + """ + expanded = set() for filename in filenames: - ProcessFile(filename, _cpplint_state.verbose_level) - # If --quiet is passed, suppress printing error count unless there are errors. - if not _cpplint_state.quiet or _cpplint_state.error_count > 0: - _cpplint_state.PrintErrorCounts() + if not os.path.isdir(filename): + expanded.add(filename) + continue + + for root, _, files in os.walk(filename): + for loopfile in files: + fullname = os.path.join(root, loopfile) + if fullname.startswith('.' + os.path.sep): + fullname = fullname[len('.' + os.path.sep):] + expanded.add(fullname) + + filtered = [] + for filename in expanded: + if os.path.splitext(filename)[1][1:] in GetAllExtensions(): + filtered.append(filename) + return filtered + +def _FilterExcludedFiles(fnames): + """Filters out files listed in the --exclude command line switch. File paths + in the switch are evaluated relative to the current working directory + """ + exclude_paths = [os.path.abspath(f) for f in _excludes] + # because globbing does not work recursively, exclude all subpath of all excluded entries + return [f for f in fnames + if not any(e for e in exclude_paths + if _IsParentOrSame(e, os.path.abspath(f)))] + +def _IsParentOrSame(parent, child): + """Return true if child is subdirectory of parent. + Assumes both paths are absolute and don't contain symlinks. + """ + parent = os.path.normpath(parent) + child = os.path.normpath(child) + if parent == child: + return True + + prefix = os.path.commonprefix([parent, child]) + if prefix != parent: + return False + # Note: os.path.commonprefix operates on character basis, so + # take extra care of situations like '/foo/ba' and '/foo/bar/baz' + child_suffix = child[len(prefix):] + child_suffix = child_suffix.lstrip(os.sep) + return child == os.path.join(prefix, child_suffix) + +def main(): + filenames = ParseArguments(sys.argv[1:]) + backup_err = sys.stderr + try: + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReader(sys.stderr, 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + # If --quiet is passed, suppress printing error count unless there are errors. + if not _cpplint_state.quiet or _cpplint_state.error_count > 0: + _cpplint_state.PrintErrorCounts() + + if _cpplint_state.output_format == 'junit': + sys.stderr.write(_cpplint_state.FormatJUnitXML()) + + finally: + sys.stderr = backup_err sys.exit(_cpplint_state.error_count > 0) diff --git a/extras/cpplint.sh b/extras/cpplint.sh index daf45aa2..0b9b14e9 100644 --- a/extras/cpplint.sh +++ b/extras/cpplint.sh @@ -1,4 +1,2 @@ -#!/bin/sh -export PATH=/cygdrive/c/Python27:/cygdrive/c/Python27/DLLs:/cygdrive/c/Python27/Scripts:$PATH -echo $PATH -python cpplint.py --filter=-build/include,-runtime/references,-build/header_guard ../src/*.* ../src/*/*.* 2>cpplint.txt \ No newline at end of file +#! /usr/bin/bash +./cpplint.py ../src/*.cpp ../src/*.h ../src/*/*.cpp ../src/*/*.h ../src/SdCard/*/*.cpp ../src/SdCard/*/*.h 2>cpplint.txt \ No newline at end of file diff --git a/extras/fmt_src.bat b/extras/fmt_src.bat index 7ddecdfe..2c65e134 100644 --- a/extras/fmt_src.bat +++ b/extras/fmt_src.bat @@ -1,3 +1,4 @@ +pause clang-format --style=Google -i *.cpp *.h rem clang-format --style=Google -i DigitalIO/*.h rem clang-format --style=Google -i DigitalIO/boards/*.h @@ -7,5 +8,6 @@ clang-format --style=Google -i FatLib/*.cpp FatLib/*.h clang-format --style=Google -i FsLib/*.cpp FsLib/*.h clang-format --style=Google -i iostream/*.cpp iostream/*.h clang-format --style=Google -i SdCard/*.cpp SdCard/*.h +clang-format --style=Google -i SdCard/*/*.cpp SdCard/*/*.h clang-format --style=Google -i SpiDriver/*.cpp SpiDriver/*.h pause diff --git a/images/SdioSpi.jpg b/images/SdioSpi.jpg new file mode 100644 index 00000000..fa590c1d Binary files /dev/null and b/images/SdioSpi.jpg differ diff --git a/images/picowbell.jpg b/images/picowbell.jpg new file mode 100644 index 00000000..8b3c607d Binary files /dev/null and b/images/picowbell.jpg differ diff --git a/library.properties b/library.properties index 12abc4f5..e8b2932e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SdFat -version=2.2.3 +version=2.3.0 license=MIT author=Bill Greiman maintainer=Bill Greiman diff --git a/src/.clang-format-ignore b/src/.clang-format-ignore new file mode 100644 index 00000000..0d15069f --- /dev/null +++ b/src/.clang-format-ignore @@ -0,0 +1 @@ +SdCard/Rp2040Sdio/PioSdioCard.pio.h \ No newline at end of file diff --git a/src/CPPLINT.cfg b/src/CPPLINT.cfg new file mode 100644 index 00000000..b5136de4 --- /dev/null +++ b/src/CPPLINT.cfg @@ -0,0 +1,3 @@ +filter=-build/include,-runtime/references,-build/header_guard +filter=-whitespace/indent_namespace +exclude_files=SdFatDebugConfig.h \ No newline at end of file diff --git a/src/ExFatLib/ExFatDbg.cpp b/src/ExFatLib/ExFatDbg.cpp index 02cc1bf7..42c0bff9 100644 --- a/src/ExFatLib/ExFatDbg.cpp +++ b/src/ExFatLib/ExFatDbg.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -33,7 +33,7 @@ static void printHex(print_t* pr, uint32_t val); static void printHex64(print_t* pr, uint64_t n); static void println64(print_t* pr, uint64_t n); //------------------------------------------------------------------------------ -static void dmpDirData(print_t* pr, DirGeneric_t* dir) { +static void dmpDirData(print_t* pr, const DirGeneric_t* dir) { for (uint8_t k = 0; k < 31; k++) { if (k) { pr->write(' '); @@ -53,7 +53,7 @@ static uint16_t exFatDirChecksum(const void* dir, uint16_t checksum) { } //------------------------------------------------------------------------------ -static uint16_t hashDir(DirName_t* dir, uint16_t hash) { +static uint16_t hashDir(const DirName_t* dir, uint16_t hash) { for (uint8_t i = 0; i < 30; i += 2) { uint16_t u = getLe16(dir->unicode + i); if (!u) { @@ -266,7 +266,7 @@ static void println64(print_t* pr, uint64_t n) { pr->println(str); } //------------------------------------------------------------------------------ -static void printMbr(print_t* pr, MbrSector_t* mbr) { +static void printMbr(print_t* pr, const MbrSector_t* mbr) { pr->print(F("mbrSig: 0x")); pr->println(getLe16(mbr->signature), HEX); for (int i = 0; i < 4; i++) { @@ -299,7 +299,7 @@ void ExFatPartition::checkUpcase(print_t* pr) { pr->println(F("read root failed")); return; } - DirUpcase_t* dir = reinterpret_cast(cache); + const DirUpcase_t* dir = reinterpret_cast(cache); pr->println(F("\nChecking upcase table")); for (size_t i = 0; i < 16; i++) { @@ -383,7 +383,7 @@ void ExFatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { pr->println(F("cache read failed")); return; } - uint32_t* fat = reinterpret_cast(cache); + const uint32_t* fat = reinterpret_cast(cache); for (size_t k = 0; k < 128; k++) { if (0 == cluster % 8) { if (k) { @@ -400,7 +400,7 @@ void ExFatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { } //------------------------------------------------------------------------------ void ExFatPartition::dmpSector(print_t* pr, uint32_t sector) { - uint8_t* cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); + const uint8_t* cache = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); if (!cache) { pr->println(F("dmpSector failed")); return; @@ -553,7 +553,7 @@ void ExFatPartition::printUpcase(print_t* pr) { uint32_t sector; uint32_t size = 0; uint32_t checksum = 0; - DirUpcase_t* dir; + const DirUpcase_t* dir; sector = clusterStartSector(m_rootDirectoryCluster); upcase = dataCachePrepare(sector, FsCache::CACHE_FOR_READ); dir = reinterpret_cast(upcase); @@ -601,7 +601,7 @@ bool ExFatPartition::printVolInfo(print_t* pr) { pr->println(F("read mbr failed")); return false; } - MbrSector_t* mbr = reinterpret_cast(cache); + const MbrSector_t* mbr = reinterpret_cast(cache); printMbr(pr, mbr); uint32_t volStart = getLe32(mbr->part->relativeSectors); uint32_t volSize = getLe32(mbr->part->totalSectors); diff --git a/src/ExFatLib/ExFatFile.cpp b/src/ExFatLib/ExFatFile.cpp index 1301d4c0..3d1f1325 100644 --- a/src/ExFatLib/ExFatFile.cpp +++ b/src/ExFatLib/ExFatFile.cpp @@ -98,7 +98,7 @@ void ExFatFile::fgetpos(fspos_t* pos) const { pos->cluster = m_curCluster; } //------------------------------------------------------------------------------ -int ExFatFile::fgets(char* str, int num, char* delim) { +int ExFatFile::fgets(char* str, int num, const char* delim) { char ch; int n = 0; int r = -1; @@ -136,7 +136,7 @@ void ExFatFile::fsetpos(const fspos_t* pos) { } //------------------------------------------------------------------------------ bool ExFatFile::getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { - DirFile_t* df = reinterpret_cast( + const DirFile_t* df = reinterpret_cast( m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); if (!df) { DBG_FAIL_MACRO; @@ -151,7 +151,7 @@ bool ExFatFile::getAccessDateTime(uint16_t* pdate, uint16_t* ptime) { } //------------------------------------------------------------------------------ bool ExFatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { - DirFile_t* df = reinterpret_cast( + const DirFile_t* df = reinterpret_cast( m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); if (!df) { DBG_FAIL_MACRO; @@ -166,7 +166,7 @@ bool ExFatFile::getCreateDateTime(uint16_t* pdate, uint16_t* ptime) { } //------------------------------------------------------------------------------ bool ExFatFile::getModifyDateTime(uint16_t* pdate, uint16_t* ptime) { - DirFile_t* df = reinterpret_cast( + const DirFile_t* df = reinterpret_cast( m_vol->dirCache(&m_dirPos, FsCache::CACHE_FOR_READ)); if (!df) { DBG_FAIL_MACRO; @@ -223,7 +223,6 @@ bool ExFatFile::open(ExFatFile* dirFile, const char* path, oflag_t oflag) { DBG_WARN_MACRO; goto fail; } - // tmpDir = *this; tmpDir.copy(this); dirFile = &tmpDir; close(); @@ -255,7 +254,6 @@ bool ExFatFile::openCwd() { DBG_FAIL_MACRO; goto fail; } - // *this = *ExFatVolume::cwv()->vwd(); this->copy(ExFatVolume::cwv()->vwd()); rewind(); return true; @@ -633,7 +631,7 @@ int ExFatFile::read(void* buf, size_t count) { DBG_FAIL_MACRO; goto fail; } - uint8_t* src = cache + sectorOffset; + const uint8_t* src = cache + sectorOffset; memcpy(dst, src, n); #if USE_MULTI_SECTOR_IO } else if (toRead >= 2 * m_vol->bytesPerSector()) { diff --git a/src/ExFatLib/ExFatFile.h b/src/ExFatLib/ExFatFile.h index 93c52510..c0fa9eaf 100644 --- a/src/ExFatLib/ExFatFile.h +++ b/src/ExFatLib/ExFatFile.h @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef ExFatFile_h -#define ExFatFile_h +#pragma once /** * \file * \brief ExFatFile class @@ -247,7 +246,7 @@ class ExFatFile { * If no data is read, fgets() returns zero for EOF or -1 if an error * occurred. */ - int fgets(char* str, int num, char* delim = nullptr); + int fgets(char* str, int num, const char* delim = nullptr); /** \return The total number of bytes in a file. */ uint64_t fileSize() const { return m_validLength; } /** \return Address of first sector or zero for empty file. */ @@ -898,4 +897,3 @@ class ExFile : public StreamFile { return tmpFile; } }; -#endif // ExFatFile_h diff --git a/src/ExFatLib/ExFatFilePrint.cpp b/src/ExFatLib/ExFatFilePrint.cpp index 38dd0f9e..ef4abc22 100644 --- a/src/ExFatLib/ExFatFilePrint.cpp +++ b/src/ExFatLib/ExFatFilePrint.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -143,7 +143,7 @@ size_t ExFatFile::printModifyDateTime(print_t* pr) { } //------------------------------------------------------------------------------ size_t ExFatFile::printName7(print_t* pr) { - DirName_t* dn; + const DirName_t* dn; size_t n = 0; uint8_t in; uint8_t buf[15]; @@ -174,7 +174,7 @@ size_t ExFatFile::printName7(print_t* pr) { } //------------------------------------------------------------------------------ size_t ExFatFile::printName8(print_t* pr) { - DirName_t* dn; + const DirName_t* dn; uint16_t hs = 0; uint32_t cp; size_t n = 0; @@ -211,7 +211,7 @@ size_t ExFatFile::printName8(print_t* pr) { DBG_FAIL_MACRO; goto fail; } - char* str = FsUtf::cpToMb(cp, buf, buf + sizeof(buf)); + const char* str = FsUtf::cpToMb(cp, buf, buf + sizeof(buf)); if (!str) { DBG_FAIL_MACRO; goto fail; diff --git a/src/ExFatLib/ExFatFileWrite.cpp b/src/ExFatLib/ExFatFileWrite.cpp index 91977e0d..92ae1680 100644 --- a/src/ExFatLib/ExFatFileWrite.cpp +++ b/src/ExFatLib/ExFatFileWrite.cpp @@ -179,7 +179,6 @@ bool ExFatFile::mkdir(ExFatFile* parent, const char* path, bool pFlag) { goto fail; } } - // tmpDir = *this; tmpDir.copy(this); parent = &tmpDir; close(); @@ -313,7 +312,6 @@ bool ExFatFile::rename(ExFatFile* dirFile, const char* newPath) { DBG_FAIL_MACRO; goto fail; } - // oldFile = *this; oldFile.copy(this); m_dirPos = file.m_dirPos; m_setCount = file.m_setCount; diff --git a/src/ExFatLib/ExFatFormatter.h b/src/ExFatLib/ExFatFormatter.h index 16ec8908..a2c9158a 100644 --- a/src/ExFatLib/ExFatFormatter.h +++ b/src/ExFatLib/ExFatFormatter.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef ExFatFormatter_h -#define ExFatFormatter_h +#pragma once #include "../common/FsBlockDevice.h" /** * \class ExFatFormatter @@ -32,7 +31,7 @@ class ExFatFormatter { public: /** Constructor. */ - ExFatFormatter() = default; + ExFatFormatter() = default; // cppcheck-suppress uninitMemberVar /** * Format an exFAT volume. * @@ -55,4 +54,3 @@ class ExFatFormatter { FsBlockDevice* m_dev; uint8_t* m_secBuf; }; -#endif // ExFatFormatter_h diff --git a/src/ExFatLib/ExFatName.cpp b/src/ExFatLib/ExFatName.cpp index 70df62d8..a0d384f5 100644 --- a/src/ExFatLib/ExFatName.cpp +++ b/src/ExFatLib/ExFatName.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -66,7 +66,7 @@ bool ExFatFile::cmpName(const DirName_t* dirName, ExName_t* fname) { } //------------------------------------------------------------------------------ size_t ExFatFile::getName7(char* name, size_t count) { - DirName_t* dn; + const DirName_t* dn; size_t n = 0; if (!isOpen()) { DBG_FAIL_MACRO; @@ -100,10 +100,10 @@ size_t ExFatFile::getName7(char* name, size_t count) { } //------------------------------------------------------------------------------ size_t ExFatFile::getName8(char* name, size_t count) { - char* end = name + count; + const char* end = name + count; char* str = name; char* ptr; - DirName_t* dn; + const DirName_t* dn; uint16_t hs = 0; uint32_t cp; if (!isOpen()) { diff --git a/src/ExFatLib/ExFatPartition.cpp b/src/ExFatLib/ExFatPartition.cpp index 2d22b11f..93545d63 100644 --- a/src/ExFatLib/ExFatPartition.cpp +++ b/src/ExFatLib/ExFatPartition.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -36,7 +36,7 @@ uint32_t ExFatPartition::bitmapFind(uint32_t cluster, uint32_t count) { uint32_t bgnAlloc = start; uint16_t sectorSize = 1 << m_bytesPerSectorShift; size_t i = (start >> 3) & (sectorSize - 1); - uint8_t* cache; + const uint8_t* cache; uint8_t mask = 1 << (start & 7); while (true) { uint32_t sector = @@ -136,7 +136,7 @@ uint32_t ExFatPartition::chainSize(uint32_t cluster) { return n; } //------------------------------------------------------------------------------ -uint8_t* ExFatPartition::dirCache(DirPos_t* pos, uint8_t options) { +uint8_t* ExFatPartition::dirCache(const DirPos_t* pos, uint8_t options) { uint32_t sector = clusterStartSector(pos->cluster); sector += (m_clusterMask & pos->position) >> m_bytesPerSectorShift; uint8_t* cache = dataCachePrepare(sector, options); @@ -164,7 +164,7 @@ int8_t ExFatPartition::dirSeek(DirPos_t* pos, uint32_t offset) { //------------------------------------------------------------------------------ // return -1 error, 0 EOC, 1 OK int8_t ExFatPartition::fatGet(uint32_t cluster, uint32_t* value) { - uint8_t* cache; + const uint8_t* cache; uint32_t next; uint32_t sector; @@ -240,7 +240,7 @@ int32_t ExFatPartition::freeClusterCount() { uint32_t nc = 0; uint32_t sector = m_clusterHeapStartSector; uint32_t usedCount = 0; - uint8_t* cache; + const uint8_t* cache; while (true) { cache = dataCachePrepare(sector++, FsCache::CACHE_FOR_READ); @@ -267,8 +267,8 @@ int32_t ExFatPartition::freeClusterCount() { //------------------------------------------------------------------------------ bool ExFatPartition::init(FsBlockDevice* dev, uint8_t part, uint32_t volStart) { pbs_t* pbs; - BpbExFat_t* bpb; - MbrSector_t* mbr; + const BpbExFat_t* bpb; + const MbrSector_t* mbr; m_fatType = 0; m_blockDev = dev; cacheInit(m_blockDev); @@ -285,7 +285,7 @@ bool ExFatPartition::init(FsBlockDevice* dev, uint8_t part, uint32_t volStart) { DBG_FAIL_MACRO; goto fail; } - MbrPart_t* mp = mbr->part + part - 1; + const MbrPart_t* mp = mbr->part + part - 1; if (mp->type == 0 || (mp->boot != 0 && mp->boot != 0X80)) { DBG_FAIL_MACRO; goto fail; diff --git a/src/ExFatLib/ExFatPartition.h b/src/ExFatLib/ExFatPartition.h index b7ecf757..227efb31 100644 --- a/src/ExFatLib/ExFatPartition.h +++ b/src/ExFatLib/ExFatPartition.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef ExFatPartition_h -#define ExFatPartition_h +#pragma once /** * \file * \brief ExFatPartition include file. @@ -60,7 +59,7 @@ struct DirPos_t { */ class ExFatPartition { public: - ExFatPartition() = default; + ExFatPartition() = default; // cppcheck-suppress uninitMemberVar /** \return the number of bytes in a cluster. */ uint32_t bytesPerCluster() const { return m_bytesPerCluster; } /** \return the power of two for bytesPerCluster. */ @@ -177,7 +176,7 @@ class ExFatPartition { return m_clusterHeapStartSector + ((cluster - 2) << m_sectorsPerClusterShift); } - uint8_t* dirCache(DirPos_t* pos, uint8_t options); + uint8_t* dirCache(const DirPos_t* pos, uint8_t options); int8_t dirSeek(DirPos_t* pos, uint32_t offset); int8_t fatGet(uint32_t cluster, uint32_t* value); bool fatPut(uint32_t cluster, uint32_t value); @@ -224,4 +223,3 @@ class ExFatPartition { uint8_t m_fatType = 0; uint8_t m_sectorsPerClusterShift; }; -#endif // ExFatPartition_h diff --git a/src/ExFatLib/ExFatVolume.cpp b/src/ExFatLib/ExFatVolume.cpp index 10e7f9a3..897e714f 100644 --- a/src/ExFatLib/ExFatVolume.cpp +++ b/src/ExFatLib/ExFatVolume.cpp @@ -37,7 +37,6 @@ bool ExFatVolume::chdir(const char* path) { DBG_FAIL_MACRO; goto fail; } - // m_vwd = dir; m_vwd.copy(&dir); return true; diff --git a/src/FatLib/FatDbg.cpp b/src/FatLib/FatDbg.cpp index 47d46f92..7209e592 100644 --- a/src/FatLib/FatDbg.cpp +++ b/src/FatLib/FatDbg.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -25,7 +25,7 @@ #include "FatLib.h" #ifndef DOXYGEN_SHOULD_SKIP_THIS //------------------------------------------------------------------------------ -static uint16_t getLfnChar(DirLfn_t* ldir, uint8_t i) { +static uint16_t getLfnChar(const DirLfn_t* ldir, uint8_t i) { if (i < 5) { return getLe16(ldir->unicode1 + 2 * i); } else if (i < 11) { @@ -245,7 +245,7 @@ void FatPartition::dmpFat(print_t* pr, uint32_t start, uint32_t count) { uint32_t sector = m_fatStartSector + start; uint32_t cluster = nf * start; for (uint32_t i = 0; i < count; i++) { - uint8_t* pc = fatCachePrepare(sector + i, FsCache::CACHE_FOR_READ); + const uint8_t* pc = fatCachePrepare(sector + i, FsCache::CACHE_FOR_READ); if (!pc) { pr->println(F("cache read failed")); return; diff --git a/src/FatLib/FatFile.cpp b/src/FatLib/FatFile.cpp index d23efeed..8aa185bb 100644 --- a/src/FatLib/FatFile.cpp +++ b/src/FatLib/FatFile.cpp @@ -197,7 +197,7 @@ bool FatFile::createContiguous(FatFile* dirFile, const char* path, } //------------------------------------------------------------------------------ bool FatFile::dirEntry(DirFat_t* dst) { - DirFat_t* dir; + const DirFat_t* dir; // Make sure fields on device are correct. if (!sync()) { DBG_FAIL_MACRO; @@ -237,7 +237,7 @@ uint32_t FatFile::dirSize() { return 512UL * n; } //------------------------------------------------------------------------------ -int FatFile::fgets(char* str, int num, char* delim) { +int FatFile::fgets(char* str, int num, const char* delim) { char ch; int n = 0; int r = -1; @@ -354,7 +354,6 @@ bool FatFile::mkdir(FatFile* parent, const char* path, bool pFlag) { goto fail; } } - // tmpDir = *this; tmpDir.copy(this); parent = &tmpDir; close(); @@ -478,7 +477,6 @@ bool FatFile::open(FatFile* dirFile, const char* path, oflag_t oflag) { DBG_WARN_MACRO; goto fail; } - // tmpDir = *this; tmpDir.copy(this); dirFile = &tmpDir; close(); @@ -497,7 +495,7 @@ bool FatFile::open(uint16_t index, oflag_t oflag) { bool FatFile::open(FatFile* dirFile, uint16_t index, oflag_t oflag) { if (index) { // Find start of LFN. - DirLfn_t* ldir; + const DirLfn_t* ldir; uint8_t n = index < 20 ? index : 20; for (uint8_t i = 1; i <= n; i++) { ldir = reinterpret_cast(dirFile->cacheDir(index - i)); @@ -634,7 +632,6 @@ bool FatFile::openCwd() { DBG_FAIL_MACRO; goto fail; } - // *this = *FatVolume::cwv()->vwd(); this->copy(FatVolume::cwv()->vwd()); rewind(); return true; @@ -645,7 +642,7 @@ bool FatFile::openCwd() { //------------------------------------------------------------------------------ bool FatFile::openNext(FatFile* dirFile, oflag_t oflag) { uint8_t checksum = 0; - DirLfn_t* ldir; + const DirLfn_t* ldir; uint8_t lfnOrd = 0; uint16_t index; @@ -840,7 +837,7 @@ int FatFile::read(void* buf, size_t nbyte) { DBG_FAIL_MACRO; goto fail; } - uint8_t* src = pc + offset; + const uint8_t* src = pc + offset; memcpy(dst, src, n); #if USE_MULTI_SECTOR_IO } else if (toRead >= 2 * m_vol->bytesPerSector()) { @@ -968,7 +965,6 @@ bool FatFile::rename(FatFile* dirFile, const char* newPath) { } // sync() and cache directory entry sync(); - // oldFile = *this; oldFile.copy(this); dir = cacheDirEntry(FsCache::CACHE_FOR_READ); if (!dir) { @@ -1064,7 +1060,7 @@ bool FatFile::rmdir() { // make sure directory is empty while (1) { - DirFat_t* dir = readDirCache(true); + const DirFat_t* dir = readDirCache(true); if (!dir) { // EOF if no error. if (!getError()) { @@ -1108,7 +1104,7 @@ bool FatFile::rmRfStar() { // remember position index = m_curPosition / FS_DIR_SIZE; - DirFat_t* dir = readDirCache(); + const DirFat_t* dir = readDirCache(); if (!dir) { // At EOF if no error. if (!getError()) { diff --git a/src/FatLib/FatFile.h b/src/FatLib/FatFile.h index 62977559..1d42f39b 100644 --- a/src/FatLib/FatFile.h +++ b/src/FatLib/FatFile.h @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FatFile_h -#define FatFile_h +#pragma once /** * \file * \brief FatFile class @@ -330,7 +329,7 @@ class FatFile { * If no data is read, fgets() returns zero for EOF or -1 if an error * occurred. */ - int fgets(char* str, int num, char* delim = nullptr); + int fgets(char* str, int num, const char* delim = nullptr); /** \return The total number of bytes in a file. */ uint32_t fileSize() const { return m_fileSize; } /** \return first sector of file or zero for empty file. */ @@ -1030,7 +1029,7 @@ class FatFile { DirFat_t* cacheDirEntry(uint8_t action); bool cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd); bool createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd); - uint16_t getLfnChar(DirLfn_t* ldir, uint8_t i); + uint16_t getLfnChar(const DirLfn_t* ldir, uint8_t i); uint8_t lfnChecksum(const uint8_t* name) { uint8_t sum = 0; for (uint8_t i = 0; i < 11; i++) { @@ -1045,8 +1044,8 @@ class FatFile { bool parsePathName(const char* str, FatSfn_t* fname, const char** ptr); bool mkdir(FatFile* parent, FatName_t* fname); bool open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag); - bool open(FatFile* dirFile, FatSfn_t* fname, oflag_t oflag); - bool openSFN(FatSfn_t* fname); + bool open(FatFile* dirFile, const FatSfn_t* fname, oflag_t oflag); + bool openSFN(const FatSfn_t* fname); bool openCachedEntry(FatFile* dirFile, uint16_t cacheIndex, oflag_t oflag, uint8_t lfnOrd); DirFat_t* readDirCache(bool skipReadOk = false); @@ -1098,4 +1097,3 @@ class File32 : public StreamFile { return tmpFile; } }; -#endif // FatFile_h diff --git a/src/FatLib/FatFileLFN.cpp b/src/FatLib/FatFileLFN.cpp index 6844a573..049b99f3 100644 --- a/src/FatLib/FatFileLFN.cpp +++ b/src/FatLib/FatFileLFN.cpp @@ -54,10 +54,9 @@ static void putLfnChar(DirLfn_t* ldir, uint8_t i, uint16_t c) { } //============================================================================== bool FatFile::cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { - // FatFile dir = *this; FatFile dir; dir.copy(this); - DirLfn_t* ldir; + const DirLfn_t* ldir; fname->reset(); for (uint8_t order = 1; order <= lfnOrd; order++) { ldir = reinterpret_cast(dir.cacheDir(index - order)); @@ -94,7 +93,6 @@ bool FatFile::cmpName(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { } //------------------------------------------------------------------------------ bool FatFile::createLFN(uint16_t index, FatLfn_t* fname, uint8_t lfnOrd) { - // FatFile dir = *this; FatFile dir; dir.copy(this); DirLfn_t* ldir; @@ -220,7 +218,7 @@ bool FatFile::makeSFN(FatLfn_t* fname) { bool FatFile::makeUniqueSfn(FatLfn_t* fname) { const uint8_t FIRST_HASH_SEQ = 2; // min value is 2 uint8_t pos = fname->seqPos; - DirFat_t* dir; + const DirFat_t* dir; uint16_t hex = 0; DBG_HALT_IF(!(fname->flags & FNAME_FLAG_LOST_CHARS)); @@ -284,7 +282,7 @@ bool FatFile::open(FatFile* dirFile, FatLfn_t* fname, oflag_t oflag) { uint16_t freeTotal; uint16_t time; DirFat_t* dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; auto vol = dirFile->m_vol; if (!dirFile->isDir() || isOpen()) { diff --git a/src/FatLib/FatFilePrint.cpp b/src/FatLib/FatFilePrint.cpp index 34f65946..12a171db 100644 --- a/src/FatLib/FatFilePrint.cpp +++ b/src/FatLib/FatFilePrint.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/FatLib/FatFileSFN.cpp b/src/FatLib/FatFileSFN.cpp index 3a5f6934..2535854b 100644 --- a/src/FatLib/FatFileSFN.cpp +++ b/src/FatLib/FatFileSFN.cpp @@ -28,7 +28,7 @@ //------------------------------------------------------------------------------ // open with filename in fname #define SFN_OPEN_USES_CHKSUM 0 -bool FatFile::open(FatFile* dirFile, FatSfn_t* fname, oflag_t oflag) { +bool FatFile::open(FatFile* dirFile, const FatSfn_t* fname, oflag_t oflag) { uint16_t date; uint16_t time; uint8_t ms10; @@ -40,7 +40,7 @@ bool FatFile::open(FatFile* dirFile, FatSfn_t* fname, oflag_t oflag) { uint16_t emptyIndex = 0; uint16_t index = 0; DirFat_t* dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; dirFile->rewind(); while (true) { @@ -156,7 +156,6 @@ bool FatFile::openExistingSFN(const char* path) { if (*path == 0) { return openRoot(vol); } - // *this = *vol->vwd(); this->copy(vol->vwd()); do { if (!parsePathName(path, &fname, &path)) { @@ -174,9 +173,9 @@ bool FatFile::openExistingSFN(const char* path) { return false; } //------------------------------------------------------------------------------ -bool FatFile::openSFN(FatSfn_t* fname) { +bool FatFile::openSFN(const FatSfn_t* fname) { DirFat_t dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; auto vol = m_vol; uint8_t lfnOrd = 0; if (!isDir()) { diff --git a/src/FatLib/FatFormatter.cpp b/src/FatLib/FatFormatter.cpp index 5ea5b4be..0dc8eb09 100644 --- a/src/FatLib/FatFormatter.cpp +++ b/src/FatLib/FatFormatter.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/FatLib/FatFormatter.h b/src/FatLib/FatFormatter.h index d48a1fab..c34014e3 100644 --- a/src/FatLib/FatFormatter.h +++ b/src/FatLib/FatFormatter.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FatFormatter_h -#define FatFormatter_h +#pragma once #include "../common/FsBlockDevice.h" #include "../common/SysCall.h" /** @@ -33,7 +32,7 @@ class FatFormatter { public: /** Constructor. */ - FatFormatter() = default; + FatFormatter() = default; // cppcheck-suppress uninitMemberVar /** * Format a FAT volume. * @@ -65,4 +64,3 @@ class FatFormatter { uint8_t m_partType; uint8_t m_sectorsPerCluster; }; -#endif // FatFormatter_h diff --git a/src/FatLib/FatLib.h b/src/FatLib/FatLib.h index 057d4041..ab3dfe99 100644 --- a/src/FatLib/FatLib.h +++ b/src/FatLib/FatLib.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,6 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FatLib_h -#define FatLib_h +#pragma once #include "FatFormatter.h" #include "FatVolume.h" -#endif // FatLib_h diff --git a/src/FatLib/FatName.cpp b/src/FatLib/FatName.cpp index 4a4dbff2..317eabc7 100644 --- a/src/FatLib/FatName.cpp +++ b/src/FatLib/FatName.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -27,7 +27,7 @@ #include "../common/FsUtf.h" #include "FatLib.h" //------------------------------------------------------------------------------ -uint16_t FatFile::getLfnChar(DirLfn_t* ldir, uint8_t i) { +uint16_t FatFile::getLfnChar(const DirLfn_t* ldir, uint8_t i) { if (i < 5) { return getLe16(ldir->unicode1 + 2 * i); } else if (i < 11) { @@ -51,7 +51,7 @@ size_t FatFile::getName(char* name, size_t size) { //------------------------------------------------------------------------------ size_t FatFile::getName7(char* name, size_t size) { FatFile dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; size_t n = 0; if (!isOpen()) { DBG_FAIL_MACRO; @@ -97,11 +97,11 @@ size_t FatFile::getName7(char* name, size_t size) { } //------------------------------------------------------------------------------ size_t FatFile::getName8(char* name, size_t size) { - char* end = name + size; + const char* end = name + size; char* str = name; char* ptr; FatFile dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; uint16_t hs = 0; uint32_t cp; if (!isOpen()) { @@ -169,8 +169,8 @@ size_t FatFile::getSFN(char* name, size_t size) { char c; uint8_t j = 0; uint8_t lcBit = FAT_CASE_LC_BASE; - uint8_t* ptr; - DirFat_t* dir; + const uint8_t* ptr; + const DirFat_t* dir; if (!isOpen()) { DBG_FAIL_MACRO; goto fail; @@ -234,7 +234,7 @@ size_t FatFile::printName(print_t* pr) { //------------------------------------------------------------------------------ size_t FatFile::printName7(print_t* pr) { FatFile dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; size_t n = 0; uint8_t buf[13]; uint8_t i; @@ -280,12 +280,12 @@ size_t FatFile::printName7(print_t* pr) { //------------------------------------------------------------------------------ size_t FatFile::printName8(print_t* pr) { FatFile dir; - DirLfn_t* ldir; + const DirLfn_t* ldir; uint16_t hs = 0; uint32_t cp; size_t n = 0; char buf[5]; - char* end = buf + sizeof(buf); + const char* end = buf + sizeof(buf); if (!isOpen()) { DBG_FAIL_MACRO; goto fail; @@ -329,7 +329,7 @@ size_t FatFile::printName8(print_t* pr) { DBG_FAIL_MACRO; goto fail; } - char* str = FsUtf::cpToMb(cp, buf, end); + const char* str = FsUtf::cpToMb(cp, buf, end); if (!str) { DBG_FAIL_MACRO; goto fail; diff --git a/src/FatLib/FatPartition.cpp b/src/FatLib/FatPartition.cpp index f6d20437..090a5410 100644 --- a/src/FatLib/FatPartition.cpp +++ b/src/FatLib/FatPartition.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -157,7 +157,7 @@ bool FatPartition::allocContiguous(uint32_t count, uint32_t* firstCluster) { int8_t FatPartition::fatGet(uint32_t cluster, uint32_t* value) { uint32_t sector; uint32_t next; - uint8_t* pc; + const uint8_t* pc; // error if reserved cluster of beyond FAT if (cluster < 2 || cluster > m_lastCluster) { @@ -362,14 +362,14 @@ int32_t FatPartition::freeClusterCount() { n = todo; } if (fatType() == 16) { - uint16_t* p16 = reinterpret_cast(pc); + const uint16_t* p16 = reinterpret_cast(pc); for (uint16_t i = 0; i < n; i++) { if (p16[i] == 0) { free++; } } } else { - uint32_t* p32 = reinterpret_cast(pc); + const uint32_t* p32 = reinterpret_cast(pc); for (uint16_t i = 0; i < n; i++) { if (p32[i] == 0) { free++; @@ -395,8 +395,8 @@ bool FatPartition::init(FsBlockDevice* dev, uint8_t part, uint32_t volStart) { uint32_t totalSectors; m_blockDev = dev; pbs_t* pbs; - BpbFat32_t* bpb; - MbrSector_t* mbr; + const BpbFat32_t* bpb; + const MbrSector_t* mbr; uint8_t tmp; m_fatType = 0; m_allocSearchStart = 1; @@ -417,7 +417,7 @@ bool FatPartition::init(FsBlockDevice* dev, uint8_t part, uint32_t volStart) { DBG_FAIL_MACRO; goto fail; } - MbrPart_t* mp = mbr->part + part - 1; + const MbrPart_t* mp = mbr->part + part - 1; if (mp->type == 0 || (mp->boot != 0 && mp->boot != 0X80)) { DBG_FAIL_MACRO; goto fail; diff --git a/src/FatLib/FatPartition.h b/src/FatLib/FatPartition.h index 076573d0..f446710c 100644 --- a/src/FatLib/FatPartition.h +++ b/src/FatLib/FatPartition.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FatPartition_h -#define FatPartition_h +#pragma once /** * \file * \brief FatPartition class @@ -53,7 +52,7 @@ class FatPartition { public: /** Create an instance of FatPartition */ - FatPartition() = default; + FatPartition() = default; // cppcheck-suppress uninitMemberVar /** \return The shift count required to multiply by bytesPerCluster. */ uint8_t bytesPerClusterShift() const { @@ -235,4 +234,3 @@ class FatPartition { bool freeChain(uint32_t cluster); bool isEOC(uint32_t cluster) const { return cluster > m_lastCluster; } }; -#endif // FatPartition diff --git a/src/FatLib/FatVolume.cpp b/src/FatLib/FatVolume.cpp index 3003546a..cc054183 100644 --- a/src/FatLib/FatVolume.cpp +++ b/src/FatLib/FatVolume.cpp @@ -37,7 +37,6 @@ bool FatVolume::chdir(const char* path) { DBG_FAIL_MACRO; goto fail; } - // m_vwd = dir; m_vwd.copy(&dir); return true; diff --git a/src/FatLib/FatVolume.h b/src/FatLib/FatVolume.h index daf64c39..3cf34146 100644 --- a/src/FatLib/FatVolume.h +++ b/src/FatLib/FatVolume.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FatVolume_h -#define FatVolume_h +#pragma once #include "FatFile.h" /** * \file @@ -344,4 +343,3 @@ class FatVolume : public FatPartition { static FatVolume* m_cwv; FatFile m_vwd; }; -#endif // FatVolume_h diff --git a/src/FreeStack.cpp b/src/FreeStack.cpp index d90959e9..e99f55e6 100644 --- a/src/FreeStack.cpp +++ b/src/FreeStack.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -53,7 +53,7 @@ inline char* stackPointer() { const char FILL = 0x55; void FillStack() { char* p = stackBegin(); - char* top = stackPointer(); + const char* top = stackPointer(); while (p < top) { *p++ = FILL; } @@ -62,7 +62,7 @@ void FillStack() { // May fail if malloc or new is used. int UnusedStack() { char* h = stackBegin(); - char* top = stackPointer(); + const char* top = stackPointer(); int n; for (n = 0; (h + n) < top; n++) { diff --git a/src/FreeStack.h b/src/FreeStack.h index 60729fac..d165fc7e 100644 --- a/src/FreeStack.h +++ b/src/FreeStack.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FreeStack_h -#define FreeStack_h +#pragma once /** * \file * \brief FreeStack() function. @@ -41,7 +40,7 @@ extern char __bss_end; * \return The number of free bytes. */ inline int FreeStack() { - char* sp = reinterpret_cast(SP); + const char* sp = reinterpret_cast(SP); return __brkval ? sp - __brkval : sp - &__bss_end; } #elif defined(ARDUINO_ARCH_APOLLO3) @@ -87,4 +86,3 @@ int UnusedStack(); inline void FillStack() {} inline int UnusedStack() { return 0; } #endif // defined(HAS_UNUSED_STACK) -#endif // FreeStack_h diff --git a/src/FsLib/FsFile.h b/src/FsLib/FsFile.h index c8df331e..191016f8 100644 --- a/src/FsLib/FsFile.h +++ b/src/FsLib/FsFile.h @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FsFile_h -#define FsFile_h +#pragma once /** * \file * \brief FsBaseFile include file. @@ -39,7 +38,7 @@ class FsBaseFile { public: /** Create an instance. */ - FsBaseFile() = default; + FsBaseFile() = default; // cppcheck-suppress uninitMemberVar /** Create a file object and open it in the current working directory. * * \param[in] path A path for a file to be opened. @@ -47,6 +46,7 @@ class FsBaseFile { * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive * OR of open flags. see FatFile::open(FatFile*, const char*, uint8_t). */ + // cppcheck-suppress uninitMemberVar FsBaseFile(const char* path, oflag_t oflag) { open(path, oflag); } /** Copy from to this. @@ -105,7 +105,7 @@ class FsBaseFile { close(); } } -#else // DESTRUCTOR_CLOSES_FILE +#else // DESTRUCTOR_CLOSES_FILE ~FsBaseFile() = default; #endif // DESTRUCTOR_CLOSES_FILE @@ -345,10 +345,6 @@ class FsBaseFile { bool isDir() const { return m_fFile ? m_fFile->isDir() : m_xFile ? m_xFile->isDir() : false; } - /** This function reports if the current file is a directory or not. - * \return true if the file is a directory. - */ - bool isDirectory() const { return isDir(); } /** \return True if this is a normal file. */ bool isFile() const { return m_fFile ? m_fFile->isFile() : m_xFile ? m_xFile->isFile() : false; @@ -385,6 +381,12 @@ class FsBaseFile { : m_xFile ? m_xFile->isSubDir() : false; } + /** \return True if this is a System file else false. */ + bool isSystem() const { + return m_fFile ? m_fFile->isSystem() + : m_xFile ? m_xFile->isSystem() + : false; + } /** \return True file is writable. */ bool isWritable() const { return m_fFile ? m_fFile->isWritable() @@ -562,8 +564,6 @@ class FsBaseFile { * \return true for success or false for failure. */ bool openRoot(FsVolume* vol); - /** \return the current file position. */ - uint64_t position() const { return curPosition(); } /** Return the next available byte without consuming it. * * \return The byte if no error and not at eof else -1; @@ -759,10 +759,6 @@ class FsBaseFile { if (m_fFile) m_fFile->rewind(); if (m_xFile) m_xFile->rewind(); } - /** Rewind a file if it is a directory */ - void rewindDirectory() { - if (isDir()) rewind(); - } /** Remove a directory file. * * The directory file will be removed only if it is empty and is not the @@ -776,13 +772,6 @@ class FsBaseFile { * \return true for success or false for failure. */ bool rmdir(); - /** Seek to a new position in the file, which must be between - * 0 and the size of the file (inclusive). - * - * \param[in] pos the new file position. - * \return true for success or false for failure. - */ - bool seek(uint64_t pos) { return seekSet(pos); } /** Set the files position to current position + \a pos. See seekSet(). * \param[in] offset The new position in bytes from the current position. * \return true for success or false for failure. @@ -805,8 +794,6 @@ class FsBaseFile { : m_xFile ? m_xFile->seekSet(pos) : false; } - /** \return the file's size. */ - uint64_t size() const { return fileSize(); } /** The sync() call causes all modified data and directory fields * to be written to the storage device. * @@ -928,4 +915,3 @@ class FsFile : public StreamFile { return tmpFile; } }; -#endif // FsFile_h diff --git a/src/FsLib/FsFormatter.h b/src/FsLib/FsFormatter.h index 96266edd..45cd223c 100644 --- a/src/FsLib/FsFormatter.h +++ b/src/FsLib/FsFormatter.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/FsLib/FsLib.h b/src/FsLib/FsLib.h index 97eeb07d..d7865064 100644 --- a/src/FsLib/FsLib.h +++ b/src/FsLib/FsLib.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/FsLib/FsNew.h b/src/FsLib/FsNew.h index 1b19f865..2dd8f5da 100644 --- a/src/FsLib/FsNew.h +++ b/src/FsLib/FsNew.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FsNew_h -#define FsNew_h +#pragma once #include #include @@ -36,11 +35,10 @@ typedef uint32_t newalign_t; /** Dimension of aligned area. */ #define NEW_ALIGN_DIM(n) \ - (((size_t)(n) + sizeof(newalign_t) - 1U) / sizeof(newalign_t)) + ((static_cast(n) + sizeof(newalign_t) - 1U) / sizeof(newalign_t)) /** Dimension of aligned area for etype or ftype class. */ #define FS_ALIGN_DIM(etype, ftype) NEW_ALIGN_DIM(FS_SIZE(etype, ftype)) /** Custom new placement operator */ void* operator new(size_t size, newalign_t* ptr); -#endif // FsNew_h diff --git a/src/FsLib/FsVolume.cpp b/src/FsLib/FsVolume.cpp index a578ff30..8334e2a9 100644 --- a/src/FsLib/FsVolume.cpp +++ b/src/FsLib/FsVolume.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/FsLib/FsVolume.h b/src/FsLib/FsVolume.h index 7a386e0b..b2e86922 100644 --- a/src/FsLib/FsVolume.h +++ b/src/FsLib/FsVolume.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FsVolume_h -#define FsVolume_h +#pragma once /** * \file * \brief FsVolume include file. @@ -39,7 +38,7 @@ class FsFile; */ class FsVolume { public: - FsVolume() = default; + FsVolume() = default; // cppcheck-suppress uninitMemberVar ~FsVolume() { end(); } /** Get file's user settable attributes. @@ -399,4 +398,3 @@ class FsVolume { FatVolume* m_fVol = nullptr; ExFatVolume* m_xVol = nullptr; }; -#endif // FsVolume_h diff --git a/src/SdCard/CPPLINT.cfg b/src/SdCard/CPPLINT.cfg deleted file mode 100644 index 7c360c78..00000000 --- a/src/SdCard/CPPLINT.cfg +++ /dev/null @@ -1 +0,0 @@ -exclude_files=SdioTeensy.h \ No newline at end of file diff --git a/src/SdCard/Rp2040Sdio/CPPLINT.cfg b/src/SdCard/Rp2040Sdio/CPPLINT.cfg new file mode 100644 index 00000000..218ac2bf --- /dev/null +++ b/src/SdCard/Rp2040Sdio/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=PioSdioCard.pio.h diff --git a/src/SdCard/Rp2040Sdio/DbgLog.h b/src/SdCard/Rp2040Sdio/DbgLog.h new file mode 100644 index 00000000..3f2168ed --- /dev/null +++ b/src/SdCard/Rp2040Sdio/DbgLog.h @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2011-2024 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include "Printable.h" + +#define NUM64_HEX_A 'a' +#define ENABLE_DBG_MSG 1 +#define DBG_LOG_PORT Serial + +#if defined(DBG_FILE) +#elif defined(__FILE_NAME__) +#define DBG_FILE __FILE_NAME__ +#else +#define DBG_FILE __FILE__ +#endif + +#if ENABLE_DBG_MSG +#define DBG_MSG(...) \ + do { \ + logmsgln(F(DBG_FILE), ":", __LINE__, " ", ##__VA_ARGS__); \ + } while (0) +#else // ENABLE_DBG_MSG +#define DBG_MSG(...) \ + do { \ + } while (0) +#endif // ENABLE_DBG_MSG + +// Print binary with byte and nibble separators. +class Bin : public Printable { + // Bin not supported for 64-bits. + explicit Bin(int64_t, uint8_t = 0) {} + explicit Bin(uint64_t, uint8_t = 0) {} + + public: + template + explicit Bin(T n, uint8_t p = 0) : n_(n), p_(p) {} + size_t printTo(Print& pr) const { + auto n = n_; + uint8_t p = p_ > 8 * sizeof(n) ? 8 * sizeof(n) : p_; + char buf[10 * sizeof(n) + 1]; + char* end = buf + sizeof(buf); + char* str = end; + uint8_t i = 0; + do { + if (i && (i % 4) == 0) { + *--str = i % 8 ? '\'' : '|'; + } + *--str = n & 1 ? '1' : '0'; + n /= 2; + i++; + } while (n || i < p); + return pr.write(str, end - str); + } + uint32_t n_; + uint8_t p_; +}; + +// Print floating point with precision. +struct Dbl : public Printable { + Dbl(double n_, int p_) : n(n_), p(p_) {} + size_t printTo(Print& pr) const { return pr.print(n, p); } + double n; + int p; +}; + +// Print in hex format. +#if __cplusplus > 201700L +template +struct Hex : public Printable { + explicit Hex(T n_) : n(n_) {} + size_t printTo(Print& pr) const { return pr.print(n, HEX); } + T n; +}; +#else // __cplusplus > 201700L +class Hex : public Printable { + // No 64-bit support unless C++17 or better. + explicit Hex(int64_t) {} + explicit Hex(uint64_t) {} + + public: + template + explicit Hex(T n_) : n(n_) {} + size_t printTo(Print& pr) const { return pr.print(n, HEX); } + uint32_t n; +}; +#endif // __cplusplus > 201700L + +// For boards with no 64-bit print, lower case hex, alt binary or precision. +class Num : public Printable { + uint64_t n_; + uint8_t b_; + uint8_t p_; + char s_; + + public: + template + explicit Num(T n, uint8_t b = 0, uint8_t p = 0, char s = 0) : b_(b), p_(p) { + n_ = b == 10 && n < 0 ? -n : n; + s_ = b == 10 && n < 0 ? '-' : s; + } + size_t printTo(Print& pr) const { + char buf[8 * sizeof(uint64_t) + 1]; + char* end = buf + sizeof(buf); + char* str = end; + uint64_t n = n_; + uint8_t p = p_ > 8 * sizeof(n) ? 8 * sizeof(n) : p_; + uint8_t base = b_ < 2 || b_ > 16 ? 10 : b_; + uint8_t i = 0; + do { + uint8_t d = n % base; + *--str = d < 10 ? d + '0' : d + NUM64_HEX_A - 10; + n /= base; + i++; + } while (n || i < p); + if (s_) { + *--str = s_; + } + return pr.write(str, end - str); + } +}; + +template +size_t logmsg(T arg) { + return DBG_LOG_PORT.print(arg); +} +size_t logmsg(bool b) { return logmsg(b ? F("true") : F("false")); } + +inline size_t logmsg() { return 0; } + +template +size_t logmsg(T var1, Types... var2) { + size_t n = logmsg(var1); + return n += logmsg(var2...); +} + +template +size_t logmsgln(Types... params) { + size_t n = logmsg(params...); + return n + logmsg("\r\n"); +} diff --git a/src/SdCard/Rp2040Sdio/PioDbgInfo.h b/src/SdCard/Rp2040Sdio/PioDbgInfo.h new file mode 100644 index 00000000..5ec5fa55 --- /dev/null +++ b/src/SdCard/Rp2040Sdio/PioDbgInfo.h @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2011-2024 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +#include + +#include "DbgLog.h" +//------------------------------------------------------------------------------ +static inline void gpioStatus(uint gpio) { + logmsgln("gpio", gpio, " drive: ", gpio_get_drive_strength(gpio)); + // logmsgln("gpio", gpio, + // " drive: ", static_cast(gpio_get_drive_strength(gpio))); + logmsgln("gpio", gpio, " slew: ", gpio_get_slew_rate(gpio)); + logmsgln("gpio", gpio, " hyst: ", gpio_is_input_hysteresis_enabled(gpio)); + logmsgln("gpio", gpio, " pull: ", gpio_is_pulled_up(gpio)); +} +//------------------------------------------------------------------------------ +static inline void pioRegs(PIO pio) { + logmsgln("ctrl: 0b", Bin(pio->ctrl)); + logmsgln("fstat: 0b", Bin(pio->fstat)); + logmsgln("fdebug: 0b", Bin(pio->fdebug)); + logmsgln("flevel: 0b", Bin(pio->flevel)); + logmsgln("padout: 0b", Bin(pio->dbg_padout)); + logmsgln("padoe: 0b", Bin(pio->dbg_padoe)); + logmsgln("cfginfo: 0x", Hex(pio->dbg_cfginfo)); + logmsgln("sync_bypass: 0b", Bin(pio->input_sync_bypass)); +} +//------------------------------------------------------------------------------ +static inline void pioSmRegs(PIO pio, uint sm) { + logmsgln("sm", sm, " clkdiv: 0x", Hex(pio->sm[sm].clkdiv)); + logmsgln("sm", sm, " execctrl: 0x", Hex(pio->sm[sm].execctrl)); + logmsgln("sm", sm, " shiftctrl: 0x", Hex(pio->sm[sm].shiftctrl)); + logmsgln("sm", sm, " addr: 0x", Hex(pio->sm[sm].addr)); + logmsgln("sm", sm, " pinctrl: 0x", Hex(pio->sm[sm].pinctrl)); +} diff --git a/src/SdCard/Rp2040Sdio/PioSdioCard.cpp b/src/SdCard/Rp2040Sdio/PioSdioCard.cpp new file mode 100644 index 00000000..153e7132 --- /dev/null +++ b/src/SdCard/Rp2040Sdio/PioSdioCard.cpp @@ -0,0 +1,954 @@ +/** + * Copyright (c) 2011-2024 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifdef ARDUINO_ARCH_RP2040 +#define DEBUG_FILE "PioSdioCard.cpp" +#include "../SdCardInfo.h" +#include "../SdioCard.h" +#include "PioDbgInfo.h" +#include "PioSdioCard.pio.h" +//------------------------------------------------------------------------------ +// Do not enable - not implemented +#define HIGH_SPEED_MODE 0 // non-zero to debug High Speed Mode +// USE_DEBUG_MODE 0 - no debug, 1 - print message, 2 - Use scope/analyzer. +#define USE_DEBUG_MODE 0 + +const uint PIO_CLK_DIV_INIT = 2; + +const uint PIO_CLK_DIV_RUN = 1; + +const uint PIN_SDIO_UNDEFINED = 31u; + +const uint DAT_FIFO_DEPTH = 8; +const uint CMD0_RETRIES = 10; +const uint CMD8_RETRIES = 3; + +//============================================================================== +// Command definitions. +enum { RSP_R0 = 0, RSP_R1 = 1, RSP_R2 = 2, RSP_R3 = 3, RSP_R6 = 6, RSP_R7 = 7 }; + +class CmdRsp_t { + public: + CmdRsp_t(uint8_t idx_, uint8_t rsp_) : idx(idx_), rsp(rsp_) {} + uint8_t idx; + uint8_t rsp; +}; + +static const CmdRsp_t CMD0_R0(CMD0, RSP_R0); +static const CmdRsp_t CMD2_R2(CMD2, RSP_R2); +static const CmdRsp_t CMD3_R6(CMD3, RSP_R6); +static const CmdRsp_t CMD6_R1(CMD6, RSP_R1); +static const CmdRsp_t CMD7_R1(CMD7, RSP_R1); +static const CmdRsp_t CMD8_R7(CMD8, RSP_R7); +static const CmdRsp_t CMD9_R2(CMD9, RSP_R2); +static const CmdRsp_t CMD10_R2(CMD10, RSP_R2); +static const CmdRsp_t CMD12_R1(CMD12, RSP_R1); +static const CmdRsp_t CMD13_R1(CMD13, RSP_R1); +static const CmdRsp_t CMD18_R1(CMD18, RSP_R1); +static const CmdRsp_t CMD25_R1(CMD25, RSP_R1); +static const CmdRsp_t CMD32_R1(CMD32, RSP_R1); +static const CmdRsp_t CMD33_R1(CMD33, RSP_R1); +static const CmdRsp_t CMD38_R1(CMD38, RSP_R1); +static const CmdRsp_t CMD55_R1(CMD55, RSP_R1); +static const CmdRsp_t ACMD6_R1(ACMD6, RSP_R1); +static const CmdRsp_t ACMD13_R1(ACMD13, RSP_R1); +static const CmdRsp_t ACMD41_R3(ACMD41, RSP_R3); +static const CmdRsp_t ACMD51_R1(ACMD51, RSP_R1); +//============================================================================== +// Global variables. +static float g_clkDiv = 0; +static uint g_clkPin = PIN_SDIO_UNDEFINED; +static uint g_cmdPin = PIN_SDIO_UNDEFINED; +static uint g_dat0Pin = PIN_SDIO_UNDEFINED; +static uint g_cardRsp; +static uint g_errorCode; +static uint g_errorLine; +static bool g_highCapacity; +static bool g_initDone = false; +static uint g_ocr; +static bool g_version2; +static PIO g_pio = nullptr; +static int g_sm0 = -1; +static int g_sm1 = -1; +static int g_cmdRspOffset = -1; +static pio_sm_config g_cmdConfig; +static int g_rdDataOffset = -1; +static pio_sm_config g_rdDataConfig; +static int g_rdClkOffset = -1; +static pio_sm_config g_rdClkConfig; +static int g_wrDataOffset = -1; +static pio_sm_config g_wrDataConfig; +static int g_wrRespOffset = -1; +static pio_sm_config g_wrRespConfig; +static uint g_rca; +static cid_t g_cid; +static csd_t g_csd; +static scr_t g_scr; +static sds_t g_sds; +//============================================================================== +class Timeout { + public: + explicit Timeout(uint ms) : _usStart(0), _usTimeout(1000 * ms) {} + bool timedOut() { + if (_usStart) { + return (usSinceBoot() - _usStart) > _usTimeout; + } + _usStart = usSinceBoot(); + return false; + } + uint32_t usSinceBoot() { return to_us_since_boot(get_absolute_time()); } + uint32_t _usStart; + uint32_t _usTimeout; +}; +//============================================================================== +// Error function and macro. +#define PIO_ERROR_ADD_PROGRAM 99 +#if USE_DEBUG_MODE +#define SDIO_FAIL() logmsgln(DEBUG_FILE, '.', __LINE__, F("SDIO_FAIL")) +#define sdError(code) setSdErrorCode(code, __LINE__, #code) +static void setSdErrorCode(uint8_t code, uint32_t line, const char* txt) { + g_errorCode = code; + g_errorLine = line; + logmsgln(F(DEBUG_FILE), '.', line, ' ', txt); +} +#else // USE_DEBUG_MODE +#define SDIO_FAIL() +#define sdError(code) setSdErrorCode(code, __LINE__) +static inline void setSdErrorCode(uint8_t code, uint32_t line) { + g_errorCode = code; + g_errorLine = line; +} +#endif // USE_DEBUG_MODE +//============================================================================== +// CRC functions. +//------------------------------------------------------------------------------ +// See this library's extras folder. +static const uint8_t crc7_table[256] = { + 0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, // 00 - 07 + 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee, // 08 - 0f + 0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, // 10 - 17 + 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc, // 18 - 1f + 0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, // 20 - 27 + 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a, // 28 - 2f + 0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, // 30 - 37 + 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8, // 38 - 3f + 0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, // 40 - 47 + 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26, // 48 - 4f + 0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, // 50 - 57 + 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14, // 58 - 5f + 0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, // 60 - 67 + 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42, // 68 - 6f + 0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, // 70 - 77 + 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70, // 78 - 7f + 0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, // 80 - 87 + 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c, // 88 - 8f + 0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, // 90 - 97 + 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e, // 98 - 9f + 0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, // a0 - a7 + 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08, // a8 - af + 0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, // b0 - b7 + 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a, // b8 - bf + 0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, // c0 - c7 + 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4, // c8 - cf + 0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, // d0 - d7 + 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96, // d8 - df + 0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, // e0 - e7 + 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0, // e8 - ef + 0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, // f0 - f7 + 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2 // f8 - ff +}; +//------------------------------------------------------------------------------ +inline static uint8_t CRC7(const uint8_t* data, uint8_t n) { + uint8_t crc = 0; + for (uint8_t i = 0; i < n; i++) { + crc = crc7_table[crc ^ data[i]]; + } + return crc | 1; +} +//------------------------------------------------------------------------------ +// Modified from sdio_crc16_4bit_checksum() in +// https://github.com/ZuluSCSI/ZuluSCSI-firmware +// +static inline __attribute__((always_inline)) uint64_t crc16(uint64_t crc, + uint32_t data_in) { + // Shift out 8 bits for each line + uint32_t data_out = crc >> 32; + crc <<= 32; + + // XOR outgoing data to itself with 4 bit delay + data_out ^= (data_out >> 16); + + // XOR incoming data to outgoing data with 4 bit delay + data_out ^= (data_in >> 16); + + // XOR outgoing and incoming data to accumulator at each tap + uint64_t xorred = data_out ^ data_in; + crc ^= xorred; + crc ^= xorred << (5 * 4); + crc ^= xorred << (12 * 4); + return crc; +} +//============================================================================== +static void pioConfig(float clkDiv) { + g_cmdConfig = + pio_cmd_rsp_program_config(g_cmdRspOffset, g_cmdPin, g_clkPin, clkDiv); + g_rdClkConfig = + pio_rd_clk_program_config(g_rdClkOffset, g_dat0Pin, g_clkPin, clkDiv); + g_rdDataConfig = + pio_rd_data_program_config(g_rdDataOffset, g_dat0Pin, clkDiv); + g_wrDataConfig = + pio_wr_data_program_config(g_wrDataOffset, g_dat0Pin, g_clkPin, clkDiv); + g_wrRespConfig = + pio_wr_resp_program_config(g_wrRespOffset, g_dat0Pin, g_clkPin, clkDiv); +} +//------------------------------------------------------------------------------ +static void pioEnd() { + if (!g_pio) { + return; + } + if (g_sm0 >= 0) { + pio_sm_unclaim(g_pio, g_sm0); + g_sm0 = -1; + } + if (g_sm1 >= 0) { + pio_sm_unclaim(g_pio, g_sm1); + g_sm1 = -1; + } + if (g_cmdRspOffset >= 0) { + pio_remove_program(g_pio, &cmd_rsp_program, g_cmdRspOffset); + g_cmdRspOffset = -1; + } + if (g_rdClkOffset >= 0) { + pio_remove_program(g_pio, &rd_clk_program, g_rdClkOffset); + g_rdClkOffset = -1; + } + if (g_rdDataOffset >= 0) { + pio_remove_program(g_pio, &rd_data_program, g_rdDataOffset); + g_rdDataOffset = -1; + } + if (g_wrDataOffset >= 0) { + pio_remove_program(g_pio, &wr_data_program, g_wrDataOffset); + g_wrDataOffset = -1; + } + if (g_wrRespOffset >= 0) { + pio_remove_program(g_pio, &wr_resp_program, g_wrRespOffset); + g_wrRespOffset = -1; + } +} +//------------------------------------------------------------------------------ +static bool pioInit() { + uint pin[] = {g_clkPin, g_cmdPin, g_dat0Pin, + g_dat0Pin + 1, g_dat0Pin + 2, g_dat0Pin + 3}; + if (g_wrRespOffset < 0) { + uint16_t patched_inst[rd_data_program.length]; // NOLINT + struct pio_program tmp_program; + tmp_program.instructions = nullptr; + tmp_program.length = cmd_rsp_program.length + rd_data_program.length + + rd_clk_program.length + wr_data_program.length + + wr_resp_program.length; + tmp_program.origin = -1; + if (pio_can_add_program(pio0, &tmp_program)) { + g_pio = pio0; + } else if (pio_can_add_program(pio1, &tmp_program)) { + g_pio = pio1; +#if NUM_PIOS > 2 + } else if (pio_can_add_program(pio2, &tmp_program)) { + g_pio = pio2; +#endif + } else { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + g_sm0 = pio_claim_unused_sm(g_pio, false); + if (g_sm0 < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + g_sm1 = pio_claim_unused_sm(g_pio, false); + if (g_sm0 < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + g_cmdRspOffset = pio_add_program(g_pio, &cmd_rsp_program); + if (g_cmdRspOffset < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + rd_data_patch_program(&tmp_program, patched_inst, g_clkPin); + g_rdDataOffset = pio_add_program(g_pio, &tmp_program); + if (g_rdDataOffset < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + g_rdClkOffset = pio_add_program(g_pio, &rd_clk_program); + if (g_rdClkOffset < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + g_wrDataOffset = pio_add_program(g_pio, &wr_data_program); + if (g_wrDataOffset < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + g_wrRespOffset = pio_add_program(g_pio, &wr_resp_program); + if (g_wrRespOffset < 0) { + sdError(PIO_ERROR_ADD_PROGRAM); + goto fail; + } + } + for (uint i = 0; i < 6U; i++) { + gpio_pull_up(pin[i]); + } + gpio_set_drive_strength(g_clkPin, GPIO_DRIVE_STRENGTH_8MA); + gpio_set_slew_rate(g_clkPin, GPIO_SLEW_RATE_FAST); + for (uint i = 0; i < 6U; i++) { + pio_gpio_init(g_pio, pin[i]); + } + + g_pio->input_sync_bypass |= + (1 << g_clkPin) | (1 << g_cmdPin) | (0XF << g_dat0Pin); + pio_sm_set_consecutive_pindirs(g_pio, g_sm0, g_clkPin, 1, true); + pio_sm_set_consecutive_pindirs(g_pio, g_sm0, g_cmdPin, 1, true); + pio_sm_set_consecutive_pindirs(g_pio, g_sm0, g_dat0Pin, 4, false); + return true; + +fail: + pioEnd(); + return false; +} +//------------------------------------------------------------------------------ +static bool cardCmd(CmdRsp_t cmd, uint32_t arg, void* rsp = nullptr) { + uint8_t buf[6]; + uint nRsp = cmd.rsp == RSP_R0 ? 0 : cmd.rsp == RSP_R2 ? 17 : 6; + io_ro_8* rxFifo = reinterpret_cast(&g_pio->rxf[g_sm0]); + io_wo_8* txFifo = reinterpret_cast(&g_pio->txf[g_sm0]); + pio_sm_set_enabled(g_pio, g_sm1, false); + pio_sm_init(g_pio, g_sm0, g_cmdRspOffset, &g_cmdConfig); + pio_sm_exec(g_pio, g_sm0, pio_encode_set(pio_pindirs, 1)); + *txFifo = 55; + pio_sm_exec(g_pio, g_sm0, pio_encode_out(pio_x, 8)); + *txFifo = nRsp ? 8 * nRsp - 1 : 0; + pio_sm_exec(g_pio, g_sm0, pio_encode_out(pio_y, 8)); + pio_sm_set_enabled(g_pio, g_sm0, true); + uint n = 0; + buf[n++] = (uint8_t)(cmd.idx | 0x40); + buf[n++] = (uint8_t)(arg >> 24U); + buf[n++] = (uint8_t)(arg >> 16U); + buf[n++] = (uint8_t)(arg >> 8U); + buf[n++] = (uint8_t)arg; + buf[n++] = CRC7(buf, 5); + *txFifo = 0XFF; + for (uint i = 0; i < n; i++) { + while (pio_sm_is_tx_fifo_full(g_pio, g_sm0)) { + } + *txFifo = buf[i]; + } + + Timeout timeout(SD_CMD_TIMEOUT); + if (!nRsp) { + uint32_t fdebug_tx_stall = 1u << (PIO_FDEBUG_TXSTALL_LSB + g_sm0); + g_pio->fdebug = fdebug_tx_stall; + while (!(g_pio->fdebug & fdebug_tx_stall)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_CMD0); + goto fail; + } + } + goto done; + } + uint8_t rtn[20]; + + for (uint i = 0; i < nRsp; i++) { + while (pio_sm_is_rx_fifo_empty(g_pio, g_sm0)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + rtn[i] = *rxFifo; + } + if (cmd.rsp == RSP_R3) { + if (rtn[0] != 0X3F || rtn[5] != 0XFF) { + sdError(SD_CARD_ERROR_ACMD41); + goto fail; + } + } else { + uint8_t crc; + if (cmd.rsp == RSP_R2) { + crc = CRC7(rtn + 1, nRsp - 2); + } else { + crc = CRC7(rtn, nRsp - 1); + } + if (rtn[nRsp - 1] != crc) { +#if USE_DEBUG_MODE + Serial.printf("CHK: %02X, CRC: %02X\n", rtn[nRsp - 1], crc); + for (uint i = 0; i < nRsp; i++) { + Serial.printf(" %02X", rtn[i]); + } + Serial.println(); +#endif // USE_DEBUG_MODE + sdError(SD_CARD_ERROR_READ_CRC); + goto fail; + } + } + if (nRsp == 6) { + g_cardRsp = (rtn[1] << 24) | (rtn[2] << 16) | (rtn[3] << 8) | rtn[4]; + if (rsp) { + *reinterpret_cast(rsp) = g_cardRsp; + } + } else if (rsp && nRsp == 17) { + memcpy(rsp, rtn + 1, 16); + } + +done: + pio_sm_set_enabled(g_pio, g_sm0, false); + return true; + +fail: +#if USE_DEBUG_MODE + Serial.printf("CMD%d failed\n", cmd.idx); +#endif // USE_DEBUG_MODE + pio_sm_set_enabled(g_pio, g_sm0, false); + return false; +} +//------------------------------------------------------------------------------ +static bool cardAcmd(uint32_t rca, CmdRsp_t cmdRsp, uint32_t arg) { + return cardCmd(CMD55_R1, rca) && cardCmd(cmdRsp, arg); +} +//------------------------------------------------------------------------------ +static bool __time_critical_func(readDat)(void* dst, uint n8) { + uint32_t buf[128]; + uint n32 = n8 / 4; + uint nr = n32 + 2; + const uint mask = (1ul << g_sm0) | (1ul << g_sm1); + io_wo_8* txFifo = reinterpret_cast(&g_pio->txf[g_sm1]); + pio_sm_init(g_pio, g_sm0, g_rdDataOffset, &g_rdDataConfig); + pio_sm_init(g_pio, g_sm1, g_rdClkOffset, &g_rdClkConfig); + pio_set_sm_mask_enabled(g_pio, mask, true); + + uint nf = nr < DAT_FIFO_DEPTH ? nr : DAT_FIFO_DEPTH; + for (uint it = 0; it < nf; it++) { + *txFifo = 0XFF; + } + io_ro_32* rxFifo = reinterpret_cast(&g_pio->rxf[g_sm0]); + uint32_t* dst32 = (uint)dst & 3 ? buf : reinterpret_cast(dst); + uint64_t crc = 0; + uint64_t chk = 0; + Timeout timeout(SD_READ_TIMEOUT); + uint ir = 0; + if (nf < nr) { + uint nb = nr - nf; + while (true) { + while (pio_sm_get_rx_fifo_level(g_pio, g_sm0) < 4) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + uint32_t tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + if (ir == nb) { + break; + } + tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + tmp = *rxFifo; + *txFifo = 0XFF; + dst32[ir++] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + } + } + for (; ir < nr; ir++) { + while (pio_sm_is_rx_fifo_empty(g_pio, g_sm0)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_TIMEOUT); + goto fail; + } + } + uint32_t tmp = *rxFifo; + if (ir < n32) { + dst32[ir] = __builtin_bswap32(tmp); + crc = crc16(crc, tmp); + } else { + chk <<= 32; + chk |= tmp; + } + } + if (crc != chk) { +#if USE_DEBUG_MODE + Serial.printf("crc: %llX\r\nchk: %llX\r\n", crc, chk); +#endif // USE_DEBUG_MODE + sdError(SD_CARD_ERROR_READ_CRC); + goto fail; + } + pio_set_sm_mask_enabled(g_pio, mask, false); + if (dst32 == buf) { + memcpy(dst, buf, n8); + } + return true; + +fail: + pio_set_sm_mask_enabled(g_pio, mask, false); + return false; +} +//------------------------------------------------------------------------------ +static bool __time_critical_func(writeDat)(const uint8_t* src) { + const uint32_t* src32; + uint32_t buf[128]; + if ((uint)src & 3) { + memcpy(buf, src, 512); + src32 = (const uint32_t*)buf; + } else { + src32 = (const uint32_t*)src; + } + uint32_t tmp; + io_wo_32* txFifo = reinterpret_cast(&g_pio->txf[g_sm0]); + io_ro_32* rxFifo = reinterpret_cast(&g_pio->rxf[g_sm1]); + uint8_t rsp; + uint mask = (1ul << g_sm0) | (1ul << g_sm1); + uint64_t crc = 0; + + Timeout timeout(SD_WRITE_TIMEOUT); + while (!gpio_get(g_dat0Pin)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_WRITE_TIMEOUT); + goto fail; + } + } + pio_sm_init(g_pio, g_sm0, g_wrDataOffset, &g_wrDataConfig); + pio_sm_init(g_pio, g_sm1, g_wrRespOffset, &g_wrRespConfig); + *txFifo = 1048; // 8 + 1024 + 16 + 1 - 1; + pio_sm_exec(g_pio, g_sm0, pio_encode_out(pio_x, 32)); + pio_sm_exec(g_pio, g_sm0, pio_encode_set(pio_pindirs, 0XF)); + *txFifo = 0xFFFFFFF0; + pio_set_sm_mask_enabled(g_pio, mask, true); + for (int i = 0; i < 128;) { + while (pio_sm_get_tx_fifo_level(g_pio, g_sm0) > 4) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_WRITE_FIFO); + goto fail; + } + } + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + tmp = __builtin_bswap32(src32[i++]); + crc = crc16(crc, tmp); + *txFifo = tmp; + } + while (pio_sm_get_tx_fifo_level(g_pio, g_sm0) > 5) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_WRITE_FIFO); + goto fail; + } + } + *txFifo = (uint32_t)(crc >> 32); + *txFifo = (uint32_t)crc; + *txFifo = 0xFFFFFFFF; + + while (pio_sm_is_rx_fifo_empty(g_pio, g_sm1)) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_READ_FIFO); + goto fail; + } + } + rsp = *rxFifo; + if ((rsp & 0X1F) != 0b101) { +#if USE_DEBUG_MODE + Serial.printf("wr rsp: %02X\n", rsp); +#endif // USE_DEBUG_MODE + sdError(SD_CARD_ERROR_WRITE_DATA); + goto fail; + } + return true; +fail: + pio_set_sm_mask_enabled(g_pio, mask, false); + return false; +} +//============================================================================== +// add to SdioCard class int the future. +// SdioCard::SdioCard() +// SdioCard::~SdioCard() +//------------------------------------------------------------------------------ +bool SdioCard::begin(SdioConfig sdioConfig) { + uint32_t arg; + m_curState = IDLE_STATE; + g_errorCode = SD_CARD_ERROR_NONE; + g_highCapacity = false; + g_initDone = false; + g_version2 = false; + g_clkDiv = PIO_CLK_DIV_INIT; + g_clkPin = sdioConfig.clkPin(); + g_cmdPin = sdioConfig.cmdPin(); + g_dat0Pin = sdioConfig.dat0Pin(); + pioInit(); + pioConfig(g_clkDiv); +#if USE_DEBUG_MODE == 2 + Serial.println(); + pioRegs(g_pio); + pioSmRegs(g_pio, g_sm0); + pioSmRegs(g_pio, g_sm1); + gpioStatus(g_clkPin); + gpioStatus(g_cmdPin); + while (Serial.read() >= 0) { + } + Serial.println("Logic Analyzer on"); + while (!Serial.available()) { + } +#endif // USE_DEBUG_MODE + Timeout timeout(SD_INIT_TIMEOUT); + for (uint i = 0; i < CMD0_RETRIES; i++) { + if (!cardCmd(CMD0_R0, 0)) { + sdError(SD_CARD_ERROR_CMD0); + goto fail; + } + } + // Try several times for case of reset delay. + for (uint32_t i = 0; i < CMD8_RETRIES; i++) { + if (cardCmd(CMD8_R7, 0X1AA)) { + if (g_cardRsp != 0X1AA) { + sdError(SD_CARD_ERROR_CMD8); + goto fail; + } + g_version2 = true; + break; + } + g_errorCode = SD_CARD_ERROR_NONE; + } + arg = g_version2 ? 0X40300000 : 0x00300000; + while (true) { + if (!cardAcmd(0, ACMD41_R3, arg)) { + sdError(SD_CARD_ERROR_ACMD41); + goto fail; + } + if (g_cardRsp & 0x80000000) { + break; + } + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_ACMD41); + goto fail; + } + } + g_ocr = g_cardRsp; + if (g_cardRsp & 0x40000000) { + // Is high capacity. + g_highCapacity = true; + } + if (!cardCmd(CMD2_R2, 0)) { + sdError(SD_CARD_ERROR_CMD2); + goto fail; + } + if (!cardCmd(CMD3_R6, 0)) { + sdError(SD_CARD_ERROR_CMD3); + goto fail; + } + g_rca = g_cardRsp & 0xFFFF0000; + if (!cardCmd(CMD9_R2, g_rca, &g_csd)) { + sdError(SD_CARD_ERROR_CMD9); + goto fail; + } + if (!cardCmd(CMD10_R2, g_rca, &g_cid)) { + sdError(SD_CARD_ERROR_CMD10); + goto fail; + } + if (!cardCmd(CMD7_R1, g_rca)) { + sdError(SD_CARD_ERROR_CMD7); + goto fail; + } + + if (!cardAcmd(g_rca, ACMD6_R1, 2)) { + sdError(SD_CARD_ERROR_ACMD6); + goto fail; + } + if (!cardAcmd(g_rca, ACMD51_R1, 0) || !readDat(&g_scr, sizeof(g_scr))) { + sdError(SD_CARD_ERROR_ACMD51); + goto fail; + } + if (!cardAcmd(g_rca, ACMD13_R1, 0) || !readDat(&g_sds, sizeof(g_sds))) { + sdError(SD_CARD_ERROR_ACMD13); + goto fail; + } +#if HIGH_SPEED_MODE + // Determine if High Speed mode is supported and set frequency. + // Check status[16] for error 0XF or status[16] for new mode 0X1. + uint8_t status[64]; + if (g_scr.sdSpec() > 0 && cardCMD6(0X00FFFFFF, status) && (2 & status[13]) && + cardCMD6(0X80FFFFF1, status) && (status[16] & 0XF) == 1) { + // kHzSdClk = 50000; + Serial.println("High Speed Mode"); + goto fail; + } else { + // kHzSdClk = 25000; + Serial.println("Default Speed Mode"); + } +#endif // HIGH_SPEED_MODE + g_clkDiv = PIO_CLK_DIV_RUN; + pioConfig(g_clkDiv); + g_initDone = true; + + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdioCard::cardCMD6(uint32_t arg, uint8_t* status) { + if (!cardCmd(CMD6_R1, arg) || !readDat(status, 64)) { + sdError(SD_CARD_ERROR_CMD6); + goto fail; + } + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +void SdioCard::end() { pioEnd(); } +//------------------------------------------------------------------------------ +bool SdioCard::erase(uint32_t firstSector, uint32_t lastSector) { + Timeout timeout(SD_ERASE_TIMEOUT); + if (!syncDevice()) { + SDIO_FAIL(); + goto fail; + } + // check for single sector erase + if (!g_csd.eraseSingleBlock()) { + // erase size mask + uint8_t m = g_csd.eraseSize() - 1; + if ((firstSector & m) != 0 || ((lastSector + 1) & m) != 0) { + // error card can't erase specified area + sdError(SD_CARD_ERROR_ERASE_SINGLE_SECTOR); + goto fail; + } + } + if (!g_highCapacity) { + firstSector <<= 9; + lastSector <<= 9; + } + if (!cardCmd(CMD32_R1, firstSector)) { + sdError(SD_CARD_ERROR_CMD32); + goto fail; + } + if (!cardCmd(CMD33_R1, lastSector)) { + sdError(SD_CARD_ERROR_CMD33); + goto fail; + } + if (!cardCmd(CMD38_R1, 0)) { + sdError(SD_CARD_ERROR_CMD38); + goto fail; + } + while (isBusy()) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_ERASE_TIMEOUT); + goto fail; + } + } + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +uint8_t SdioCard::errorCode() const { return g_errorCode; } +//------------------------------------------------------------------------------ +uint32_t SdioCard::errorData() const { return g_cardRsp; } +//------------------------------------------------------------------------------ +uint32_t SdioCard::errorLine() const { return g_errorLine; } +//------------------------------------------------------------------------------ +bool SdioCard::isBusy() { + return gpio_get(g_dat0Pin) ? false : !(status() & CARD_STATUS_READY_FOR_DATA); +} +//------------------------------------------------------------------------------ +bool SdioCard::readSector(uint32_t sector, uint8_t* dst) { + if (m_curState != READ_STATE || sector != m_curSector) { + if (!syncDevice()) { + SDIO_FAIL(); + goto fail; + } + if (!readStart(sector)) { + sdError(SD_CARD_ERROR_READ_START); + goto fail; + } + m_curSector = sector; + m_curState = READ_STATE; + } + if (!readData(dst)) { + SDIO_FAIL(); + goto fail; + } + m_curSector++; + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { + for (size_t i = 0; i < ns; i++) { + if (!readSector(sector + i, dst + i * 512UL)) { + SDIO_FAIL(); + goto fail; + } + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdioCard::readCID(cid_t* cid) { + memcpy(cid, &g_cid, sizeof(cid_t)); + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readCSD(csd_t* csd) { + memcpy(csd, &g_csd, sizeof(csd_t)); + return true; +} +//------------------------------------------------------------------------------ +bool __time_critical_func(SdioCard::readData)(uint8_t* dst) { + return readDat(dst, 512); +} +//------------------------------------------------------------------------------ +bool SdioCard::readOCR(uint32_t* ocr) { + *ocr = g_ocr; + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readSCR(scr_t* scr) { + memcpy(scr, &g_scr, sizeof(scr_t)); + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readSDS(sds_t* sds) { + memcpy(sds, &g_sds, sizeof(sds_t)); + return true; +} +//------------------------------------------------------------------------------ +bool SdioCard::readStart(uint32_t sector) { + uint arg = g_highCapacity ? sector : 512 * sector; + if (!cardCmd(CMD18_R1, arg)) { + sdError(SD_CARD_ERROR_CMD18); + goto fail; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdioCard::readStop() { return syncDevice(); } +//------------------------------------------------------------------------------ +uint32_t SdioCard::status() { + return cardCmd(CMD13_R1, g_rca) ? g_cardRsp : CARD_STATUS_ERROR; +} +//------------------------------------------------------------------------------ +uint32_t SdioCard::sectorCount() { + csd_t csd; + return readCSD(&csd) ? csd.capacity() : 0; +} +//------------------------------------------------------------------------------ +bool SdioCard::syncDevice() { + if (m_curState != IDLE_STATE) { + Timeout timeout(SD_INIT_TIMEOUT); + if (!cardCmd(CMD12_R1, 0)) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + while (isBusy()) { + if (timeout.timedOut()) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + } + m_curState = IDLE_STATE; + } + return true; + +fail: + return false; +} +//------------------------------------------------------------------------------ +uint8_t SdioCard::type() const { + return !g_initDone ? 0 + : !g_version2 ? SD_CARD_TYPE_SD1 + : !g_highCapacity ? SD_CARD_TYPE_SD2 + : SD_CARD_TYPE_SDHC; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeSector(uint32_t sector, const uint8_t* src) { + return writeSectors(sector, src, 1); +} +//------------------------------------------------------------------------------ +bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t ns) { + if (m_curState != WRITE_STATE || m_curSector != sector) { + if (!syncDevice()) { + sdError(SD_CARD_ERROR_CMD12); + goto fail; + } + if (!writeStart(sector)) { + sdError(SD_CARD_ERROR_WRITE_START); + goto fail; + } + m_curSector = sector; + m_curState = WRITE_STATE; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + sdError(SD_CARD_ERROR_WRITE_DATA); + goto fail; + } + } + m_curSector += ns; + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeData(const uint8_t* src) { return writeDat(src); } +//------------------------------------------------------------------------------ +bool SdioCard::writeStart(uint32_t sector) { + uint arg = g_highCapacity ? sector : 512 * sector; + if (!cardCmd(CMD25_R1, arg)) { + sdError(SD_CARD_ERROR_CMD25); + goto fail; + } + return true; +fail: + return false; +} +//------------------------------------------------------------------------------ +bool SdioCard::writeStop() { return syncDevice(); } +#endif // ARDUINO_ARCH_RP2040 diff --git a/src/SdCard/Rp2040Sdio/PioSdioCard.pio b/src/SdCard/Rp2040Sdio/PioSdioCard.pio new file mode 100644 index 00000000..b4d90007 --- /dev/null +++ b/src/SdCard/Rp2040Sdio/PioSdioCard.pio @@ -0,0 +1,175 @@ +; Copyright (c) 2011-2024 Bill Greiman +; This file is part of the SdFat library for SD memory cards. +; +; MIT License +; +; Permission is hereby granted, free of charge, to any person obtaining a +; copy of this software and associated documentation files (the "Software"), +; to deal in the Software without restriction, including without limitation +; the rights to use, copy, modify, merge, publish, distribute, sublicense, +; and/or sell copies of the Software, and to permit persons to whom the +; Software is furnished to do so, subject to the following conditions: +; +; The above copyright notice and this permission notice shall be included +; in all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +; OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +; DEALINGS IN THE SOFTWARE. +; +; Online assembler used to produce PioSdioCard.pio.h +; https://wokwi.com/tools/pioasm + +.define public SDIO_IRQ 7 + +.program cmd_rsp +.side_set 1 opt +.wrap_target +cmd_begin: +send_cmd: + out pins, 1 side 0 [1] + jmp X-- send_cmd side 1 [1] + + jmp !Y cmd_begin side 0 [1] + set pindirs, 0 side 1 [3] +wait_resp: + nop side 0 [3] + nop side 1 [2] + jmp PIN wait_resp + +read_resp: + in pins, 1 + push iffull block side 0 [2] + jmp Y-- read_resp side 1 [1] +.wrap + +% c-sdk { +static inline pio_sm_config pio_cmd_rsp_program_config(uint offset, uint cmd_pin, uint clk_pin, float clk_div) { + pio_sm_config c = cmd_rsp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, cmd_pin, 1); + sm_config_set_in_pins(&c, cmd_pin); + sm_config_set_set_pins(&c, cmd_pin, 1); + sm_config_set_jmp_pin(&c, cmd_pin); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} +.program rd_clk +.side_set 1 opt +wait_d0: + nop side 0 [3] + jmp PIN wait_d0 side 1 [3] + + irq SDIO_IRQ +.wrap_target + out null, 1 side 0 [1] ; Clock stops when txFifo is empty + nop side 1 [1] +.wrap + +% c-sdk { +static inline pio_sm_config pio_rd_clk_program_config(uint offset, uint d0_pin, uint clk_pin, float clk_div) { + pio_sm_config c = rd_clk_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, d0_pin); + sm_config_set_jmp_pin(&c, d0_pin); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} + +.program rd_data + wait 1 irq SDIO_IRQ +.wrap_target +public wait0: + wait 0 gpio 0 ; See rd_data_patch_program for CLK pin +public wait1: + wait 1 gpio 0 ; See rd_data_patch_program for CLK pin + in pins, 4 +.wrap + +% c-sdk { + +static inline void rd_data_patch_program(pio_program *prog, uint16_t* inst, uint clk_pin) { + *prog = rd_data_program; + prog->instructions = inst; + memcpy(inst, rd_data_program_instructions, sizeof(rd_data_program_instructions)); + inst[rd_data_offset_wait0] = pio_encode_wait_gpio(0, clk_pin); + inst[rd_data_offset_wait1] = pio_encode_wait_gpio(1, clk_pin); +} + +static inline pio_sm_config pio_rd_data_program_config(uint offset, uint data_pin, float clk_div) { + pio_sm_config c = rd_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} + + +; Data transmission program +; +; Before running this program, pindirs should be set as output +; and register X should be initialized with the number of nibbles +; to send minus 1 (typically 8 + 1024 + 16 + 1 - 1 = 1048) +; +; Words written to TX FIFO must be: +; - Word 0: start token 0xFFFFFFF0 +; - Word 1-128: transmitted data (512 bytes) +; - Word 129-130: CRC checksum +; - Word 131: end token 0xFFFFFFFF +.program wr_data +.side_set 1 opt +; out X, 32 +; set pindirs, 0XF +tx_loop: + out pins, 4 side 0 [1] + jmp X-- tx_loop side 1 [1] + irq SDIO_IRQ +.wrap_target + nop +.wrap +% c-sdk { + + +static inline pio_sm_config pio_wr_data_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, data_pin, 4); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_out_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} +.program wr_resp +.side_set 1 opt + wait 1 irq SDIO_IRQ + set pindirs, 0 [1] +.wrap_target + in pins, 1 side 1 [4] + push iffull noblock side 0 [4] +.wrap + +% c-sdk { +static inline pio_sm_config pio_wr_resp_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_resp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} +%} diff --git a/src/SdCard/Rp2040Sdio/PioSdioCard.pio.h b/src/SdCard/Rp2040Sdio/PioSdioCard.pio.h new file mode 100644 index 00000000..017e8ff0 --- /dev/null +++ b/src/SdCard/Rp2040Sdio/PioSdioCard.pio.h @@ -0,0 +1,241 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +#define SDIO_IRQ 7 + +// ------- // +// cmd_rsp // +// ------- // + +#define cmd_rsp_wrap_target 0 +#define cmd_rsp_wrap 9 + +static const uint16_t cmd_rsp_program_instructions[] = { + // .wrap_target + 0x7101, // 0: out pins, 1 side 0 [1] + 0x1940, // 1: jmp x--, 0 side 1 [1] + 0x1160, // 2: jmp !y, 0 side 0 [1] + 0xfb80, // 3: set pindirs, 0 side 1 [3] + 0xb342, // 4: nop side 0 [3] + 0xba42, // 5: nop side 1 [2] + 0x00c4, // 6: jmp pin, 4 + 0x4001, // 7: in pins, 1 + 0x9260, // 8: push iffull block side 0 [2] + 0x1987, // 9: jmp y--, 7 side 1 [1] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program cmd_rsp_program = { + .instructions = cmd_rsp_program_instructions, + .length = 10, + .origin = -1, +}; + +static inline pio_sm_config cmd_rsp_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + cmd_rsp_wrap_target, offset + cmd_rsp_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_cmd_rsp_program_config(uint offset, uint cmd_pin, uint clk_pin, float clk_div) { + pio_sm_config c = cmd_rsp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, cmd_pin, 1); + sm_config_set_in_pins(&c, cmd_pin); + sm_config_set_set_pins(&c, cmd_pin, 1); + sm_config_set_jmp_pin(&c, cmd_pin); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------ // +// rd_clk // +// ------ // + +#define rd_clk_wrap_target 3 +#define rd_clk_wrap 4 + +static const uint16_t rd_clk_program_instructions[] = { + 0xb342, // 0: nop side 0 [3] + 0x1bc0, // 1: jmp pin, 0 side 1 [3] + 0xc007, // 2: irq nowait 7 + // .wrap_target + 0x7161, // 3: out null, 1 side 0 [1] + 0xb942, // 4: nop side 1 [1] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program rd_clk_program = { + .instructions = rd_clk_program_instructions, + .length = 5, + .origin = -1, +}; + +static inline pio_sm_config rd_clk_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + rd_clk_wrap_target, offset + rd_clk_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_rd_clk_program_config(uint offset, uint d0_pin, uint clk_pin, float clk_div) { + pio_sm_config c = rd_clk_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, d0_pin); + sm_config_set_jmp_pin(&c, d0_pin); + sm_config_set_out_shift(&c, false, true, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------- // +// rd_data // +// ------- // + +#define rd_data_wrap_target 1 +#define rd_data_wrap 3 + +#define rd_data_offset_wait0 1u +#define rd_data_offset_wait1 2u + +static const uint16_t rd_data_program_instructions[] = { + 0x20c7, // 0: wait 1 irq, 7 + // .wrap_target + 0x2000, // 1: wait 0 gpio, 0 + 0x2080, // 2: wait 1 gpio, 0 + 0x4004, // 3: in pins, 4 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program rd_data_program = { + .instructions = rd_data_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config rd_data_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + rd_data_wrap_target, offset + rd_data_wrap); + return c; +} + +static inline void rd_data_patch_program(pio_program *prog, uint16_t* inst, uint clk_pin) { + *prog = rd_data_program; + prog->instructions = inst; + memcpy(inst, rd_data_program_instructions, sizeof(rd_data_program_instructions)); + inst[rd_data_offset_wait0] = pio_encode_wait_gpio(0, clk_pin); + inst[rd_data_offset_wait1] = pio_encode_wait_gpio(1, clk_pin); +} +static inline pio_sm_config pio_rd_data_program_config(uint offset, uint data_pin, float clk_div) { + pio_sm_config c = rd_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------- // +// wr_data // +// ------- // + +#define wr_data_wrap_target 3 +#define wr_data_wrap 3 + +static const uint16_t wr_data_program_instructions[] = { + 0x7104, // 0: out pins, 4 side 0 [1] + 0x1940, // 1: jmp x--, 0 side 1 [1] + 0xc007, // 2: irq nowait 7 + // .wrap_target + 0xa042, // 3: nop + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program wr_data_program = { + .instructions = wr_data_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config wr_data_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + wr_data_wrap_target, offset + wr_data_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_wr_data_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_data_program_get_default_config(offset); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_out_pins(&c, data_pin, 4); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_out_shift(&c, false, true, 32); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif + +// ------- // +// wr_resp // +// ------- // + +#define wr_resp_wrap_target 2 +#define wr_resp_wrap 3 + +static const uint16_t wr_resp_program_instructions[] = { + 0x20c7, // 0: wait 1 irq, 7 + 0xe180, // 1: set pindirs, 0 [1] + // .wrap_target + 0x5c01, // 2: in pins, 1 side 1 [4] + 0x9440, // 3: push iffull noblock side 0 [4] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program wr_resp_program = { + .instructions = wr_resp_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config wr_resp_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + wr_resp_wrap_target, offset + wr_resp_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline pio_sm_config pio_wr_resp_program_config(uint offset, uint data_pin, uint clk_pin, float clk_div) { + pio_sm_config c = wr_resp_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, clk_pin); + sm_config_set_in_pins(&c, data_pin); + sm_config_set_set_pins(&c, data_pin, 4); + sm_config_set_in_shift(&c, false, false, 8); + sm_config_set_clkdiv(&c, clk_div); + return c; +} + +#endif diff --git a/src/SdCard/Rp2040Sdio/Rp2040SdioConfig.h b/src/SdCard/Rp2040Sdio/Rp2040SdioConfig.h new file mode 100644 index 00000000..90f4f842 --- /dev/null +++ b/src/SdCard/Rp2040Sdio/Rp2040SdioConfig.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2011-2024 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#pragma once +/** + * \class SdioConfig + * \brief SDIO card configuration. + */ +class SdioConfig { + public: + /** + * SdioConfig constructor. + * \param[in] clkPin gpio pin for SDIO CLK. + * \param[in] cmdPin gpio pin for SDIO CMD. + * \param[in] dat0Pin gpio start pin for SDIO DAT[4]. + */ + SdioConfig(uint clkPin, uint cmdPin, uint dat0Pin) + : m_clkPin(clkPin), m_cmdPin(cmdPin), m_dat0Pin(dat0Pin) {} + /** \return gpio for SDIO CLK */ + uint clkPin() { return m_clkPin; } + /** \return gpio for SDIO CMD */ + uint cmdPin() { return m_cmdPin; } + /** \return gpio for SDIO DAT0 */ + uint dat0Pin() { return m_dat0Pin; } + + private: + SdioConfig() : m_clkPin(31u), m_cmdPin(31u), m_dat0Pin(31u) {} + const uint8_t m_clkPin; + const uint8_t m_cmdPin; + const uint8_t m_dat0Pin; +}; diff --git a/src/SdCard/SdCard.h b/src/SdCard/SdCard.h index fc795470..f4fc4a96 100644 --- a/src/SdCard/SdCard.h +++ b/src/SdCard/SdCard.h @@ -26,8 +26,7 @@ * \file * \brief Top level include for SPI and SDIO cards. */ -#ifndef SdCard_h -#define SdCard_h +#pragma once #include "SdSpiCard.h" #include "SdioCard.h" #if HAS_SDIO_CLASS @@ -91,4 +90,3 @@ class SdCardFactory { #endif // HAS_SDIO_CLASS SdSpiCard m_spiCard; }; -#endif // SdCard_h diff --git a/src/SdCard/SdSpiCard.cpp b/src/SdCard/SdSpiCard.cpp index e924336c..5dd06fbe 100644 --- a/src/SdCard/SdSpiCard.cpp +++ b/src/SdCard/SdSpiCard.cpp @@ -120,9 +120,9 @@ static uint16_t CRC_CCITT(const uint8_t* data, size_t n) { #endif // CRC_CCITT #endif // USE_SD_CRC //============================================================================== -// SharedSpiCard member functions +// SdSpiCard member functions //------------------------------------------------------------------------------ -bool SharedSpiCard::begin(SdSpiConfig spiConfig) { +bool SdSpiCard::begin(SdSpiConfig spiConfig) { uint8_t cardType; uint32_t arg; Timeout timeout; @@ -213,6 +213,9 @@ bool SharedSpiCard::begin(SdSpiConfig spiConfig) { spiStop(); spiSetSckSpeed(spiConfig.maxSck); m_type = cardType; +#if ENABLE_DEDICATED_SPI + m_dedicatedSpi = spiOptionDedicated(spiConfig.options); +#endif return true; fail: @@ -220,7 +223,7 @@ bool SharedSpiCard::begin(SdSpiConfig spiConfig) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::cardCMD6(uint32_t arg, uint8_t* status) { +bool SdSpiCard::cardCMD6(uint32_t arg, uint8_t* status) { if (cardCommand(CMD6, arg)) { error(SD_CARD_ERROR_CMD6); goto fail; @@ -237,7 +240,7 @@ bool SharedSpiCard::cardCMD6(uint32_t arg, uint8_t* status) { } //------------------------------------------------------------------------------ // send command and return error code. Return zero for OK -uint8_t SharedSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { +uint8_t SdSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { if (!syncDevice()) { return 0XFF; } @@ -267,7 +270,7 @@ uint8_t SharedSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { spiSend(cmd | 0x40); // send argument - uint8_t* pa = reinterpret_cast(&arg); + const uint8_t* pa = reinterpret_cast(&arg); for (int8_t i = 3; i >= 0; i--) { spiSend(pa[i]); } @@ -287,7 +290,7 @@ uint8_t SharedSpiCard::cardCommand(uint8_t cmd, uint32_t arg) { return m_status; } //------------------------------------------------------------------------------ -void SharedSpiCard::end() { +void SdSpiCard::end() { if (m_beginCalled) { syncDevice(); spiEnd(); @@ -295,7 +298,7 @@ void SharedSpiCard::end() { } } //------------------------------------------------------------------------------ -bool SharedSpiCard::erase(uint32_t firstSector, uint32_t lastSector) { +bool SdSpiCard::erase(uint32_t firstSector, uint32_t lastSector) { csd_t csd; if (!readCSD(&csd)) { goto fail; @@ -331,12 +334,12 @@ bool SharedSpiCard::erase(uint32_t firstSector, uint32_t lastSector) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::eraseSingleSectorEnable() { +bool SdSpiCard::eraseSingleSectorEnable() { csd_t csd; return readCSD(&csd) ? csd.eraseSingleBlock() : false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::isBusy() { +bool SdSpiCard::isBusy() { if (m_state == READ_STATE) { return false; } @@ -351,9 +354,9 @@ bool SharedSpiCard::isBusy() { return rtn; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readData(uint8_t* dst) { return readData(dst, 512); } +bool SdSpiCard::readData(uint8_t* dst) { return readData(dst, 512); } //------------------------------------------------------------------------------ -bool SharedSpiCard::readData(uint8_t* dst, size_t count) { +bool SdSpiCard::readData(uint8_t* dst, size_t count) { #if USE_SD_CRC uint16_t crc; #endif // USE_SD_CRC @@ -371,6 +374,8 @@ bool SharedSpiCard::readData(uint8_t* dst, size_t count) { goto fail; } // transfer data + // cppcheck wrong - Due can return non-zero. + // cppcheck-suppress knownConditionTrueFalse if ((m_status = spiReceive(dst, count))) { error(SD_CARD_ERROR_DMA); goto fail; @@ -395,7 +400,7 @@ bool SharedSpiCard::readData(uint8_t* dst, size_t count) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readOCR(uint32_t* ocr) { +bool SdSpiCard::readOCR(uint32_t* ocr) { uint8_t* p = reinterpret_cast(ocr); if (cardCommand(CMD58, 0)) { error(SD_CARD_ERROR_CMD58); @@ -417,7 +422,7 @@ bool SharedSpiCard::readOCR(uint32_t* ocr) { } //------------------------------------------------------------------------------ /** read CID or CSR register */ -bool SharedSpiCard::readRegister(uint8_t cmd, void* buf) { +bool SdSpiCard::readRegister(uint8_t cmd, void* buf) { uint8_t* dst = reinterpret_cast(buf); if (cardCommand(cmd, 0)) { error(SD_CARD_ERROR_READ_REG); @@ -434,7 +439,7 @@ bool SharedSpiCard::readRegister(uint8_t cmd, void* buf) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readSCR(scr_t* scr) { +bool SdSpiCard::readSCR(scr_t* scr) { uint8_t* dst = reinterpret_cast(scr); if (cardAcmd(ACMD51, 0)) { error(SD_CARD_ERROR_ACMD51); @@ -451,7 +456,10 @@ bool SharedSpiCard::readSCR(scr_t* scr) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readSector(uint32_t sector, uint8_t* dst) { +bool SdSpiCard::readSector(uint32_t sector, uint8_t* dst) { +#if ENABLE_DEDICATED_SPI + return readSectors(sector, dst, 1); +#else // use address if not SDHC card if (type() != SD_CARD_TYPE_SDHC) { sector <<= 9; @@ -469,9 +477,25 @@ bool SharedSpiCard::readSector(uint32_t sector, uint8_t* dst) { fail: spiStop(); return false; +#endif } //------------------------------------------------------------------------------ -bool SharedSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { +bool SdSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { +#if ENABLE_DEDICATED_SPI + if (sdState() != READ_STATE || sector != m_curSector) { + if (!readStart(sector)) { + goto fail; + } + m_curSector = sector; + } + for (size_t i = 0; i < ns; i++, dst += 512) { + if (!readData(dst)) { + goto fail; + } + } + m_curSector += ns; + return m_dedicatedSpi ? true : readStop(); +#else if (!readStart(sector)) { goto fail; } @@ -481,11 +505,12 @@ bool SharedSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { } } return readStop(); +#endif fail: return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readStart(uint32_t sector) { +bool SdSpiCard::readStart(uint32_t sector) { if (type() != SD_CARD_TYPE_SDHC) { sector <<= 9; } @@ -501,7 +526,7 @@ bool SharedSpiCard::readStart(uint32_t sector) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readSDS(sds_t* sds) { +bool SdSpiCard::readSDS(sds_t* sds) { uint8_t* dst = reinterpret_cast(sds); // retrun is R2 so read extra status byte. if (cardAcmd(ACMD13, 0) || spiReceive()) { @@ -519,7 +544,7 @@ bool SharedSpiCard::readSDS(sds_t* sds) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::readStop() { +bool SdSpiCard::readStop() { m_state = IDLE_STATE; if (cardCommand(CMD12, 0)) { error(SD_CARD_ERROR_CMD12); @@ -533,12 +558,25 @@ bool SharedSpiCard::readStop() { return false; } //------------------------------------------------------------------------------ -uint32_t SharedSpiCard::sectorCount() { +uint32_t SdSpiCard::sectorCount() { csd_t csd; return readCSD(&csd) ? csd.capacity() : 0; } //------------------------------------------------------------------------------ -void SharedSpiCard::spiStart() { +bool SdSpiCard::setDedicatedSpi(bool value) { +#if ENABLE_DEDICATED_SPI + if (!syncDevice()) { + return false; + } + m_dedicatedSpi = value; + return true; +#else // ENABLE_DEDICATED_SPI + (void)value; + return false; +#endif // ENABLE_DEDICATED_SPI +} +//------------------------------------------------------------------------------ +void SdSpiCard::spiStart() { SPI_ASSERT_NOT_ACTIVE; if (!m_spiActive) { spiActivate(); @@ -549,7 +587,7 @@ void SharedSpiCard::spiStart() { } } //------------------------------------------------------------------------------ -void SharedSpiCard::spiStop() { +void SdSpiCard::spiStop() { SPI_ASSERT_ACTIVE; if (m_spiActive) { spiUnselect(); @@ -560,7 +598,7 @@ void SharedSpiCard::spiStop() { } } //------------------------------------------------------------------------------ -bool SharedSpiCard::syncDevice() { +bool SdSpiCard::syncDevice() { if (m_state == WRITE_STATE) { return writeStop(); } @@ -570,7 +608,7 @@ bool SharedSpiCard::syncDevice() { return true; } //------------------------------------------------------------------------------ -bool SharedSpiCard::waitReady(uint16_t ms) { +bool SdSpiCard::waitReady(uint16_t ms) { Timeout timeout(ms); while (spiReceive() != 0XFF) { if (timeout.timedOut()) { @@ -580,7 +618,7 @@ bool SharedSpiCard::waitReady(uint16_t ms) { return true; } //------------------------------------------------------------------------------ -bool SharedSpiCard::writeData(const uint8_t* src) { +bool SdSpiCard::writeData(const uint8_t* src) { // wait for previous write to finish if (!waitReady(SD_WRITE_TIMEOUT)) { error(SD_CARD_ERROR_WRITE_TIMEOUT); @@ -597,7 +635,7 @@ bool SharedSpiCard::writeData(const uint8_t* src) { } //------------------------------------------------------------------------------ // send one sector of data for write sector or write multiple sectors -bool SharedSpiCard::writeData(uint8_t token, const uint8_t* src) { +bool SdSpiCard::writeData(uint8_t token, const uint8_t* src) { #if USE_SD_CRC uint16_t crc = CRC_CCITT(src, 512); #else // USE_SD_CRC @@ -620,7 +658,14 @@ bool SharedSpiCard::writeData(uint8_t token, const uint8_t* src) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::writeSector(uint32_t sector, const uint8_t* src) { +bool SdSpiCard::writeSector(uint32_t sector, const uint8_t* src) { +#ifndef OLD_WAY_WRITE_SECTOR +#if ENABLE_DEDICATED_SPI + if (m_dedicatedSpi) { + return writeSectors(sector, src, 1); + } +#endif +#endif // OLD_WAY_WRITE_SECTOR // use address if not SDHC card if (type() != SD_CARD_TYPE_SDHC) { sector <<= 9; @@ -654,8 +699,22 @@ bool SharedSpiCard::writeSector(uint32_t sector, const uint8_t* src) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::writeSectors(uint32_t sector, const uint8_t* src, - size_t ns) { +bool SdSpiCard::writeSectors(uint32_t sector, const uint8_t* src, size_t ns) { +#if ENABLE_DEDICATED_SPI + if (sdState() != WRITE_STATE || m_curSector != sector) { + if (!writeStart(sector)) { + goto fail; + } + m_curSector = sector; + } + for (size_t i = 0; i < ns; i++, src += 512) { + if (!writeData(src)) { + goto fail; + } + } + m_curSector += ns; + return m_dedicatedSpi ? true : writeStop(); +#else if (!writeStart(sector)) { goto fail; } @@ -665,13 +724,13 @@ bool SharedSpiCard::writeSectors(uint32_t sector, const uint8_t* src, } } return writeStop(); - +#endif fail: spiStop(); return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::writeStart(uint32_t sector) { +bool SdSpiCard::writeStart(uint32_t sector) { // use address if not SDHC card if (type() != SD_CARD_TYPE_SDHC) { sector <<= 9; @@ -688,7 +747,7 @@ bool SharedSpiCard::writeStart(uint32_t sector) { return false; } //------------------------------------------------------------------------------ -bool SharedSpiCard::writeStop() { +bool SdSpiCard::writeStop() { if (!waitReady(SD_WRITE_TIMEOUT)) { goto fail; } @@ -702,69 +761,3 @@ bool SharedSpiCard::writeStop() { spiStop(); return false; } -//============================================================================== -bool DedicatedSpiCard::begin(SdSpiConfig spiConfig) { - if (!SharedSpiCard::begin(spiConfig)) { - return false; - } - m_dedicatedSpi = spiOptionDedicated(spiConfig.options); - return true; -} -//------------------------------------------------------------------------------ -bool DedicatedSpiCard::readSector(uint32_t sector, uint8_t* dst) { - return readSectors(sector, dst, 1); -} -//------------------------------------------------------------------------------ -bool DedicatedSpiCard::readSectors(uint32_t sector, uint8_t* dst, size_t ns) { - if (sdState() != READ_STATE || sector != m_curSector) { - if (!readStart(sector)) { - goto fail; - } - m_curSector = sector; - } - for (size_t i = 0; i < ns; i++, dst += 512) { - if (!readData(dst)) { - goto fail; - } - } - m_curSector += ns; - return m_dedicatedSpi ? true : readStop(); - -fail: - return false; -} -//------------------------------------------------------------------------------ -bool DedicatedSpiCard::setDedicatedSpi(bool value) { - if (!syncDevice()) { - return false; - } - m_dedicatedSpi = value; - return true; -} -//------------------------------------------------------------------------------ -bool DedicatedSpiCard::writeSector(uint32_t sector, const uint8_t* src) { - if (m_dedicatedSpi) { - return writeSectors(sector, src, 1); - } - return SharedSpiCard::writeSector(sector, src); -} -//------------------------------------------------------------------------------ -bool DedicatedSpiCard::writeSectors(uint32_t sector, const uint8_t* src, - size_t ns) { - if (sdState() != WRITE_STATE || m_curSector != sector) { - if (!writeStart(sector)) { - goto fail; - } - m_curSector = sector; - } - for (size_t i = 0; i < ns; i++, src += 512) { - if (!writeData(src)) { - goto fail; - } - } - m_curSector += ns; - return m_dedicatedSpi ? true : writeStop(); - -fail: - return false; -} diff --git a/src/SdCard/SdSpiCard.h b/src/SdCard/SdSpiCard.h index 4b1477c3..7ed9cf6b 100644 --- a/src/SdCard/SdSpiCard.h +++ b/src/SdCard/SdSpiCard.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -26,8 +26,7 @@ * \file * \brief Classes for SPI access to SD/SDHC cards. */ -#ifndef SdSpiCard_h -#define SdSpiCard_h +#pragma once #include #include "../SpiDriver/SdSpiDriver.h" @@ -43,8 +42,8 @@ if (!m_spiActive) { \ Serial.print(F("SPI_ASSERT_ACTIVE")); \ Serial.println(__LINE__); \ - while (true) \ - ; \ + while (true) { \ + } \ } \ } #define SPI_ASSERT_NOT_ACTIVE \ @@ -52,8 +51,8 @@ if (m_spiActive) { \ Serial.print(F("SPI_ASSERT_NOT_ACTIVE")); \ Serial.println(__LINE__); \ - while (true) \ - ; \ + while (true) { \ + } \ } \ } #else // CHECK_SPI_ACTIVE @@ -64,15 +63,15 @@ #endif // CHECK_SPI_ACTIVE //============================================================================== /** - * \class SharedSpiCard + * \class SdSpiCard * \brief Raw access to SD and SDHC flash memory cards via shared SPI port. */ #if HAS_SDIO_CLASS -class SharedSpiCard : public SdCardInterface { +class SdSpiCard : public SdCardInterface { #elif USE_BLOCK_DEVICE_INTERFACE -class SharedSpiCard : public FsBlockDeviceInterface { +class SdSpiCard : public FsBlockDeviceInterface { #else // HAS_SDIO_CLASS -class SharedSpiCard { +class SdSpiCard { #endif // HAS_SDIO_CLASS public: /** SD is in idle state */ @@ -81,8 +80,8 @@ class SharedSpiCard { static const uint8_t READ_STATE = 1; /** SD is in multi-sector write state. */ static const uint8_t WRITE_STATE = 2; - /** Construct an instance of SharedSpiCard. */ - SharedSpiCard() { initSharedSpiCard(); } + /** Construct an instance of SdSpiCard. */ + SdSpiCard() { initSharedSpiCard(); } /** Initialize the SD card. * \param[in] spiConfig SPI card configuration. * \return true for success or false for failure. @@ -131,16 +130,24 @@ class SharedSpiCard { uint8_t errorCode() const { return m_errorCode; } /** \return error data for last error. */ uint32_t errorData() const { return m_status; } - /** \return false for shared class. */ +/** \return false for shared class. */ +#if ENABLE_DEDICATED_SPI + bool hasDedicatedSpi() { return true; } +#else bool hasDedicatedSpi() { return false; } +#endif /** * Check for busy. MISO low indicates the card is busy. * * \return true if busy else false. */ bool isBusy(); - /** \return false, can't be in dedicated state. */ + /** \return true if in dedicated SPI state. */ +#if ENABLE_DEDICATED_SPI + bool isDedicatedSpi() { return m_dedicatedSpi; } +#else // ENABLE_DEDICATED_SPI bool isDedicatedSpi() { return false; } +#endif // ENABLE_DEDICATED_SPI /** \return true if card is on SPI bus. */ bool isSpi() { return true; } /** @@ -198,7 +205,6 @@ class SharedSpiCard { * \return true for success or false for failure. */ bool readSectors(uint32_t sector, uint8_t* dst, size_t ns); - /** Start a read multiple sector sequence. * * \param[in] sector Address of first sector in sequence. @@ -228,19 +234,12 @@ class SharedSpiCard { * or zero if an error occurs. */ uint32_t sectorCount(); -#ifndef DOXYGEN_SHOULD_SKIP_THIS - // Use sectorCount(). cardSize() will be removed in the future. - uint32_t __attribute__((error("use sectorCount()"))) cardSize(); -#endif // DOXYGEN_SHOULD_SKIP_THIS /** Set SPI sharing state * \param[in] value desired state. - * \return false for shared card + * \return true for success. */ - bool setDedicatedSpi(bool value) { - (void)value; - return false; - } - /** end a mult-sector transfer. + bool setDedicatedSpi(bool value); + /** end a multi-sector transfer. * * \return true for success or false for failure. */ @@ -362,6 +361,10 @@ class SharedSpiCard { m_status = 0; m_type = 0; } +#if ENABLE_DEDICATED_SPI + uint32_t m_curSector = 0; + bool m_dedicatedSpi = false; +#endif // ENABLE_DEDICATED_SPI bool m_beginCalled; SdCsPin_t m_csPin; uint8_t m_errorCode; @@ -370,74 +373,3 @@ class SharedSpiCard { uint8_t m_status; uint8_t m_type; }; -//============================================================================== -/** - * \class DedicatedSpiCard - * \brief Raw access to SD and SDHC flash memory cards via dedicate SPI port. - */ -class DedicatedSpiCard : public SharedSpiCard { - public: - /** Construct an instance of DedicatedSpiCard. */ - DedicatedSpiCard() = default; - /** Initialize the SD card. - * \param[in] spiConfig SPI card configuration. - * \return true for success or false for failure. - */ - bool begin(SdSpiConfig spiConfig); - /** \return true, can be in dedicaded state. */ - bool hasDedicatedSpi() { return true; } - /** \return true if in dedicated SPI state. */ - bool isDedicatedSpi() { return m_dedicatedSpi; } - /** - * Read a 512 byte sector from an SD card. - * - * \param[in] sector Logical sector to be read. - * \param[out] dst Pointer to the location that will receive the data. - * \return true for success or false for failure. - */ - bool readSector(uint32_t sector, uint8_t* dst); - /** - * Read multiple 512 byte sectors from an SD card. - * - * \param[in] sector Logical sector to be read. - * \param[in] ns Number of sectors to be read. - * \param[out] dst Pointer to the location that will receive the data. - * \return true for success or false for failure. - */ - bool readSectors(uint32_t sector, uint8_t* dst, size_t ns); - /** Set SPI sharing state - * \param[in] value desired state. - * \return true for success else false; - */ - bool setDedicatedSpi(bool value); - /** - * Write a 512 byte sector to an SD card. - * - * \param[in] sector Logical sector to be written. - * \param[in] src Pointer to the location of the data to be written. - * \return true for success or false for failure. - */ - bool writeSector(uint32_t sector, const uint8_t* src); - /** - * Write multiple 512 byte sectors to an SD card. - * - * \param[in] sector Logical sector to be written. - * \param[in] ns Number of sectors to be written. - * \param[in] src Pointer to the location of the data to be written. - * \return true for success or false for failure. - */ - bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns); - - private: - uint32_t m_curSector = 0; - bool m_dedicatedSpi = false; -}; -//============================================================================== -#if ENABLE_DEDICATED_SPI -/** typedef for dedicated SPI. */ -typedef DedicatedSpiCard SdSpiCard; -#else -/** typedef for shared SPI. */ -typedef SharedSpiCard SdSpiCard; -#endif -#endif // SdSpiCard_h diff --git a/src/SdCard/SdioCard.h b/src/SdCard/SdioCard.h index eb032e4a..42b89e0f 100644 --- a/src/SdCard/SdioCard.h +++ b/src/SdCard/SdioCard.h @@ -26,34 +26,18 @@ * \file * \brief Classes for SDIO cards. */ -#ifndef SdioCard_h -#define SdioCard_h +#pragma once #include "../common/SysCall.h" #include "SdCardInterface.h" -/** Use programmed I/O with FIFO. */ -#define FIFO_SDIO 0 -/** Use programmed I/O with DMA. */ -#define DMA_SDIO 1 +#ifdef SDIO_CONFIG_INCLUDE +#include SDIO_CONFIG_INCLUDE +#else // SDIO_CONFIG_INCLUDE /** * \class SdioConfig - * \brief SDIO card configuration. + * \brief Empty SDIO card configuration. */ -class SdioConfig { - public: - SdioConfig() {} - /** - * SdioConfig constructor. - * \param[in] opt SDIO options. - */ - explicit SdioConfig(uint8_t opt) : m_options(opt) {} - /** \return SDIO card options. */ - uint8_t options() { return m_options; } - /** \return true if DMA_SDIO. */ - bool useDma() { return m_options & DMA_SDIO; } - - private: - uint8_t m_options = FIFO_SDIO; -}; +class SdioConfig {}; +#endif // SDIO_CONFIG_INCLUDE //------------------------------------------------------------------------------ /** * \class SdioCard @@ -62,10 +46,10 @@ class SdioConfig { class SdioCard : public SdCardInterface { public: /** Initialize the SD card. - * \param[in] sdioConfig SDIO card configuration. + * \param[in] config SDIO card configuration. * \return true for success or false for failure. */ - bool begin(SdioConfig sdioConfig); + bool begin(SdioConfig config); /** CMD6 Switch mode: Check Function Set Function. * \param[in] arg CMD6 argument. * \param[out] status return status data. @@ -76,7 +60,7 @@ class SdioCard : public SdCardInterface { /** Disable an SDIO card. * not implemented. */ - void end() {} + void end(); #ifndef DOXYGEN_SHOULD_SKIP_THIS uint32_t __attribute__((error("use sectorCount()"))) cardSize(); @@ -253,7 +237,5 @@ class SdioCard : public SdCardInterface { static const uint8_t READ_STATE = 1; static const uint8_t WRITE_STATE = 2; uint32_t m_curSector; - SdioConfig m_sdioConfig; uint8_t m_curState = IDLE_STATE; }; -#endif // SdioCard_h diff --git a/src/SdCard/TeensySdio/CPPLINT.cfg b/src/SdCard/TeensySdio/CPPLINT.cfg new file mode 100644 index 00000000..2cdd1d38 --- /dev/null +++ b/src/SdCard/TeensySdio/CPPLINT.cfg @@ -0,0 +1 @@ +exclude_files=TeensySdioDefs.h diff --git a/src/SdCard/SdioTeensy.cpp b/src/SdCard/TeensySdio/TeensySdio.cpp similarity index 98% rename from src/SdCard/SdioTeensy.cpp rename to src/SdCard/TeensySdio/TeensySdio.cpp index c4b81330..5be1d0db 100644 --- a/src/SdCard/SdioTeensy.cpp +++ b/src/SdCard/TeensySdio/TeensySdio.cpp @@ -23,10 +23,9 @@ * DEALINGS IN THE SOFTWARE. */ #if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__) -#include "SdioTeensy.h" - -#include "SdCardInfo.h" -#include "SdioCard.h" +#include "../SdCardInfo.h" +#include "../SdioCard.h" +#include "TeensySdioDefs.h" //============================================================================== // limit of K66 due to errata KINETIS_K_0N65N. const uint32_t MAX_BLKCNT = 0XFFFF; @@ -194,9 +193,10 @@ static bool waitTimeout(bool (*fcn)()); //------------------------------------------------------------------------------ static bool (*m_busyFcn)() = 0; static bool m_initDone = false; -static bool m_version2; -static bool m_highCapacity; +static bool m_version2 = false; +static bool m_highCapacity = false; static bool m_transferActive = false; +static bool m_useDma = false; static uint8_t m_errorCode = SD_CARD_ERROR_INIT_NOT_CALLED; static uint32_t m_errorLine = 0; static uint32_t m_rca; @@ -689,7 +689,7 @@ static bool waitTransferComplete() { bool SdioCard::begin(SdioConfig sdioConfig) { uint32_t kHzSdClk; uint32_t arg; - m_sdioConfig = sdioConfig; + m_useDma = sdioConfig.useDma(); m_curState = IDLE_STATE; m_initDone = false; m_errorCode = SD_CARD_ERROR_NONE; @@ -777,7 +777,7 @@ bool SdioCard::begin(SdioConfig sdioConfig) { } if ((status[16] & 0XF) == 1) { kHzSdClk = 50000; - } else { + } else { return sdError(SD_CARD_ERROR_CMD6); } } @@ -812,6 +812,10 @@ bool SdioCard::cardCMD6(uint32_t arg, uint8_t* status) { return true; } //------------------------------------------------------------------------------ +void SdioCard::end() { + // to do +} +//------------------------------------------------------------------------------ bool SdioCard::erase(uint32_t firstSector, uint32_t lastSector) { if (m_curState != IDLE_STATE && !syncDevice()) { return false; @@ -851,7 +855,7 @@ uint32_t SdioCard::errorData() const { return m_irqstat; } uint32_t SdioCard::errorLine() const { return m_errorLine; } //------------------------------------------------------------------------------ bool SdioCard::isBusy() { - if (m_sdioConfig.useDma()) { + if (m_useDma) { return m_busyFcn ? m_busyFcn() : m_initDone && isBusyCMD13(); } else { if (m_transferActive) { @@ -932,7 +936,7 @@ bool SdioCard::readSDS(sds_t* sds) { } //------------------------------------------------------------------------------ bool SdioCard::readSector(uint32_t sector, uint8_t* dst) { - if (m_sdioConfig.useDma()) { + if (m_useDma) { uint8_t aligned[512]; uint8_t* ptr = (uint32_t)dst & 3 ? aligned : dst; @@ -973,7 +977,7 @@ bool SdioCard::readSector(uint32_t sector, uint8_t* dst) { } //------------------------------------------------------------------------------ bool SdioCard::readSectors(uint32_t sector, uint8_t* dst, size_t n) { - if (m_sdioConfig.useDma()) { + if (m_useDma) { if ((uint32_t)dst & 3) { for (size_t i = 0; i < n; i++, sector++, dst += 512) { if (!readSector(sector, dst)) { @@ -1081,7 +1085,7 @@ bool SdioCard::writeData(const uint8_t* src) { } //------------------------------------------------------------------------------ bool SdioCard::writeSector(uint32_t sector, const uint8_t* src) { - if (m_sdioConfig.useDma()) { + if (m_useDma) { uint8_t* ptr; uint8_t aligned[512]; if (3 & (uint32_t)src) { @@ -1124,7 +1128,7 @@ bool SdioCard::writeSector(uint32_t sector, const uint8_t* src) { } //------------------------------------------------------------------------------ bool SdioCard::writeSectors(uint32_t sector, const uint8_t* src, size_t n) { - if (m_sdioConfig.useDma()) { + if (m_useDma) { uint8_t* ptr = const_cast(src); if (3 & (uint32_t)ptr) { for (size_t i = 0; i < n; i++, sector++, ptr += 512) { diff --git a/src/ExFatLib/ExFatConfig.h b/src/SdCard/TeensySdio/TeensySdioConfig.h similarity index 65% rename from src/ExFatLib/ExFatConfig.h rename to src/SdCard/TeensySdio/TeensySdioConfig.h index a512314c..511eff7a 100644 --- a/src/ExFatLib/ExFatConfig.h +++ b/src/SdCard/TeensySdio/TeensySdioConfig.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,12 +22,28 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef ExFatConfig_h -#define ExFatConfig_h -#include "SdFatConfig.h" - -#ifndef EXFAT_READ_ONLY -#define EXFAT_READ_ONLY 0 -#endif // EXFAT_READ_ONLY +#pragma once +/** Use programmed I/O with FIFO. */ +#define FIFO_SDIO 0 +/** Use programmed I/O with DMA. */ +#define DMA_SDIO 1 +/** + * \class SdioConfig + * \brief SDIO card configuration. + */ +class SdioConfig { + public: + SdioConfig() {} + /** + * SdioConfig constructor. + * \param[in] opt SDIO options. + */ + explicit SdioConfig(uint8_t opt) : m_options(opt) {} + /** \return SDIO card options. */ + uint8_t options() { return m_options; } + /** \return true if DMA_SDIO. */ + bool useDma() { return m_options & DMA_SDIO; } -#endif // ExFatConfig_h + private: + uint8_t m_options = FIFO_SDIO; +}; diff --git a/src/SdCard/SdioTeensy.h b/src/SdCard/TeensySdio/TeensySdioDefs.h similarity index 98% rename from src/SdCard/SdioTeensy.h rename to src/SdCard/TeensySdio/TeensySdioDefs.h index 5d97e569..e7665b4d 100644 --- a/src/SdCard/SdioTeensy.h +++ b/src/SdCard/TeensySdio/TeensySdioDefs.h @@ -2,16 +2,14 @@ * \file * \brief Definitions for Teensy HDHC. */ - -#ifndef SdioTeensy_h -#define SdioTeensy_h +#pragma once // From Paul's SD.h driver. #if defined(__IMXRT1062__) #define MAKE_REG_MASK(m, s) (((uint32_t)(((uint32_t)(m) << s)))) #define MAKE_REG_GET(x, m, s) (((uint32_t)(((uint32_t)(x) >> s) & m))) -#define MAKE_REG_SET(x, m, s) (((uint32_t)(((uint32_t)(x)&m) << s))) +#define MAKE_REG_SET(x, m, s) (((uint32_t)(((uint32_t)(x) & m) << s))) #define SDHC_BLKATTR_BLKSIZE_MASK \ MAKE_REG_MASK( \ @@ -467,7 +465,8 @@ // Version #define CCM_ANALOG_PFD_528_PFD0_FRAC_MASK 0x3f -#define CCM_ANALOG_PFD_528_PFD0_FRAC(n) ((n)&CCM_ANALOG_PFD_528_PFD0_FRAC_MASK) +#define CCM_ANALOG_PFD_528_PFD0_FRAC(n) \ + ((n) & CCM_ANALOG_PFD_528_PFD0_FRAC_MASK) #define CCM_ANALOG_PFD_528_PFD1_FRAC_MASK (0x3f << 8) #define CCM_ANALOG_PFD_528_PFD1_FRAC(n) \ (((n) << 8) & CCM_ANALOG_PFD_528_PFD1_FRAC_MASK) @@ -515,16 +514,15 @@ #define SDHC_PREV_CLKFS(x, y) ((x) >>= (y)) #define CCM_CSCDR1_USDHC1_CLK_PODF_MASK (0x7 << 11) -#define CCM_CSCDR1_USDHC1_CLK_PODF(n) (((n)&0x7) << 11) +#define CCM_CSCDR1_USDHC1_CLK_PODF(n) (((n) & 0x7) << 11) #define IOMUXC_SW_PAD_CTL_PAD_SRE ((0x1 <) < 0) #define IOMUXC_SW_PAD_CTL_PAD_PKE ((0x1) << 12) #define IOMUXC_SW_PAD_CTL_PAD_PUE ((0x1) << 13) #define IOMUXC_SW_PAD_CTL_PAD_HYS ((0x1) << 16) -#define IOMUXC_SW_PAD_CTL_PAD_SPEED(n) (((n)&0x3) << 6) -#define IOMUXC_SW_PAD_CTL_PAD_PUS(n) (((n)&0x3) << 14) +#define IOMUXC_SW_PAD_CTL_PAD_SPEED(n) (((n) & 0x3) << 6) +#define IOMUXC_SW_PAD_CTL_PAD_PUS(n) (((n) & 0x3) << 14) #define IOMUXC_SW_PAD_CTL_PAD_PUS_MASK ((0x3) << 14) -#define IOMUXC_SW_PAD_CTL_PAD_DSE(n) (((n)&0x7) << 3) +#define IOMUXC_SW_PAD_CTL_PAD_DSE(n) (((n) & 0x7) << 3) #define IOMUXC_SW_PAD_CTL_PAD_DSE_MASK ((0x7) << 3) -#endif // defined(__IMXRT1062__) -#endif // SdioTeensy_h \ No newline at end of file +#endif // defined(__IMXRT1062__) \ No newline at end of file diff --git a/src/SdFat.h b/src/SdFat.h index d17393df..b9bc875f 100644 --- a/src/SdFat.h +++ b/src/SdFat.h @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef SdFat_h -#define SdFat_h +#pragma once /** * \file * \brief main SdFs include file. @@ -38,9 +37,9 @@ #endif // INCLUDE_SDIOS //------------------------------------------------------------------------------ /** SdFat version for cpp use. */ -#define SD_FAT_VERSION 20203 +#define SD_FAT_VERSION 20300 /** SdFat version as string. */ -#define SD_FAT_VERSION_STR "2.2.3" +#define SD_FAT_VERSION_STR "2.3.0" //============================================================================== /** * \class SdBase @@ -463,7 +462,7 @@ typedef FsFile File; #endif // HAS_INCLUDE_FS_H /** * \class SdFile - * \brief FAT16/FAT32 file with Print. + * \brief File with Print. */ class SdFile : public PrintFile { public: @@ -506,4 +505,3 @@ class SdFile : public PrintFile { /** Cancel the date/time callback function. */ static void dateTimeCallbackCancel() { FsDateTime::clearCallback(); } }; -#endif // SdFat_h diff --git a/src/SdFatConfig.h b/src/SdFatConfig.h index 353aa101..4b64f9d0 100644 --- a/src/SdFatConfig.h +++ b/src/SdFatConfig.h @@ -26,13 +26,13 @@ * \file * \brief configuration definitions */ -#ifndef SdFatConfig_h -#define SdFatConfig_h +#pragma once #include #ifdef __AVR__ #include #endif // __AVR__ -// + +// #include "SdFatDebugConfig.h" // To try UTF-8 encoded filenames. // #define USE_UTF8_LONG_NAMES 1 // @@ -53,7 +53,7 @@ * private to prevent multiple copies of a instance for a file. * * File move constructors and move assignment operators are public to permit - * return of a file instance for compilers that aren't able to use copy elision. + * return of a file instance for compilers that aren't able to use copy elision. * */ /** File copy constructors and copy assignment operators are deleted */ @@ -87,13 +87,18 @@ #define DESTRUCTOR_CLOSES_FILE 0 #endif // DESTRUCTOR_CLOSES_FILE //------------------------------------------------------------------------------ - -/** For Debug - must be one */ +/** For Debug - must be one on Arduino */ +#ifndef ENABLE_ARDUINO_FEATURES #define ENABLE_ARDUINO_FEATURES 1 -/** For Debug - must be one */ +#endif //ENABLE_ARDUINO_FEATURES +/** For Debug - must be one on Arduino */ +#ifndef ENABLE_ARDUINO_SERIAL #define ENABLE_ARDUINO_SERIAL 1 -/** For Debug - must be one */ +#endif //ENABLE_ARDUINO_SERIAL +/** For Debug - must be one on Arduino */ +#ifndef ENABLE_ARDUINO_STRING #define ENABLE_ARDUINO_STRING 1 +#endif //ENABLE_ARDUINO_STRING //------------------------------------------------------------------------------ #if ENABLE_ARDUINO_FEATURES #include "Arduino.h" @@ -183,7 +188,11 @@ * receive and transfer(buf, rxTmp, count) for send. Try this with STM32. */ #ifndef USE_SPI_ARRAY_TRANSFER +#if defined(ARDUINO_ARCH_RP2040) +#define USE_SPI_ARRAY_TRANSFER 2 +#else // defined(ARDUINO_ARCH_RP2040) #define USE_SPI_ARRAY_TRANSFER 0 +#endif // defined(ARDUINO_ARCH_RP2040) #endif // USE_SPI_ARRAY_TRANSFER //------------------------------------------------------------------------------ /** @@ -435,6 +444,12 @@ typedef uint8_t SdCsPin_t; #endif // RAMEND //------------------------------------------------------------------------------ /** Enable SDIO driver if available. */ +#if defined(ARDUINO_ARCH_RP2040) +#define HAS_RP2040_SDIO 1 +#define HAS_SDIO_CLASS 1 +#define SDIO_CONFIG_INCLUDE "Rp2040Sdio/Rp2040SdioConfig.h" +#endif // defined(ARDUINO_ARCH_RP2040) + #if defined(__MK64FX512__) || defined(__MK66FX1M0__) // Pseudo pin select for SDIO. #ifndef BUILTIN_SDCARD @@ -448,10 +463,11 @@ typedef uint8_t SdCsPin_t; #define SDCARD_SCK_PIN 60 #define SDCARD_SS_PIN 62 #endif // SDCARD_SPI -#define HAS_SDIO_CLASS 1 #endif // defined(__MK64FX512__) || defined(__MK66FX1M0__) -#if defined(__IMXRT1062__) +#if defined(__IMXRT1062__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) #define HAS_SDIO_CLASS 1 +#define HAS_TEENSY_SDIO 1 +#define SDIO_CONFIG_INCLUDE "TeensySdio/TeensySdioConfig.h" #endif // defined(__IMXRT1062__) //------------------------------------------------------------------------------ /** @@ -460,9 +476,8 @@ typedef uint8_t SdCsPin_t; #if defined(ARDUINO_ARCH_APOLLO3) || \ (defined(__AVR__) && defined(SPDR) && defined(SPSR) && defined(SPIF)) || \ (defined(__AVR__) && defined(SPI0) && defined(SPI_RXCIF_bm)) || \ - defined(ESP8266) || defined(ESP32) || defined(PLATFORM_ID) || \ defined(ARDUINO_SAM_DUE) || defined(STM32_CORE_VERSION) || \ - defined(__STM32F1__) || defined(__STM32F4__) || \ + defined(__STM32F1__) || defined(__STM32F4__) || defined(PLATFORM_ID) || \ (defined(CORE_TEENSY) && defined(__arm__)) #define SD_HAS_CUSTOM_SPI 1 #else // SD_HAS_CUSTOM_SPI @@ -474,5 +489,3 @@ typedef uint8_t SdCsPin_t; /** Default is no SDIO. */ #define HAS_SDIO_CLASS 0 #endif // HAS_SDIO_CLASS - -#endif // SdFatConfig_h diff --git a/src/SdFatDebugConfig.h b/src/SdFatDebugConfig.h new file mode 100644 index 00000000..7267e67e --- /dev/null +++ b/src/SdFatDebugConfig.h @@ -0,0 +1,15 @@ +#pragma once +// File to be included in SdFatConfig.h for debug definitions. +#if defined(ARDUINO_ADAFRUIT_METRO_RP2040) +#define RP_CLK_GPIO 18 +#define RP_CMD_GPIO 19 +#define RP_DAT0_GPIO 20 // DAT1: GPIO21, DAT2: GPIO22, DAT3: GPIO23. +#elif defined(ARDUINO_RASPBERRY_PI_PICO) || defined(ARDUINO_RASPBERRY_PI_PICO_2) +#define RP_CLK_GPIO 16 +#define RP_CMD_GPIO 17 +#define RP_DAT0_GPIO 18 // DAT1: GPIO19, DAT2: GPIO20, DAT3: GPIO21. +#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2350_HSTX) +#define RP_CLK_GPIO 11 +#define RP_CMD_GPIO 10 +#define RP_DAT0_GPIO 22 // DAT1: GPIO23, DAT2: GPIO24, DAT3: GPIO25. +#endif // defined(ARDUINO_ADAFRUIT_METRO_RP2040)) diff --git a/src/SpiDriver/SdSpiChipSelect.cpp b/src/SpiDriver/SdSpiChipSelect.cpp index f7927f2c..20f8e3e7 100644 --- a/src/SpiDriver/SdSpiChipSelect.cpp +++ b/src/SpiDriver/SdSpiChipSelect.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -28,13 +28,15 @@ //------------------------------------------------------------------------------ void sdCsInit(SdCsPin_t pin) { pinMode(pin, OUTPUT); } //------------------------------------------------------------------------------ -void sdCsWrite(SdCsPin_t pin, bool level) { digitalWrite(pin, level); } +void sdCsWrite(SdCsPin_t pin, bool level) { + digitalWrite(pin, level ? HIGH : LOW); +} #elif SD_CHIP_SELECT_MODE == 1 //------------------------------------------------------------------------------ __attribute__((weak)) void sdCsInit(SdCsPin_t pin) { pinMode(pin, OUTPUT); } //------------------------------------------------------------------------------ __attribute__((weak)) void sdCsWrite(SdCsPin_t pin, bool level) { - digitalWrite(pin, level); + digitalWrite(pin, level ? HIGH : LOW); } #endif // SD_CHIP_SELECT_MODE == 0 #endif // ENABLE_ARDUINO_FEATURES diff --git a/src/SpiDriver/SdSpiESP.cpp b/src/SpiDriver/SdSpiESP.cpp deleted file mode 100644 index 27a51803..00000000 --- a/src/SpiDriver/SdSpiESP.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2011-2022 Bill Greiman - * This file is part of the SdFat library for SD memory cards. - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include "SdSpiDriver.h" -#if defined(SD_USE_CUSTOM_SPI) && (defined(ESP8266) || defined(ESP32)) -#define ESP_UNALIGN_OK 1 -//------------------------------------------------------------------------------ -void SdSpiArduinoDriver::activate() { m_spi->beginTransaction(m_spiSettings); } -//------------------------------------------------------------------------------ -void SdSpiArduinoDriver::begin(SdSpiConfig spiConfig) { - if (spiConfig.spiPort) { - m_spi = spiConfig.spiPort; -#if defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) - } else if (spiConfig.csPin == SDCARD_SS_PIN) { - m_spi = &SDCARD_SPI; -#endif // defined(SDCARD_SPI) && defined(SDCARD_SS_PIN) - } else { - m_spi = &SPI; - } - m_spi->begin(); -} -//------------------------------------------------------------------------------ -void SdSpiArduinoDriver::deactivate() { m_spi->endTransaction(); } -//------------------------------------------------------------------------------ -void SdSpiArduinoDriver::end() { m_spi->end(); } -//------------------------------------------------------------------------------ -uint8_t SdSpiArduinoDriver::receive() { return m_spi->transfer(0XFF); } -//------------------------------------------------------------------------------ -uint8_t SdSpiArduinoDriver::receive(uint8_t* buf, size_t count) { -#if ESP_UNALIGN_OK - m_spi->transferBytes(nullptr, buf, count); -#else // ESP_UNALIGN_OK - // Adjust to 32-bit alignment. - while ((reinterpret_cast(buf) & 0X3) && count) { - *buf++ = m_spi->transfer(0xff); - count--; - } - // Do multiple of four byte transfers. - size_t n4 = 4 * (count / 4); - if (n4) { - m_spi->transferBytes(nullptr, buf, n4); - } - // Transfer up to three remaining bytes. - for (buf += n4, count -= n4; count; count--) { - *buf++ = m_spi->transfer(0xff); - } -#endif // ESP_UNALIGN_OK - return 0; -} -//------------------------------------------------------------------------------ -void SdSpiArduinoDriver::send(uint8_t data) { m_spi->transfer(data); } -//------------------------------------------------------------------------------ -void SdSpiArduinoDriver::send(const uint8_t* buf, size_t count) { -#if !ESP_UNALIGN_OK - // Adjust to 32-bit alignment. - while ((reinterpret_cast(buf) & 0X3) && count) { - SPI.transfer(*buf++); - count--; - } -#endif // #if ESP_UNALIGN_OK - - m_spi->transferBytes(const_cast(buf), nullptr, count); -} -#endif // defined(SD_USE_CUSTOM_SPI) && (defined(ESP8266) || defined(ESP32)) diff --git a/src/common/ArduinoFiles.h b/src/common/ArduinoFiles.h index e5a06d64..d0a42eae 100644 --- a/src/common/ArduinoFiles.h +++ b/src/common/ArduinoFiles.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef ArduinoFiles_h -#define ArduinoFiles_h +#pragma once #include "SysCall.h" //------------------------------------------------------------------------------ /** Arduino SD.h style flag for open for read. */ @@ -44,13 +43,29 @@ class PrintFile : public print_t, public BaseFile { public: using BaseFile::clearWriteError; using BaseFile::getWriteError; - using BaseFile::read; using BaseFile::write; + + /** Ensure that any bytes written to the file are saved to the SD card. */ +#if defined(ARDUINO_SAM_DUE) && !defined(ARDUINO_API_VERSION) + void flush() { BaseFile::sync(); } +#else + void flush() override { BaseFile::sync(); } +#endif + /** Write a single byte. * \param[in] b byte to write. * \return one for success. */ - size_t write(uint8_t b) { return BaseFile::write(&b, 1); } + size_t write(uint8_t b) override { return BaseFile::write(&b, 1); } + + /** Write data to an open file. + * \param[in] buffer pointer + * \param[in] size of the buffer + * \return number of bytes actually written + */ + size_t write(const uint8_t* buffer, size_t size) override { + return BaseFile::write(buffer, size); + } }; //------------------------------------------------------------------------------ /** @@ -64,35 +79,39 @@ class StreamFile : public stream_t, public BaseFile { using BaseFile::getWriteError; using BaseFile::read; using BaseFile::write; - StreamFile() {} /** \return number of bytes available from the current position to EOF * or INT_MAX if more than INT_MAX bytes are available. */ - int available() { return BaseFile::available(); } + int available() override { return BaseFile::available(); } /** Ensure that any bytes written to the file are saved to the SD card. */ - void flush() { BaseFile::sync(); } + void flush() override { BaseFile::sync(); } + /** This function reports if the current file is a directory or not. * \return true if the file is a directory. */ bool isDirectory() { return BaseFile::isDir(); } + #ifndef DOXYGEN_SHOULD_SKIP_THIS char* __attribute__((error("use getName(name, size)"))) name(); #endif // DOXYGEN_SHOULD_SKIP_THIS + /** Return the next available byte without consuming it. * * \return The byte if no error and not at eof else -1; */ - int peek() { return BaseFile::peek(); } + int peek() override { return BaseFile::peek(); } /** \return the current file position. */ PosType position() { return BaseFile::curPosition(); } + /** Read the next byte from a file. * * \return For success return the next byte in the file as an int. * If an error occurs or end of file is reached return -1. */ - int read() { return BaseFile::read(); } + int read() override { return BaseFile::read(); } + /** Rewind a file if it is a directory */ void rewindDirectory() { if (BaseFile::isDir()) { @@ -114,7 +133,7 @@ class StreamFile : public stream_t, public BaseFile { * Use getWriteError to check for errors. * \return 1 for success and 0 for failure. */ - size_t write(uint8_t b) { return BaseFile::write(b); } + size_t write(uint8_t b) override { return BaseFile::write(b); } /** Write data to an open file. * * \note Data is moved to the cache but may not be written to the @@ -127,8 +146,7 @@ class StreamFile : public stream_t, public BaseFile { * \return For success write() returns the number of bytes written, always * \a size. */ - size_t write(const uint8_t* buffer, size_t size) { + size_t write(const uint8_t* buffer, size_t size) override { return BaseFile::write(buffer, size); } }; -#endif // ArduinoFiles_h diff --git a/src/common/CPPLINT.cfg b/src/common/CPPLINT.cfg index f274762d..d575b373 100644 --- a/src/common/CPPLINT.cfg +++ b/src/common/CPPLINT.cfg @@ -1,3 +1,3 @@ exclude_files=PrintBasic.cpp exclude_files=PrintBasic.h -exclude_files=PrintTemplates.h \ No newline at end of file +exclude_files=PrintTemplates.h diff --git a/src/common/DebugMacros.h b/src/common/DebugMacros.h index 653cc86e..07771ac4 100644 --- a/src/common/DebugMacros.h +++ b/src/common/DebugMacros.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef DebugMacros_h -#define DebugMacros_h +#pragma once #include "SysCall.h" // 0 - disable, 1 - fail, halt 2 - fail, halt, warn @@ -78,4 +77,3 @@ __attribute__((unused)) static void dbgWarn(uint16_t line) { #define DBG_WARN_MACRO #define DBG_WARN_IF(b) #endif // USE_DBG_MACROS > 1 -#endif // DebugMacros_h diff --git a/src/common/FmtNumber.cpp b/src/common/FmtNumber.cpp index 906d8ee0..a118d538 100644 --- a/src/common/FmtNumber.cpp +++ b/src/common/FmtNumber.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -174,7 +174,8 @@ char* fmtBase10(char* str, uint16_t n) { // n = n + (n >> 16); // no code for 16-bit n n = n >> 3; uint8_t r = t - (((n << 2) + n) << 1); - if (r > 9) { + // cppcheck wrong. + if (r > 9) { // cppcheck-suppress knownConditionTrueFalse n++; r -= 10; } diff --git a/src/common/FsCache.h b/src/common/FsCache.h index 54778012..da121b94 100644 --- a/src/common/FsCache.h +++ b/src/common/FsCache.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FsCache_h -#define FsCache_h +#pragma once /** * \file * \brief Common cache code for exFAT and FAT. @@ -54,7 +53,7 @@ class FsCache { CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ; //---------------------------------------------------------------------------- /** Cobstructor. */ - FsCache() { init(nullptr); } + FsCache() { init(nullptr); } // cppcheck-suppress uninitMemberVar /** \return Cache buffer address. */ uint8_t* cacheBuffer() { return m_buffer; } /** @@ -173,4 +172,3 @@ class FsCache { uint32_t m_mirrorOffset; uint8_t m_buffer[512]; }; -#endif // FsCache_h diff --git a/src/common/FsDateTime.cpp b/src/common/FsDateTime.cpp index be3736b6..bc1e64b2 100644 --- a/src/common/FsDateTime.cpp +++ b/src/common/FsDateTime.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/common/FsDateTime.h b/src/common/FsDateTime.h index 3080c14b..b198b2b9 100644 --- a/src/common/FsDateTime.h +++ b/src/common/FsDateTime.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FsDateTime_h -#define FsDateTime_h +#pragma once #include #include "CompileDateTime.h" @@ -188,4 +187,3 @@ size_t fsPrintDateTime(print_t* pr, uint32_t dateTime, uint8_t s100, int8_t tz); size_t fsPrintTime(print_t* pr, uint16_t time); size_t fsPrintTime(print_t* pr, uint16_t time, uint8_t sec100); size_t fsPrintTimeZone(print_t* pr, int8_t tz); -#endif // FsDateTime_h diff --git a/src/common/FsUtf.cpp b/src/common/FsUtf.cpp index 7b7ef143..9584566e 100644 --- a/src/common/FsUtf.cpp +++ b/src/common/FsUtf.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License diff --git a/src/common/FsUtf.h b/src/common/FsUtf.h index 8d4e9701..6abda05c 100644 --- a/src/common/FsUtf.h +++ b/src/common/FsUtf.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef FsUtf_h -#define FsUtf_h +#pragma once /** * \file * \brief Unicode Transformation Format functions. @@ -98,4 +97,3 @@ const char* mbToCp(const char* str, const char* end, uint32_t* rtn); const char* mbToU16(const char* str, const char* end, uint16_t* hs, uint16_t* ls); } // namespace FsUtf -#endif // FsUtf_h diff --git a/src/common/PrintBasic.cpp b/src/common/PrintBasic.cpp new file mode 100644 index 00000000..87cd5e1b --- /dev/null +++ b/src/common/PrintBasic.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include "PrintBasic.h" +#if ENABLE_ARDUINO_FEATURES == 0 +#include + +size_t PrintBasic::print(long n, uint8_t base) { + if (n < 0 && base == 10) { + return print('-') + printNum(-n, base); + } + return printNum(n, base); +} +size_t PrintBasic::printNum(unsigned long n, uint8_t base) { + const uint8_t DIM = 8 * sizeof(long); + char buf[DIM]; + char *str = &buf[DIM]; + + if (base < 2) return 0; + + do { + char c = n % base; + n /= base; + *--str = c + (c < 10 ? '0' : 'A' - 10); + } while (n); + return write(str, &buf[DIM] - str); +} + +size_t PrintBasic::printDouble(double n, uint8_t prec) { + // Max printable 32-bit floating point number. AVR uses 32-bit double. + const double maxfp = static_cast(0XFFFFFF00UL); + size_t rtn = 0; + + if (isnan(n)) { + return write("NaN"); + } + if (n < 0) { + n = -n; + rtn += print('-'); + } + if (isinf(n)) { + return rtn + write("Inf"); + } + if (n > maxfp) { + return rtn + write("Ovf"); + } + + double round = 0.5; + for (uint8_t i = 0; i < prec; ++i) { + round *= 0.1; + } + + n += round; + + uint32_t whole = (uint32_t)n; + rtn += print(whole); + + if (prec) { + rtn += print('.'); + double fraction = n - static_cast(whole); + for (uint8_t i = 0; i < prec; i++) { + fraction *= 10.0; + uint8_t digit = fraction; + rtn += print(digit); + fraction -= digit; + } + } + return rtn; +} +#endif // ENABLE_ARDUINO_FEATURES == 0 diff --git a/src/common/PrintBasic.h b/src/common/PrintBasic.h new file mode 100644 index 00000000..55cb60a5 --- /dev/null +++ b/src/common/PrintBasic.h @@ -0,0 +1,148 @@ +/** + * Copyright (c) 2011-2020 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef PrintBasic_h +#define PrintBasic_h +/** + * \file + * \brief Stream/Print like replacement for non-Arduino systems. + */ +#include +#include +#include + +#include "../SdFatConfig.h" + +#ifndef F +#if defined(__AVR__) +#include +class __FlashStringHelper; +#define F(string_literal) \ + (reinterpret_cast(PSTR(string_literal))) +#else // defined(__AVR__) +#define F(str) (str) +#endif // defined(__AVR__) +#endif // F + +#ifdef BIN +#undef BIN +#endif // BIN +#define BIN 2 +#define OCT 8 +#define DEC 10 +#define HEX 16 + +class PrintBasic { + public: + PrintBasic() : m_error(0) {} + + void clearWriteError() { setWriteError(0); } + int getWriteError() { return m_error; } + size_t print(char c) { return write(c); } + size_t print(const char *str) { return write(str); } + size_t print(const __FlashStringHelper *str) { +#ifdef __AVR__ + PGM_P p = reinterpret_cast(str); + size_t n = 0; + for (uint8_t c; (c = pgm_read_byte(p + n)) && write(c); n++) { + } + return n; +#else // __AVR__ + return print(reinterpret_cast(str)); +#endif // __AVR__ + } + size_t println(const __FlashStringHelper *str) { +#ifdef __AVR__ + return print(str) + println(); +#else // __AVR__ + return println(reinterpret_cast(str)); +#endif // __AVR__ + } + size_t print(double n, uint8_t prec = 2) { return printDouble(n, prec); } + size_t print(signed char n, uint8_t base = 10) { + return print((long)n, base); + } + size_t print(unsigned char n, uint8_t base = 10) { + return print((unsigned long)n, base); + } + size_t print(int n, uint8_t base = 10) { return print((long)n, base); } + size_t print(unsigned int n, uint8_t base = 10) { + return print((unsigned long)n, base); + } + size_t print(long n, uint8_t base = 10); + size_t print(unsigned long n, uint8_t base = 10) { return printNum(n, base); } + size_t println() { return write("\r\n"); } + size_t println(char c) { return write(c) + println(); } + size_t println(const char *str) { return print(str) + println(); } + size_t println(double n, uint8_t prec = 2) { + return print(n, prec) + println(); + } + size_t println(signed char n, uint8_t base = 10) { + return print(n, base) + println(); + } + size_t println(unsigned char n, uint8_t base = 10) { + return print(n, base) + println(); + } + size_t println(int n, uint8_t base = 10) { + return print(n, base) + println(); + } + size_t println(unsigned int n, uint8_t base = 10) { + return print(n, base) + println(); + } + size_t println(long n, uint8_t base = 10) { + return print(n, base) + println(); + } + size_t println(unsigned long n, uint8_t base = 10) { + return print(n, base) + println(); + } + size_t write(const char *str) { return write(str, strlen(str)); } + virtual size_t write(uint8_t b) = 0; + + virtual size_t write(const uint8_t *buffer, size_t size) { + size_t i; + for (i = 0; i < size; i++) { + if (!write(buffer[i])) break; + } + return i; + } + size_t write(const char *buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } + + protected: + void setWriteError(int err = 1) { m_error = err; } + + private: + size_t printDouble(double n, uint8_t prec); + size_t printNum(unsigned long n, uint8_t base); + int m_error; +}; +//------------------------------------------------------------------------------ +class StreamBasic : public PrintBasic { + public: + virtual int available() = 0; + virtual int peek() = 0; + virtual int read() = 0; +}; +#endif // PrintBasic_h diff --git a/src/fmt_src.bat b/src/fmt_src.bat new file mode 100644 index 00000000..a9710bb4 --- /dev/null +++ b/src/fmt_src.bat @@ -0,0 +1,15 @@ +clang-format --style=Google -i *.cpp *.h +rem clang-format --style=Google -i DigitalIO/*.h +rem clang-format --style=Google -i DigitalIO/boards/*.h +clang-format --style=Google -i common/*.cpp common/*.h +clang-format --style=Google -i ExFatLib/*.cpp ExFatLib/*.h +clang-format --style=Google -i FatLib/*.cpp FatLib/*.h +clang-format --style=Google -i FsLib/*.cpp FsLib/*.h +clang-format --style=Google -i iostream/*.cpp iostream/*.h +clang-format --style=Google -i SdCard/*.cpp SdCard/*.h +rem clang-format --style=Google -i SdCard/Rp2040Sdio/DbgLogMsg.h SdCard/Rp2040Sdio/PioDbgInfo.h +rem clang-format --style=Google -i SdCard/Rp2040Sdio/PioSdioCard.h SdCard/Rp2040Sdio/Rp2040SdioConfig.h +clang-format --style=Google -i SdCard/Rp2040Sdio/*.cpp SdCard/Rp2040Sdio/*.h +clang-format --style=Google -i SdCard/TeensySdio/*.cpp SdCard/TeensySdio/*.h +clang-format --style=Google -i SpiDriver/*.cpp SpiDriver/*.h +pause diff --git a/src/iostream/StdioStream.cpp b/src/iostream/StdioStream.cpp index 1433e3d7..4c2e9fa3 100644 --- a/src/iostream/StdioStream.cpp +++ b/src/iostream/StdioStream.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -243,6 +243,8 @@ size_t StdioStream::fwrite(const void* ptr, size_t size, size_t count) { return write(ptr, count * size) < 0 ? EOF : count; } //------------------------------------------------------------------------------ +// allow shadow of rewind() in StreamBaseFile, +// cppcheck-suppress duplInheritedMember int StdioStream::write(const void* buf, size_t count) { const uint8_t* src = static_cast(buf); size_t todo = count; @@ -282,7 +284,7 @@ size_t StdioStream::print(const __FlashStringHelper* str) { //------------------------------------------------------------------------------ int StdioStream::printDec(float value, uint8_t prec) { char buf[24]; - char* ptr = fmtDouble(buf + sizeof(buf), value, prec, false); + const char* ptr = fmtDouble(buf + sizeof(buf), value, prec, false); return write(ptr, buf + sizeof(buf) - ptr); } //------------------------------------------------------------------------------ @@ -314,7 +316,7 @@ int StdioStream::printDec(int16_t n) { //------------------------------------------------------------------------------ int StdioStream::printDec(uint16_t n) { char buf[5]; - char* ptr = fmtBase10(buf + sizeof(buf), n); + const char* ptr = fmtBase10(buf + sizeof(buf), n); uint8_t len = buf + sizeof(buf) - ptr; return write(ptr, len); } @@ -334,18 +336,20 @@ int StdioStream::printDec(int32_t n) { //------------------------------------------------------------------------------ int StdioStream::printDec(uint32_t n) { char buf[10]; - char* ptr = fmtBase10(buf + sizeof(buf), n); + const char* ptr = fmtBase10(buf + sizeof(buf), n); uint8_t len = buf + sizeof(buf) - ptr; return write(ptr, len); } //------------------------------------------------------------------------------ int StdioStream::printHex(uint32_t n) { char buf[8]; - char* ptr = fmtHex(buf + sizeof(buf), n); + const char* ptr = fmtHex(buf + sizeof(buf), n); uint8_t len = buf + sizeof(buf) - ptr; return write(ptr, len); } //------------------------------------------------------------------------------ +// allow shadow of rewind() in StreamBaseFile, +// cppcheck-suppress duplInheritedMember bool StdioStream::rewind() { if (m_status & S_SWR) { if (!flushBuf()) { diff --git a/src/iostream/StdioStream.h b/src/iostream/StdioStream.h index 276c7790..cea02825 100644 --- a/src/iostream/StdioStream.h +++ b/src/iostream/StdioStream.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef StdioStream_h -#define StdioStream_h +#pragma once /** * \file * \brief StdioStream class @@ -112,6 +111,7 @@ const uint8_t UNGETC_BUF_SIZE = 2; */ class StdioStream : private StreamBaseFile { public: + using StreamBaseFile::printField; /** Constructor * */ @@ -545,38 +545,6 @@ class StdioStream : private StreamBaseFile { */ int printDec(float value, uint8_t prec); //---------------------------------------------------------------------------- - /** Print a number followed by a field terminator. - * \param[in] value The number to be printed. - * \param[in] term The field terminator. - * \param[in] prec Number of digits after decimal point. - * \return The number of bytes written or -1 if an error occurs. - */ - int printField(double value, char term, uint8_t prec = 2) { - return printField(static_cast(value), term, prec) > 0; - } - //---------------------------------------------------------------------------- - /** Print a number followed by a field terminator. - * \param[in] value The number to be printed. - * \param[in] term The field terminator. - * \param[in] prec Number of digits after decimal point. - * \return The number of bytes written or -1 if an error occurs. - */ - int printField(float value, char term, uint8_t prec = 2) { - int rtn = printDec(value, prec); - return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; - } - //---------------------------------------------------------------------------- - /** Print a number followed by a field terminator. - * \param[in] value The number to be printed. - * \param[in] term The field terminator. - * \return The number of bytes written or -1 if an error occurs. - */ - template - int printField(T value, char term) { - int rtn = printDec(value); - return rtn < 0 || putc(term) < 0 ? -1 : rtn + 1; - } - //---------------------------------------------------------------------------- /** Print HEX * \param[in] n number to be printed as HEX. * @@ -640,5 +608,3 @@ class StdioStream : private StreamBaseFile { uint8_t m_r = 0; uint8_t m_w = 0; }; -//------------------------------------------------------------------------------ -#endif // StdioStream_h diff --git a/src/iostream/StreamBaseClass.cpp b/src/iostream/StreamBaseClass.cpp index ca2a3396..89886ae9 100644 --- a/src/iostream/StreamBaseClass.cpp +++ b/src/iostream/StreamBaseClass.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -150,9 +150,3 @@ bool StreamBaseClass::seekoff(off_type off, seekdir way) { } return seekpos(pos); } -//------------------------------------------------------------------------------ -int StreamBaseClass::write(const void* buf, size_t n) { - return StreamBaseFile::write(buf, n); -} -//------------------------------------------------------------------------------ -void StreamBaseClass::write(char c) { StreamBaseFile::write(&c, 1); } diff --git a/src/iostream/fstream.h b/src/iostream/fstream.h index e6e222e2..37c67dd7 100644 --- a/src/iostream/fstream.h +++ b/src/iostream/fstream.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -26,8 +26,7 @@ * \file * \brief iostreams for files. */ -#ifndef fstream_h -#define fstream_h +#pragma once #include "iostream.h" //------------------------------------------------------------------------------ /** @@ -36,12 +35,15 @@ */ class StreamBaseClass : protected StreamBaseFile, virtual public ios { protected: - void clearWriteError() { StreamBaseFile::clearWriteError(); } + using StreamBaseFile::clearWriteError; + using StreamBaseFile::getWriteError; + using StreamBaseFile::write; + /* Internal do not use * \return mode */ int16_t getch(); - bool getWriteError() { return StreamBaseFile::getWriteError(); } + void open(const char* path, ios::openmode mode); /** Internal do not use * \return mode @@ -58,8 +60,6 @@ class StreamBaseClass : protected StreamBaseFile, virtual public ios { * \param[in] mode */ void setmode(ios::openmode mode) { m_mode = mode; } - int write(const void* buf, size_t n); - void write(char c); private: ios::openmode m_mode; @@ -72,6 +72,7 @@ class StreamBaseClass : protected StreamBaseFile, virtual public ios { class fstream : public iostream, StreamBaseClass { public: using iostream::peek; + using StreamBaseClass::close; fstream() {} /** Constructor with open * \param[in] path file to open @@ -86,14 +87,10 @@ class fstream : public iostream, StreamBaseClass { /** Clear state and writeError * \param[in] state new state for stream */ - void clear(iostate state = goodbit) { + void clear(iostate state = goodbit) override { ios::clear(state); StreamBaseClass::clearWriteError(); } - /** Close a file and force cached data and directory information - * to be written to the storage device. - */ - void close() { StreamBaseClass::close(); } /** Open a fstream * \param[in] path path to open * \param[in] mode open mode @@ -127,29 +124,29 @@ class fstream : public iostream, StreamBaseClass { /** Internal - do not use * \return */ - int16_t getch() { return StreamBaseClass::getch(); } + int16_t getch() override { return StreamBaseClass::getch(); } /** Internal - do not use * \param[out] pos */ - void getpos(pos_t* pos) { StreamBaseFile::fgetpos(pos); } + void getpos(pos_t* pos) override { StreamBaseFile::fgetpos(pos); } /** Internal - do not use * \param[in] c */ - void putch(char c) { StreamBaseClass::putch(c); } + void putch(char c) override { StreamBaseClass::putch(c); } /** Internal - do not use * \param[in] str */ - void putstr(const char* str) { StreamBaseClass::putstr(str); } + void putstr(const char* str) override { StreamBaseClass::putstr(str); } /** Internal - do not use * \param[in] pos */ - bool seekoff(off_type off, seekdir way) { + bool seekoff(off_type off, seekdir way) override { return StreamBaseClass::seekoff(off, way); } - bool seekpos(pos_type pos) { return StreamBaseClass::seekpos(pos); } - void setpos(pos_t* pos) { StreamBaseFile::fsetpos(pos); } - bool sync() { return StreamBaseClass::sync(); } - pos_type tellpos() { return StreamBaseFile::curPosition(); } + bool seekpos(pos_type pos) override { return StreamBaseClass::seekpos(pos); } + void setpos(const pos_t* pos) { StreamBaseFile::fsetpos(pos); } + bool sync() override { return StreamBaseClass::sync(); } + pos_type tellpos() override { return StreamBaseFile::curPosition(); } /// @endcond }; //============================================================================== @@ -160,6 +157,7 @@ class fstream : public iostream, StreamBaseClass { class ifstream : public istream, StreamBaseClass { public: using istream::peek; + using StreamBaseClass::close; ifstream() {} /** Constructor with open * \param[in] path file to open @@ -169,10 +167,6 @@ class ifstream : public istream, StreamBaseClass { #if DESTRUCTOR_CLOSES_FILE ~ifstream() {} #endif // DESTRUCTOR_CLOSES_FILE - /** Close a file and force cached data and directory information - * to be written to the storage device. - */ - void close() { StreamBaseClass::close(); } /** \return True if stream is open else false. */ bool is_open() { return StreamBaseFile::isOpen(); } /** Open an ifstream @@ -213,6 +207,7 @@ class ifstream : public istream, StreamBaseClass { */ class ofstream : public ostream, StreamBaseClass { public: + using StreamBaseClass::close; ofstream() {} /** Constructor with open * \param[in] path file to open @@ -225,14 +220,10 @@ class ofstream : public ostream, StreamBaseClass { /** Clear state and writeError * \param[in] state new state for stream */ - void clear(iostate state = goodbit) { + void clear(iostate state = goodbit) override { ios::clear(state); StreamBaseClass::clearWriteError(); } - /** Close a file and force cached data and directory information - * to be written to the storage device. - */ - void close() { StreamBaseClass::close(); } /** Open an ofstream * \param[in] path file to open * \param[in] mode open mode @@ -265,4 +256,3 @@ class ofstream : public ostream, StreamBaseClass { pos_type tellpos() override { return StreamBaseFile::curPosition(); } /// @endcond }; -#endif // fstream_h diff --git a/src/iostream/ios.h b/src/iostream/ios.h index 50eb58fe..c788a700 100644 --- a/src/iostream/ios.h +++ b/src/iostream/ios.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2022 Bill Greiman + * Copyright (c) 2011-2024 Bill Greiman * This file is part of the SdFat library for SD memory cards. * * MIT License @@ -22,8 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ -#ifndef ios_h -#define ios_h +#pragma once #include "../FsLib/FsLib.h" /** * \file @@ -412,7 +411,7 @@ class ios : public ios_base { * * \param[in] state The flags you want to set after clearing all flags. **/ - void clear(iostate state = goodbit) { m_iostate = state; } + virtual void clear(iostate state = goodbit) { m_iostate = state; } /** Set iostate bits. * * \param[in] state Bitts to set. @@ -422,4 +421,3 @@ class ios : public ios_base { private: iostate m_iostate = 0; }; -#endif // ios_h